[FEATURE] format plugin to set yt-dlp format easier (#714)

Makes setting yt-dlp's `format` field easier by giving it its own documented plugin.

Old:
```
my_format_preset:
  ytdl_options:
    format: "best"
```

New:
```
my_format_preset:
  format: "best"
```

The old method will still work, so do not worry about updating configs ASAP. However, the option is available to save a few lines 😉
This commit is contained in:
Jesse Bannon 2023-09-13 00:02:19 -07:00 committed by GitHub
parent 792da1ba86
commit a48efdc84c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 78 additions and 52 deletions

View file

@ -183,6 +183,12 @@ file_convert
-------------------------------------------------------------------------------
format
''''''
.. autoclass:: ytdl_sub.plugins.format.FormatOptions()
-------------------------------------------------------------------------------
match_filters
'''''''''''''
.. autoclass:: ytdl_sub.plugins.match_filters.MatchFiltersOptions()

View file

@ -8,6 +8,7 @@ from ytdl_sub.plugins.chapters import ChaptersPlugin
from ytdl_sub.plugins.date_range import DateRangePlugin
from ytdl_sub.plugins.embed_thumbnail import EmbedThumbnailPlugin
from ytdl_sub.plugins.file_convert import FileConvertPlugin
from ytdl_sub.plugins.format import FormatPlugin
from ytdl_sub.plugins.internal.view import ViewPlugin
from ytdl_sub.plugins.match_filters import MatchFiltersPlugin
from ytdl_sub.plugins.music_tags import MusicTagsPlugin
@ -30,6 +31,7 @@ class PluginMapping:
"date_range": DateRangePlugin,
"embed_thumbnail": EmbedThumbnailPlugin,
"file_convert": FileConvertPlugin,
"format": FormatPlugin,
"match_filters": MatchFiltersPlugin,
"music_tags": MusicTagsPlugin,
"video_tags": VideoTagsPlugin,

View file

@ -0,0 +1,44 @@
from typing import Dict
from typing import Optional
from ytdl_sub.config.plugin import Plugin
from ytdl_sub.config.preset_options import OptionsValidator
from ytdl_sub.validators.validators import StringValidator
class FormatOptions(OptionsValidator):
"""
Set ``--format`` to pass into yt-dlp to download a specific format quality.
Uses the same syntax as yt-dlp.
Usage:
.. code-block:: yaml
presets:
my_example_preset:
format: "(bv*[height<=1080]+bestaudio/best[height<=1080])"
"""
def __init__(self, name, value):
super().__init__(name, value)
self._format = StringValidator(name=name, value=value).value
@property
def format(self) -> str:
"""
yt-dlp format, uses same syntax as yt-dlp.
"""
return self._format
class FormatPlugin(Plugin[FormatOptions]):
plugin_options_type = FormatOptions
def ytdl_options(self) -> Optional[Dict]:
"""
Returns
-------
yt-dlp format
"""
return {"format": self.plugin_options.format}

View file

@ -3,13 +3,13 @@
presets:
best_video_quality:
format: "bestvideo+bestaudio/best"
ytdl_options:
format: "bestvideo+bestaudio/best"
merge_output_format: "mp4"
max_1080p:
format: "(bv*[height<=1080]+bestaudio/best[height<=1080])"
ytdl_options:
format: "(bv*[height<=1080]+bestaudio/best[height<=1080])"
merge_output_format: "mp4"
chunk_initial_download:

View file

@ -6,8 +6,7 @@ presets:
- "_plex_base"
- "_episode_base"
- "_episode_video_tags"
ytdl_options:
format: "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)"
format: "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)"
overrides:
tv_show_poster_file_name: "poster.jpg"
tv_show_fanart_file_name: "fanart.jpg"

View file

@ -12,6 +12,7 @@ from ytdl_sub.plugins.audio_extract import AudioExtractPlugin
from ytdl_sub.plugins.chapters import ChaptersPlugin
from ytdl_sub.plugins.date_range import DateRangePlugin
from ytdl_sub.plugins.file_convert import FileConvertPlugin
from ytdl_sub.plugins.format import FormatPlugin
from ytdl_sub.plugins.match_filters import MatchFiltersPlugin
from ytdl_sub.plugins.subtitles import SubtitlesPlugin
from ytdl_sub.utils.ffmpeg import FFMPEG
@ -107,6 +108,7 @@ class SubscriptionYTDLOptions:
self._global_options,
self._output_options,
self._plugin_ytdl_options(DateRangePlugin),
self._plugin_ytdl_options(FormatPlugin),
self._user_ytdl_options, # user ytdl options...
self._info_json_only_options, # then info_json_only options
)
@ -126,6 +128,7 @@ class SubscriptionYTDLOptions:
self._plugin_ytdl_options(SubtitlesPlugin),
self._plugin_ytdl_options(ChaptersPlugin),
self._plugin_ytdl_options(AudioExtractPlugin),
self._plugin_ytdl_options(FormatPlugin),
self._user_ytdl_options, # user ytdl options...
self._download_only_options, # then download_only options
)

