chapters and split by chapters plugins WIP
This commit is contained in:
parent
871c051aa8
commit
653681040d
5 changed files with 179 additions and 7 deletions
107
src/ytdl_sub/plugins/chapters.py
Normal file
107
src/ytdl_sub/plugins/chapters.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import re
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
from ytdl_sub.entries.entry import Entry
|
||||
from ytdl_sub.plugins.plugin import Plugin
|
||||
from ytdl_sub.plugins.plugin import PluginOptions
|
||||
from ytdl_sub.validators.regex_validator import RegexListValidator
|
||||
from ytdl_sub.validators.string_select_validator import StringSelectValidator
|
||||
from ytdl_sub.validators.validators import ListValidator
|
||||
|
||||
SPONSORBLOCK_HIGHLIGHT_CATEGORIES: Set[str] = {"poi_highlight"}
|
||||
SPONSORBLOCK_CATEGORIES: Set[str] = SPONSORBLOCK_HIGHLIGHT_CATEGORIES | {
|
||||
"sponsor",
|
||||
"intro",
|
||||
"outro",
|
||||
"selfpromo",
|
||||
"preview",
|
||||
"filler",
|
||||
"interaction",
|
||||
"music_offtopic",
|
||||
}
|
||||
|
||||
|
||||
class SponsorBlockCategoriesValidator(StringSelectValidator):
|
||||
_expected_value_type = "sponsorblock category"
|
||||
_select_values = {"all"} | SPONSORBLOCK_CATEGORIES
|
||||
|
||||
|
||||
class SponsorBlockCategoryListValidator(ListValidator[SponsorBlockCategoriesValidator]):
|
||||
_expected_value_type = "sponsorblock category"
|
||||
_inner_list_type = SponsorBlockCategoriesValidator
|
||||
|
||||
|
||||
class ChaptersOptions(PluginOptions):
|
||||
"""
|
||||
Add chapters to video files if they are present. Options to add SponsorBlock chapters and
|
||||
remove them or existing chapters.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
chapters:
|
||||
sponsorblock_categories:
|
||||
- "outro"
|
||||
- "selfpromo"
|
||||
- "preview"
|
||||
- "interaction"
|
||||
- "sponsor"
|
||||
- "music_offtopic"
|
||||
- "intro"
|
||||
remove_sponsorblock_categories: "all"
|
||||
remove_chapters_regex:
|
||||
- "sponsor"
|
||||
|
||||
"""
|
||||
|
||||
_optional_keys = {
|
||||
"sponsorblock_categories",
|
||||
"remove_sponsorblock_categories",
|
||||
"remove_chapters_regex",
|
||||
}
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
self._sponsorblock_categories = self._validate_key_if_present(
|
||||
key="sponsorblock_categories", validator=SponsorBlockCategoryListValidator
|
||||
)
|
||||
self._remove_sponsorblock_categories = self._validate_key_if_present(
|
||||
key="remove_sponsorblock_categories", validator=SponsorBlockCategoryListValidator
|
||||
)
|
||||
self._remove_chapters_regex = self._validate_key_if_present(
|
||||
key="remove_chapters_regex", validator=RegexListValidator
|
||||
)
|
||||
|
||||
@property
|
||||
def sponsorblock_categories(self) -> Optional[List[str]]:
|
||||
if self._sponsorblock_categories:
|
||||
return [validator.value for validator in self._sponsorblock_categories.list]
|
||||
return None
|
||||
|
||||
@property
|
||||
def remove_sponsorblock_categories(self) -> Optional[List[str]]:
|
||||
if self._remove_sponsorblock_categories:
|
||||
return [validator.value for validator in self._remove_sponsorblock_categories.list]
|
||||
return None
|
||||
|
||||
@property
|
||||
def remove_chapters_regex(self) -> Optional[List[re.Pattern]]:
|
||||
if self._remove_chapters_regex:
|
||||
return [validator.compiled_regex for validator in self._remove_chapters_regex.list]
|
||||
return None
|
||||
|
||||
|
||||
class ChaptersPlugin(Plugin[ChaptersOptions]):
|
||||
plugin_options_type = ChaptersOptions
|
||||
|
||||
def modify_entry(self, entry: Entry) -> Optional[Entry]:
|
||||
"""
|
||||
Tags the entry's audio file using values defined in the metadata options
|
||||
"""
|
||||
return entry
|
||||
54
src/ytdl_sub/plugins/split_by_chapters.py
Normal file
54
src/ytdl_sub/plugins/split_by_chapters.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
from typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
from ytdl_sub.entries.entry import Entry
|
||||
from ytdl_sub.plugins.plugin import Plugin
|
||||
from ytdl_sub.plugins.plugin import PluginOptions
|
||||
from ytdl_sub.validators.string_select_validator import StringSelectValidator
|
||||
|
||||
|
||||
class WhenNoChaptersValidator(StringSelectValidator):
|
||||
_expected_value_type = "when no chapters option"
|
||||
_select_values = {"pass", "drop", "error"}
|
||||
|
||||
|
||||
class SplitByChaptersOptions(PluginOptions):
|
||||
"""
|
||||
Splits a file by chapters into multiple files. Each file becomes its own entry with ``title``
|
||||
set to its chapter name, and is processed separately by other plugins.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
split_by_chapters:
|
||||
when_no_chapters: "pass" # "drop"/"error"
|
||||
"""
|
||||
|
||||
_required_keys = {"when_no_chapters"}
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
self._when_no_chapters = self._validate_key(
|
||||
key="when_no_chapters", validator=WhenNoChaptersValidator
|
||||
).value
|
||||
|
||||
@property
|
||||
def when_no_chapters(self) -> str:
|
||||
"""
|
||||
Behavior to perform when no chapters are present. Supports "pass" (continue processing),
|
||||
"drop" (exclude it from output), and "error" (stop processing for everything).
|
||||
"""
|
||||
return self._when_no_chapters
|
||||
|
||||
|
||||
class SplitByChaptersPlugin(Plugin[SplitByChaptersOptions]):
|
||||
plugin_options_type = SplitByChaptersOptions
|
||||
|
||||
def modify_entry(self, entry: Entry) -> Optional[Entry]:
|
||||
"""
|
||||
Tags the entry's audio file using values defined in the metadata options
|
||||
"""
|
||||
return entry
|
||||
|
|
@ -28,6 +28,15 @@ class RegexValidator(StringValidator):
|
|||
"""
|
||||
return self._compiled_regex.groups
|
||||
|
||||
@property
|
||||
def compiled_regex(self) -> re.Pattern:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
The regex compiled
|
||||
"""
|
||||
return self._compiled_regex
|
||||
|
||||
def match(self, input_str: str) -> Optional[List[str]]:
|
||||
"""
|
||||
Parameters
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ from ytdl_sub.subscriptions.subscription import Subscription
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def single_song_preset_dict(output_directory):
|
||||
def single_song_preset_dict(output_directory, timestamps_file_path):
|
||||
return {
|
||||
"preset": "yt_song",
|
||||
"youtube": {"video_url": "https://www.youtube.com/watch?v=2lAe1cqCOXo"},
|
||||
"youtube": {
|
||||
"video_url": "https://www.youtube.com/watch?v=gr0XWmEbiMQ",
|
||||
},
|
||||
"output_options": {"output_directory": output_directory},
|
||||
# download the worst format so it is fast
|
||||
"ytdl_options": {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
Files created in '{output_directory}'
|
||||
----------------------------------------
|
||||
YouTube Rewind 2019: For the Record | #YouTubeRewind.mp3
|
||||
Gabor Szabo - Dreams (1968) [full album].mp3
|
||||
Music Tags:
|
||||
album: Singles
|
||||
albumartist: YouTube
|
||||
artist: YouTube
|
||||
albumartist: Cubensis Records
|
||||
artist: Cubensis Records
|
||||
genre: Unset
|
||||
title: YouTube Rewind 2019: For the Record | #YouTubeRewind
|
||||
title: Gabor Szabo - Dreams (1968) [full album]
|
||||
track: 1
|
||||
year: 2019
|
||||
year: 2015
|
||||
Loading…
Reference in a new issue