kwargs not exposed outside of entry

This commit is contained in:
Jesse Bannon 2023-12-13 17:07:07 -08:00
parent 25d9b1b69c
commit eb23f59aa0
9 changed files with 85 additions and 82 deletions

View file

@ -491,21 +491,9 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
upload_date_standardized=entry.get(v.upload_date_standardized, str) upload_date_standardized=entry.get(v.upload_date_standardized, str)
) )
download_idx = self._enhanced_download_archive.num_entries download_idx = self._enhanced_download_archive.num_entries
entry.add(
{
# Tracks number of entries downloaded
v.download_index: download_idx + 1,
# Tracks number of entries with the same upload date to make them unique
v.upload_date_index: upload_date_idx + 1,
v.requested_subtitles: download_entry.kwargs_get(
v.requested_subtitles.metadata_key
),
v.chapters: download_entry.kwargs_get(v.chapters.metadata_key),
v.sponsorblock_chapters: download_entry.kwargs_get(
v.sponsorblock_chapters.metadata_key
),
v.comments: download_entry.kwargs_get(v.comments.metadata_key),
}
)
return entry return entry.add_injected_variables(
download_entry=download_entry,
download_idx=download_idx,
upload_date_idx=upload_date_idx,
)

View file

@ -42,7 +42,7 @@ class BaseEntry(ABC):
str str
The entry's unique ID The entry's unique ID
""" """
return str(self.kwargs(v.uid.metadata_key)) return str(self._kwargs[v.uid.metadata_key])
@property @property
def extractor(self: "BaseEntry") -> str: def extractor(self: "BaseEntry") -> str:
@ -52,21 +52,25 @@ class BaseEntry(ABC):
# pylint: disable=line-too-long # pylint: disable=line-too-long
# Taken from https://github.com/yt-dlp/yt-dlp/blob/e6ab678e36c40ded0aae305bbb866cdab554d417/yt_dlp/YoutubeDL.py#L3514 # Taken from https://github.com/yt-dlp/yt-dlp/blob/e6ab678e36c40ded0aae305bbb866cdab554d417/yt_dlp/YoutubeDL.py#L3514
# pylint: enable=line-too-long # pylint: enable=line-too-long
return self.kwargs_get(v.extractor_key.metadata_key) or self.kwargs(v.ie_key.metadata_key) return (
self._kwargs_get(v.extractor_key.metadata_key)
or self._kwargs_get(v.ie_key.metadata_key)
or "NO_EXTRACTOR"
)
@property @property
def title(self: "BaseEntry") -> str: def title(self: "BaseEntry") -> str:
""" """
The title of the entry. If a title does not exist, returns its unique ID. The title of the entry. If a title does not exist, returns its unique ID.
""" """
return self.kwargs_get(v.title.metadata_key, self.uid) return self._kwargs_get(v.title.metadata_key, self.uid)
@property @property
def webpage_url(self: "BaseEntry") -> str: def webpage_url(self: "BaseEntry") -> str:
""" """
The url to the webpage. The url to the webpage.
""" """
return self.kwargs(v.webpage_url.metadata_key) return self._kwargs[v.webpage_url.metadata_key]
@property @property
def info_json_ext(self) -> str: def info_json_ext(self) -> str:
@ -78,21 +82,15 @@ class BaseEntry(ABC):
""" """
The uploader id if it exists, otherwise return the unique ID. The uploader id if it exists, otherwise return the unique ID.
""" """
return self.kwargs_get(v.uploader_id.metadata_key, self.uid) return self._kwargs_get(v.uploader_id.metadata_key, self.uid)
def kwargs(self, key) -> Any: def _kwargs_get(self, key: str, default: Optional[Any] = None) -> Any:
"""Returns an internal kwarg value supplied from ytdl"""
if key not in self._kwargs:
raise KeyError(f"Expected '{key}' in {self.__class__.__name__} but does not exist.")
return self._kwargs[key]
def kwargs_get(self, key: str, default: Optional[Any] = None) -> Any:
""" """
Dict get on kwargs Dict get on kwargs
""" """
if key not in self._kwargs or self.kwargs(key) is None: if (out := self._kwargs.get(key)) is None:
return default return default
return self.kwargs(key) return out
def working_directory(self) -> str: def working_directory(self) -> str:
""" """
@ -153,7 +151,7 @@ class BaseEntry(ABC):
""" """
entry_type: Optional[str] = None entry_type: Optional[str] = None
if isinstance(entry_dict, cls): if isinstance(entry_dict, cls):
entry_type = entry_dict.kwargs_get("_type") entry_type = entry_dict._kwargs_get("_type")
if isinstance(entry_dict, dict): if isinstance(entry_dict, dict):
entry_type = entry_dict.get("_type") entry_type = entry_dict.get("_type")
@ -168,7 +166,7 @@ class BaseEntry(ABC):
""" """
entry_ext: Optional[str] = None entry_ext: Optional[str] = None
if isinstance(entry_dict, cls): if isinstance(entry_dict, cls):
entry_ext = entry_dict.kwargs_get("ext") entry_ext = entry_dict._kwargs_get("ext")
if isinstance(entry_dict, dict): if isinstance(entry_dict, dict):
entry_ext = entry_dict.get("ext") entry_ext = entry_dict.get("ext")