View file

@ -11,9 +11,8 @@ def single_song_preset_dict_old_format(output_directory):
"preset": "single",
# test multi-tags
"music_tags": {"embed_thumbnail": True, "tags": {"genres": ["multi_tag_1", "multi_tag_2"]}},
# download the worst format so it is fast
"format": "worst[ext=mp4]", # download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
"postprocessor_args": {"ffmpeg": ["-bitexact"]}, # Must add this for reproducibility
},
"overrides": {
@ -31,9 +30,8 @@ def single_song_preset_dict(output_directory):
"music_tags": {"genres": ["multi_tag_1", "multi_tag_2"]},
# test the new embed_thumbnail plugin
"embed_thumbnail": True,
# download the worst format so it is fast
"format": "worst[ext=mp4]", # download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
"postprocessor_args": {"ffmpeg": ["-bitexact"]}, # Must add this for reproducibility
},
"overrides": {
@ -53,9 +51,8 @@ def multiple_songs_preset_dict(output_directory):
return {
"preset": "albums_from_playlists",
"audio_extract": {"codec": "vorbis", "quality": 140},
# download the worst format so it is fast
"format": "worst[ext=mp4]", # download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
"postprocessor_args": {"ffmpeg": ["-bitexact"]}, # Must add this for reproducibility
},
"overrides": {

View file

@ -35,9 +35,8 @@ def sponsorblock_and_subs_preset_dict(output_directory) -> Dict:
"Outro",
],
},
# download the worst format so it is fast
"format": "worst[ext=mp4]", # download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
"postprocessor_args": {"ffmpeg": ["-bitexact"]}, # Must add this for reproducibility
},
"overrides": {"artist": "JMC"},

View file

@ -15,9 +15,8 @@ def recent_preset_dict(output_directory):
return {
"preset": "tv_show",
"date_range": {"after": "20150101"},
# download the worst format so it is fast
"format": "worst[ext=mp4]", # download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
"max_views": 100000, # do not download the popular PJ concert
},
"overrides": {

View file

@ -13,9 +13,8 @@ def single_song_video_dict(output_directory):
"output_options": {"output_directory": output_directory, "file_name": "will_error.mp4"},
# test multi-tags
"music_tags": {"genres": ["multi_tag_1", "multi_tag_2"]},
# download the worst format so it is fast
"format": "worst[ext=mp4]", # download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
"postprocessor_args": {"ffmpeg": ["-bitexact"]}, # Must add this for reproducibility
},
}

View file

