audio extract test working

This commit is contained in:
Jesse Bannon 2023-12-12 15:01:35 -08:00
parent f97ab6aaa8
commit 5c031acd9e
8 changed files with 62 additions and 25 deletions

View file

@ -4,14 +4,14 @@ import os
from pathlib import Path from pathlib import Path
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
from typing import Set
from typing import Type from typing import Type
from typing import TypeVar from typing import TypeVar
from typing import final from typing import final
from ytdl_sub.entries.base_entry import BaseEntry from ytdl_sub.entries.base_entry import BaseEntry
from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
from ytdl_sub.entries.script.variable_definitions import Variable from ytdl_sub.entries.script.variable_definitions import Variable
from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
from ytdl_sub.utils.script import ScriptUtils from ytdl_sub.utils.script import ScriptUtils
from ytdl_sub.utils.scriptable import Scriptable from ytdl_sub.utils.scriptable import Scriptable
from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS
@ -33,10 +33,8 @@ class Entry(BaseEntry, Scriptable):
def _add_entry_kwargs_to_script(self) -> None: def _add_entry_kwargs_to_script(self) -> None:
# Add entry metadata, but avoid the `.add()` helper since it also adds sanitized # Add entry metadata, but avoid the `.add()` helper since it also adds sanitized
self.unresolvable.remove(VARIABLES.entry_metadata.variable_name) self.unresolvable.remove(v.entry_metadata.variable_name)
self.script.add( self.script.add({v.entry_metadata.variable_name: ScriptUtils.to_script(self._kwargs)})
{VARIABLES.entry_metadata.variable_name: ScriptUtils.to_script(self._kwargs)}
)
self.update_script() self.update_script()
def initialize_script(self, other: Optional[Scriptable] = None) -> "Entry": def initialize_script(self, other: Optional[Scriptable] = None) -> "Entry":
@ -53,6 +51,12 @@ class Entry(BaseEntry, Scriptable):
out = self.script.resolve(unresolvable=self.unresolvable).get_native(variable.variable_name) out = self.script.resolve(unresolvable=self.unresolvable).get_native(variable.variable_name)
return expected_type(out) return expected_type(out)
def try_get(self, variable: Variable, expected_type: Type[TType]) -> Optional[TType]:
try:
return self.get(variable=variable, expected_type=expected_type)
except ScriptVariableNotResolved:
return None
def get_str(self, variable: Variable) -> str: def get_str(self, variable: Variable) -> str:
return self.get(variable, str) return self.get(variable, str)
@ -66,7 +70,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.get_str(VARIABLES.ext) ext = self.try_get(v.ext, str) or self.kwargs(key=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):
@ -92,7 +96,7 @@ class Entry(BaseEntry, Scriptable):
------- -------
The download thumbnail's file name The download thumbnail's file name
""" """
return f"{self.get_str(VARIABLES.uid)}.{self.get_str(VARIABLES.thumbnail_ext)}" return f"{self.get_str(v.uid)}.{self.get_str(v.thumbnail_ext)}"
def get_download_thumbnail_path(self) -> str: def get_download_thumbnail_path(self) -> str:
"""Returns the entry's thumbnail's file path to where it was downloaded""" """Returns the entry's thumbnail's file path to where it was downloaded"""

View file

@ -28,7 +28,6 @@ EPOCH = _("epoch")
CHANNEL = _("channel") CHANNEL = _("channel")
CHANNEL_ID = _("channel_id") CHANNEL_ID = _("channel_id")
CREATOR = _("creator") CREATOR = _("creator")
EXT = _("ext")
TITLE = _("title") TITLE = _("title")
DESCRIPTION = _("description") DESCRIPTION = _("description")
WEBPAGE_URL = _("webpage_url") WEBPAGE_URL = _("webpage_url")

View file