View file

@ -22,6 +22,8 @@ from ytdl_sub.validators.audo_codec_validator import VIDEO_CODEC_EXTS
v: VariableDefinitions = VARIABLES v: VariableDefinitions = VARIABLES
_YTDL_SUB_ENTRY_VARIABLES_KWARG_KEY: str = "ytdl_sub_entry_variables" _YTDL_SUB_ENTRY_VARIABLES_KWARG_KEY: str = "ytdl_sub_entry_variables"
ytdl_sub_chapters_from_comments = Variable("ytdl_sub_chapters_from_comments")
ytdl_sub_split_by_chapters_parent_uid = Variable("ytdl_sub_split_by_chapters_parent_uid")
TypeT = TypeVar("TypeT") TypeT = TypeVar("TypeT")
@ -70,6 +72,27 @@ class Entry(BaseEntry, Scriptable):
except ScriptVariableNotResolved: except ScriptVariableNotResolved:
return None return None
def add_injected_variables(
self, download_entry: "Entry", download_idx: int, upload_date_idx: int
) -> "Entry":
self.add(
{
# Tracks number of entries downloaded
v.download_index: download_idx + 1,
# Tracks number of entries with the same upload date to make them unique
v.upload_date_index: upload_date_idx + 1,
v.requested_subtitles: download_entry._kwargs_get(
v.requested_subtitles.metadata_key, []
),
v.chapters: download_entry._kwargs_get(v.chapters.metadata_key, []),
v.sponsorblock_chapters: download_entry._kwargs_get(
v.sponsorblock_chapters.metadata_key, []
),
v.comments: download_entry._kwargs_get(v.comments.metadata_key, []),
}
)
return self
@property @property
def ext(self) -> str: def ext(self) -> str:
""" """
@ -77,7 +100,7 @@ class Entry(BaseEntry, Scriptable):
This is not reflected in the entry. See if the mkv file exists and return "mkv" if so, This is not reflected in the entry. See if the mkv file exists and return "mkv" if so,
otherwise, return the original extension. otherwise, return the original extension.
""" """
ext = self.try_get(v.ext, str) or self.kwargs(key=v.ext.metadata_key) ext = self.try_get(v.ext, str) or self._kwargs[v.ext.metadata_key]
for possible_ext in [ext, "mkv"]: for possible_ext in [ext, "mkv"]:
file_path = str(Path(self.working_directory()) / f"{self.uid}.{possible_ext}") file_path = str(Path(self.working_directory()) / f"{self.uid}.{possible_ext}")
if os.path.isfile(file_path): if os.path.isfile(file_path):
@ -114,7 +137,7 @@ class Entry(BaseEntry, Scriptable):
The source `thumbnail` value and the actual downloaded thumbnail extension sometimes do The source `thumbnail` value and the actual downloaded thumbnail extension sometimes do
not match. Return the actual downloaded thumbnail path. not match. Return the actual downloaded thumbnail path.
""" """
thumbnails = self.kwargs_get("thumbnails", []) thumbnails = self._kwargs_get("thumbnails", [])
possible_thumbnail_exts = {"jpg", "webp"} # Always check for jpg and webp thumbs possible_thumbnail_exts = {"jpg", "webp"} # Always check for jpg and webp thumbs
for thumbnail in thumbnails: for thumbnail in thumbnails:
@ -195,3 +218,18 @@ class Entry(BaseEntry, Scriptable):
Dictionary containing all variables Dictionary containing all variables
""" """
return self.script.resolve().as_native() return self.script.resolve().as_native()
@classmethod
def create_split_entry(cls, entry: "Entry", new_uid: str) -> "Entry":
"""
Creates a copy of an entry with a new uid to use as the starting point for a split entry
"""
new_entry = copy.deepcopy(entry)
new_entry._kwargs[v.uid.metadata_key] = new_uid
new_entry.add(
{
v.uid.variable_name: new_uid,
ytdl_sub_split_by_chapters_parent_uid.variable_name: entry.uid,
}
)
return new_entry