@ -11,10 +11,7 @@ def subscription_dict(output_directory):
"download": "https://www.youtube.com/shorts/ucYmEqmlhFw",
# override the output directory with our fixture-generated dir
"output_options": {"output_directory": output_directory},
# download the worst format so it is fast
"ytdl_options": {
"format": "best[height<=480]",
},
"format": "best[height<=480]", # download the worst format so it is fast
"nfo_tags": {
"tags": {
"kodi_safe_title 🎸": "kodi_safe_value 🎸",

View file

@ -20,10 +20,7 @@ def regex_subscription_dict_base(output_directory):
"download": "https://youtube.com/playlist?list=PL5BC0FC26BECA5A35",
# override the output directory with our fixture-generated dir
"output_options": {"output_directory": output_directory},
# download the worst format so it is fast
"ytdl_options": {
"format": "best[height<=480]",
},
"format": "best[height<=480]", # download the worst format so it is fast
"regex": {
# tests that skip_if_match_fails defaults to True
"from": {

View file

@ -13,9 +13,8 @@ from ytdl_sub.utils.exceptions import ValidationException
def yt_album_as_chapters_preset_dict(output_directory):
return {
"preset": "albums_from_chapters",
# download the worst format so it is fast
"format": "worst[ext=mp4]", # download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
"postprocessor_args": {"ffmpeg": ["-bitexact"]}, # Must add this for reproducibility
},
"overrides": {

View file

@ -18,10 +18,7 @@ def single_video_subs_embed_preset_dict(output_directory):
"languages": ["en", "de"],
"allow_auto_generated_subtitles": True,
},
# download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
},
"format": "worst[ext=mp4]", # download the worst format so it is fast
"overrides": {"artist": "JMC"},
}

View file

@ -9,10 +9,7 @@ from ytdl_sub.subscriptions.subscription import Subscription
def subscription_dict(output_directory):
return {
"preset": "soundcloud_discography",
# download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp3]",
},
"format": "worst[ext=mp3]", # download the worst format so it is fast
"overrides": {
"track_artist": "j_b",
"sc_artist_url": "https://soundcloud.com/jessebannon",

View file

@ -9,8 +9,8 @@ from ytdl_sub.subscriptions.subscription import Subscription
def channel_preset_dict(output_directory):
return {
"preset": "tv_show",
"format": "worst[ext=mp4]", # download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]", # download the worst format so it is fast
"max_views": 100000, # do not download the popular PJ concert
"break_on_reject": False, # do not break from max views
},

View file

@ -16,10 +16,7 @@ def playlist_preset_dict(output_directory):
"season_by_collection__episode_by_year_month_day",
"collection_season_1",
],
# download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
},
"format": "worst[ext=mp4]", # download the worst format so it is fast
"output_directory_nfo_tags": {
"nfo_name": "tvshow.nfo",
"nfo_root": "test",

View file

@ -23,12 +23,8 @@ def single_video_preset_dict_old_video_tags_format(output_directory):
"output_directory": output_directory,
"maintain_download_archive": False,
},
# embed thumb into the video
"embed_thumbnail": True,
# download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
},
"embed_thumbnail": True, # embed thumb into the video
"format": "worst[ext=mp4]", # download the worst format so it is fast
# also test video tags
"video_tags": {
"tags": {
@ -49,12 +45,8 @@ def single_video_preset_dict(output_directory):
"output_directory": output_directory,
"maintain_download_archive": False,
},
# embed thumb into the video
"embed_thumbnail": True,
# download the worst format so it is fast
"ytdl_options": {
"format": "worst[ext=mp4]",
},
"embed_thumbnail": True, # embed thumb into the video
"format": "worst[ext=mp4]", # download the worst format so it is fast
# also test video tags
"video_tags": {
"title": "{title}",
@ -76,8 +68,8 @@ def single_tv_show_video_nulled_values_preset_dict(output_directory):
"thumbnail_name": "",
"info_json_name": "",
},
"format": "worst[ext=mp4]",
"ytdl_options": {
"format": "worst[ext=mp4]",
"max_downloads": 2,
},
# test override variables added by ytdl-sub

View file

@ -38,6 +38,7 @@ class TestConfigFilePartiallyValidatesPresets:
{"output_options": {"file_name": "test"}},
{"output_options": {"keep_files_after": "today"}},
{"ytdl_options": {"format": "best"}},
{"format": "best"},
{"overrides": {"a": "b"}},
],
)
@ -52,6 +53,7 @@ class TestConfigFilePartiallyValidatesPresets:
def test_success__empty_plugins(self, plugin: str):
excluded_plugins = [
"embed_thumbnail", # value is bool, not dict
"format", # value is string, not dict
]
if plugin not in excluded_plugins:
self._partial_validate({plugin: {}})