@ -2,11 +2,14 @@ import os.path
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
from typing import Set
from ytdl_sub.config.plugin import Plugin from ytdl_sub.config.plugin import Plugin
from ytdl_sub.config.preset_options import OptionsDictValidator from ytdl_sub.config.preset_options import OptionsDictValidator
from ytdl_sub.config.preset_options import PluginOperation
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.script.variable_definitions import VARIABLES as v
from ytdl_sub.utils.exceptions import FileNotDownloadedException from ytdl_sub.utils.exceptions import FileNotDownloadedException
from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS
@ -65,6 +68,14 @@ class AudioExtractOptions(OptionsDictValidator):
return self._quality.value return self._quality.value
return None return None
def added_source_variables(
self, unresolved_variables: Set[str]
) -> Dict[PluginOperation, Set[str]]:
"""
Possibly changes ``ext``, so do not resolve until this has run
"""
return {PluginOperation.MODIFY_ENTRY: {v.ext.variable_name}}
class AudioExtractPlugin(Plugin[AudioExtractOptions]): class AudioExtractPlugin(Plugin[AudioExtractOptions]):
plugin_options_type = AudioExtractOptions plugin_options_type = AudioExtractOptions
@ -125,7 +136,7 @@ class AudioExtractPlugin(Plugin[AudioExtractOptions]):
new_ext = AUDIO_CODEC_TYPES_EXTENSION_MAPPING[self.plugin_options.codec] new_ext = AUDIO_CODEC_TYPES_EXTENSION_MAPPING[self.plugin_options.codec]
extracted_audio_file = entry.get_download_file_path().removesuffix(entry.ext) + new_ext extracted_audio_file = entry.get_download_file_path().removesuffix(entry.ext) + new_ext
entry.add_kwargs({"ext": new_ext}) entry.add({v.ext.variable_name: new_ext})
if not self.is_dry_run: if not self.is_dry_run:
if not os.path.isfile(extracted_audio_file): if not os.path.isfile(extracted_audio_file):

View file

@ -9,7 +9,6 @@ from ytdl_sub.config.plugin import Plugin
from ytdl_sub.config.preset_options import OptionsDictValidator from ytdl_sub.config.preset_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.script.variable_definitions import VARIABLES
from ytdl_sub.entries.script.variable_definitions import VARIABLES as v from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
from ytdl_sub.entries.variables.kwargs import YTDL_SUB_CUSTOM_CHAPTERS from ytdl_sub.entries.variables.kwargs import YTDL_SUB_CUSTOM_CHAPTERS
from ytdl_sub.utils.chapters import Chapters from ytdl_sub.utils.chapters import Chapters

View file

@ -2,12 +2,15 @@ import os
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
from typing import Set
from ytdl_sub.config.overrides import Overrides
from ytdl_sub.config.plugin import Plugin from ytdl_sub.config.plugin import Plugin
from ytdl_sub.config.plugin import PluginPriority from ytdl_sub.config.plugin import PluginPriority
from ytdl_sub.config.preset_options import OptionsDictValidator from ytdl_sub.config.preset_options import OptionsDictValidator
from ytdl_sub.config.preset_options import PluginOperation
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.entries.variables.kwargs import EXT from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
from ytdl_sub.utils.exceptions import FileNotDownloadedException from ytdl_sub.utils.exceptions import FileNotDownloadedException
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
@ -16,6 +19,7 @@ from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.validators.audo_codec_validator import FileTypeValidator from ytdl_sub.validators.audo_codec_validator import FileTypeValidator
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
from ytdl_sub.validators.string_select_validator import StringSelectValidator from ytdl_sub.validators.string_select_validator import StringSelectValidator
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
class FileConvertWithValidator(StringSelectValidator): class FileConvertWithValidator(StringSelectValidator):
@ -115,6 +119,11 @@ class FileConvertOptions(OptionsDictValidator):
""" """
return self._ffmpeg_post_process_args return self._ffmpeg_post_process_args
def added_source_variables(
self, unresolved_variables: Set[str]
) -> Dict[PluginOperation, Set[str]]:
return {PluginOperation.MODIFY_ENTRY: {v.ext.variable_name}}
class FileConvertPlugin(Plugin[FileConvertOptions]): class FileConvertPlugin(Plugin[FileConvertOptions]):
plugin_options_type = FileConvertOptions plugin_options_type = FileConvertOptions
@ -123,6 +132,20 @@ class FileConvertPlugin(Plugin[FileConvertOptions]):
modify_entry=PluginPriority.MODIFY_ENTRY_AFTER_SPLIT + 1 modify_entry=PluginPriority.MODIFY_ENTRY_AFTER_SPLIT + 1
) )
def __init__(
self,
options: FileConvertOptions,
overrides: Overrides,
enhanced_download_archive: EnhancedDownloadArchive,
):
super().__init__(
options=options,
overrides=overrides,
enhanced_download_archive=enhanced_download_archive,
)
# Lookup of entry id to what it was converted from for logging
self._converted_from_lookup: Dict[str, str] = {}
def ytdl_options(self) -> Optional[Dict]: def ytdl_options(self) -> Optional[Dict]:
""" """
Returns Returns
@ -198,13 +221,9 @@ class FileConvertPlugin(Plugin[FileConvertOptions]):
FileHandler.delete(tmp_output_file) FileHandler.delete(tmp_output_file)
if original_ext != new_ext: if original_ext != new_ext:
entry.add_kwargs( self._converted_from_lookup[entry.ytdl_uid()] = original_ext
{
"__converted_from": original_ext,
}
)
entry.add_kwargs({EXT: new_ext}) entry.add({v.ext.variable_name: new_ext})
return entry return entry
@ -212,7 +231,7 @@ class FileConvertPlugin(Plugin[FileConvertOptions]):
""" """
Add metadata about conversion if it happened Add metadata about conversion if it happened
""" """
if converted_from := entry.kwargs_get("__converted_from"): if converted_from := self._converted_from_lookup.get(entry.ytdl_uid()):
return FileMetadata(f"Converted from {converted_from}") return FileMetadata(f"Converted from {converted_from}")
return None return None

