mirror of
https://github.com/alexta69/metube.git
synced 2026-06-13 16:40:05 +00:00
Add video codec selector and codec/quality columns in done list
Allow users to prefer a specific video codec (H.264, H.265, AV1, VP9) when adding downloads. The selector filters available formats via yt-dlp format strings, falling back to best available if the preferred codec is not found. The completed downloads table now shows Quality and Codec columns.
This commit is contained in:
+15
-1
@@ -3,6 +3,13 @@ import copy
|
||||
AUDIO_FORMATS = ("m4a", "mp3", "opus", "wav", "flac")
|
||||
CAPTION_MODES = ("auto_only", "manual_only", "prefer_manual", "prefer_auto")
|
||||
|
||||
CODEC_FILTER_MAP = {
|
||||
'h264': "[vcodec~='^(h264|avc)']",
|
||||
'h265': "[vcodec~='^(h265|hevc)']",
|
||||
'av1': "[vcodec~='^av0?1']",
|
||||
'vp9': "[vcodec~='^vp0?9']",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_caption_mode(mode: str) -> str:
|
||||
mode = (mode or "").strip()
|
||||
@@ -14,13 +21,14 @@ def _normalize_subtitle_language(language: str) -> str:
|
||||
return language or "en"
|
||||
|
||||
|
||||
def get_format(format: str, quality: str) -> str:
|
||||
def get_format(format: str, quality: str, video_codec: str = "auto") -> str:
|
||||
"""
|
||||
Returns format for download
|
||||
|
||||
Args:
|
||||
format (str): format selected
|
||||
quality (str): quality selected
|
||||
video_codec (str): video codec filter (auto, h264, h265, av1, vp9)
|
||||
|
||||
Raises:
|
||||
Exception: unknown quality, unknown format
|
||||
@@ -52,6 +60,7 @@ def get_format(format: str, quality: str) -> str:
|
||||
vfmt, afmt = ("[ext=mp4]", "[ext=m4a]") if format == "mp4" else ("", "")
|
||||
vres = f"[height<={quality}]" if quality not in ("best", "best_ios", "worst") else ""
|
||||
vcombo = vres + vfmt
|
||||
codec_filter = CODEC_FILTER_MAP.get(video_codec, "")
|
||||
|
||||
if quality == "best_ios":
|
||||
# iOS has strict requirements for video files, requiring h264 or h265
|
||||
@@ -61,6 +70,10 @@ def get_format(format: str, quality: str) -> str:
|
||||
# convert if needed), and falls back to getting the best available MP4
|
||||
# file.
|
||||
return f"bestvideo[vcodec~='^((he|a)vc|h26[45])']{vres}+bestaudio[acodec=aac]/bestvideo[vcodec~='^((he|a)vc|h26[45])']{vres}+bestaudio{afmt}/bestvideo{vcombo}+bestaudio{afmt}/best{vcombo}"
|
||||
|
||||
if codec_filter:
|
||||
# Try codec-filtered first, fall back to unfiltered
|
||||
return f"bestvideo{codec_filter}{vcombo}+bestaudio{afmt}/bestvideo{vcombo}+bestaudio{afmt}/best{vcombo}"
|
||||
return f"bestvideo{vcombo}+bestaudio{afmt}/best{vcombo}"
|
||||
|
||||
raise Exception(f"Unkown format {format}")
|
||||
@@ -73,6 +86,7 @@ def get_opts(
|
||||
subtitle_format: str = "srt",
|
||||
subtitle_language: str = "en",
|
||||
subtitle_mode: str = "prefer_manual",
|
||||
video_codec: str = "auto",
|
||||
) -> dict:
|
||||
"""
|
||||
Returns extra download options
|
||||
|
||||
@@ -288,6 +288,7 @@ async def add(request):
|
||||
subtitle_format = post.get('subtitle_format')
|
||||
subtitle_language = post.get('subtitle_language')
|
||||
subtitle_mode = post.get('subtitle_mode')
|
||||
video_codec = post.get('video_codec')
|
||||
|
||||
if custom_name_prefix is None:
|
||||
custom_name_prefix = ''
|
||||
@@ -319,6 +320,12 @@ async def add(request):
|
||||
if subtitle_mode not in VALID_SUBTITLE_MODES:
|
||||
raise web.HTTPBadRequest(reason=f'subtitle_mode must be one of {sorted(VALID_SUBTITLE_MODES)}')
|
||||
|
||||
if video_codec is None:
|
||||
video_codec = 'auto'
|
||||
video_codec = str(video_codec).strip().lower()
|
||||
if video_codec not in ('auto', 'h264', 'h265', 'av1', 'vp9'):
|
||||
raise web.HTTPBadRequest(reason="video_codec must be one of ['auto', 'h264', 'h265', 'av1', 'vp9']")
|
||||
|
||||
playlist_item_limit = int(playlist_item_limit)
|
||||
|
||||
status = await dqueue.add(
|
||||
@@ -334,6 +341,7 @@ async def add(request):
|
||||
subtitle_format,
|
||||
subtitle_language,
|
||||
subtitle_mode,
|
||||
video_codec=video_codec,
|
||||
)
|
||||
return web.Response(text=serializer.encode(status))
|
||||
|
||||
|
||||
+12
-2
@@ -162,6 +162,7 @@ class DownloadInfo:
|
||||
subtitle_format="srt",
|
||||
subtitle_language="en",
|
||||
subtitle_mode="prefer_manual",
|
||||
video_codec="auto",
|
||||
):
|
||||
self.id = id if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{id}'
|
||||
self.title = title if len(custom_name_prefix) == 0 else f'{custom_name_prefix}.{title}'
|
||||
@@ -184,6 +185,7 @@ class DownloadInfo:
|
||||
self.subtitle_language = subtitle_language
|
||||
self.subtitle_mode = subtitle_mode
|
||||
self.subtitle_files = []
|
||||
self.video_codec = video_codec
|
||||
|
||||
class Download:
|
||||
manager = None
|
||||
@@ -194,7 +196,7 @@ class Download:
|
||||
self.output_template = output_template
|
||||
self.output_template_chapter = output_template_chapter
|
||||
self.info = info
|
||||
self.format = get_format(format, quality)
|
||||
self.format = get_format(format, quality, video_codec=getattr(info, 'video_codec', 'auto'))
|
||||
self.ytdl_opts = get_opts(
|
||||
format,
|
||||
quality,
|
||||
@@ -202,6 +204,7 @@ class Download:
|
||||
subtitle_format=getattr(info, 'subtitle_format', 'srt'),
|
||||
subtitle_language=getattr(info, 'subtitle_language', 'en'),
|
||||
subtitle_mode=getattr(info, 'subtitle_mode', 'prefer_manual'),
|
||||
video_codec=getattr(info, 'video_codec', 'auto'),
|
||||
)
|
||||
if "impersonate" in self.ytdl_opts:
|
||||
self.ytdl_opts["impersonate"] = yt_dlp.networking.impersonate.ImpersonateTarget.from_str(self.ytdl_opts["impersonate"])
|
||||
@@ -687,6 +690,7 @@ class DownloadQueue:
|
||||
subtitle_mode,
|
||||
already,
|
||||
_add_gen=None,
|
||||
video_codec="auto",
|
||||
):
|
||||
if not entry:
|
||||
return {'status': 'error', 'msg': "Invalid/empty data was given."}
|
||||
@@ -718,6 +722,7 @@ class DownloadQueue:
|
||||
subtitle_mode,
|
||||
already,
|
||||
_add_gen,
|
||||
video_codec,
|
||||
)
|
||||
elif etype == 'playlist' or etype == 'channel':
|
||||
log.debug(f'Processing as a {etype}')
|
||||
@@ -757,6 +762,7 @@ class DownloadQueue:
|
||||
subtitle_mode,
|
||||
already,
|
||||
_add_gen,
|
||||
video_codec,
|
||||
)
|
||||
)
|
||||
if any(res['status'] == 'error' for res in results):
|
||||
@@ -785,6 +791,7 @@ class DownloadQueue:
|
||||
subtitle_format,
|
||||
subtitle_language,
|
||||
subtitle_mode,
|
||||
video_codec,
|
||||
)
|
||||
await self.__add_download(dl, auto_start)
|
||||
return {'status': 'ok'}
|
||||
@@ -806,11 +813,13 @@ class DownloadQueue:
|
||||
subtitle_mode="prefer_manual",
|
||||
already=None,
|
||||
_add_gen=None,
|
||||
video_codec="auto",
|
||||
):
|
||||
log.info(
|
||||
f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} '
|
||||
f'{playlist_item_limit=} {auto_start=} {split_by_chapters=} {chapter_template=} '
|
||||
f'{subtitle_format=} {subtitle_language=} {subtitle_mode=}'
|
||||
f'{subtitle_format=} {subtitle_language=} {subtitle_mode=} '
|
||||
f'{video_codec=}'
|
||||
)
|
||||
if already is None:
|
||||
_add_gen = self._add_generation
|
||||
@@ -840,6 +849,7 @@ class DownloadQueue:
|
||||
subtitle_mode,
|
||||
already,
|
||||
_add_gen,
|
||||
video_codec,
|
||||
)
|
||||
|
||||
async def start_pending(self, ids):
|
||||
|
||||
Reference in New Issue
Block a user