View file

@ -19,14 +19,15 @@ v: VariableDefinitions = VARIABLES
# pylint: disable=protected-access # pylint: disable=protected-access
def _sort_entries(entries: List[TBaseEntry]) -> List[TBaseEntry]:
"""Try sorting by playlist_id first, then fall back to uid"""
return sorted(
entries, key=lambda ent: (ent.kwargs_get(v.playlist_index.metadata_key, math.inf), ent.uid)
)
class EntryParent(BaseEntry): class EntryParent(BaseEntry):
@classmethod
def _sort_entries(cls, entries: List[TBaseEntry]) -> List[TBaseEntry]:
"""Try sorting by playlist_id first, then fall back to uid"""
return sorted(
entries,
key=lambda ent: (ent._kwargs_get(v.playlist_index.metadata_key, math.inf), ent.uid),
)
def __init__(self, entry_dict: Dict, working_directory: str): def __init__(self, entry_dict: Dict, working_directory: str):
super().__init__(entry_dict=entry_dict, working_directory=working_directory) super().__init__(entry_dict=entry_dict, working_directory=working_directory)
self._parent_children: List["EntryParent"] = [] self._parent_children: List["EntryParent"] = []
@ -57,7 +58,7 @@ class EntryParent(BaseEntry):
) )
for entry in self.entry_children(): for entry in self.entry_children():
sibling_entry_metadata.append( sibling_entry_metadata.append(
{var.metadata_key: entry.kwargs_get(var.metadata_key) for var in variable_filter} {var.metadata_key: entry._kwargs_get(var.metadata_key) for var in variable_filter}
) )
return sibling_entry_metadata return sibling_entry_metadata
@ -79,7 +80,7 @@ class EntryParent(BaseEntry):
) )
for entry_child in self.entry_children(): for entry_child in self.entry_children():
entry_child.add_kwargs(kwargs_to_add) entry_child._kwargs = dict(entry_child._kwargs, **kwargs_to_add)
for parent_child in self.parent_children(): for parent_child in self.parent_children():
parent_child._set_child_variables(parents=parents + [parent_child]) parent_child._set_child_variables(parents=parents + [parent_child])
@ -99,8 +100,10 @@ class EntryParent(BaseEntry):
if entry_dict in self if entry_dict in self
] ]
self._parent_children = _sort_entries([ent for ent in entries if self.is_entry_parent(ent)]) self._parent_children = self._sort_entries(
self._entry_children = _sort_entries( [ent for ent in entries if self.is_entry_parent(ent)]
)
self._entry_children = self._sort_entries(
[ent.to_type(Entry) for ent in entries if self.is_entry(ent)] [ent.to_type(Entry) for ent in entries if self.is_entry(ent)]
) )
@ -119,7 +122,7 @@ class EntryParent(BaseEntry):
------- -------
Desired thumbnail url if it exists. None if it does not. Desired thumbnail url if it exists. None if it does not.
""" """
for thumbnail in self.kwargs_get("thumbnails", []): for thumbnail in self._kwargs_get("thumbnails", []):
if thumbnail["id"] == thumbnail_id: if thumbnail["id"] == thumbnail_id:
return thumbnail["url"] return thumbnail["url"]
return None return None
@ -129,7 +132,7 @@ class EntryParent(BaseEntry):
if isinstance(item, dict): if isinstance(item, dict):
playlist_id = item.get("playlist_id") playlist_id = item.get("playlist_id")
elif isinstance(item, BaseEntry): elif isinstance(item, BaseEntry):
playlist_id = item.kwargs_get("playlist_id") playlist_id = item._kwargs_get("playlist_id")
if not playlist_id: if not playlist_id:
return False return False