View file

@ -9,6 +9,7 @@ import mediafile
from ytdl_sub.config.plugin import Plugin from ytdl_sub.config.plugin import Plugin
from ytdl_sub.config.preset_options import OptionsDictValidator from ytdl_sub.config.preset_options import OptionsDictValidator
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.utils.logger import Logger from ytdl_sub.utils.logger import Logger
from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS
@ -132,9 +133,9 @@ class MusicTagsPlugin(Plugin[MusicTagsOptions]):
""" """
Tags the entry's audio file using values defined in the metadata options Tags the entry's audio file using values defined in the metadata options
""" """
if entry.ext not in AUDIO_CODEC_EXTS: if (ext := entry.get_str(v.ext)) not in AUDIO_CODEC_EXTS:
raise self.plugin_options.validation_exception( raise self.plugin_options.validation_exception(
f"music_tags plugin received a video with the extension '{entry.ext}'. Only audio " f"music_tags plugin received a video with the extension '{ext}'. Only audio "
f"files are supported for setting music tags. Ensure you are converting the video " f"files are supported for setting music tags. Ensure you are converting the video "
f"to audio using the audio_extract plugin." f"to audio using the audio_extract plugin."
) )

View file

@ -3,6 +3,7 @@ from typing import Any
from typing import Dict from typing import Dict
from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import Resolvable
from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
@dataclass(frozen=True) @dataclass(frozen=True)
@ -16,13 +17,17 @@ class ScriptOutput:
return {name: out.native for name, out in self.output.items()} return {name: out.native for name, out in self.output.items()}
def get(self, name: str) -> Resolvable: def get(self, name: str) -> Resolvable:
if name not in self.output:
raise ScriptVariableNotResolved(
f"Tried to access resolved variable {name}, but it has not resolved"
)
return self.output[name] return self.output[name]
def get_native(self, name: str) -> Any: def get_native(self, name: str) -> Any:
return self.output[name].native return self.get(name).native
def get_str(self, name: str) -> str: def get_str(self, name: str) -> str:
return str(self.output[name]) return str(self.get(name))
def get_int(self, name: str) -> int: def get_int(self, name: str) -> int:
out = self.get_native(name) out = self.get_native(name)

View file

@ -15,7 +15,6 @@ from ytdl_sub.downloaders.ytdlp import YTDLP
from ytdl_sub.entries.script.variable_definitions import VARIABLES as v from ytdl_sub.entries.script.variable_definitions import VARIABLES as v
from ytdl_sub.entries.variables.kwargs import DESCRIPTION from ytdl_sub.entries.variables.kwargs import DESCRIPTION
from ytdl_sub.entries.variables.kwargs import EPOCH from ytdl_sub.entries.variables.kwargs import EPOCH
from ytdl_sub.entries.variables.kwargs import EXT
from ytdl_sub.entries.variables.kwargs import EXTRACTOR from ytdl_sub.entries.variables.kwargs import EXTRACTOR
from ytdl_sub.entries.variables.kwargs import EXTRACTOR_KEY from ytdl_sub.entries.variables.kwargs import EXTRACTOR_KEY
from ytdl_sub.entries.variables.kwargs import TITLE from ytdl_sub.entries.variables.kwargs import TITLE
@ -68,7 +67,7 @@ def mock_entry_dict_factory(mock_downloaded_file_path) -> Callable:
EXTRACTOR: "mock-entry-dict", EXTRACTOR: "mock-entry-dict",
EXTRACTOR_KEY: "mock-extractor-key", EXTRACTOR_KEY: "mock-extractor-key",
TITLE: f"Mock Entry {uid}", TITLE: f"Mock Entry {uid}",
EXT: "mp4", "ext": "mp4",
UPLOAD_DATE: upload_date, UPLOAD_DATE: upload_date,
WEBPAGE_URL: f"https://{uid}.com", WEBPAGE_URL: f"https://{uid}.com",
v.playlist_metadata.metadata_key: {"thumbnails": []}, v.playlist_metadata.metadata_key: {"thumbnails": []},