mirror of
https://github.com/alexta69/metube.git
synced 2026-06-16 16:20:07 +00:00
reoganize quality and codec selections
This commit is contained in:
+108
-19
@@ -194,6 +194,68 @@ routes = web.RouteTableDef()
|
||||
VALID_SUBTITLE_FORMATS = {'srt', 'txt', 'vtt', 'ttml', 'sbv', 'scc', 'dfxp'}
|
||||
VALID_SUBTITLE_MODES = {'auto_only', 'manual_only', 'prefer_manual', 'prefer_auto'}
|
||||
SUBTITLE_LANGUAGE_RE = re.compile(r'^[A-Za-z0-9][A-Za-z0-9-]{0,34}$')
|
||||
VALID_DOWNLOAD_TYPES = {'video', 'audio', 'captions', 'thumbnail'}
|
||||
VALID_VIDEO_CODECS = {'auto', 'h264', 'h265', 'av1', 'vp9'}
|
||||
VALID_VIDEO_FORMATS = {'any', 'mp4', 'ios'}
|
||||
VALID_AUDIO_FORMATS = {'m4a', 'mp3', 'opus', 'wav', 'flac'}
|
||||
VALID_THUMBNAIL_FORMATS = {'jpg'}
|
||||
|
||||
|
||||
def _migrate_legacy_request(post: dict) -> dict:
|
||||
"""
|
||||
BACKWARD COMPATIBILITY: Translate old API request schema into the new one.
|
||||
|
||||
Old API:
|
||||
format (any/mp4/m4a/mp3/opus/wav/flac/thumbnail/captions)
|
||||
quality
|
||||
video_codec
|
||||
subtitle_format (only when format=captions)
|
||||
|
||||
New API:
|
||||
download_type (video/audio/captions/thumbnail)
|
||||
codec
|
||||
format
|
||||
quality
|
||||
"""
|
||||
if "download_type" in post:
|
||||
return post
|
||||
|
||||
old_format = str(post.get("format") or "any").strip().lower()
|
||||
old_quality = str(post.get("quality") or "best").strip().lower()
|
||||
old_video_codec = str(post.get("video_codec") or "auto").strip().lower()
|
||||
|
||||
if old_format in VALID_AUDIO_FORMATS:
|
||||
post["download_type"] = "audio"
|
||||
post["codec"] = "auto"
|
||||
post["format"] = old_format
|
||||
elif old_format == "thumbnail":
|
||||
post["download_type"] = "thumbnail"
|
||||
post["codec"] = "auto"
|
||||
post["format"] = "jpg"
|
||||
post["quality"] = "best"
|
||||
elif old_format == "captions":
|
||||
post["download_type"] = "captions"
|
||||
post["codec"] = "auto"
|
||||
post["format"] = str(post.get("subtitle_format") or "srt").strip().lower()
|
||||
post["quality"] = "best"
|
||||
else:
|
||||
# old_format is usually any/mp4 (legacy video path)
|
||||
post["download_type"] = "video"
|
||||
post["codec"] = old_video_codec
|
||||
if old_quality == "best_ios":
|
||||
post["format"] = "ios"
|
||||
post["quality"] = "best"
|
||||
elif old_quality == "audio":
|
||||
# Legacy "audio only" under video format maps to m4a audio.
|
||||
post["download_type"] = "audio"
|
||||
post["codec"] = "auto"
|
||||
post["format"] = "m4a"
|
||||
post["quality"] = "best"
|
||||
else:
|
||||
post["format"] = old_format
|
||||
post["quality"] = old_quality
|
||||
|
||||
return post
|
||||
|
||||
class Notifier(DownloadQueueNotifier):
|
||||
async def added(self, dl):
|
||||
@@ -272,23 +334,24 @@ if config.YTDL_OPTIONS_FILE:
|
||||
async def add(request):
|
||||
log.info("Received request to add download")
|
||||
post = await request.json()
|
||||
post = _migrate_legacy_request(post)
|
||||
log.info(f"Request data: {post}")
|
||||
url = post.get('url')
|
||||
quality = post.get('quality')
|
||||
if not url or not quality:
|
||||
log.error("Bad request: missing 'url' or 'quality'")
|
||||
raise web.HTTPBadRequest()
|
||||
download_type = post.get('download_type')
|
||||
codec = post.get('codec')
|
||||
format = post.get('format')
|
||||
quality = post.get('quality')
|
||||
if not url or not quality or not download_type:
|
||||
log.error("Bad request: missing 'url', 'download_type', or 'quality'")
|
||||
raise web.HTTPBadRequest()
|
||||
folder = post.get('folder')
|
||||
custom_name_prefix = post.get('custom_name_prefix')
|
||||
playlist_item_limit = post.get('playlist_item_limit')
|
||||
auto_start = post.get('auto_start')
|
||||
split_by_chapters = post.get('split_by_chapters')
|
||||
chapter_template = post.get('chapter_template')
|
||||
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 = ''
|
||||
@@ -302,46 +365,72 @@ async def add(request):
|
||||
split_by_chapters = False
|
||||
if chapter_template is None:
|
||||
chapter_template = config.OUTPUT_TEMPLATE_CHAPTER
|
||||
if subtitle_format is None:
|
||||
subtitle_format = 'srt'
|
||||
if subtitle_language is None:
|
||||
subtitle_language = 'en'
|
||||
if subtitle_mode is None:
|
||||
subtitle_mode = 'prefer_manual'
|
||||
subtitle_format = str(subtitle_format).strip().lower()
|
||||
download_type = str(download_type).strip().lower()
|
||||
codec = str(codec or 'auto').strip().lower()
|
||||
format = str(format or '').strip().lower()
|
||||
quality = str(quality).strip().lower()
|
||||
subtitle_language = str(subtitle_language).strip()
|
||||
subtitle_mode = str(subtitle_mode).strip()
|
||||
|
||||
if chapter_template and ('..' in chapter_template or chapter_template.startswith('/') or chapter_template.startswith('\\')):
|
||||
raise web.HTTPBadRequest(reason='chapter_template must not contain ".." or start with a path separator')
|
||||
if subtitle_format not in VALID_SUBTITLE_FORMATS:
|
||||
raise web.HTTPBadRequest(reason=f'subtitle_format must be one of {sorted(VALID_SUBTITLE_FORMATS)}')
|
||||
if not SUBTITLE_LANGUAGE_RE.fullmatch(subtitle_language):
|
||||
raise web.HTTPBadRequest(reason='subtitle_language must match pattern [A-Za-z0-9-] and be at most 35 characters')
|
||||
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']")
|
||||
if download_type not in VALID_DOWNLOAD_TYPES:
|
||||
raise web.HTTPBadRequest(reason=f'download_type must be one of {sorted(VALID_DOWNLOAD_TYPES)}')
|
||||
if codec not in VALID_VIDEO_CODECS:
|
||||
raise web.HTTPBadRequest(reason=f'codec must be one of {sorted(VALID_VIDEO_CODECS)}')
|
||||
|
||||
if download_type == 'video':
|
||||
if format not in VALID_VIDEO_FORMATS:
|
||||
raise web.HTTPBadRequest(reason=f'format must be one of {sorted(VALID_VIDEO_FORMATS)} for video')
|
||||
if quality not in {'best', 'worst', '2160', '1440', '1080', '720', '480', '360', '240'}:
|
||||
raise web.HTTPBadRequest(reason="quality must be one of ['best', '2160', '1440', '1080', '720', '480', '360', '240', 'worst'] for video")
|
||||
elif download_type == 'audio':
|
||||
if format not in VALID_AUDIO_FORMATS:
|
||||
raise web.HTTPBadRequest(reason=f'format must be one of {sorted(VALID_AUDIO_FORMATS)} for audio')
|
||||
allowed_audio_qualities = {'best'}
|
||||
if format == 'mp3':
|
||||
allowed_audio_qualities |= {'320', '192', '128'}
|
||||
elif format == 'm4a':
|
||||
allowed_audio_qualities |= {'192', '128'}
|
||||
if quality not in allowed_audio_qualities:
|
||||
raise web.HTTPBadRequest(reason=f'quality must be one of {sorted(allowed_audio_qualities)} for format {format}')
|
||||
codec = 'auto'
|
||||
elif download_type == 'captions':
|
||||
if format not in VALID_SUBTITLE_FORMATS:
|
||||
raise web.HTTPBadRequest(reason=f'format must be one of {sorted(VALID_SUBTITLE_FORMATS)} for captions')
|
||||
quality = 'best'
|
||||
codec = 'auto'
|
||||
elif download_type == 'thumbnail':
|
||||
if format not in VALID_THUMBNAIL_FORMATS:
|
||||
raise web.HTTPBadRequest(reason=f'format must be one of {sorted(VALID_THUMBNAIL_FORMATS)} for thumbnail')
|
||||
quality = 'best'
|
||||
codec = 'auto'
|
||||
|
||||
playlist_item_limit = int(playlist_item_limit)
|
||||
|
||||
status = await dqueue.add(
|
||||
url,
|
||||
quality,
|
||||
download_type,
|
||||
codec,
|
||||
format,
|
||||
quality,
|
||||
folder,
|
||||
custom_name_prefix,
|
||||
playlist_item_limit,
|
||||
auto_start,
|
||||
split_by_chapters,
|
||||
chapter_template,
|
||||
subtitle_format,
|
||||
subtitle_language,
|
||||
subtitle_mode,
|
||||
video_codec=video_codec,
|
||||
)
|
||||
return web.Response(text=serializer.encode(status))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user