mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-03-17 16:47:29 +00:00
Merge branch 'main' into feature/add-rest-api
This commit is contained in:
@@ -180,6 +180,12 @@ class dl:
|
||||
help="Required subtitle languages. Downloads all subtitles only if these languages exist. Cannot be used with --s-lang.",
|
||||
)
|
||||
@click.option("-fs", "--forced-subs", is_flag=True, default=False, help="Include forced subtitle tracks.")
|
||||
@click.option(
|
||||
"--exact-lang",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Use exact language matching (no variants). With this flag, -l es-419 matches ONLY es-419, not es-ES or other variants.",
|
||||
)
|
||||
@click.option(
|
||||
"--proxy",
|
||||
type=str,
|
||||
@@ -258,6 +264,7 @@ class dl:
|
||||
@click.option(
|
||||
"--no-source", is_flag=True, default=False, help="Disable the source tag from the output file name and path."
|
||||
)
|
||||
@click.option("--no-mux", is_flag=True, default=False, help="Do not mux tracks into a container file.")
|
||||
@click.option(
|
||||
"--workers",
|
||||
type=int,
|
||||
@@ -467,6 +474,7 @@ class dl:
|
||||
s_lang: list[str],
|
||||
require_subs: list[str],
|
||||
forced_subs: bool,
|
||||
exact_lang: bool,
|
||||
sub_format: Optional[Subtitle.Codec],
|
||||
video_only: bool,
|
||||
audio_only: bool,
|
||||
@@ -484,6 +492,7 @@ class dl:
|
||||
no_proxy: bool,
|
||||
no_folder: bool,
|
||||
no_source: bool,
|
||||
no_mux: bool,
|
||||
workers: Optional[int],
|
||||
downloads: int,
|
||||
best_available: bool,
|
||||
@@ -707,7 +716,9 @@ class dl:
|
||||
else:
|
||||
if language not in processed_video_lang:
|
||||
processed_video_lang.append(language)
|
||||
title.tracks.videos = title.tracks.by_language(title.tracks.videos, processed_video_lang)
|
||||
title.tracks.videos = title.tracks.by_language(
|
||||
title.tracks.videos, processed_video_lang, exact_match=exact_lang
|
||||
)
|
||||
if not title.tracks.videos:
|
||||
self.log.error(f"There's no {processed_video_lang} Video Track...")
|
||||
sys.exit(1)
|
||||
@@ -790,16 +801,20 @@ class dl:
|
||||
f"Required languages found ({', '.join(require_subs)}), downloading all available subtitles"
|
||||
)
|
||||
elif s_lang and "all" not in s_lang:
|
||||
from unshackle.core.utilities import is_exact_match
|
||||
|
||||
match_func = is_exact_match if exact_lang else is_close_match
|
||||
|
||||
missing_langs = [
|
||||
lang_
|
||||
for lang_ in s_lang
|
||||
if not any(is_close_match(lang_, [sub.language]) for sub in title.tracks.subtitles)
|
||||
if not any(match_func(lang_, [sub.language]) for sub in title.tracks.subtitles)
|
||||
]
|
||||
if missing_langs:
|
||||
self.log.error(", ".join(missing_langs) + " not found in tracks")
|
||||
sys.exit(1)
|
||||
|
||||
title.tracks.select_subtitles(lambda x: is_close_match(x.language, s_lang))
|
||||
title.tracks.select_subtitles(lambda x: match_func(x.language, s_lang))
|
||||
if not title.tracks.subtitles:
|
||||
self.log.error(f"There's no {s_lang} Subtitle Track...")
|
||||
sys.exit(1)
|
||||
@@ -863,7 +878,7 @@ class dl:
|
||||
elif "all" not in processed_lang:
|
||||
per_language = 1
|
||||
title.tracks.audio = title.tracks.by_language(
|
||||
title.tracks.audio, processed_lang, per_language=per_language
|
||||
title.tracks.audio, processed_lang, per_language=per_language, exact_match=exact_lang
|
||||
)
|
||||
if not title.tracks.audio:
|
||||
self.log.error(f"There's no {processed_lang} Audio Track, cannot continue...")
|
||||
@@ -1139,7 +1154,12 @@ class dl:
|
||||
|
||||
muxed_paths = []
|
||||
|
||||
if isinstance(title, (Movie, Episode)):
|
||||
if no_mux:
|
||||
# Skip muxing, handle individual track files
|
||||
for track in title.tracks:
|
||||
if track.path and track.path.exists():
|
||||
muxed_paths.append(track.path)
|
||||
elif isinstance(title, (Movie, Episode)):
|
||||
progress = Progress(
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
SpinnerColumn(finished_text=""),
|
||||
@@ -1258,19 +1278,65 @@ class dl:
|
||||
# dont mux
|
||||
muxed_paths.append(title.tracks.audio[0].path)
|
||||
|
||||
for muxed_path in muxed_paths:
|
||||
media_info = MediaInfo.parse(muxed_path)
|
||||
if no_mux:
|
||||
# Handle individual track files without muxing
|
||||
final_dir = config.directories.downloads
|
||||
final_filename = title.get_filename(media_info, show_service=not no_source)
|
||||
|
||||
if not no_folder and isinstance(title, (Episode, Song)):
|
||||
final_dir /= title.get_filename(media_info, show_service=not no_source, folder=True)
|
||||
# Create folder based on title
|
||||
# Use first available track for filename generation
|
||||
sample_track = title.tracks.videos[0] if title.tracks.videos else (
|
||||
title.tracks.audio[0] if title.tracks.audio else (
|
||||
title.tracks.subtitles[0] if title.tracks.subtitles else None
|
||||
)
|
||||
)
|
||||
if sample_track and sample_track.path:
|
||||
media_info = MediaInfo.parse(sample_track.path)
|
||||
final_dir /= title.get_filename(media_info, show_service=not no_source, folder=True)
|
||||
|
||||
final_dir.mkdir(parents=True, exist_ok=True)
|
||||
final_path = final_dir / f"{final_filename}{muxed_path.suffix}"
|
||||
|
||||
shutil.move(muxed_path, final_path)
|
||||
tags.tag_file(final_path, title, self.tmdb_id)
|
||||
for track_path in muxed_paths:
|
||||
# Generate appropriate filename for each track
|
||||
media_info = MediaInfo.parse(track_path)
|
||||
base_filename = title.get_filename(media_info, show_service=not no_source)
|
||||
|
||||
# Add track type suffix to filename
|
||||
track = next((t for t in title.tracks if t.path == track_path), None)
|
||||
if track:
|
||||
if isinstance(track, Video):
|
||||
track_suffix = f".{track.codec.name if hasattr(track.codec, 'name') else 'video'}"
|
||||
elif isinstance(track, Audio):
|
||||
lang_suffix = f".{track.language}" if track.language else ""
|
||||
track_suffix = f"{lang_suffix}.{track.codec.name if hasattr(track.codec, 'name') else 'audio'}"
|
||||
elif isinstance(track, Subtitle):
|
||||
lang_suffix = f".{track.language}" if track.language else ""
|
||||
forced_suffix = ".forced" if track.forced else ""
|
||||
sdh_suffix = ".sdh" if track.sdh else ""
|
||||
track_suffix = f"{lang_suffix}{forced_suffix}{sdh_suffix}"
|
||||
else:
|
||||
track_suffix = ""
|
||||
|
||||
final_path = final_dir / f"{base_filename}{track_suffix}{track_path.suffix}"
|
||||
else:
|
||||
final_path = final_dir / f"{base_filename}{track_path.suffix}"
|
||||
|
||||
shutil.move(track_path, final_path)
|
||||
self.log.debug(f"Saved: {final_path.name}")
|
||||
else:
|
||||
# Handle muxed files
|
||||
for muxed_path in muxed_paths:
|
||||
media_info = MediaInfo.parse(muxed_path)
|
||||
final_dir = config.directories.downloads
|
||||
final_filename = title.get_filename(media_info, show_service=not no_source)
|
||||
|
||||
if not no_folder and isinstance(title, (Episode, Song)):
|
||||
final_dir /= title.get_filename(media_info, show_service=not no_source, folder=True)
|
||||
|
||||
final_dir.mkdir(parents=True, exist_ok=True)
|
||||
final_path = final_dir / f"{final_filename}{muxed_path.suffix}"
|
||||
|
||||
shutil.move(muxed_path, final_path)
|
||||
tags.tag_file(final_path, title, self.tmdb_id)
|
||||
|
||||
title_dl_time = time_elapsed_since(dl_start_time)
|
||||
console.print(
|
||||
|
||||
Reference in New Issue
Block a user