View file

@ -10,11 +10,11 @@ from ytdl_sub.config.plugin.plugin_operation import PluginOperation
from ytdl_sub.config.validators.options import OptionsDictValidator from ytdl_sub.config.validators.options import OptionsDictValidator
from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.entries.entry import ytdl_sub_chapters_from_comments
from ytdl_sub.entries.entry import ytdl_sub_split_by_chapters_parent_uid
from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.entries.script.variable_definitions import VARIABLES
from ytdl_sub.entries.script.variable_definitions import VariableDefinitions from ytdl_sub.entries.script.variable_definitions import VariableDefinitions
from ytdl_sub.utils.chapters import Chapters from ytdl_sub.utils.chapters import Chapters
from ytdl_sub.utils.chapters import ytdl_sub_chapters_from_comments
from ytdl_sub.utils.chapters import ytdl_sub_split_by_chapters_parent_uid
from ytdl_sub.utils.ffmpeg import set_ffmpeg_metadata_chapters from ytdl_sub.utils.ffmpeg import set_ffmpeg_metadata_chapters
from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.validators.regex_validator import RegexListValidator from ytdl_sub.validators.regex_validator import RegexListValidator

View file

@ -1,5 +1,3 @@
import copy
from pathlib import Path
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import List from typing import List
@ -11,11 +9,11 @@ from ytdl_sub.config.plugin.plugin import SplitPlugin
from ytdl_sub.config.plugin.plugin_operation import PluginOperation from ytdl_sub.config.plugin.plugin_operation import PluginOperation
from ytdl_sub.config.validators.options import OptionsDictValidator from ytdl_sub.config.validators.options import OptionsDictValidator
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.entries.entry import ytdl_sub_split_by_chapters_parent_uid
from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.entries.script.variable_definitions import VARIABLES
from ytdl_sub.entries.script.variable_definitions import VariableDefinitions from ytdl_sub.entries.script.variable_definitions import VariableDefinitions
from ytdl_sub.utils.chapters import Chapters from ytdl_sub.utils.chapters import Chapters
from ytdl_sub.utils.chapters import Timestamp from ytdl_sub.utils.chapters import Timestamp
from ytdl_sub.utils.chapters import ytdl_sub_split_by_chapters_parent_uid
from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.ffmpeg import FFMPEG from ytdl_sub.utils.ffmpeg import FFMPEG
from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_handler import FileHandler
@ -188,27 +186,16 @@ class SplitByChaptersPlugin(SplitPlugin[SplitByChaptersOptions]):
) )
for idx, title in enumerate(chapters.titles): for idx, title in enumerate(chapters.titles):
new_entry = copy.deepcopy(entry) new_entry = Entry.create_split_entry(
new_uid = _split_video_uid(source_uid=entry.uid, idx=idx) entry=entry, new_uid=_split_video_uid(source_uid=entry.uid, idx=idx)
new_entry.add(
{
v.uid.variable_name: new_uid,
ytdl_sub_split_by_chapters_parent_uid.variable_name: entry.uid,
}
) )
new_entry.add_kwargs({v.uid.metadata_key: new_uid})
if not self.is_dry_run: if not self.is_dry_run:
# Get the input/output file paths
input_file = entry.get_download_file_path()
output_file = str(Path(self.working_directory) / f"{new_uid}.{entry.ext}")
# Run ffmpeg to create the split the video # Run ffmpeg to create the split the video
FFMPEG.run( FFMPEG.run(
_split_video_ffmpeg_cmd( _split_video_ffmpeg_cmd(
input_file=input_file, input_file=entry.get_download_file_path(),
output_file=output_file, output_file=new_entry.get_download_file_path(),
timestamps=chapters.timestamps, timestamps=chapters.timestamps,
idx=idx, idx=idx,
) )
@ -219,8 +206,7 @@ class SplitByChaptersPlugin(SplitPlugin[SplitByChaptersOptions]):
if entry.is_thumbnail_downloaded(): if entry.is_thumbnail_downloaded():
FileHandler.copy( FileHandler.copy(
src_file_path=entry.get_download_thumbnail_path(), src_file_path=entry.get_download_thumbnail_path(),
dst_file_path=Path(self.working_directory) dst_file_path=new_entry.get_download_thumbnail_path(),
/ f"{new_uid}.{entry.get(v.thumbnail_ext, str)}",
) )
# Format the split video # Format the split video

View file

@ -4,16 +4,13 @@ from typing import List
from typing import Tuple from typing import Tuple
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.entries.entry import ytdl_sub_chapters_from_comments
from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.entries.script.variable_definitions import VARIABLES
from ytdl_sub.entries.script.variable_definitions import Variable
from ytdl_sub.entries.script.variable_definitions import VariableDefinitions from ytdl_sub.entries.script.variable_definitions import VariableDefinitions
from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.file_handler import FileMetadata
v: VariableDefinitions = VARIABLES v: VariableDefinitions = VARIABLES
ytdl_sub_chapters_from_comments = Variable("ytdl_sub_chapters_from_comments")
ytdl_sub_split_by_chapters_parent_uid = Variable("ytdl_sub_split_by_chapters_parent_uid")
class Timestamp: class Timestamp:

View file

@ -14,9 +14,9 @@ from yt_dlp import DateRange
from yt_dlp.utils import make_archive_id from yt_dlp.utils import make_archive_id
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.entries.entry import ytdl_sub_split_by_chapters_parent_uid
from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.entries.script.variable_definitions import VARIABLES
from ytdl_sub.entries.script.variable_definitions import VariableDefinitions from ytdl_sub.entries.script.variable_definitions import VariableDefinitions
from ytdl_sub.utils.chapters import ytdl_sub_split_by_chapters_parent_uid
from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.file_handler import FileMetadata

View file

@ -15,13 +15,6 @@ class TestEntry(object):
for key, expected_value in mock_entry_to_dict.items(): for key, expected_value in mock_entry_to_dict.items():
assert out[key] == expected_value, f"{key} does not equal" assert out[key] == expected_value, f"{key} does not equal"
def test_entry_missing_kwarg(self, mock_entry):
key = "dne"
expected_error_msg = f"Expected '{key}' in Entry but does not exist."
with pytest.raises(KeyError, match=expected_error_msg):
mock_entry.kwargs(key)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"upload_date, year_rev, month_rev, day_rev, month_rev_pad, day_rev_pad", "upload_date, year_rev, month_rev, day_rev, month_rev_pad, day_rev_pad",
[ [