[FEATURE] Automatically handle playlists ordered in reverse (#948)
Playlists have always been a pain-point with ytdl-sub. If an author adds new videos to the end of a playlist, as opposed to the front, it breaks ytdl-sub's intuition of incremental scraping by breaking on the first (oldest) video. This update now makes it possible to handle this in the prebuilt TV Show presets:
- Add each URL variable (`url`, `url2`, ...) into the `download` portion of the subscription twice
- First definition is what we all know and use, simply scrapes first-to-last, then downloads last-to-first
- Second definition does the following:
- Check to see if a URL is a YouTube playlist URL, if so...
- Set the field to download, but with modifications to scrape last-to-first, then download first-to-last
- Otherwise...
- Set the field to an empty string, which means ytdl-sub will skip it
This commit is contained in:
parent
083db0d9dc
commit
017db953bf
62 changed files with 2015 additions and 162 deletions
|
|
@ -647,3 +647,15 @@ ytdl_sub_input_url
|
|||
:type: ``String``
|
||||
:description:
|
||||
The input URL used in ytdl-sub to create this entry.
|
||||
|
||||
ytdl_sub_input_url_count
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
:type: ``Integer``
|
||||
:description:
|
||||
The total number of input URLs as defined in the subscription.
|
||||
|
||||
ytdl_sub_input_url_index
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
:type: ``Integer``
|
||||
:description:
|
||||
The index of the input URL as defined in the subscription, top-most being the 0th index.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ Static Variables
|
|||
Subscription Variables
|
||||
----------------------
|
||||
|
||||
subscription_has_download_archive
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Returns True if the subscription has any entries recorded in a download archive. False
|
||||
otherwise.
|
||||
|
||||
subscription_indent_i
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
For subscriptions in the form of
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import mergedeep
|
|||
|
||||
from ytdl_sub.entries.entry import Entry
|
||||
from ytdl_sub.entries.script.variable_definitions import VARIABLES
|
||||
from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES
|
||||
from ytdl_sub.entries.variables.override_variables import OverrideHelpers
|
||||
from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
|
||||
from ytdl_sub.script.parser import parse
|
||||
from ytdl_sub.script.script import Script
|
||||
from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved
|
||||
|
|
@ -62,6 +62,7 @@ class Overrides(DictFormatterValidator, Scriptable):
|
|||
self.ensure_variable_name_valid(key)
|
||||
|
||||
self.unresolvable.add(VARIABLES.entry_metadata.variable_name)
|
||||
self.unresolvable.update(REQUIRED_OVERRIDE_VARIABLE_NAMES)
|
||||
|
||||
def ensure_added_plugin_variable_valid(self, added_variable: str) -> bool:
|
||||
"""
|
||||
|
|
@ -127,17 +128,10 @@ class Overrides(DictFormatterValidator, Scriptable):
|
|||
)
|
||||
return ScriptUtils.add_sanitized_variables(initial_variables)
|
||||
|
||||
def initialize_script(
|
||||
self, subscription_name: str, unresolved_variables: Set[str]
|
||||
) -> "Overrides":
|
||||
def initialize_script(self, unresolved_variables: Set[str]) -> "Overrides":
|
||||
"""
|
||||
Initialize the override script with override variables + any unresolved variables
|
||||
Initialize the override script with any unresolved variables
|
||||
"""
|
||||
self.script.add(
|
||||
ScriptUtils.add_sanitized_variables(
|
||||
{SubscriptionVariables.subscription_name(): subscription_name}
|
||||
)
|
||||
)
|
||||
self.script.add(
|
||||
self.initial_variables(
|
||||
unresolved_variables={
|
||||
|
|
|
|||
|
|
@ -198,9 +198,7 @@ class Preset(_PresetShell):
|
|||
downloader_options=self.downloader_options,
|
||||
output_options=self.output_options,
|
||||
plugins=self.plugins,
|
||||
).initialize_overrides(
|
||||
subscription_name=self.name, overrides=self.overrides
|
||||
).ensure_proper_usage()
|
||||
).initialize_preset_overrides(overrides=self.overrides).ensure_proper_usage()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
|
|||
|
|
@ -13,13 +13,24 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
|
|||
from ytdl_sub.config.preset_options import OutputOptions
|
||||
from ytdl_sub.config.validators.options import OptionsValidator
|
||||
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
|
||||
from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
|
||||
from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_NAMES
|
||||
from ytdl_sub.script.script import Script
|
||||
from ytdl_sub.script.script import _is_function
|
||||
from ytdl_sub.utils.scriptable import BASE_SCRIPT
|
||||
from ytdl_sub.validators.string_formatter_validators import to_variable_dependency_format_string
|
||||
from ytdl_sub.validators.string_formatter_validators import validate_formatters
|
||||
|
||||
# Entry variables to mock during validation
|
||||
_DUMMY_ENTRY_VARIABLES: Dict[str, str] = {
|
||||
name: to_variable_dependency_format_string(
|
||||
# pylint: disable=protected-access
|
||||
script=BASE_SCRIPT,
|
||||
parsed_format_string=BASE_SCRIPT._variables[name]
|
||||
# pylint: enable=protected-access
|
||||
)
|
||||
for name in BASE_SCRIPT.variable_names
|
||||
}
|
||||
|
||||
|
||||
def _add_dummy_variables(variables: Iterable[str]) -> Dict[str, str]:
|
||||
dummy_variables: Dict[str, str] = {}
|
||||
|
|
@ -72,20 +83,7 @@ def _get_added_and_modified_variables(
|
|||
|
||||
|
||||
def _override_variables(overrides: Overrides) -> Set[str]:
|
||||
return set(list(overrides.initial_variables().keys())) | {
|
||||
SubscriptionVariables.subscription_name()
|
||||
}
|
||||
|
||||
|
||||
_DUMMY_ENTRY_VARIABLES: Dict[str, str] = {
|
||||
name: to_variable_dependency_format_string(
|
||||
# pylint: disable=protected-access
|
||||
script=BASE_SCRIPT,
|
||||
parsed_format_string=BASE_SCRIPT._variables[name]
|
||||
# pylint: enable=protected-access
|
||||
)
|
||||
for name in BASE_SCRIPT.variable_names
|
||||
}
|
||||
return set(list(overrides.initial_variables().keys()))
|
||||
|
||||
|
||||
class VariableValidation:
|
||||
|
|
@ -103,13 +101,11 @@ class VariableValidation:
|
|||
self.resolved_variables: Set[str] = set()
|
||||
self.unresolved_variables: Set[str] = set()
|
||||
|
||||
def initialize_overrides(
|
||||
self, subscription_name: str, overrides: Overrides
|
||||
) -> "VariableValidation":
|
||||
def initialize_preset_overrides(self, overrides: Overrides) -> "VariableValidation":
|
||||
"""
|
||||
Do some gymnastics to initialize the Overrides script.
|
||||
"""
|
||||
override_variables = _override_variables(overrides)
|
||||
override_variables = set(list(overrides.initial_variables().keys()))
|
||||
|
||||
# Set resolved variables as all entry + override variables
|
||||
# at this point to generate every possible added/modified variable
|
||||
|
|
@ -145,9 +141,7 @@ class VariableValidation:
|
|||
# Initialize overrides with unresolved variables + modified variables to throw an error.
|
||||
# For modified variables, this is to prevent a resolve(update=True) to setting any
|
||||
# dependencies until it has been explicitly added
|
||||
overrides = overrides.initialize_script(
|
||||
subscription_name=subscription_name, unresolved_variables=self.unresolved_variables
|
||||
)
|
||||
overrides = overrides.initialize_script(unresolved_variables=self.unresolved_variables)
|
||||
|
||||
# copy the script and mock entry variables
|
||||
self.script = copy.deepcopy(overrides.script)
|
||||
|
|
@ -162,7 +156,16 @@ class VariableValidation:
|
|||
def _update_script(self) -> None:
|
||||
_ = self.script.resolve(unresolvable=self.unresolved_variables, update=True)
|
||||
|
||||
def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> Set[str]:
|
||||
def _add_subscription_override_variables(self) -> None:
|
||||
"""
|
||||
Add dummy subscription variables for script validation
|
||||
"""
|
||||
self.resolved_variables |= REQUIRED_OVERRIDE_VARIABLE_NAMES
|
||||
|
||||
def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None:
|
||||
"""
|
||||
Add dummy variables for script validation
|
||||
"""
|
||||
added_variables = options.added_variables(
|
||||
resolved_variables=self.resolved_variables,
|
||||
unresolved_variables=self.unresolved_variables,
|
||||
|
|
@ -175,14 +178,14 @@ class VariableValidation:
|
|||
self.resolved_variables |= resolved_variables
|
||||
self.unresolved_variables -= resolved_variables
|
||||
|
||||
return added_variables
|
||||
|
||||
def ensure_proper_usage(self) -> None:
|
||||
"""
|
||||
Validate variables resolve as plugins are executed, and return
|
||||
a mock script which contains actualized added variables from the plugins
|
||||
"""
|
||||
|
||||
self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options)
|
||||
self._add_subscription_override_variables()
|
||||
|
||||
# Metadata variables to be added
|
||||
for plugin_options in PluginMapping.order_options_by(
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ class InfoJsonDownloader(SourcePlugin[InfoJsonDownloaderOptions]):
|
|||
)
|
||||
entries.append(entry)
|
||||
|
||||
# TODO: MATCH A URL TO A URL_VALIDATOR !!!
|
||||
for entry in sorted(entries, key=lambda ent: ent.get(v.download_index, int)):
|
||||
# Remove each entry from the live download archive since it will get re-added
|
||||
# unless it is filtered
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from ytdl_sub.entries.entry import Entry
|
|||
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
|
||||
|
||||
|
||||
class SourcePluginExtension(Plugin[TOptionsValidator], ABC):
|
||||
class SourcePluginExtension(Plugin[TOptionsValidator], Generic[TOptionsValidator], ABC):
|
||||
"""
|
||||
Plugins that get added automatically by using a downloader. Downloader options
|
||||
are the plugin options.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from ytdl_sub.entries.script.variable_definitions import VARIABLES
|
|||
from ytdl_sub.entries.script.variable_definitions import VariableDefinitions
|
||||
from ytdl_sub.utils.file_handler import FileHandler
|
||||
from ytdl_sub.utils.logger import Logger
|
||||
from ytdl_sub.utils.script import ScriptUtils
|
||||
from ytdl_sub.utils.thumbnail import ThumbnailTypes
|
||||
from ytdl_sub.utils.thumbnail import download_and_convert_url_thumbnail
|
||||
from ytdl_sub.utils.thumbnail import try_convert_download_thumbnail
|
||||
|
|
@ -41,7 +42,30 @@ class URLDownloadState:
|
|||
self.entries_downloaded = 0
|
||||
|
||||
|
||||
class UrlDownloaderThumbnailPlugin(SourcePluginExtension):
|
||||
class UrlDownloaderBasePluginExtension(SourcePluginExtension[MultiUrlValidator]):
|
||||
def _match_entry_to_url_validator(self, entry: Entry) -> UrlValidator:
|
||||
"""
|
||||
Handle matching a URL to its original validator. This is for .info.json updates
|
||||
when older entries have missing variables
|
||||
"""
|
||||
input_url_idx = entry.get(v.ytdl_sub_input_url_index, int)
|
||||
entry_input_url = entry.get(v.ytdl_sub_input_url, str)
|
||||
|
||||
if 0 <= input_url_idx < len(self.plugin_options.urls.list):
|
||||
validator = self.plugin_options.urls.list[input_url_idx]
|
||||
if self.overrides.apply_formatter(validator.url) == entry_input_url:
|
||||
return validator
|
||||
|
||||
# Match the first validator based on the URL, if one exists
|
||||
for validator in self.plugin_options.urls.list:
|
||||
if self.overrides.apply_formatter(validator.url) == entry_input_url:
|
||||
return validator
|
||||
|
||||
# Return the first validator if none exist
|
||||
return self.plugin_options.urls.list[0]
|
||||
|
||||
|
||||
class UrlDownloaderThumbnailPlugin(UrlDownloaderBasePluginExtension):
|
||||
def __init__(
|
||||
self,
|
||||
options: MultiUrlValidator,
|
||||
|
|
@ -54,10 +78,6 @@ class UrlDownloaderThumbnailPlugin(SourcePluginExtension):
|
|||
enhanced_download_archive=enhanced_download_archive,
|
||||
)
|
||||
self._thumbnails_downloaded: Set[str] = set()
|
||||
self._collection_url_mapping: Dict[str, UrlValidator] = {
|
||||
self.overrides.apply_formatter(collection_url.url): collection_url
|
||||
for collection_url in options.urls.list
|
||||
}
|
||||
|
||||
def _download_parent_thumbnails(
|
||||
self,
|
||||
|
|
@ -136,15 +156,14 @@ class UrlDownloaderThumbnailPlugin(SourcePluginExtension):
|
|||
if not self.is_dry_run:
|
||||
try_convert_download_thumbnail(entry=entry)
|
||||
|
||||
if (input_url := entry.get(v.ytdl_sub_input_url, str)) in self._collection_url_mapping:
|
||||
self._download_url_thumbnails(
|
||||
collection_url=self._collection_url_mapping[input_url],
|
||||
entry=entry,
|
||||
)
|
||||
self._download_url_thumbnails(
|
||||
collection_url=self._match_entry_to_url_validator(entry=entry),
|
||||
entry=entry,
|
||||
)
|
||||
return entry
|
||||
|
||||
|
||||
class UrlDownloaderCollectionVariablePlugin(SourcePluginExtension):
|
||||
class UrlDownloaderCollectionVariablePlugin(UrlDownloaderBasePluginExtension):
|
||||
def __init__(
|
||||
self,
|
||||
options: MultiUrlValidator,
|
||||
|
|
@ -157,25 +176,12 @@ class UrlDownloaderCollectionVariablePlugin(SourcePluginExtension):
|
|||
enhanced_download_archive=enhanced_download_archive,
|
||||
)
|
||||
self._thumbnails_downloaded: Set[str] = set()
|
||||
self._collection_url_mapping: Dict[str, UrlValidator] = {
|
||||
self.overrides.apply_formatter(collection_url.url): collection_url
|
||||
for collection_url in options.urls.list
|
||||
}
|
||||
|
||||
def modify_entry_metadata(self, entry: Entry) -> Optional[Entry]:
|
||||
"""
|
||||
Add collection variables to the entry
|
||||
"""
|
||||
# COLLECTION_URL is a recent variable that may not exist for old entries when updating.
|
||||
# Try to use source_webpage_url if it does not exist
|
||||
entry_collection_url = entry.get(v.ytdl_sub_input_url, str)
|
||||
|
||||
# If the collection URL cannot find its mapping, use the last URL
|
||||
collection_url = (
|
||||
self._collection_url_mapping.get(entry_collection_url)
|
||||
or list(self._collection_url_mapping.values())[-1]
|
||||
)
|
||||
|
||||
collection_url = self._match_entry_to_url_validator(entry=entry)
|
||||
entry.add(collection_url.variables.dict_with_format_strings)
|
||||
|
||||
return entry
|
||||
|
|
@ -232,8 +238,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
self._downloaded_entries: Set[str] = set()
|
||||
self._url_state: Optional[URLDownloadState] = None
|
||||
|
||||
@property
|
||||
def download_ytdl_options(self) -> Dict:
|
||||
def download_ytdl_options(self, url_idx: Optional[int] = None) -> Dict:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -242,19 +247,26 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
return (
|
||||
self._download_ytdl_options_builder.clone()
|
||||
.add(self.ytdl_option_defaults(), before=True)
|
||||
.add(
|
||||
self.plugin_options.urls.list[url_idx].ytdl_options.dict
|
||||
if url_idx is not None
|
||||
else None,
|
||||
before=True,
|
||||
)
|
||||
.to_dict()
|
||||
)
|
||||
|
||||
@property
|
||||
def metadata_ytdl_options(self) -> Dict:
|
||||
def metadata_ytdl_options(self, ytdl_option_overrides: Dict) -> Dict:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
YTDL options dict for fetching metadata
|
||||
"""
|
||||
|
||||
return (
|
||||
self._metadata_ytdl_options_builder.clone()
|
||||
.add(self.ytdl_option_defaults(), before=True)
|
||||
.add(ytdl_option_overrides, before=True)
|
||||
.to_dict()
|
||||
)
|
||||
|
||||
|
|
@ -265,7 +277,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
-------
|
||||
True if dry-run is enabled. False otherwise.
|
||||
"""
|
||||
return self.download_ytdl_options.get("skip_download", False)
|
||||
return self.download_ytdl_options().get("skip_download", False)
|
||||
|
||||
@property
|
||||
def is_entry_thumbnails_enabled(self) -> bool:
|
||||
|
|
@ -274,7 +286,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
-------
|
||||
True if entry thumbnails should be downloaded. False otherwise.
|
||||
"""
|
||||
return self.download_ytdl_options.get("writethumbnail", False)
|
||||
return self.download_ytdl_options().get("writethumbnail", False)
|
||||
|
||||
###############################################################################################
|
||||
# DOWNLOAD FUNCTIONS
|
||||
|
|
@ -301,7 +313,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
clear_info_json_files
|
||||
Whether to delete info.json files after yield
|
||||
"""
|
||||
archive_path = self.download_ytdl_options.get("download_archive", "")
|
||||
archive_path = self.download_ytdl_options().get("download_archive", "")
|
||||
backup_archive_path = f"{archive_path}.backup"
|
||||
|
||||
# If archive path exists, maintain download archive is enable
|
||||
|
|
@ -336,7 +348,9 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
|
||||
def _extract_entry_info_with_retry(self, entry: Entry) -> Entry:
|
||||
download_entry_dict = YTDLP.extract_info_with_retry(
|
||||
ytdl_options_overrides=self.download_ytdl_options,
|
||||
ytdl_options_overrides=self.download_ytdl_options(
|
||||
url_idx=entry.get(v.ytdl_sub_input_url_index, int)
|
||||
),
|
||||
is_downloaded_fn=None if self.is_dry_run else entry.is_downloaded,
|
||||
is_thumbnail_downloaded_fn=None
|
||||
if (self.is_dry_run or not self.is_entry_thumbnails_enabled)
|
||||
|
|
@ -352,27 +366,29 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
self, entries: List[Entry], download_reversed: bool
|
||||
) -> Iterator[Entry]:
|
||||
# Iterate a list of entries, and delete the entries after yielding
|
||||
indices = list(range(len(entries)))
|
||||
entries_to_iter: List[Optional[Entry]] = entries
|
||||
|
||||
indices = list(range(len(entries_to_iter)))
|
||||
if download_reversed:
|
||||
indices = reversed(indices)
|
||||
|
||||
for idx in indices:
|
||||
self._url_state.entries_downloaded += 1
|
||||
|
||||
if self._is_downloaded(entries[idx]):
|
||||
if self._is_downloaded(entries_to_iter[idx]):
|
||||
download_logger.info(
|
||||
"Already downloaded entry %d/%d: %s",
|
||||
self._url_state.entries_downloaded,
|
||||
self._url_state.entries_total,
|
||||
entries[idx].title,
|
||||
entries_to_iter[idx].title,
|
||||
)
|
||||
del entries[idx]
|
||||
entries_to_iter[idx] = None
|
||||
continue
|
||||
|
||||
yield entries[idx]
|
||||
self._mark_downloaded(entries[idx])
|
||||
yield entries_to_iter[idx]
|
||||
self._mark_downloaded(entries_to_iter[idx])
|
||||
|
||||
del entries[idx]
|
||||
entries_to_iter[idx] = None
|
||||
|
||||
def _iterate_parent_entry(
|
||||
self, parent: EntryParent, download_reversed: bool
|
||||
|
|
@ -390,7 +406,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
yield entry_child
|
||||
|
||||
def _download_url_metadata(
|
||||
self, url: str, include_sibling_metadata: bool
|
||||
self, url: str, include_sibling_metadata: bool, ytdl_options_overrides: Dict
|
||||
) -> Tuple[List[EntryParent], List[Entry]]:
|
||||
"""
|
||||
Downloads only info.json files and forms EntryParent trees
|
||||
|
|
@ -398,7 +414,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
with self._separate_download_archives():
|
||||
entry_dicts = YTDLP.extract_info_via_info_json(
|
||||
working_directory=self.working_directory,
|
||||
ytdl_options_overrides=self.metadata_ytdl_options,
|
||||
ytdl_options_overrides=ytdl_options_overrides,
|
||||
log_prefix_on_info_json_dl="Downloading metadata for",
|
||||
url=url,
|
||||
)
|
||||
|
|
@ -439,34 +455,50 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
|
|||
):
|
||||
yield orphan
|
||||
|
||||
def _download_metadata(self, url: str, validator: UrlValidator) -> Iterable[Entry]:
|
||||
metadata_ytdl_options = self.metadata_ytdl_options(
|
||||
ytdl_option_overrides=validator.ytdl_options.dict
|
||||
)
|
||||
download_reversed = ScriptUtils.bool_formatter_output(
|
||||
self.overrides.apply_formatter(validator.download_reverse)
|
||||
)
|
||||
|
||||
parents, orphan_entries = self._download_url_metadata(
|
||||
url=url,
|
||||
include_sibling_metadata=validator.include_sibling_metadata,
|
||||
ytdl_options_overrides=metadata_ytdl_options,
|
||||
)
|
||||
|
||||
# TODO: Encapsulate this logic into its own class
|
||||
self._url_state = URLDownloadState(
|
||||
entries_total=sum(parent.num_children() for parent in parents) + len(orphan_entries)
|
||||
)
|
||||
|
||||
download_logger.info("Beginning downloads for %s", url)
|
||||
for entry in self._iterate_entries(
|
||||
parents=parents,
|
||||
orphans=orphan_entries,
|
||||
download_reversed=download_reversed,
|
||||
):
|
||||
yield entry
|
||||
|
||||
def download_metadata(self) -> Iterable[Entry]:
|
||||
"""The function to perform the download of all media entries"""
|
||||
# download the bottom-most urls first since they are top-priority
|
||||
for collection_url in reversed(self.collection.urls.list):
|
||||
for idx, url_validator in reversed(list(enumerate(self.collection.urls.list))):
|
||||
# URLs can be empty. If they are, then skip
|
||||
if not (url := self.overrides.apply_formatter(collection_url.url)):
|
||||
if not (url := self.overrides.apply_formatter(url_validator.url)):
|
||||
continue
|
||||
|
||||
parents, orphan_entries = self._download_url_metadata(
|
||||
url=url, include_sibling_metadata=collection_url.include_sibling_metadata
|
||||
)
|
||||
|
||||
# TODO: Encapsulate this logic into its own class
|
||||
self._url_state = URLDownloadState(
|
||||
entries_total=sum(parent.num_children() for parent in parents) + len(orphan_entries)
|
||||
)
|
||||
|
||||
download_logger.info(
|
||||
"Beginning downloads for %s", self.overrides.apply_formatter(collection_url.url)
|
||||
)
|
||||
for entry in self._iterate_entries(
|
||||
parents=parents,
|
||||
orphans=orphan_entries,
|
||||
download_reversed=collection_url.download_reverse,
|
||||
):
|
||||
for entry in self._download_metadata(url=url, validator=url_validator):
|
||||
entry.initialize_script(self.overrides).add(
|
||||
{v.ytdl_sub_input_url: self.overrides.apply_formatter(collection_url.url)}
|
||||
{
|
||||
v.ytdl_sub_input_url: url,
|
||||
v.ytdl_sub_input_url_index: idx,
|
||||
v.ytdl_sub_input_url_count: len(self.collection.urls.list),
|
||||
}
|
||||
)
|
||||
|
||||
yield entry
|
||||
|
||||
def download(self, entry: Entry) -> Optional[Entry]:
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ from typing import Optional
|
|||
from typing import Set
|
||||
|
||||
from ytdl_sub.config.plugin.plugin_operation import PluginOperation
|
||||
from ytdl_sub.config.preset_options import YTDLOptions
|
||||
from ytdl_sub.config.validators.options import OptionsValidator
|
||||
from ytdl_sub.script.parser import parse
|
||||
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import OverridesBooleanFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
|
||||
from ytdl_sub.validators.validators import BoolValidator
|
||||
|
|
@ -49,6 +51,7 @@ class UrlValidator(StrictDictValidator):
|
|||
"source_thumbnails",
|
||||
"playlist_thumbnails",
|
||||
"download_reverse",
|
||||
"ytdl_options",
|
||||
"include_sibling_metadata",
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +80,10 @@ class UrlValidator(StrictDictValidator):
|
|||
key="playlist_thumbnails", validator=UrlThumbnailListValidator, default=[]
|
||||
)
|
||||
self._download_reverse = self._validate_key(
|
||||
key="download_reverse", validator=BoolValidator, default=True
|
||||
key="download_reverse", validator=OverridesBooleanFormatterValidator, default="True"
|
||||
)
|
||||
self._ytdl_options = self._validate_key(
|
||||
key="ytdl_options", validator=YTDLOptions, default={}
|
||||
)
|
||||
self._include_sibling_metadata = self._validate_key(
|
||||
key="include_sibling_metadata", validator=BoolValidator, default=False
|
||||
|
|
@ -148,12 +154,20 @@ class UrlValidator(StrictDictValidator):
|
|||
return self._source_thumbnails
|
||||
|
||||
@property
|
||||
def download_reverse(self) -> bool:
|
||||
def download_reverse(self) -> OverridesBooleanFormatterValidator:
|
||||
"""
|
||||
Optional. Whether to download entries in the reverse order of the metadata downloaded.
|
||||
Defaults to True.
|
||||
"""
|
||||
return self._download_reverse.value
|
||||
return self._download_reverse
|
||||
|
||||
@property
|
||||
def ytdl_options(self) -> YTDLOptions:
|
||||
"""
|
||||
Optional. ``ytdl_options`` that only apply to this URL. These take precedence
|
||||
over the plugin ``ytdl_options``.
|
||||
"""
|
||||
return self._ytdl_options
|
||||
|
||||
@property
|
||||
def include_sibling_metadata(self) -> bool:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from ytdl_sub.entries.script.variable_definitions import VariableDefinitions
|
|||
|
||||
v: VariableDefinitions = VARIABLES
|
||||
|
||||
# TODO: Make this a proper class with docstrings
|
||||
CUSTOM_FUNCTION_SCRIPTS: Dict[str, str] = {
|
||||
#############################################################################################
|
||||
# SIBLING GETTER
|
||||
|
|
|
|||
|
|
@ -750,6 +750,24 @@ class YtdlSubVariableDefinitions(ABC):
|
|||
"""
|
||||
return StringVariable(variable_name="ytdl_sub_input_url", definition="{ %string('') }")
|
||||
|
||||
@cached_property
|
||||
def ytdl_sub_input_url_index(self: "VariableDefinitions") -> IntegerVariable:
|
||||
"""
|
||||
:description:
|
||||
The index of the input URL as defined in the subscription, top-most being the 0th index.
|
||||
"""
|
||||
# init as -1 so if prior downloaded entries are known when they do not have this value
|
||||
# in their .info.json
|
||||
return IntegerVariable(variable_name="ytdl_sub_input_url_index", definition="{ %int(-1) }")
|
||||
|
||||
@cached_property
|
||||
def ytdl_sub_input_url_count(self: "VariableDefinitions") -> IntegerVariable:
|
||||
"""
|
||||
:description:
|
||||
The total number of input URLs as defined in the subscription.
|
||||
"""
|
||||
return IntegerVariable(variable_name="ytdl_sub_input_url_count", definition="{ %int(0) }")
|
||||
|
||||
@cached_property
|
||||
def download_index(self: "VariableDefinitions") -> IntegerVariable:
|
||||
"""
|
||||
|
|
@ -1100,6 +1118,8 @@ class VariableDefinitions(
|
|||
self.chapters,
|
||||
self.sponsorblock_chapters,
|
||||
self.ytdl_sub_input_url,
|
||||
self.ytdl_sub_input_url_index,
|
||||
self.ytdl_sub_input_url_count,
|
||||
}
|
||||
|
||||
@cache
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from typing import TypeVar
|
|||
|
||||
from ytdl_sub.script.types.array import Array
|
||||
from ytdl_sub.script.types.map import Map
|
||||
from ytdl_sub.script.types.resolvable import Boolean
|
||||
from ytdl_sub.script.types.resolvable import Integer
|
||||
from ytdl_sub.script.types.resolvable import String
|
||||
|
||||
|
|
@ -63,6 +64,13 @@ class Variable(ABC):
|
|||
"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BooleanVariable(Variable):
|
||||
@classmethod
|
||||
def human_readable_type(cls) -> str:
|
||||
return Boolean.__name__
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StringVariable(Variable):
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
from typing import Dict
|
||||
from typing import Set
|
||||
|
||||
from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS
|
||||
from ytdl_sub.entries.script.variable_definitions import VARIABLE_SCRIPTS
|
||||
from ytdl_sub.entries.script.variable_types import BooleanVariable
|
||||
from ytdl_sub.entries.script.variable_types import MapVariable
|
||||
from ytdl_sub.entries.script.variable_types import StringVariable
|
||||
from ytdl_sub.entries.script.variable_types import Variable
|
||||
from ytdl_sub.script.functions import Functions
|
||||
from ytdl_sub.script.utils.name_validation import is_valid_name
|
||||
|
||||
|
|
@ -9,15 +16,15 @@ SUBSCRIPTION_ARRAY = "subscription_array"
|
|||
|
||||
class SubscriptionVariables:
|
||||
@staticmethod
|
||||
def subscription_name() -> str:
|
||||
def subscription_name() -> StringVariable:
|
||||
"""
|
||||
Name of the subscription. For subscriptions types that use a prefix (``~``, ``+``),
|
||||
the prefix and all whitespace afterwards is stripped from the subscription name.
|
||||
"""
|
||||
return "subscription_name"
|
||||
return StringVariable(variable_name="subscription_name", definition="{ %string('') }")
|
||||
|
||||
@staticmethod
|
||||
def subscription_value() -> str:
|
||||
def subscription_value() -> StringVariable:
|
||||
"""
|
||||
For subscriptions in the form of
|
||||
|
||||
|
|
@ -27,10 +34,10 @@ class SubscriptionVariables:
|
|||
|
||||
``subscription_value`` gets set to ``https://...``.
|
||||
"""
|
||||
return "subscription_value"
|
||||
return StringVariable(variable_name="subscription_value", definition="{ %string('') }")
|
||||
|
||||
@staticmethod
|
||||
def subscription_indent_i(index: int) -> str:
|
||||
def subscription_indent_i(index: int) -> StringVariable:
|
||||
"""
|
||||
For subscriptions in the form of
|
||||
|
||||
|
|
@ -43,10 +50,12 @@ class SubscriptionVariables:
|
|||
``subscription_indent_1`` and ``subscription_indent_2`` get set to
|
||||
``Indent Value 1`` and ``Indent Value 2``.
|
||||
"""
|
||||
return f"subscription_indent_{index + 1}"
|
||||
return StringVariable(
|
||||
variable_name=f"subscription_indent_{index + 1}", definition="{ %string('') }"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def subscription_value_i(index: int) -> str:
|
||||
def subscription_value_i(index: int) -> StringVariable:
|
||||
"""
|
||||
For subscriptions in the form of
|
||||
|
||||
|
|
@ -60,10 +69,12 @@ class SubscriptionVariables:
|
|||
and ``https://url2.com/...``. Note that ``subscription_value_1`` also gets set to
|
||||
``subscription_value``.
|
||||
"""
|
||||
return f"subscription_value_{index + 1}"
|
||||
return StringVariable(
|
||||
variable_name=f"subscription_value_{index + 1}", definition="{ %string('') }"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def subscription_map() -> str:
|
||||
def subscription_map() -> MapVariable:
|
||||
"""
|
||||
For subscriptions in the form of
|
||||
|
||||
|
|
@ -89,7 +100,17 @@ class SubscriptionVariables:
|
|||
]
|
||||
}
|
||||
"""
|
||||
return "subscription_map"
|
||||
return MapVariable(variable_name="subscription_map", definition="{ {} }")
|
||||
|
||||
@staticmethod
|
||||
def subscription_has_download_archive() -> BooleanVariable:
|
||||
"""
|
||||
Returns True if the subscription has any entries recorded in a download archive. False
|
||||
otherwise.
|
||||
"""
|
||||
return BooleanVariable(
|
||||
variable_name="subscription_has_download_archive", definition="{ %bool(True) }"
|
||||
)
|
||||
|
||||
|
||||
class OverrideHelpers:
|
||||
|
|
@ -124,3 +145,17 @@ class OverrideHelpers:
|
|||
return is_valid_name(name=name[1:])
|
||||
|
||||
return is_valid_name(name=name)
|
||||
|
||||
|
||||
REQUIRED_OVERRIDE_VARIABLES: Set[Variable] = {
|
||||
SubscriptionVariables.subscription_name(),
|
||||
SubscriptionVariables.subscription_has_download_archive(),
|
||||
}
|
||||
|
||||
REQUIRED_OVERRIDE_VARIABLE_DEFINITIONS: Dict[str, str] = {
|
||||
var.variable_name: var.definition for var in REQUIRED_OVERRIDE_VARIABLES
|
||||
}
|
||||
|
||||
REQUIRED_OVERRIDE_VARIABLE_NAMES: Set[str] = {
|
||||
var.variable_name for var in REQUIRED_OVERRIDE_VARIABLES
|
||||
}
|
||||
|
|
|
|||
|
|
@ -330,3 +330,418 @@ presets:
|
|||
url98: "{subscription_value_98}"
|
||||
url99: "{subscription_value_99}"
|
||||
url100: "{subscription_value_100}"
|
||||
|
||||
|
||||
# multi-url with bilateral scraping built into it via
|
||||
# inspection of the URL and conditionally adding another
|
||||
# ytdl-sub url download with the scraping and download reversed
|
||||
_multi_url_bilateral_inner:
|
||||
preset:
|
||||
- "_url_bilateral_overrides"
|
||||
|
||||
download:
|
||||
- url: "{%bilateral_url(url) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url2) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url3) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url4) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url5) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url6) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url7) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url8) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url9) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url10) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url11) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url12) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url13) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url14) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url15) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url16) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url17) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url18) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url19) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url20) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url21) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url22) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url23) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url24) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url25) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url26) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url27) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url28) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url29) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url30) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url31) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url32) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url33) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url34) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url35) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url36) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url37) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url38) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url39) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url40) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url41) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url42) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url43) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url44) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url45) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url46) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url47) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url48) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url49) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url50) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url51) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url52) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url53) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url54) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url55) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url56) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url57) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url58) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url59) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url60) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url61) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url62) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url63) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url64) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url65) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url66) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url67) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url68) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url69) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url70) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url71) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url72) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url73) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url74) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url75) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url76) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url77) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url78) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url79) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url80) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url81) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url82) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url83) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url84) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url85) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url86) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url87) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url88) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url89) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url90) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url91) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url92) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url93) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url94) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url95) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url96) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url97) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url98) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{ %bilateral_url(url99) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
- url: "{%bilateral_url(url100) }"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
_multi_url_bilateral:
|
||||
preset:
|
||||
- "_multi_url_bilateral_inner"
|
||||
- "_multi_url"
|
||||
21
src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml
Normal file
21
src/ytdl_sub/prebuilt_presets/helpers/url_bilateral.yaml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
presets:
|
||||
# multi-url with bilateral scraping built into it via
|
||||
# inspection of the URL and conditionally adding another
|
||||
# ytdl-sub url download with the scraping and download reversed
|
||||
_url_bilateral_overrides:
|
||||
overrides:
|
||||
enable_bilateral_scraping: True
|
||||
"%is_bilateral_url": >-
|
||||
{ %contains( $0, "youtube.com/playlist" ) }
|
||||
"%bilateral_url": >-
|
||||
{
|
||||
%if(
|
||||
%and(
|
||||
enable_bilateral_scraping,
|
||||
subscription_has_download_archive,
|
||||
%is_bilateral_url($0)
|
||||
),
|
||||
$0,
|
||||
""
|
||||
)
|
||||
}
|
||||
|
|
@ -29,15 +29,6 @@ presets:
|
|||
- "_kodi_base"
|
||||
- "_jellyfin_tv_show"
|
||||
|
||||
####################################################################################################
|
||||
|
||||
# TV show from one or more sources. Uses {url}'s avatar and banner as poster and fanart
|
||||
_tv_show_by_date:
|
||||
preset: "_multi_url"
|
||||
overrides:
|
||||
avatar_uncropped_thumbnail_file_name: "{tv_show_poster_file_name}"
|
||||
banner_uncropped_thumbnail_file_name: "{tv_show_fanart_file_name}"
|
||||
|
||||
####################################################################################################
|
||||
|
||||
_episode_video_tags:
|
||||
|
|
|
|||
|
|
@ -31,6 +31,15 @@ presets:
|
|||
- "plex_tv_show_by_date"
|
||||
- "season_by_year__episode_by_month_day"
|
||||
|
||||
####################################################################################################
|
||||
|
||||
# TV show from one or more sources. Uses {url}'s avatar and banner as poster and fanart
|
||||
_tv_show_by_date:
|
||||
preset: "_multi_url_bilateral"
|
||||
overrides:
|
||||
avatar_uncropped_thumbnail_file_name: "{tv_show_poster_file_name}"
|
||||
banner_uncropped_thumbnail_file_name: "{tv_show_fanart_file_name}"
|
||||
|
||||
####################################################################################################
|
||||
|
||||
_season_by_year:
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ presets:
|
|||
##############
|
||||
|
||||
_tv_show_collection:
|
||||
preset:
|
||||
- "_tv_show_collection_bilateral"
|
||||
|
||||
download:
|
||||
- url: "{collection_season_1_url}"
|
||||
variables:
|
||||
|
|
@ -79,6 +82,7 @@ presets:
|
|||
uid: "avatar_uncropped"
|
||||
- name: "{tv_show_fanart_file_name}"
|
||||
uid: "banner_uncropped"
|
||||
|
||||
- url: "{collection_season_2_url}"
|
||||
variables:
|
||||
collection_season_number: "2"
|
||||
|
|
@ -86,6 +90,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_3_url}"
|
||||
variables:
|
||||
collection_season_number: "3"
|
||||
|
|
@ -93,6 +98,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_4_url}"
|
||||
variables:
|
||||
collection_season_number: "4"
|
||||
|
|
@ -100,6 +106,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_5_url}"
|
||||
variables:
|
||||
collection_season_number: "5"
|
||||
|
|
@ -107,6 +114,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_6_url}"
|
||||
variables:
|
||||
collection_season_number: "6"
|
||||
|
|
@ -114,6 +122,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_7_url}"
|
||||
variables:
|
||||
collection_season_number: "7"
|
||||
|
|
@ -121,6 +130,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_8_url}"
|
||||
variables:
|
||||
collection_season_number: "8"
|
||||
|
|
@ -128,6 +138,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_9_url}"
|
||||
variables:
|
||||
collection_season_number: "9"
|
||||
|
|
@ -135,6 +146,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_10_url}"
|
||||
variables:
|
||||
collection_season_number: "10"
|
||||
|
|
@ -142,6 +154,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_11_url}"
|
||||
variables:
|
||||
collection_season_number: "11"
|
||||
|
|
@ -149,6 +162,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_12_url}"
|
||||
variables:
|
||||
collection_season_number: "12"
|
||||
|
|
@ -156,6 +170,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_13_url}"
|
||||
variables:
|
||||
collection_season_number: "13"
|
||||
|
|
@ -163,6 +178,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_14_url}"
|
||||
variables:
|
||||
collection_season_number: "14"
|
||||
|
|
@ -170,6 +186,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_15_url}"
|
||||
variables:
|
||||
collection_season_number: "15"
|
||||
|
|
@ -177,6 +194,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_16_url}"
|
||||
variables:
|
||||
collection_season_number: "16"
|
||||
|
|
@ -184,6 +202,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_17_url}"
|
||||
variables:
|
||||
collection_season_number: "17"
|
||||
|
|
@ -191,6 +210,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_18_url}"
|
||||
variables:
|
||||
collection_season_number: "18"
|
||||
|
|
@ -198,6 +218,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_19_url}"
|
||||
variables:
|
||||
collection_season_number: "19"
|
||||
|
|
@ -205,6 +226,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_20_url}"
|
||||
variables:
|
||||
collection_season_number: "20"
|
||||
|
|
@ -212,6 +234,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_21_url}"
|
||||
variables:
|
||||
collection_season_number: "21"
|
||||
|
|
@ -219,6 +242,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_22_url}"
|
||||
variables:
|
||||
collection_season_number: "22"
|
||||
|
|
@ -226,6 +250,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_23_url}"
|
||||
variables:
|
||||
collection_season_number: "23"
|
||||
|
|
@ -233,6 +258,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_24_url}"
|
||||
variables:
|
||||
collection_season_number: "24"
|
||||
|
|
@ -240,6 +266,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_25_url}"
|
||||
variables:
|
||||
collection_season_number: "25"
|
||||
|
|
@ -247,6 +274,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_26_url}"
|
||||
variables:
|
||||
collection_season_number: "26"
|
||||
|
|
@ -254,6 +282,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_27_url}"
|
||||
variables:
|
||||
collection_season_number: "27"
|
||||
|
|
@ -261,6 +290,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_28_url}"
|
||||
variables:
|
||||
collection_season_number: "28"
|
||||
|
|
@ -268,6 +298,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_29_url}"
|
||||
variables:
|
||||
collection_season_number: "29"
|
||||
|
|
@ -275,6 +306,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_30_url}"
|
||||
variables:
|
||||
collection_season_number: "30"
|
||||
|
|
@ -282,6 +314,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_31_url}"
|
||||
variables:
|
||||
collection_season_number: "31"
|
||||
|
|
@ -289,6 +322,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_32_url}"
|
||||
variables:
|
||||
collection_season_number: "32"
|
||||
|
|
@ -296,6 +330,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_33_url}"
|
||||
variables:
|
||||
collection_season_number: "33"
|
||||
|
|
@ -303,6 +338,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_34_url}"
|
||||
variables:
|
||||
collection_season_number: "34"
|
||||
|
|
@ -310,6 +346,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_35_url}"
|
||||
variables:
|
||||
collection_season_number: "35"
|
||||
|
|
@ -317,6 +354,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_36_url}"
|
||||
variables:
|
||||
collection_season_number: "36"
|
||||
|
|
@ -324,6 +362,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_37_url}"
|
||||
variables:
|
||||
collection_season_number: "37"
|
||||
|
|
@ -331,6 +370,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_38_url}"
|
||||
variables:
|
||||
collection_season_number: "38"
|
||||
|
|
@ -338,6 +378,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_39_url}"
|
||||
variables:
|
||||
collection_season_number: "39"
|
||||
|
|
@ -345,6 +386,7 @@ presets:
|
|||
playlist_thumbnails:
|
||||
- name: "{season_poster_file_name}"
|
||||
uid: "latest_entry"
|
||||
|
||||
- url: "{collection_season_40_url}"
|
||||
variables:
|
||||
collection_season_number: "40"
|
||||
|
|
@ -648,6 +690,332 @@ presets:
|
|||
s39_url: ""
|
||||
s40_url: ""
|
||||
|
||||
_tv_show_collection_bilateral:
|
||||
preset:
|
||||
- "_url_bilateral_overrides"
|
||||
|
||||
download:
|
||||
- url: "{ %bilateral_url(collection_season_1_url) }"
|
||||
variables:
|
||||
collection_season_number: "1"
|
||||
collection_season_name: "{collection_season_1_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_2_url) }"
|
||||
variables:
|
||||
collection_season_number: "2"
|
||||
collection_season_name: "{collection_season_2_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_3_url) }"
|
||||
variables:
|
||||
collection_season_number: "3"
|
||||
collection_season_name: "{collection_season_3_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_4_url) }"
|
||||
variables:
|
||||
collection_season_number: "4"
|
||||
collection_season_name: "{collection_season_4_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_5_url) }"
|
||||
variables:
|
||||
collection_season_number: "5"
|
||||
collection_season_name: "{collection_season_5_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_6_url) }"
|
||||
variables:
|
||||
collection_season_number: "6"
|
||||
collection_season_name: "{collection_season_6_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_7_url) }"
|
||||
variables:
|
||||
collection_season_number: "7"
|
||||
collection_season_name: "{collection_season_7_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_8_url) }"
|
||||
variables:
|
||||
collection_season_number: "8"
|
||||
collection_season_name: "{collection_season_8_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_9_url) }"
|
||||
variables:
|
||||
collection_season_number: "9"
|
||||
collection_season_name: "{collection_season_9_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_10_url) }"
|
||||
variables:
|
||||
collection_season_number: "10"
|
||||
collection_season_name: "{collection_season_10_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_11_url) }"
|
||||
variables:
|
||||
collection_season_number: "11"
|
||||
collection_season_name: "{collection_season_11_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_12_url) }"
|
||||
variables:
|
||||
collection_season_number: "12"
|
||||
collection_season_name: "{collection_season_12_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_13_url) }"
|
||||
variables:
|
||||
collection_season_number: "13"
|
||||
collection_season_name: "{collection_season_13_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_14_url) }"
|
||||
variables:
|
||||
collection_season_number: "14"
|
||||
collection_season_name: "{collection_season_14_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_15_url) }"
|
||||
variables:
|
||||
collection_season_number: "15"
|
||||
collection_season_name: "{collection_season_15_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_16_url) }"
|
||||
variables:
|
||||
collection_season_number: "16"
|
||||
collection_season_name: "{collection_season_16_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_17_url) }"
|
||||
variables:
|
||||
collection_season_number: "17"
|
||||
collection_season_name: "{collection_season_17_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_18_url) }"
|
||||
variables:
|
||||
collection_season_number: "18"
|
||||
collection_season_name: "{collection_season_18_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_19_url) }"
|
||||
variables:
|
||||
collection_season_number: "19"
|
||||
collection_season_name: "{collection_season_19_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_20_url) }"
|
||||
variables:
|
||||
collection_season_number: "20"
|
||||
collection_season_name: "{collection_season_20_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_21_url) }"
|
||||
variables:
|
||||
collection_season_number: "21"
|
||||
collection_season_name: "{collection_season_21_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_22_url) }"
|
||||
variables:
|
||||
collection_season_number: "22"
|
||||
collection_season_name: "{collection_season_22_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_23_url) }"
|
||||
variables:
|
||||
collection_season_number: "23"
|
||||
collection_season_name: "{collection_season_23_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_24_url) }"
|
||||
variables:
|
||||
collection_season_number: "24"
|
||||
collection_season_name: "{collection_season_24_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_25_url) }"
|
||||
variables:
|
||||
collection_season_number: "25"
|
||||
collection_season_name: "{collection_season_25_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_26_url) }"
|
||||
variables:
|
||||
collection_season_number: "26"
|
||||
collection_season_name: "{collection_season_26_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_27_url) }"
|
||||
variables:
|
||||
collection_season_number: "27"
|
||||
collection_season_name: "{collection_season_27_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_28_url) }"
|
||||
variables:
|
||||
collection_season_number: "28"
|
||||
collection_season_name: "{collection_season_28_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_29_url) }"
|
||||
variables:
|
||||
collection_season_number: "29"
|
||||
collection_season_name: "{collection_season_29_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_30_url) }"
|
||||
variables:
|
||||
collection_season_number: "30"
|
||||
collection_season_name: "{collection_season_30_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_31_url) }"
|
||||
variables:
|
||||
collection_season_number: "31"
|
||||
collection_season_name: "{collection_season_31_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_32_url) }"
|
||||
variables:
|
||||
collection_season_number: "32"
|
||||
collection_season_name: "{collection_season_32_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_33_url) }"
|
||||
variables:
|
||||
collection_season_number: "33"
|
||||
collection_season_name: "{collection_season_33_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_34_url) }"
|
||||
variables:
|
||||
collection_season_number: "34"
|
||||
collection_season_name: "{collection_season_34_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_35_url) }"
|
||||
variables:
|
||||
collection_season_number: "35"
|
||||
collection_season_name: "{collection_season_35_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_36_url) }"
|
||||
variables:
|
||||
collection_season_number: "36"
|
||||
collection_season_name: "{collection_season_36_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_37_url) }"
|
||||
variables:
|
||||
collection_season_number: "37"
|
||||
collection_season_name: "{collection_season_37_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_38_url) }"
|
||||
variables:
|
||||
collection_season_number: "38"
|
||||
collection_season_name: "{collection_season_38_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_39_url) }"
|
||||
variables:
|
||||
collection_season_number: "39"
|
||||
collection_season_name: "{collection_season_39_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
- url: "{ %bilateral_url(collection_season_40_url) }"
|
||||
variables:
|
||||
collection_season_number: "40"
|
||||
collection_season_name: "{collection_season_40_name}"
|
||||
download_reverse: False
|
||||
ytdl_options:
|
||||
playlist_items: "-1:0:-1"
|
||||
|
||||
|
||||
####################################################################################################
|
||||
# DEPRECATED SEASON PRESETS
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from ytdl_sub.config.preset import Preset
|
|||
from ytdl_sub.config.preset_options import OutputOptions
|
||||
from ytdl_sub.config.preset_options import YTDLOptions
|
||||
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
|
||||
from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
|
||||
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
||||
from ytdl_sub.utils.logger import Logger
|
||||
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
|
||||
|
|
@ -16,6 +17,24 @@ from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadAr
|
|||
logger = Logger.get("subscription")
|
||||
|
||||
|
||||
def _initialize_download_archive(
|
||||
output_options: OutputOptions,
|
||||
overrides: Overrides,
|
||||
working_directory: str,
|
||||
output_directory: str,
|
||||
) -> EnhancedDownloadArchive:
|
||||
migrated_file_name: Optional[str] = None
|
||||
if migrated_file_name_option := output_options.migrated_download_archive_name:
|
||||
migrated_file_name = overrides.apply_formatter(migrated_file_name_option)
|
||||
|
||||
return EnhancedDownloadArchive(
|
||||
file_name=overrides.apply_formatter(output_options.download_archive_name),
|
||||
working_directory=working_directory,
|
||||
output_directory=output_directory,
|
||||
migrated_file_name=migrated_file_name,
|
||||
).reinitialize(dry_run=True)
|
||||
|
||||
|
||||
class BaseSubscription(ABC):
|
||||
"""
|
||||
Subscription classes are the 'controllers' that perform...
|
||||
|
|
@ -48,20 +67,43 @@ class BaseSubscription(ABC):
|
|||
self._config_options = config_options
|
||||
self._preset_options = preset_options
|
||||
|
||||
migrated_file_name: Optional[str] = None
|
||||
if migrated_file_name_option := self.output_options.migrated_download_archive_name:
|
||||
migrated_file_name = self.overrides.apply_formatter(migrated_file_name_option)
|
||||
# Add overrides pre-archive
|
||||
self.overrides.add(
|
||||
{
|
||||
SubscriptionVariables.subscription_name(): self.name,
|
||||
}
|
||||
)
|
||||
|
||||
# TODO: Do not include this as part of the subscription
|
||||
self._enhanced_download_archive = EnhancedDownloadArchive(
|
||||
file_name=self.overrides.apply_formatter(self.output_options.download_archive_name),
|
||||
self._enhanced_download_archive: Optional[
|
||||
EnhancedDownloadArchive
|
||||
] = _initialize_download_archive(
|
||||
output_options=self.output_options,
|
||||
overrides=self.overrides,
|
||||
working_directory=self.working_directory,
|
||||
output_directory=self.output_directory,
|
||||
migrated_file_name=migrated_file_name,
|
||||
)
|
||||
|
||||
# Add post-archive variables
|
||||
self.overrides.add(
|
||||
{
|
||||
SubscriptionVariables.subscription_has_download_archive(): f"""{{
|
||||
%bool({self.download_archive.num_entries > 0})
|
||||
}}""",
|
||||
}
|
||||
)
|
||||
|
||||
self._exception: Optional[Exception] = None
|
||||
|
||||
@property
|
||||
def download_archive(self) -> EnhancedDownloadArchive:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
Initialized download archive
|
||||
"""
|
||||
assert self._enhanced_download_archive is not None
|
||||
return self._enhanced_download_archive
|
||||
|
||||
@property
|
||||
def downloader_options(self) -> MultiUrlValidator:
|
||||
"""
|
||||
|
|
@ -141,7 +183,7 @@ class BaseSubscription(ABC):
|
|||
-------
|
||||
Number of entries added
|
||||
"""
|
||||
return self._enhanced_download_archive.num_entries_added
|
||||
return self.download_archive.num_entries_added
|
||||
|
||||
@property
|
||||
def num_entries_modified(self) -> int:
|
||||
|
|
@ -150,7 +192,7 @@ class BaseSubscription(ABC):
|
|||
-------
|
||||
Number of entries modified
|
||||
"""
|
||||
return self._enhanced_download_archive.num_entries_modified
|
||||
return self.download_archive.num_entries_modified
|
||||
|
||||
@property
|
||||
def num_entries_removed(self) -> int:
|
||||
|
|
@ -159,7 +201,7 @@ class BaseSubscription(ABC):
|
|||
-------
|
||||
Number of entries removed
|
||||
"""
|
||||
return self._enhanced_download_archive.num_entries_removed
|
||||
return self.download_archive.num_entries_removed
|
||||
|
||||
@property
|
||||
def num_entries(self) -> int:
|
||||
|
|
@ -168,7 +210,7 @@ class BaseSubscription(ABC):
|
|||
-------
|
||||
The number of entries
|
||||
"""
|
||||
return self._enhanced_download_archive.num_entries
|
||||
return self.download_archive.num_entries
|
||||
|
||||
@property
|
||||
def transaction_log(self) -> FileHandlerTransactionLog:
|
||||
|
|
@ -177,7 +219,7 @@ class BaseSubscription(ABC):
|
|||
-------
|
||||
Transaction log from the subscription
|
||||
"""
|
||||
return self._enhanced_download_archive.get_file_handler_transaction_log()
|
||||
return self.download_archive.get_file_handler_transaction_log()
|
||||
|
||||
@property
|
||||
def exception(self) -> Optional[Exception]:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
output_file_name = self.overrides.apply_formatter(
|
||||
formatter=self.output_options.file_name, entry=entry
|
||||
)
|
||||
self._enhanced_download_archive.save_file_to_output_directory(
|
||||
self.download_archive.save_file_to_output_directory(
|
||||
file_name=entry.get_download_file_name(),
|
||||
file_metadata=entry_metadata,
|
||||
output_file_name=output_file_name,
|
||||
|
|
@ -81,7 +81,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
)
|
||||
|
||||
# Copy the thumbnails since they could be used later for other things
|
||||
self._enhanced_download_archive.save_file_to_output_directory(
|
||||
self.download_archive.save_file_to_output_directory(
|
||||
file_name=entry.get_download_thumbnail_name(),
|
||||
output_file_name=output_thumbnail_name,
|
||||
entry=entry,
|
||||
|
|
@ -101,7 +101,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
if not dry_run:
|
||||
entry.write_info_json()
|
||||
|
||||
self._enhanced_download_archive.save_file_to_output_directory(
|
||||
self.download_archive.save_file_to_output_directory(
|
||||
file_name=entry.get_download_info_json_name(),
|
||||
output_file_name=output_info_json_name,
|
||||
entry=entry,
|
||||
|
|
@ -135,7 +135,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
Context manager to initialize the enhanced download archive
|
||||
"""
|
||||
if self.maintain_download_archive:
|
||||
self._enhanced_download_archive.prepare_download_archive()
|
||||
self.download_archive.prepare_download_archive()
|
||||
|
||||
yield
|
||||
|
||||
|
|
@ -156,19 +156,19 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
)
|
||||
|
||||
if date_range_to_keep or self.output_options.keep_max_files is not None:
|
||||
self._enhanced_download_archive.remove_stale_files(
|
||||
self.download_archive.remove_stale_files(
|
||||
date_range=date_range_to_keep, keep_max_files=keep_max_files
|
||||
)
|
||||
|
||||
self._enhanced_download_archive.save_download_mappings()
|
||||
FileHandler.delete(self._enhanced_download_archive.working_file_path)
|
||||
self.download_archive.save_download_mappings()
|
||||
FileHandler.delete(self.download_archive.working_file_path)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _remove_empty_directories_in_output_directory(self):
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if not self._enhanced_download_archive.is_dry_run:
|
||||
if not self.download_archive.is_dry_run:
|
||||
for root, dir_names, _ in os.walk(Path(self.output_directory), topdown=False):
|
||||
for dir_name in dir_names:
|
||||
dir_path = Path(root) / dir_name
|
||||
|
|
@ -194,7 +194,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
plugin_type(
|
||||
options=plugin_options,
|
||||
overrides=self.overrides,
|
||||
enhanced_download_archive=self._enhanced_download_archive,
|
||||
enhanced_download_archive=self.download_archive,
|
||||
)
|
||||
for plugin_type, plugin_options in self.plugins.zipped()
|
||||
]
|
||||
|
|
@ -233,7 +233,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
|
||||
# Re-save the download archive after each entry is moved to the output directory
|
||||
if self.maintain_download_archive:
|
||||
self._enhanced_download_archive.save_download_mappings()
|
||||
self.download_archive.save_download_mappings()
|
||||
|
||||
def _process_entry(
|
||||
self, plugins: List[Plugin], dry_run: bool, entry: Entry, entry_metadata: FileMetadata
|
||||
|
|
@ -323,7 +323,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
for plugin in plugins:
|
||||
plugin.post_process_subscription()
|
||||
|
||||
return self._enhanced_download_archive.get_file_handler_transaction_log()
|
||||
return self.download_archive.get_file_handler_transaction_log()
|
||||
|
||||
def download(self, dry_run: bool = False) -> FileHandlerTransactionLog:
|
||||
"""
|
||||
|
|
@ -336,14 +336,14 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
directory.
|
||||
"""
|
||||
self._exception = None
|
||||
self._enhanced_download_archive.reinitialize(dry_run=dry_run)
|
||||
self.download_archive.reinitialize(dry_run=dry_run)
|
||||
|
||||
plugins = self._initialize_plugins()
|
||||
|
||||
subscription_ytdl_options = SubscriptionYTDLOptions(
|
||||
preset=self._preset_options,
|
||||
plugins=plugins,
|
||||
enhanced_download_archive=self._enhanced_download_archive,
|
||||
enhanced_download_archive=self.download_archive,
|
||||
overrides=self.overrides,
|
||||
working_directory=self.working_directory,
|
||||
dry_run=dry_run,
|
||||
|
|
@ -351,7 +351,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
|
||||
downloader = MultiUrlDownloader(
|
||||
options=self.downloader_options,
|
||||
enhanced_download_archive=self._enhanced_download_archive,
|
||||
enhanced_download_archive=self.download_archive,
|
||||
download_ytdl_options=subscription_ytdl_options.download_builder(),
|
||||
metadata_ytdl_options=subscription_ytdl_options.metadata_builder(),
|
||||
overrides=self.overrides,
|
||||
|
|
@ -389,14 +389,14 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
If true, do not modify any video/audio files or move anything to the output directory.
|
||||
"""
|
||||
self._exception = None
|
||||
self._enhanced_download_archive.reinitialize(dry_run=dry_run)
|
||||
self.download_archive.reinitialize(dry_run=dry_run)
|
||||
|
||||
plugins = self._initialize_plugins()
|
||||
|
||||
subscription_ytdl_options = SubscriptionYTDLOptions(
|
||||
preset=self._preset_options,
|
||||
plugins=plugins,
|
||||
enhanced_download_archive=self._enhanced_download_archive,
|
||||
enhanced_download_archive=self.download_archive,
|
||||
overrides=self.overrides,
|
||||
working_directory=self.working_directory,
|
||||
dry_run=dry_run,
|
||||
|
|
@ -406,7 +406,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
plugins.extend(
|
||||
MultiUrlDownloader(
|
||||
options=self.downloader_options,
|
||||
enhanced_download_archive=self._enhanced_download_archive,
|
||||
enhanced_download_archive=self.download_archive,
|
||||
download_ytdl_options=subscription_ytdl_options.download_builder(),
|
||||
metadata_ytdl_options=subscription_ytdl_options.metadata_builder(),
|
||||
overrides=self.overrides,
|
||||
|
|
@ -415,7 +415,7 @@ class SubscriptionDownload(BaseSubscription, ABC):
|
|||
|
||||
downloader = InfoJsonDownloader(
|
||||
options=InfoJsonDownloaderOptions(name="no-op", value={}),
|
||||
enhanced_download_archive=self._enhanced_download_archive,
|
||||
enhanced_download_archive=self.download_archive,
|
||||
download_ytdl_options=YTDLOptionsBuilder(),
|
||||
metadata_ytdl_options=YTDLOptionsBuilder(),
|
||||
overrides=self.overrides,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class SubscriptionOutput(Validator, ABC):
|
|||
indent overrides to merge with the preset dict's overrides
|
||||
"""
|
||||
return {
|
||||
SubscriptionVariables.subscription_indent_i(i): self._indent_overrides[i]
|
||||
SubscriptionVariables.subscription_indent_i(i).variable_name: self._indent_overrides[i]
|
||||
for i in range(len(self._indent_overrides))
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +143,9 @@ class SubscriptionValueValidator(SubscriptionLeafValidator, StringValidator):
|
|||
presets=presets,
|
||||
indent_overrides=indent_overrides,
|
||||
)
|
||||
self._overrides_to_add[SubscriptionVariables.subscription_value()] = self.value
|
||||
self._overrides_to_add[
|
||||
SubscriptionVariables.subscription_value().variable_name
|
||||
] = self.value
|
||||
|
||||
|
||||
class SubscriptionListValuesValidator(SubscriptionLeafValidator, StringListValidator):
|
||||
|
|
@ -169,11 +171,11 @@ class SubscriptionListValuesValidator(SubscriptionLeafValidator, StringListValid
|
|||
# Write the first list value into subscription_value as well
|
||||
if idx == 0:
|
||||
self._overrides_to_add[
|
||||
SubscriptionVariables.subscription_value()
|
||||
SubscriptionVariables.subscription_value().variable_name
|
||||
] = list_value.value
|
||||
|
||||
self._overrides_to_add[
|
||||
SubscriptionVariables.subscription_value_i(index=idx)
|
||||
SubscriptionVariables.subscription_value_i(index=idx).variable_name
|
||||
] = list_value.value
|
||||
|
||||
|
||||
|
|
@ -217,9 +219,9 @@ class SubscriptionMapValidator(SubscriptionLeafValidator, LiteralDictValidator):
|
|||
presets=presets,
|
||||
indent_overrides=indent_overrides,
|
||||
)
|
||||
self._overrides_to_add[SubscriptionVariables.subscription_map()] = ScriptUtils.to_script(
|
||||
self.dict
|
||||
)
|
||||
self._overrides_to_add[
|
||||
SubscriptionVariables.subscription_map().variable_name
|
||||
] = ScriptUtils.to_script(self.dict)
|
||||
|
||||
|
||||
class SubscriptionValidator(SubscriptionOutput):
|
||||
|
|
|
|||
|
|
@ -9,13 +9,16 @@ from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS
|
|||
from ytdl_sub.entries.script.variable_definitions import UNRESOLVED_VARIABLES
|
||||
from ytdl_sub.entries.script.variable_definitions import VARIABLE_SCRIPTS
|
||||
from ytdl_sub.entries.script.variable_types import Variable
|
||||
from ytdl_sub.entries.variables.override_variables import REQUIRED_OVERRIDE_VARIABLE_DEFINITIONS
|
||||
from ytdl_sub.script.script import Script
|
||||
from ytdl_sub.script.utils.exceptions import RuntimeException
|
||||
from ytdl_sub.utils.exceptions import StringFormattingException
|
||||
from ytdl_sub.utils.script import ScriptUtils
|
||||
|
||||
BASE_SCRIPT: Script = Script(
|
||||
dict(ScriptUtils.add_sanitized_variables(VARIABLE_SCRIPTS), **CUSTOM_FUNCTION_SCRIPTS)
|
||||
ScriptUtils.add_sanitized_variables(VARIABLE_SCRIPTS)
|
||||
| ScriptUtils.add_sanitized_variables(REQUIRED_OVERRIDE_VARIABLE_DEFINITIONS)
|
||||
| CUSTOM_FUNCTION_SCRIPTS
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
0
tests/e2e/entries/__init__.py
Normal file
0
tests/e2e/entries/__init__.py
Normal file
0
tests/e2e/entries/script/__init__.py
Normal file
0
tests/e2e/entries/script/__init__.py
Normal file
22
tests/e2e/entries/script/test_custom_functions.py
Normal file
22
tests/e2e/entries/script/test_custom_functions.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from unit.script.conftest import single_variable_output
|
||||
|
||||
|
||||
class TestCustomFunctions:
|
||||
def test_is_playlist_ordered_by_newest_true(self):
|
||||
assert (
|
||||
single_variable_output(
|
||||
"{%is_playlist_ordered_by_newest('https://www.youtube.com/playlist?list=PL5BC0FC26BECA5A35')}"
|
||||
)
|
||||
is True
|
||||
)
|
||||
|
||||
def test_is_playlist_ordered_by_newest_false(self):
|
||||
assert (
|
||||
single_variable_output(
|
||||
"{%is_playlist_ordered_by_newest('https://www.youtube.com/playlist?list=PL2KvlCGf4yFftX466OnFS8wvuSosBfUgm')}"
|
||||
)
|
||||
is False
|
||||
)
|
||||
|
||||
def test_is_playlist_ordered_by_newest_defaults_false(self):
|
||||
assert single_variable_output("{%is_playlist_ordered_by_newest('aaaaaa')}") is False
|
||||
|
|
@ -51,6 +51,49 @@ def playlist_preset_dict(output_directory):
|
|||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tv_show_by_date_bilateral_dict(output_directory):
|
||||
return {
|
||||
"preset": [
|
||||
"Jellyfin TV Show by Date",
|
||||
],
|
||||
"format": "worst[ext=mp4]",
|
||||
"match_filters": {"filters": ["title *= Feb.1"]},
|
||||
"overrides": {
|
||||
"url": "https://www.youtube.com/playlist?list=PLd4Q7G88JqoekF0b30NYQcOTnTiIe9Ali",
|
||||
"tv_show_directory": output_directory,
|
||||
},
|
||||
"nfo_tags": {
|
||||
"tags": {
|
||||
"subscription_has_download_archive": "{subscription_has_download_archive}",
|
||||
"download_index": "{download_index}",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tv_show_collection_bilateral_dict(output_directory):
|
||||
return {
|
||||
"preset": [
|
||||
"Jellyfin TV Show Collection",
|
||||
],
|
||||
"format": "worst[ext=mp4]",
|
||||
"match_filters": {"filters": ["title *= Feb.1"]},
|
||||
"overrides": {
|
||||
"s01_url": "https://www.youtube.com/playlist?list=PLd4Q7G88JqoekF0b30NYQcOTnTiIe9Ali",
|
||||
"s01_name": "bilateral test",
|
||||
"tv_show_directory": output_directory,
|
||||
},
|
||||
"nfo_tags": {
|
||||
"tags": {
|
||||
"subscription_has_download_archive": "{subscription_has_download_archive}",
|
||||
"download_index": "{download_index}",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class TestPlaylist:
|
||||
"""
|
||||
Downloads my old minecraft youtube channel, pretends they are music videos. Ensure the above
|
||||
|
|
@ -222,3 +265,71 @@ class TestPlaylist:
|
|||
|
||||
assert len(subscriptions) == 1
|
||||
assert subscriptions[0].transaction_log.is_empty
|
||||
|
||||
def test_tv_show_by_date_downloads_bilateral(
|
||||
self,
|
||||
tv_show_by_date_bilateral_dict: Dict,
|
||||
output_directory: str,
|
||||
default_config: ConfigFile,
|
||||
):
|
||||
playlist_subscription = Subscription.from_dict(
|
||||
config=default_config,
|
||||
preset_name="bilateral_test",
|
||||
preset_dict=tv_show_by_date_bilateral_dict,
|
||||
)
|
||||
|
||||
transaction_log = playlist_subscription.download(dry_run=False)
|
||||
assert_transaction_log_matches(
|
||||
output_directory=output_directory,
|
||||
transaction_log=transaction_log,
|
||||
transaction_log_summary_file_name="youtube/test_playlist_bilateral_p1.txt",
|
||||
)
|
||||
|
||||
# Now that one vid is downloaded, attempt to download all and see if bilateral
|
||||
# logic kicks in
|
||||
del tv_show_by_date_bilateral_dict["match_filters"]
|
||||
playlist_subscription = Subscription.from_dict(
|
||||
config=default_config,
|
||||
preset_name="bilateral_test",
|
||||
preset_dict=tv_show_by_date_bilateral_dict,
|
||||
)
|
||||
transaction_log = playlist_subscription.download(dry_run=True)
|
||||
assert_transaction_log_matches(
|
||||
output_directory=output_directory,
|
||||
transaction_log=transaction_log,
|
||||
transaction_log_summary_file_name="youtube/test_playlist_bilateral_p2.txt",
|
||||
)
|
||||
|
||||
def test_tv_show_collection_downloads_bilateral(
|
||||
self,
|
||||
tv_show_collection_bilateral_dict: Dict,
|
||||
output_directory: str,
|
||||
default_config: ConfigFile,
|
||||
):
|
||||
playlist_subscription = Subscription.from_dict(
|
||||
config=default_config,
|
||||
preset_name="bilateral_test",
|
||||
preset_dict=tv_show_collection_bilateral_dict,
|
||||
)
|
||||
|
||||
transaction_log = playlist_subscription.download(dry_run=False)
|
||||
assert_transaction_log_matches(
|
||||
output_directory=output_directory,
|
||||
transaction_log=transaction_log,
|
||||
transaction_log_summary_file_name="youtube/test_playlist_bilateral_collection_p1.txt",
|
||||
)
|
||||
|
||||
# Now that one vid is downloaded, attempt to download all and see if bilateral
|
||||
# logic kicks in
|
||||
del tv_show_collection_bilateral_dict["match_filters"]
|
||||
playlist_subscription = Subscription.from_dict(
|
||||
config=default_config,
|
||||
preset_name="bilateral_test",
|
||||
preset_dict=tv_show_collection_bilateral_dict,
|
||||
)
|
||||
transaction_log = playlist_subscription.download(dry_run=True)
|
||||
assert_transaction_log_matches(
|
||||
output_directory=output_directory,
|
||||
transaction_log=transaction_log,
|
||||
transaction_log_summary_file_name="youtube/test_playlist_bilateral_collection_p2.txt",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
s2020.e000005 - Mock Entry 20-7.info.json
|
||||
s2020.e000006 - Mock Entry 20-6.info.json
|
||||
s2020.e000007 - Mock Entry 20-5.info.json
|
||||
s2020.e000008 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
s2020.e000005 - Mock Entry 20-7.info.json
|
||||
s2020.e000006 - Mock Entry 20-6.info.json
|
||||
s2020.e000007 - Mock Entry 20-5.info.json
|
||||
s2020.e000008 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -208,6 +208,11 @@ Files created:
|
|||
title: 2021-08-08 - Mock Entry 21-1
|
||||
year: 2021
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -208,6 +208,11 @@ Files created:
|
|||
title: 2021-08-08 - Mock Entry 21-1
|
||||
year: 2021
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-3.info.json
|
||||
s01.e000002 - Mock Entry 20-2.info.json
|
||||
s01.e000003 - Mock Entry 20-1.info.json
|
||||
s01.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-3.info.json
|
||||
s01.e000002 - Mock Entry 20-2.info.json
|
||||
s01.e000003 - Mock Entry 20-1.info.json
|
||||
s01.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-7.info.json
|
||||
s01.e000002 - Mock Entry 20-6.info.json
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
s01.e000004 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 02
|
||||
s02.e000001 - Mock Entry 20-3.info.json
|
||||
s02.e000002 - Mock Entry 20-2.info.json
|
||||
s02.e000003 - Mock Entry 20-1.info.json
|
||||
s02.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-7.info.json
|
||||
s01.e000002 - Mock Entry 20-6.info.json
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
s01.e000004 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 02
|
||||
s02.e000001 - Mock Entry 20-3.info.json
|
||||
s02.e000002 - Mock Entry 20-2.info.json
|
||||
s02.e000003 - Mock Entry 20-1.info.json
|
||||
s02.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
s2020.e000005 - Mock Entry 20-7.info.json
|
||||
s2020.e000006 - Mock Entry 20-6.info.json
|
||||
s2020.e000007 - Mock Entry 20-5.info.json
|
||||
s2020.e000008 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
s2020.e000005 - Mock Entry 20-7.info.json
|
||||
s2020.e000006 - Mock Entry 20-6.info.json
|
||||
s2020.e000007 - Mock Entry 20-5.info.json
|
||||
s2020.e000008 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -208,6 +208,11 @@ Files created:
|
|||
title: 2021-08-08 - Mock Entry 21-1
|
||||
year: 2021
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -208,6 +208,11 @@ Files created:
|
|||
title: 2021-08-08 - Mock Entry 21-1
|
||||
year: 2021
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-3.info.json
|
||||
s01.e000002 - Mock Entry 20-2.info.json
|
||||
s01.e000003 - Mock Entry 20-1.info.json
|
||||
s01.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-3.info.json
|
||||
s01.e000002 - Mock Entry 20-2.info.json
|
||||
s01.e000003 - Mock Entry 20-1.info.json
|
||||
s01.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-7.info.json
|
||||
s01.e000002 - Mock Entry 20-6.info.json
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
s01.e000004 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 02
|
||||
s02.e000001 - Mock Entry 20-3.info.json
|
||||
s02.e000002 - Mock Entry 20-2.info.json
|
||||
s02.e000003 - Mock Entry 20-1.info.json
|
||||
s02.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-7.info.json
|
||||
s01.e000002 - Mock Entry 20-6.info.json
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
s01.e000004 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 02
|
||||
s02.e000001 - Mock Entry 20-3.info.json
|
||||
s02.e000002 - Mock Entry 20-2.info.json
|
||||
s02.e000003 - Mock Entry 20-1.info.json
|
||||
s02.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
s2020.e000005 - Mock Entry 20-7.info.json
|
||||
s2020.e000006 - Mock Entry 20-6.info.json
|
||||
s2020.e000007 - Mock Entry 20-5.info.json
|
||||
s2020.e000008 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
s2020.e000005 - Mock Entry 20-7.info.json
|
||||
s2020.e000006 - Mock Entry 20-6.info.json
|
||||
s2020.e000007 - Mock Entry 20-5.info.json
|
||||
s2020.e000008 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2020
|
||||
s2020.e000001 - Mock Entry 20-3.info.json
|
||||
s2020.e000002 - Mock Entry 20-2.info.json
|
||||
s2020.e000003 - Mock Entry 20-1.info.json
|
||||
{output_directory}/Season 2021
|
||||
s2021.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,11 @@ Files created:
|
|||
title: 2021-08-08 - Mock Entry 21-1
|
||||
year: 2021
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,11 @@ Files created:
|
|||
title: 2021-08-08 - Mock Entry 21-1
|
||||
year: 2021
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-3.info.json
|
||||
s01.e000002 - Mock Entry 20-2.info.json
|
||||
s01.e000003 - Mock Entry 20-1.info.json
|
||||
s01.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-3.info.json
|
||||
s01.e000002 - Mock Entry 20-2.info.json
|
||||
s01.e000003 - Mock Entry 20-1.info.json
|
||||
s01.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-7.info.json
|
||||
s01.e000002 - Mock Entry 20-6.info.json
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
s01.e000004 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 02
|
||||
s02.e000001 - Mock Entry 20-3.info.json
|
||||
s02.e000002 - Mock Entry 20-2.info.json
|
||||
s02.e000003 - Mock Entry 20-1.info.json
|
||||
s02.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,19 @@ Files created:
|
|||
{output_directory}
|
||||
.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e000001 - Mock Entry 20-7.info.json
|
||||
s01.e000002 - Mock Entry 20-6.info.json
|
||||
s01.e000003 - Mock Entry 20-5.info.json
|
||||
s01.e000004 - Mock Entry 20-4.info.json
|
||||
{output_directory}/Season 02
|
||||
s02.e000001 - Mock Entry 20-3.info.json
|
||||
s02.e000002 - Mock Entry 20-2.info.json
|
||||
s02.e000003 - Mock Entry 20-1.info.json
|
||||
s02.e000004 - Mock Entry 21-1.info.json
|
||||
|
||||
Files removed:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
Files created:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
.ytdl-sub-bilateral_test-download-archive.json
|
||||
fanart.jpg
|
||||
poster.jpg
|
||||
season01-poster.jpg
|
||||
tvshow.nfo
|
||||
NFO tags:
|
||||
tvshow:
|
||||
genre: ytdl-sub
|
||||
mpaa: TV-14
|
||||
namedseason:
|
||||
attributes:
|
||||
number: 1
|
||||
tag: bilateral test
|
||||
title: bilateral_test
|
||||
{output_directory}/Season 01
|
||||
s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg
|
||||
s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1].info.json
|
||||
s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1].mp4
|
||||
Video Tags:
|
||||
contentRating: TV-14
|
||||
date: 2011-02-01
|
||||
episode_id: 11020101
|
||||
genre: ytdl-sub
|
||||
show: bilateral_test
|
||||
synopsis:
|
||||
https://www.youtube.com/watch?v=0SVukUyys10
|
||||
|
||||
To join the server, you must apply at:
|
||||
http://www.jesseminecraft.webs.com/
|
||||
|
||||
This is just a brief video of the server as of Feb. 1, 2011.
|
||||
|
||||
Texture Pack I Use:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
title: 2011-02-01 - Jesse's Minecraft Server [Trailer - Feb.1]
|
||||
year: 2011
|
||||
s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1].nfo
|
||||
NFO tags:
|
||||
episodedetails:
|
||||
aired: 2011-02-01
|
||||
download_index: 1
|
||||
episode: 11020101
|
||||
genre: ytdl-sub
|
||||
mpaa: TV-14
|
||||
plot:
|
||||
https://www.youtube.com/watch?v=0SVukUyys10
|
||||
|
||||
To join the server, you must apply at:
|
||||
http://www.jesseminecraft.webs.com/
|
||||
|
||||
This is just a brief video of the server as of Feb. 1, 2011.
|
||||
|
||||
Texture Pack I Use:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
season: 1
|
||||
subscription_has_download_archive: false
|
||||
title: 2011-02-01 - Jesse's Minecraft Server [Trailer - Feb.1]
|
||||
year: 2011
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
Files created:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 01
|
||||
s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg
|
||||
s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27].info.json
|
||||
s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27].mp4
|
||||
Video Tags:
|
||||
contentRating: TV-14
|
||||
date: 2011-02-27
|
||||
episode_id: 11022701
|
||||
genre: ytdl-sub
|
||||
show: bilateral_test
|
||||
synopsis:
|
||||
https://www.youtube.com/watch?v=qPybBrXspds
|
||||
|
||||
Website Link:
|
||||
http://jesseminecraft.webs.com/
|
||||
|
||||
All you have to do is read the rules, and fill out a quick, little application to join the Server so we know you read them. We do this to keep the griefers/newbs out, it only takes three minutes, it's not that big of a deal.
|
||||
|
||||
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 750 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
|
||||
|
||||
There are over 200 empty properties that are ready for anyone to own! Join now!
|
||||
|
||||
This is the server state as of Feb. 27, 2011.
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
Texture Pack:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
|
||||
Recording Software:
|
||||
http://www.fraps.com/download.php
|
||||
|
||||
Video Editing Software:
|
||||
http://explore.live.com/windows-live-movie-maker?os=other
|
||||
|
||||
Song:
|
||||
Given to Fly - Pearl Jam
|
||||
(Off of the 'Yield' album)
|
||||
|
||||
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
|
||||
title: 2011-02-27 - Jesse's Minecraft Server [Trailer - Feb.27]
|
||||
year: 2011
|
||||
s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27].nfo
|
||||
NFO tags:
|
||||
episodedetails:
|
||||
aired: 2011-02-27
|
||||
download_index: 2
|
||||
episode: 11022701
|
||||
genre: ytdl-sub
|
||||
mpaa: TV-14
|
||||
plot:
|
||||
https://www.youtube.com/watch?v=qPybBrXspds
|
||||
|
||||
Website Link:
|
||||
http://jesseminecraft.webs.com/
|
||||
|
||||
All you have to do is read the rules, and fill out a quick, little application to join the Server so we know you read them. We do this to keep the griefers/newbs out, it only takes three minutes, it's not that big of a deal.
|
||||
|
||||
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 750 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
|
||||
|
||||
There are over 200 empty properties that are ready for anyone to own! Join now!
|
||||
|
||||
This is the server state as of Feb. 27, 2011.
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
Texture Pack:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
|
||||
Recording Software:
|
||||
http://www.fraps.com/download.php
|
||||
|
||||
Video Editing Software:
|
||||
http://explore.live.com/windows-live-movie-maker?os=other
|
||||
|
||||
Song:
|
||||
Given to Fly - Pearl Jam
|
||||
(Off of the 'Yield' album)
|
||||
|
||||
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
|
||||
season: 1
|
||||
subscription_has_download_archive: true
|
||||
title: 2011-02-27 - Jesse's Minecraft Server [Trailer - Feb.27]
|
||||
year: 2011
|
||||
s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg
|
||||
s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21].info.json
|
||||
s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21].mp4
|
||||
Video Tags:
|
||||
contentRating: TV-14
|
||||
date: 2011-03-21
|
||||
episode_id: 11032101
|
||||
genre: ytdl-sub
|
||||
show: bilateral_test
|
||||
synopsis:
|
||||
https://www.youtube.com/watch?v=DBjFvs6HafU
|
||||
|
||||
Website Link:
|
||||
http://jesseminecraft.webs.com/
|
||||
|
||||
To get on the whitelist, please look at the website linked above (^^^). Due to the overwhelming amount of people trying to join, I've made it so it costs $2 to become a member through paypal. All of it is explained on the website.
|
||||
|
||||
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 1000 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
|
||||
|
||||
There are over 300 empty properties that are ready for anyone to own! Join now!
|
||||
|
||||
This is the server state as of Mar. 21, 2011.
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
Texture Pack:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
|
||||
Recording Software:
|
||||
http://www.fraps.com/download.php
|
||||
|
||||
Video Editing Software:
|
||||
http://explore.live.com/windows-live-movie-maker?os=other
|
||||
|
||||
Song:
|
||||
Indifference - Pearl Jam
|
||||
(Off of the 'Vs.' album)
|
||||
|
||||
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
|
||||
title: 2011-03-21 - Jesse's Minecraft Server [Trailer - Mar.21]
|
||||
year: 2011
|
||||
s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21].nfo
|
||||
NFO tags:
|
||||
episodedetails:
|
||||
aired: 2011-03-21
|
||||
download_index: 3
|
||||
episode: 11032101
|
||||
genre: ytdl-sub
|
||||
mpaa: TV-14
|
||||
plot:
|
||||
https://www.youtube.com/watch?v=DBjFvs6HafU
|
||||
|
||||
Website Link:
|
||||
http://jesseminecraft.webs.com/
|
||||
|
||||
To get on the whitelist, please look at the website linked above (^^^). Due to the overwhelming amount of people trying to join, I've made it so it costs $2 to become a member through paypal. All of it is explained on the website.
|
||||
|
||||
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 1000 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
|
||||
|
||||
There are over 300 empty properties that are ready for anyone to own! Join now!
|
||||
|
||||
This is the server state as of Mar. 21, 2011.
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
Texture Pack:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
|
||||
Recording Software:
|
||||
http://www.fraps.com/download.php
|
||||
|
||||
Video Editing Software:
|
||||
http://explore.live.com/windows-live-movie-maker?os=other
|
||||
|
||||
Song:
|
||||
Indifference - Pearl Jam
|
||||
(Off of the 'Vs.' album)
|
||||
|
||||
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
|
||||
season: 1
|
||||
subscription_has_download_archive: true
|
||||
title: 2011-03-21 - Jesse's Minecraft Server [Trailer - Mar.21]
|
||||
year: 2011
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
.ytdl-sub-bilateral_test-download-archive.json
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
Files created:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
.ytdl-sub-bilateral_test-download-archive.json
|
||||
fanart.jpg
|
||||
poster.jpg
|
||||
tvshow.nfo
|
||||
NFO tags:
|
||||
tvshow:
|
||||
genre: ytdl-sub
|
||||
mpaa: TV-14
|
||||
title: bilateral_test
|
||||
{output_directory}/Season 2011
|
||||
s2011.e020101 - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg
|
||||
s2011.e020101 - Jesse's Minecraft Server [Trailer - Feb.1].info.json
|
||||
s2011.e020101 - Jesse's Minecraft Server [Trailer - Feb.1].mp4
|
||||
Video Tags:
|
||||
contentRating: TV-14
|
||||
date: 2011-02-01
|
||||
episode_id: 20101
|
||||
genre: ytdl-sub
|
||||
show: bilateral_test
|
||||
synopsis:
|
||||
https://www.youtube.com/watch?v=0SVukUyys10
|
||||
|
||||
To join the server, you must apply at:
|
||||
http://www.jesseminecraft.webs.com/
|
||||
|
||||
This is just a brief video of the server as of Feb. 1, 2011.
|
||||
|
||||
Texture Pack I Use:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
title: 2011-02-01 - Jesse's Minecraft Server [Trailer - Feb.1]
|
||||
year: 2011
|
||||
s2011.e020101 - Jesse's Minecraft Server [Trailer - Feb.1].nfo
|
||||
NFO tags:
|
||||
episodedetails:
|
||||
aired: 2011-02-01
|
||||
download_index: 1
|
||||
episode: 20101
|
||||
genre: ytdl-sub
|
||||
mpaa: TV-14
|
||||
plot:
|
||||
https://www.youtube.com/watch?v=0SVukUyys10
|
||||
|
||||
To join the server, you must apply at:
|
||||
http://www.jesseminecraft.webs.com/
|
||||
|
||||
This is just a brief video of the server as of Feb. 1, 2011.
|
||||
|
||||
Texture Pack I Use:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
season: 2011
|
||||
subscription_has_download_archive: false
|
||||
title: 2011-02-01 - Jesse's Minecraft Server [Trailer - Feb.1]
|
||||
year: 2011
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
Files created:
|
||||
----------------------------------------
|
||||
{output_directory}/Season 2011
|
||||
s2011.e022701 - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg
|
||||
s2011.e022701 - Jesse's Minecraft Server [Trailer - Feb.27].info.json
|
||||
s2011.e022701 - Jesse's Minecraft Server [Trailer - Feb.27].mp4
|
||||
Video Tags:
|
||||
contentRating: TV-14
|
||||
date: 2011-02-27
|
||||
episode_id: 22701
|
||||
genre: ytdl-sub
|
||||
show: bilateral_test
|
||||
synopsis:
|
||||
https://www.youtube.com/watch?v=qPybBrXspds
|
||||
|
||||
Website Link:
|
||||
http://jesseminecraft.webs.com/
|
||||
|
||||
All you have to do is read the rules, and fill out a quick, little application to join the Server so we know you read them. We do this to keep the griefers/newbs out, it only takes three minutes, it's not that big of a deal.
|
||||
|
||||
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 750 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
|
||||
|
||||
There are over 200 empty properties that are ready for anyone to own! Join now!
|
||||
|
||||
This is the server state as of Feb. 27, 2011.
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
Texture Pack:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
|
||||
Recording Software:
|
||||
http://www.fraps.com/download.php
|
||||
|
||||
Video Editing Software:
|
||||
http://explore.live.com/windows-live-movie-maker?os=other
|
||||
|
||||
Song:
|
||||
Given to Fly - Pearl Jam
|
||||
(Off of the 'Yield' album)
|
||||
|
||||
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
|
||||
title: 2011-02-27 - Jesse's Minecraft Server [Trailer - Feb.27]
|
||||
year: 2011
|
||||
s2011.e022701 - Jesse's Minecraft Server [Trailer - Feb.27].nfo
|
||||
NFO tags:
|
||||
episodedetails:
|
||||
aired: 2011-02-27
|
||||
download_index: 2
|
||||
episode: 22701
|
||||
genre: ytdl-sub
|
||||
mpaa: TV-14
|
||||
plot:
|
||||
https://www.youtube.com/watch?v=qPybBrXspds
|
||||
|
||||
Website Link:
|
||||
http://jesseminecraft.webs.com/
|
||||
|
||||
All you have to do is read the rules, and fill out a quick, little application to join the Server so we know you read them. We do this to keep the griefers/newbs out, it only takes three minutes, it's not that big of a deal.
|
||||
|
||||
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 750 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
|
||||
|
||||
There are over 200 empty properties that are ready for anyone to own! Join now!
|
||||
|
||||
This is the server state as of Feb. 27, 2011.
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
Texture Pack:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
|
||||
Recording Software:
|
||||
http://www.fraps.com/download.php
|
||||
|
||||
Video Editing Software:
|
||||
http://explore.live.com/windows-live-movie-maker?os=other
|
||||
|
||||
Song:
|
||||
Given to Fly - Pearl Jam
|
||||
(Off of the 'Yield' album)
|
||||
|
||||
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
|
||||
season: 2011
|
||||
subscription_has_download_archive: true
|
||||
title: 2011-02-27 - Jesse's Minecraft Server [Trailer - Feb.27]
|
||||
year: 2011
|
||||
s2011.e032101 - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg
|
||||
s2011.e032101 - Jesse's Minecraft Server [Trailer - Mar.21].info.json
|
||||
s2011.e032101 - Jesse's Minecraft Server [Trailer - Mar.21].mp4
|
||||
Video Tags:
|
||||
contentRating: TV-14
|
||||
date: 2011-03-21
|
||||
episode_id: 32101
|
||||
genre: ytdl-sub
|
||||
show: bilateral_test
|
||||
synopsis:
|
||||
https://www.youtube.com/watch?v=DBjFvs6HafU
|
||||
|
||||
Website Link:
|
||||
http://jesseminecraft.webs.com/
|
||||
|
||||
To get on the whitelist, please look at the website linked above (^^^). Due to the overwhelming amount of people trying to join, I've made it so it costs $2 to become a member through paypal. All of it is explained on the website.
|
||||
|
||||
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 1000 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
|
||||
|
||||
There are over 300 empty properties that are ready for anyone to own! Join now!
|
||||
|
||||
This is the server state as of Mar. 21, 2011.
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
Texture Pack:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
|
||||
Recording Software:
|
||||
http://www.fraps.com/download.php
|
||||
|
||||
Video Editing Software:
|
||||
http://explore.live.com/windows-live-movie-maker?os=other
|
||||
|
||||
Song:
|
||||
Indifference - Pearl Jam
|
||||
(Off of the 'Vs.' album)
|
||||
|
||||
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
|
||||
title: 2011-03-21 - Jesse's Minecraft Server [Trailer - Mar.21]
|
||||
year: 2011
|
||||
s2011.e032101 - Jesse's Minecraft Server [Trailer - Mar.21].nfo
|
||||
NFO tags:
|
||||
episodedetails:
|
||||
aired: 2011-03-21
|
||||
download_index: 3
|
||||
episode: 32101
|
||||
genre: ytdl-sub
|
||||
mpaa: TV-14
|
||||
plot:
|
||||
https://www.youtube.com/watch?v=DBjFvs6HafU
|
||||
|
||||
Website Link:
|
||||
http://jesseminecraft.webs.com/
|
||||
|
||||
To get on the whitelist, please look at the website linked above (^^^). Due to the overwhelming amount of people trying to join, I've made it so it costs $2 to become a member through paypal. All of it is explained on the website.
|
||||
|
||||
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 1000 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
|
||||
|
||||
There are over 300 empty properties that are ready for anyone to own! Join now!
|
||||
|
||||
This is the server state as of Mar. 21, 2011.
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
Texture Pack:
|
||||
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
|
||||
|
||||
Recording Software:
|
||||
http://www.fraps.com/download.php
|
||||
|
||||
Video Editing Software:
|
||||
http://explore.live.com/windows-live-movie-maker?os=other
|
||||
|
||||
Song:
|
||||
Indifference - Pearl Jam
|
||||
(Off of the 'Vs.' album)
|
||||
|
||||
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
|
||||
season: 2011
|
||||
subscription_has_download_archive: true
|
||||
title: 2011-03-21 - Jesse's Minecraft Server [Trailer - Mar.21]
|
||||
year: 2011
|
||||
|
||||
Files modified:
|
||||
----------------------------------------
|
||||
{output_directory}
|
||||
.ytdl-sub-bilateral_test-download-archive.json
|
||||
|
|
@ -74,7 +74,7 @@ class TestConfigFilePartiallyValidatesPresets:
|
|||
expected_error_message="Validation error in partial_preset.download.1: "
|
||||
"'partial_preset.download.1' contains the field 'bad_key' which is not allowed. "
|
||||
"Allowed fields: download_reverse, include_sibling_metadata, playlist_thumbnails, "
|
||||
"source_thumbnails, url, variables",
|
||||
"source_thumbnails, url, variables, ytdl_options",
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ class TestScript:
|
|||
}
|
||||
)
|
||||
|
||||
assert script.resolve_once({"url": "{ %bilateral_url('nope') }"})["url"].native == "nope"
|
||||
script.add({"%bilateral_url_wrap": "{ %bilateral_url($0) }"})
|
||||
|
||||
assert (
|
||||
|
|
|
|||
Loading…
Reference in a new issue