forked from DRMTalks/devine
feat(dl): Support multiple -r/--range and mux ranges separately
Multiple -r/--range values can be used with multiple -q/--quality values. Closes #63
This commit is contained in:
parent
6e8efc3f63
commit
0201c41feb
|
@ -14,7 +14,7 @@ from concurrent.futures import ThreadPoolExecutor
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from http.cookiejar import CookieJar, MozillaCookieJar
|
from http.cookiejar import CookieJar, MozillaCookieJar
|
||||||
from itertools import zip_longest
|
from itertools import product
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Optional
|
||||||
|
@ -32,7 +32,7 @@ from rich.console import Group
|
||||||
from rich.live import Live
|
from rich.live import Live
|
||||||
from rich.padding import Padding
|
from rich.padding import Padding
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeRemainingColumn
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskID, TextColumn, TimeRemainingColumn
|
||||||
from rich.rule import Rule
|
from rich.rule import Rule
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
@ -50,7 +50,7 @@ from devine.core.titles import Movie, Song, Title_T
|
||||||
from devine.core.titles.episode import Episode
|
from devine.core.titles.episode import Episode
|
||||||
from devine.core.tracks import Audio, Subtitle, Tracks, Video
|
from devine.core.tracks import Audio, Subtitle, Tracks, Video
|
||||||
from devine.core.utilities import get_binary_path, is_close_match, time_elapsed_since
|
from devine.core.utilities import get_binary_path, is_close_match, time_elapsed_since
|
||||||
from devine.core.utils.click_types import LANGUAGE_RANGE, QUALITY_LIST, SEASON_RANGE, ContextData
|
from devine.core.utils.click_types import LANGUAGE_RANGE, QUALITY_LIST, SEASON_RANGE, ContextData, MultipleChoice
|
||||||
from devine.core.utils.collections import merge_dict
|
from devine.core.utils.collections import merge_dict
|
||||||
from devine.core.utils.subprocess import ffprobe
|
from devine.core.utils.subprocess import ffprobe
|
||||||
from devine.core.vaults import Vaults
|
from devine.core.vaults import Vaults
|
||||||
|
@ -81,9 +81,9 @@ class dl:
|
||||||
@click.option("-ab", "--abitrate", type=int,
|
@click.option("-ab", "--abitrate", type=int,
|
||||||
default=None,
|
default=None,
|
||||||
help="Audio Bitrate to download (in kbps), defaults to highest available.")
|
help="Audio Bitrate to download (in kbps), defaults to highest available.")
|
||||||
@click.option("-r", "--range", "range_", type=click.Choice(Video.Range, case_sensitive=False),
|
@click.option("-r", "--range", "range_", type=MultipleChoice(Video.Range, case_sensitive=False),
|
||||||
default=Video.Range.SDR,
|
default=[Video.Range.SDR],
|
||||||
help="Video Color Range, defaults to SDR.")
|
help="Video Color Range(s) to download, defaults to SDR.")
|
||||||
@click.option("-c", "--channels", type=float,
|
@click.option("-c", "--channels", type=float,
|
||||||
default=None,
|
default=None,
|
||||||
help="Audio Channel(s) to download. Matches sub-channel layouts like 5.1 with 6.0 implicitly.")
|
help="Audio Channel(s) to download. Matches sub-channel layouts like 5.1 with 6.0 implicitly.")
|
||||||
|
@ -254,7 +254,7 @@ class dl:
|
||||||
acodec: Optional[Audio.Codec],
|
acodec: Optional[Audio.Codec],
|
||||||
vbitrate: int,
|
vbitrate: int,
|
||||||
abitrate: int,
|
abitrate: int,
|
||||||
range_: Video.Range,
|
range_: list[Video.Range],
|
||||||
channels: float,
|
channels: float,
|
||||||
wanted: list[str],
|
wanted: list[str],
|
||||||
lang: list[str],
|
lang: list[str],
|
||||||
|
@ -363,10 +363,12 @@ class dl:
|
||||||
self.log.error(f"There's no {vcodec.name} Video Track...")
|
self.log.error(f"There's no {vcodec.name} Video Track...")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
title.tracks.select_video(lambda x: x.range == range_)
|
if range_:
|
||||||
if not title.tracks.videos:
|
title.tracks.select_video(lambda x: x.range in range_)
|
||||||
self.log.error(f"There's no {range_.name} Video Track...")
|
for color_range in range_:
|
||||||
sys.exit(1)
|
if not any(x.range == color_range for x in title.tracks.videos):
|
||||||
|
self.log.error(f"There's no {color_range.name} Video Tracks...")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if vbitrate:
|
if vbitrate:
|
||||||
title.tracks.select_video(lambda x: x.bitrate and x.bitrate // 1000 == vbitrate)
|
title.tracks.select_video(lambda x: x.bitrate and x.bitrate // 1000 == vbitrate)
|
||||||
|
@ -382,7 +384,7 @@ class dl:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if quality:
|
if quality:
|
||||||
title.tracks.by_resolutions(quality, per_resolution=1)
|
title.tracks.by_resolutions(quality)
|
||||||
missing_resolutions = []
|
missing_resolutions = []
|
||||||
for resolution in quality:
|
for resolution in quality:
|
||||||
if any(video.height == resolution for video in title.tracks.videos):
|
if any(video.height == resolution for video in title.tracks.videos):
|
||||||
|
@ -398,8 +400,27 @@ class dl:
|
||||||
plural = "s" if len(missing_resolutions) > 1 else ""
|
plural = "s" if len(missing_resolutions) > 1 else ""
|
||||||
self.log.error(f"There's no {res_list} Video Track{plural}...")
|
self.log.error(f"There's no {res_list} Video Track{plural}...")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
|
||||||
title.tracks.videos = [title.tracks.videos[0]]
|
# choose best track by range and quality
|
||||||
|
title.tracks.videos = [
|
||||||
|
track
|
||||||
|
for resolution, color_range in product(
|
||||||
|
quality or [None],
|
||||||
|
range_ or [None]
|
||||||
|
)
|
||||||
|
for track in [next(
|
||||||
|
t
|
||||||
|
for t in title.tracks.videos
|
||||||
|
if (not resolution and not color_range) or
|
||||||
|
(
|
||||||
|
(not resolution or (
|
||||||
|
(t.height == resolution) or
|
||||||
|
(int(t.width * (9 / 16)) == resolution)
|
||||||
|
))
|
||||||
|
and (not color_range or t.range == color_range)
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
]
|
||||||
|
|
||||||
# filter subtitle tracks
|
# filter subtitle tracks
|
||||||
if s_lang and "all" not in s_lang:
|
if s_lang and "all" not in s_lang:
|
||||||
|
@ -599,26 +620,31 @@ class dl:
|
||||||
TimeRemainingColumn(compact=True, elapsed_when_finished=True),
|
TimeRemainingColumn(compact=True, elapsed_when_finished=True),
|
||||||
console=console
|
console=console
|
||||||
)
|
)
|
||||||
multi_jobs = len(title.tracks.videos) > 1
|
|
||||||
tasks = [
|
multiplex_tasks: list[tuple[TaskID, Tracks]] = []
|
||||||
progress.add_task(
|
for video_track in title.tracks.videos:
|
||||||
f"Multiplexing{f' {x.height}p' if multi_jobs else ''}...",
|
task_description = "Multiplexing"
|
||||||
total=None,
|
if len(quality) > 1:
|
||||||
start=False
|
task_description += f" {video_track.height}p"
|
||||||
)
|
if len(range_) > 1:
|
||||||
for x in title.tracks.videos or [None]
|
task_description += f" {video_track.range.name}"
|
||||||
]
|
|
||||||
|
task_id = progress.add_task(f"{task_description}...", total=None, start=False)
|
||||||
|
|
||||||
|
task_tracks = Tracks(title.tracks)
|
||||||
|
task_tracks.videos = [video_track]
|
||||||
|
|
||||||
|
multiplex_tasks.append((task_id, task_tracks))
|
||||||
|
|
||||||
with Live(
|
with Live(
|
||||||
Padding(progress, (0, 5, 1, 5)),
|
Padding(progress, (0, 5, 1, 5)),
|
||||||
console=console
|
console=console
|
||||||
):
|
):
|
||||||
for task, video_track in zip_longest(tasks, title.tracks.videos, fillvalue=None):
|
for task_id, task_tracks in multiplex_tasks:
|
||||||
if video_track:
|
progress.start_task(task_id) # TODO: Needed?
|
||||||
title.tracks.videos = [video_track]
|
muxed_path, return_code = task_tracks.mux(
|
||||||
progress.start_task(task) # TODO: Needed?
|
|
||||||
muxed_path, return_code = title.tracks.mux(
|
|
||||||
str(title),
|
str(title),
|
||||||
progress=partial(progress.update, task_id=task),
|
progress=partial(progress.update, task_id=task_id),
|
||||||
delete=False
|
delete=False
|
||||||
)
|
)
|
||||||
muxed_paths.append(muxed_path)
|
muxed_paths.append(muxed_path)
|
||||||
|
@ -627,8 +653,7 @@ class dl:
|
||||||
elif return_code >= 2:
|
elif return_code >= 2:
|
||||||
self.log.error(f"Failed to Mux video to Matroska file ({return_code})")
|
self.log.error(f"Failed to Mux video to Matroska file ({return_code})")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if video_track:
|
task_tracks.videos[0].delete()
|
||||||
video_track.delete()
|
|
||||||
for track in title.tracks:
|
for track in title.tracks:
|
||||||
track.delete()
|
track.delete()
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue