From 65d4a3e9340f1dcd1efada2c913801f2aaf821e1 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Thu, 18 Aug 2022 17:27:38 -0700 Subject: [PATCH] ready --- docs/config.rst | 8 +++++ src/ytdl_sub/plugins/plugin.py | 14 +++++--- src/ytdl_sub/plugins/split_by_chapters.py | 42 +++++++++++++---------- src/ytdl_sub/utils/chapters.py | 21 +++++++++++- src/ytdl_sub/utils/thumbnail.py | 2 +- 5 files changed, 63 insertions(+), 24 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index b058dcf1..531652f4 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -219,6 +219,14 @@ regex ------------------------------------------------------------------------------- +split_by_chapters +''''''''''''''''' +.. autoclass:: ytdl_sub.plugins.split_by_chapters.SplitByChaptersOptions() + :members: when_no_chapters + :member-order: bysource + +------------------------------------------------------------------------------- + subtitles ''''''''' .. autoclass:: ytdl_sub.plugins.subtitles.SubtitleOptions() diff --git a/src/ytdl_sub/plugins/plugin.py b/src/ytdl_sub/plugins/plugin.py index c0297ccd..d0bee3f4 100644 --- a/src/ytdl_sub/plugins/plugin.py +++ b/src/ytdl_sub/plugins/plugin.py @@ -31,6 +31,11 @@ class PluginPriority: @property def modify_entry_after_split(self) -> bool: + """ + Returns + ------- + True if the plugin should modify an entry after a potential split. False otherwise. + """ return self.modify_entry >= PluginPriority.MODIFY_ENTRY_AFTER_SPLIT @@ -96,6 +101,7 @@ class Plugin(DownloadArchiver, Generic[PluginOptionsT], ABC): # TODO pass yaml snake case name in the class somewhere, and use it for the logger self._logger = Logger.get(self.__class__.__name__) + # pylint: disable=no-self-use,unused-argument def ytdl_options(self) -> Optional[Dict]: """ Returns @@ -117,9 +123,8 @@ class Plugin(DownloadArchiver, Generic[PluginOptionsT], ABC): ------- List of entries and metadata created from the source entry """ - raise NotImplemented() + return [] - # pylint: disable=no-self-use def modify_entry(self, entry: Entry) -> Optional[Entry]: """ For each entry downloaded, modify the entry in some way before sending it to @@ -136,8 +141,6 @@ class Plugin(DownloadArchiver, Generic[PluginOptionsT], ABC): """ return entry - # pylint: enable=no-self-use - def post_process_entry(self, entry: Entry) -> Optional[FileMetadata]: """ For each entry downloaded, apply post processing to it. @@ -151,6 +154,9 @@ class Plugin(DownloadArchiver, Generic[PluginOptionsT], ABC): ------- Optional file metadata for the entry media file. """ + return None + + # pylint: enable=no-self-use,unused-argument def post_process_subscription(self): """ diff --git a/src/ytdl_sub/plugins/split_by_chapters.py b/src/ytdl_sub/plugins/split_by_chapters.py index 07ba0b34..78fd43cb 100644 --- a/src/ytdl_sub/plugins/split_by_chapters.py +++ b/src/ytdl_sub/plugins/split_by_chapters.py @@ -5,6 +5,8 @@ from typing import List from typing import Optional from typing import Tuple +from yt_dlp.utils import sanitize_filename + from ytdl_sub.entries.entry import Entry from ytdl_sub.plugins.plugin import Plugin from ytdl_sub.plugins.plugin import PluginOptions @@ -17,16 +19,6 @@ from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.thumbnail import convert_download_thumbnail from ytdl_sub.validators.string_select_validator import StringSelectValidator -# -# modify_entry BEFORE SPLIT -# - audio_extract -# - subtitles? -# -# TODO: make regex's modify_entry into a new function -# and call modify_entry before split -# -# maybe modify_downloaded_entry ?? - def _split_video_ffmpeg_cmd( input_file: str, output_file: str, timestamps: List[Timestamp], idx: int @@ -53,8 +45,14 @@ class WhenNoChaptersValidator(StringSelectValidator): class SplitByChaptersOptions(PluginOptions): """ Splits a file by chapters into multiple files. Each file becomes its own entry with the - new source variables ``chapter_title``, ``chapter_index``, ``chapter_index_padded``, - ``chapter_count``. + new source variables ``chapter_title``, ``chapter_title_sanitized``, ``chapter_index``, + ``chapter_index_padded``, ``chapter_count``. + + If a file has no chapters, and ``when_no_chapters`` is set to "pass", then ``chapter_title`` is + set to the entry's title and ``chapter_index``, ``chapter_count`` are both set to 1. + + Note that when using this plugin and performing dry-run, it assumes embedded chapters are being + used with no modifications. Usage: @@ -63,7 +61,7 @@ class SplitByChaptersOptions(PluginOptions): presets: my_example_preset: split_by_chapters: - when_no_chapters: "pass" # "drop"/"error" + when_no_chapters: "pass" """ _required_keys = {"when_no_chapters"} @@ -75,7 +73,13 @@ class SplitByChaptersOptions(PluginOptions): ).value def added_source_variables(self) -> List[str]: - return ["chapter_title", "chapter_index", "chapter_index_padded", "chapter_count"] + return [ + "chapter_title", + "chapter_title_sanitized", + "chapter_index", + "chapter_index_padded", + "chapter_count", + ] @property def when_no_chapters(self) -> str: @@ -91,7 +95,7 @@ class SplitByChaptersPlugin(Plugin[SplitByChaptersOptions]): is_split_plugin = True def _create_split_entry( - self, dry_run: bool, source_entry: Entry, title: str, idx: int, chapters: Chapters + self, source_entry: Entry, title: str, idx: int, chapters: Chapters ) -> Tuple[Entry, FileMetadata]: """ Runs ffmpeg to create the split video @@ -101,17 +105,20 @@ class SplitByChaptersPlugin(Plugin[SplitByChaptersOptions]): entry.add_variables( { "chapter_title": title, + "chapter_title_sanitized": sanitize_filename(title), "chapter_index": idx + 1, "chapter_index_padded": f"{(idx + 1):02d}", "chapter_count": len(chapters.timestamps), } ) - entry._kwargs["id"] = _split_video_uid(source_uid=entry.uid, idx=idx) + # pylint: disable=protected-access + entry._kwargs["id"] = _split_video_uid(source_uid=entry.uid, idx=idx) if "chapters" in entry._kwargs: del entry._kwargs["chapters"] if "sponsorblock_chapters" in entry._kwargs: del entry._kwargs["sponsorblock_chapters"] + # pylint: enable=protected-access timestamp_begin = chapters.timestamps[idx].readable_str timestamp_end = Timestamp(entry.kwargs("duration")).readable_str @@ -119,7 +126,7 @@ class SplitByChaptersPlugin(Plugin[SplitByChaptersOptions]): timestamp_end = chapters.timestamps[idx + 1].readable_str metadata_value_dict = {} - if dry_run: + if self.is_dry_run: metadata_value_dict[ "Warning" ] = "Dry-run assumes embedded chapters with no modifications" @@ -200,7 +207,6 @@ class SplitByChaptersPlugin(Plugin[SplitByChaptersOptions]): # Format the split video as a YoutubePlaylistVideo split_videos_and_metadata.append( self._create_split_entry( - dry_run=self.is_dry_run, source_entry=entry, title=title, idx=idx, diff --git a/src/ytdl_sub/utils/chapters.py b/src/ytdl_sub/utils/chapters.py index 3d315d2b..2c016402 100644 --- a/src/ytdl_sub/utils/chapters.py +++ b/src/ytdl_sub/utils/chapters.py @@ -2,7 +2,6 @@ import json import os import re import subprocess -from io import BytesIO from typing import List from typing import Optional from typing import Tuple @@ -222,6 +221,16 @@ class Chapters: @classmethod def from_embedded_chapters(cls, file_path: str) -> "Chapters": + """ + Parameters + ---------- + file_path + File to read ffmpeg chapter metadata from + + Returns + ------- + Chapters object + """ proc = subprocess.run( [ "ffprobe", @@ -249,6 +258,16 @@ class Chapters: @classmethod def from_entry_chapters(cls, entry: Entry) -> "Chapters": + """ + Parameters + ---------- + entry + Entry with yt-dlp chapter metadata + + Returns + ------- + Chapters object + """ timestamps: List[Timestamp] = [] titles: List[str] = [] diff --git a/src/ytdl_sub/utils/thumbnail.py b/src/ytdl_sub/utils/thumbnail.py index 9bb33816..4f866a61 100644 --- a/src/ytdl_sub/utils/thumbnail.py +++ b/src/ytdl_sub/utils/thumbnail.py @@ -5,7 +5,7 @@ from ytdl_sub.entries.entry import Entry from ytdl_sub.utils.ffmpeg import FFMPEG -def convert_download_thumbnail(entry: Entry, error_if_not_found=True) -> None: +def convert_download_thumbnail(entry: Entry, error_if_not_found: bool = True) -> None: """ Converts an entry's downloaded thumbnail into jpg format