chapters complete

This commit is contained in:
jbannon 2022-08-15 22:45:56 +00:00
parent cb37f5938c
commit 84c8b93e4f
2 changed files with 58 additions and 67 deletions

View file

@ -28,6 +28,18 @@ SPONSORBLOCK_CATEGORIES: Set[str] = SPONSORBLOCK_HIGHLIGHT_CATEGORIES | {
} }
def _chapters(entry: Entry) -> List[Dict]:
if entry.kwargs_contains("chapters"):
return entry.kwargs("chapters")
return []
def _sponsorblock_chapters(entry: Entry) -> List[Dict]:
if entry.kwargs_contains("sponsorblock_chapters"):
return entry.kwargs("sponsorblock_chapters")
return []
class SponsorBlockCategoriesValidator(StringSelectValidator): class SponsorBlockCategoriesValidator(StringSelectValidator):
_expected_value_type_name = "sponsorblock category" _expected_value_type_name = "sponsorblock category"
_select_values = {"all"} | SPONSORBLOCK_CATEGORIES _select_values = {"all"} | SPONSORBLOCK_CATEGORIES
@ -40,8 +52,8 @@ class SponsorBlockCategoryListValidator(ListValidator[SponsorBlockCategoriesVali
class ChaptersOptions(PluginOptions): class ChaptersOptions(PluginOptions):
""" """
Add chapters to video files if they are present. Additional options to add SponsorBlock Embeds chapters to video files if they are present. Additional options to add SponsorBlock
chapters and remove specific ones. Can also remove chapters using regex patterns. chapters and remove specific ones. Can also remove chapters using regex.
Note that at this time, chapter removal with regex will not work with chapters added via Note that at this time, chapter removal with regex will not work with chapters added via
timestamp file. timestamp file.
@ -66,6 +78,14 @@ class ChaptersOptions(PluginOptions):
- "Intro" - "Intro"
- "Outro" - "Outro"
force_key_frames: False force_key_frames: False
To simply embed chapters from the entry file and nothing more, specify the following:
.. code-block:: yaml
presets:
my_example_preset:
chapters:
""" """
_optional_keys = { _optional_keys = {
@ -97,6 +117,11 @@ class ChaptersOptions(PluginOptions):
@property @property
def sponsorblock_categories(self) -> Optional[List[str]]: def sponsorblock_categories(self) -> Optional[List[str]]:
"""
Optional. List of SponsorBlock categories to embed as chapters. Supports "sponsor",
"intro", "outro", "selfpromo", "preview", "filler", "interaction", "music_offtopic",
"poi_highlight", or "all" to include all categories.
"""
if self._sponsorblock_categories: if self._sponsorblock_categories:
category_list = [validator.value for validator in self._sponsorblock_categories.list] category_list = [validator.value for validator in self._sponsorblock_categories.list]
if "all" in category_list: if "all" in category_list:
@ -106,6 +131,11 @@ class ChaptersOptions(PluginOptions):
@property @property
def remove_sponsorblock_categories(self) -> Optional[List[str]]: def remove_sponsorblock_categories(self) -> Optional[List[str]]:
"""
Optional. List of SponsorBlock categories to remove from the output file. Can only remove
categories that are specified in ``sponsorblock_categories`` or "all", which removes
everything specified in ``sponsorblock_categories``.
"""
if self._remove_sponsorblock_categories: if self._remove_sponsorblock_categories:
category_list = [ category_list = [
validator.value for validator in self._remove_sponsorblock_categories.list validator.value for validator in self._remove_sponsorblock_categories.list
@ -117,12 +147,21 @@ class ChaptersOptions(PluginOptions):
@property @property
def remove_chapters_regex(self) -> Optional[List[re.Pattern]]: def remove_chapters_regex(self) -> Optional[List[re.Pattern]]:
"""
Optional. List of regex patterns to match chapter titles against and remove them from the
entry.
"""
if self._remove_chapters_regex: if self._remove_chapters_regex:
return [validator.compiled_regex for validator in self._remove_chapters_regex.list] return [validator.compiled_regex for validator in self._remove_chapters_regex.list]
return None return None
@property @property
def force_key_frames(self) -> bool: def force_key_frames(self) -> bool:
"""
Optional. Force keyframes at cuts when removing sections. This is slow due to needing a
re-encode, but the resulting video may have fewer artifacts around the cuts. Defaults to
False.
"""
return self._force_key_frames return self._force_key_frames
@ -137,6 +176,11 @@ class ChaptersPlugin(Plugin[ChaptersOptions]):
) )
def ytdl_options(self) -> Optional[Dict]: def ytdl_options(self) -> Optional[Dict]:
"""
Returns
-------
YTDL options to embed chapters, add/remove SponsorBlock segments, remove chapters via regex
"""
builder = YTDLOptionsBuilder() builder = YTDLOptionsBuilder()
if self.plugin_options.sponsorblock_categories: if self.plugin_options.sponsorblock_categories:
builder.add( builder.add(
@ -180,20 +224,10 @@ class ChaptersPlugin(Plugin[ChaptersOptions]):
return builder.to_dict() return builder.to_dict()
def _chapters(self, entry: Entry) -> List[Dict]:
if entry.kwargs_contains("chapters"):
return entry.kwargs("chapters")
return []
def _sponsorblock_chapters(self, entry: Entry) -> List[Dict]:
if entry.kwargs_contains("sponsorblock_chapters"):
return entry.kwargs("sponsorblock_chapters")
return []
def _get_removed_chapters(self, entry: Entry) -> List[str]: def _get_removed_chapters(self, entry: Entry) -> List[str]:
removed_chapters: List[str] = [] removed_chapters: List[str] = []
for pattern in self.plugin_options.remove_chapters_regex or []: for pattern in self.plugin_options.remove_chapters_regex or []:
for chapter in self._chapters(entry): for chapter in _chapters(entry):
if pattern.search(chapter["title"]): if pattern.search(chapter["title"]):
removed_chapters.append(chapter["title"]) removed_chapters.append(chapter["title"])
return removed_chapters return removed_chapters
@ -201,7 +235,7 @@ class ChaptersPlugin(Plugin[ChaptersOptions]):
def _get_removed_sponsorblock_category_counts(self, entry: Entry) -> Dict: def _get_removed_sponsorblock_category_counts(self, entry: Entry) -> Dict:
removed_category_counts = collections.Counter() removed_category_counts = collections.Counter()
for category in self.plugin_options.remove_sponsorblock_categories or []: for category in self.plugin_options.remove_sponsorblock_categories or []:
for chapter in self._sponsorblock_chapters(entry): for chapter in _sponsorblock_chapters(entry):
if chapter["category"] == category: if chapter["category"] == category:
removed_category_counts.update({chapter["title"]: 1}) removed_category_counts.update({chapter["title"]: 1})
@ -214,6 +248,16 @@ class ChaptersPlugin(Plugin[ChaptersOptions]):
) )
def post_process_entry(self, entry: Entry) -> Optional[FileMetadata]: def post_process_entry(self, entry: Entry) -> Optional[FileMetadata]:
"""
Parameters
----------
entry:
Entry with possibly removed chapters
Returns
-------
FileMetadata outlining which chapters/SponsorBlock segments got removed
"""
metadata_dict = {} metadata_dict = {}
removed_chapters = self._get_removed_chapters(entry) removed_chapters = self._get_removed_chapters(entry)
removed_sponsorblock = self._get_removed_sponsorblock_category_counts(entry) removed_sponsorblock = self._get_removed_sponsorblock_category_counts(entry)

View file

@ -1,53 +0,0 @@
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