ready
This commit is contained in:
parent
7450f0e727
commit
65d4a3e934
5 changed files with 63 additions and 24 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue