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")
|
2023-03-05 10:34:49 +01:00
|
|
|
|
2024-05-20 20:07:38 -04:00
|
|
|
|
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"
|
|
|
|
|
2023-03-05 10:34:49 +01:00
|
|
|
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
|
|
|
|
"""
|
2024-05-20 20:07:38 -04:00
|
|
|
|
2021-12-01 21:48:01 +01:00
|
|
|
opts = copy.deepcopy(ytdl_opts)
|
|
|
|
|
2023-04-11 19:44:02 +03:00
|
|
|
postprocessors = []
|
2021-10-28 11:19:17 +01:00
|
|
|
|
2023-03-05 10:34:49 +01:00
|
|
|
if format in AUDIO_FORMATS:
|
2024-05-20 20:07:38 -04:00
|
|
|
postprocessors.append(
|
|
|
|
{
|
|
|
|
"key": "FFmpegExtractAudio",
|
|
|
|
"preferredcodec": format,
|
|
|
|
"preferredquality": 0 if quality == "best" else quality,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
# Audio formats without thumbnail
|
2023-04-11 19:44:02 +03:00
|
|
|
if format not in ("wav") and "writethumbnail" not in opts:
|
2023-02-19 21:48:18 +01:00
|
|
|
opts["writethumbnail"] = True
|
2024-05-20 20:07:38 -04:00
|
|
|
postprocessors.append(
|
|
|
|
{
|
|
|
|
"key": "FFmpegThumbnailsConvertor",
|
|
|
|
"format": "jpg",
|
|
|
|
"when": "before_dl",
|
|
|
|
}
|
|
|
|
)
|
2023-04-11 19:44:02 +03:00
|
|
|
postprocessors.append({"key": "FFmpegMetadata"})
|
|
|
|
postprocessors.append({"key": "EmbedThumbnail"})
|
2024-05-20 20:07:38 -04:00
|
|
|
|
2022-06-06 16:26:53 +02:00
|
|
|
if format == "thumbnail":
|
|
|
|
opts["skip_download"] = True
|
|
|
|
opts["writethumbnail"] = True
|
2024-05-20 20:07:38 -04:00
|
|
|
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
|