metube/app/dl_formats.py

105 lines
3.4 KiB
Python
Raw Permalink Normal View History

2021-12-01 21:48:01 +01:00
import copy
2024-06-07 10:35:04 +08:00
AUDIO_FORMATS = ("m4a", "mp3", "opus", "wav", "flac")
2021-10-28 11:19:17 +01:00
def get_format(format: str, quality: str) -> str:
"""
Returns format for download
Args:
format (str): format selected
quality (str): quality selected
Raises:
Exception: unknown quality, unknown format
Returns:
dl_format: Formatted download string
"""
2021-11-27 16:45:21 +02:00
format = format or "any"
2021-10-28 11:19:17 +01:00
2021-10-30 18:06:56 +01:00
if format.startswith("custom:"):
2021-11-20 10:12:08 +02:00
return format[7:]
2021-10-28 11:19:17 +01:00
2022-06-06 16:26:53 +02:00
if format == "thumbnail":
# Quality is irrelevant in this case since we skip the download
return "bestaudio/best"
if format in AUDIO_FORMATS:
2021-11-20 10:12:08 +02:00
# Audio quality needs to be set post-download, set in opts
2024-04-30 14:10:55 -04:00
return f"bestaudio[ext={format}]/bestaudio/best"
2021-11-20 10:12:08 +02:00
if format in ("mp4", "any"):
if quality == "audio":
return "bestaudio/best"
# video {res} {vfmt} + audio {afmt} {res} {vfmt}
vfmt, afmt = ("[ext=mp4]", "[ext=m4a]") if format == "mp4" else ("", "")
2025-01-15 07:55:38 +08:00
vres = f"[height<={quality}]" if quality not in ("best", "best_ios", "worst") else ""
2021-11-20 10:12:08 +02:00
vcombo = vres + vfmt
Add separate quality entry for iOS compatibility The iOS-compatible video may not be the best quality. Add a separate quality option to accommodate people who want the best available versus the best compatible with iOS's strict requirements. Testing with https://www.youtube.com/watch?v=YiRMs5ZhcH4 where the best quality video is 2160p and not iOS-compatible. With best quality, the VP9 video format is used (better quality but not iOS-compatible): ``` % ffprobe -hide_banner Who\ Can\ Find\ the\ Weirdest\ PC\ Parts\ on\ AliExpress?.mp4 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'Who Can Find the Weirdest PC Parts on AliExpress?.mp4': Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2mp41 encoder : Lavf60.16.100 Duration: 00:19:02.72, start: 0.000000, bitrate: 10941 kb/s Stream #0:0[0x1](und): Video: vp9 (Profile 0) (vp09 / 0x39307076), yuv420p(tv, bt709), 3840x1920, 10805 kb/s, 29.97 fps, 29.97 tbr, 16k tbn (default) Metadata: handler_name : ISO Media file produced by Google Inc. Created on: 06/15/2024. vendor_id : [0][0][0][0] Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default) Metadata: handler_name : ISO Media file produced by Google Inc. vendor_id : [0][0][0][0] ``` With "Best (iOS)" quality, the H264 video (lower quality but iOS-compatible) is used: ``` % ffprobe -hide_banner Who\ Can\ Find\ the\ Weirdest\ PC\ Parts\ on\ AliExpress?.mp4 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'Who Can Find the Weirdest PC Parts on AliExpress?.mp4': Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 encoder : Lavf60.16.100 Duration: 00:19:02.72, start: 0.000000, bitrate: 1846 kb/s Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x960 [SAR 1:1 DAR 2:1], 1710 kb/s, 29.97 fps, 29.97 tbr, 30k tbn (default) Metadata: handler_name : ISO Media file produced by Google Inc. vendor_id : [0][0][0][0] Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default) Metadata: handler_name : ISO Media file produced by Google Inc. vendor_id : [0][0][0][0] ``` Included a README note about the new quality option.
2024-06-15 13:36:04 -04:00
if quality == "best_ios":
# iOS has strict requirements for video files, requiring h264 or h265
# video codec and aac audio codec in MP4 container. This format string
# attempts to get the fully compatible formats first, then the h264/h265
# video codec with any M4A audio codec (because audio is faster to
# 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}"
return f"bestvideo{vcombo}+bestaudio{afmt}/best{vcombo}"
2021-11-20 10:12:08 +02:00
raise Exception(f"Unkown format {format}")
2021-10-28 11:19:17 +01:00
def get_opts(format: str, quality: str, ytdl_opts: dict) -> dict:
"""
Returns extra download options
Mostly postprocessing options
Args:
format (str): format selected
quality (str): quality of format selected (needed for some formats)
ytdl_opts (dict): current options selected
Returns:
ytdl_opts: Extra options
"""
2021-12-01 21:48:01 +01:00
opts = copy.deepcopy(ytdl_opts)
postprocessors = []
2021-10-28 11:19:17 +01:00
if format in AUDIO_FORMATS:
postprocessors.append(
{
"key": "FFmpegExtractAudio",
"preferredcodec": format,
"preferredquality": 0 if quality == "best" else quality,
}
)
# Audio formats without thumbnail
if format not in ("wav") and "writethumbnail" not in opts:
2023-02-19 21:48:18 +01:00
opts["writethumbnail"] = True
postprocessors.append(
{
"key": "FFmpegThumbnailsConvertor",
"format": "jpg",
"when": "before_dl",
}
)
postprocessors.append({"key": "FFmpegMetadata"})
postprocessors.append({"key": "EmbedThumbnail"})
2022-06-06 16:26:53 +02:00
if format == "thumbnail":
opts["skip_download"] = True
opts["writethumbnail"] = True
postprocessors.append(
{"key": "FFmpegThumbnailsConvertor", "format": "jpg", "when": "before_dl"}
)
opts["postprocessors"] = postprocessors + (
opts["postprocessors"] if "postprocessors" in opts else []
)
2021-12-01 21:48:01 +01:00
return opts