From 7364aac00caebc59e4c0400d60e1a4af8f471bba Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Thu, 16 Oct 2025 08:01:37 -0700 Subject: [PATCH 01/46] [BUGFIX] Embedded null byte ffmpeg fix (#1363) Closes https://github.com/jmbannon/ytdl-sub/issues/710 Fixes the notorious embedded null byte error seen during FFMPEG metadata writes when handling special characters. --- src/ytdl_sub/utils/ffmpeg.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ytdl_sub/utils/ffmpeg.py b/src/ytdl_sub/utils/ffmpeg.py index 6d16814d..52374464 100644 --- a/src/ytdl_sub/utils/ffmpeg.py +++ b/src/ytdl_sub/utils/ffmpeg.py @@ -201,7 +201,11 @@ def add_ffmpeg_metadata_key_values(file_path: str, key_values: Dict[str, str]) - "-dn", # ignore data streams ] for key, value in key_values.items(): - ffmpeg_args.extend(["-metadata", f"{key}={value}"]) + # Some special characters (utf-16 maybe?) have null-byte + # which results in ffmpeg error, remove them outright + value_null_byte_removed = value.replace("\0", "") + ffmpeg_args.extend(["-metadata", f"{key}={value_null_byte_removed}"]) + ffmpeg_args.extend(["-codec", "copy", "-bitexact", tmp_file_path]) FFMPEG.run(ffmpeg_args) From a5dd4383986e9f5b1a09d5cbddf9fd97316be09a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 01:28:34 -0700 Subject: [PATCH 02/46] Bump yt-dlp[default] from 2025.10.14 to 2025.10.22 (#1366) Bumps [yt-dlp[default]](https://github.com/yt-dlp/yt-dlp) from 2025.10.14 to 2025.10.22. - [Release notes](https://github.com/yt-dlp/yt-dlp/releases) - [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md) - [Commits](https://github.com/yt-dlp/yt-dlp/compare/2025.10.14...2025.10.22) --- updated-dependencies: - dependency-name: yt-dlp[default] dependency-version: 2025.10.22 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index de023078..d35923ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ - "yt-dlp[default]==2025.10.14", + "yt-dlp[default]==2025.10.22", "colorama~=0.4", "mergedeep~=1.3", "mediafile~=0.12", From d3e978e51724b4dca600551c39f917485801e54a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 26 Oct 2025 23:57:35 -0700 Subject: [PATCH 03/46] [DEV] Bump pylint from 3.3.8 to 4.0.1 (#1361) * Bump pylint from 3.3.8 to 4.0.1 Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.8 to 4.0.1. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.8...v4.0.1) --- updated-dependencies: - dependency-name: pylint dependency-version: 4.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * update fixes --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Bannon --- pyproject.toml | 2 +- src/ytdl_sub/config/defaults.py | 2 ++ src/ytdl_sub/script/utils/type_checking.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d35923ef..4aa1e735 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ test = [ lint = [ "black==24.10.0", "isort==6.1.0", - "pylint==3.3.8", + "pylint==4.0.1", ] docs = [ "sphinx>=7,<9", diff --git a/src/ytdl_sub/config/defaults.py b/src/ytdl_sub/config/defaults.py index cf1f3096..e0746e1e 100644 --- a/src/ytdl_sub/config/defaults.py +++ b/src/ytdl_sub/config/defaults.py @@ -2,6 +2,8 @@ import os from ytdl_sub.utils.system import IS_WINDOWS +# pylint: disable=invalid-name + def _existing_path(*paths: str) -> str: """ diff --git a/src/ytdl_sub/script/utils/type_checking.py b/src/ytdl_sub/script/utils/type_checking.py index e8a8bc79..7598a526 100644 --- a/src/ytdl_sub/script/utils/type_checking.py +++ b/src/ytdl_sub/script/utils/type_checking.py @@ -2,6 +2,7 @@ import inspect from dataclasses import dataclass from inspect import FullArgSpec +from types import NoneType from typing import Callable from typing import List from typing import Optional @@ -50,7 +51,7 @@ def get_optional_type(optional_type: Type) -> Type[NamedType]: ------- Type within the Optional[Type] """ - return [arg for arg in optional_type.__args__ if arg != type(None)][0] + return [arg for arg in optional_type.__args__ if not isinstance(arg, NoneType)][0] def _is_union_compatible( From c3ca3c6379c264acfa6bdd444c2ca427eb9f2ec0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 08:45:00 -0700 Subject: [PATCH 04/46] [DEV] Bump isort from 6.1.0 to 7.0.0 (#1359) Bumps [isort](https://github.com/PyCQA/isort) from 6.1.0 to 7.0.0. - [Release notes](https://github.com/PyCQA/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - [Commits](https://github.com/PyCQA/isort/compare/6.1.0...7.0.0) --- updated-dependencies: - dependency-name: isort dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4aa1e735..ef02e11d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ test = [ ] lint = [ "black==24.10.0", - "isort==6.1.0", + "isort==7.0.0", "pylint==4.0.1", ] docs = [ From ed55f3a3f73520a9232f70cdd21ec6461bbfc24d Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Thu, 6 Nov 2025 21:05:18 -0800 Subject: [PATCH 05/46] [FEATURE] `cli-to-sub` argument to convert yt-dlp args to ytdl-sub (#1376) Example usage: ``` $ ytdl-sub cli-to-sub -S vcodec:h264,res:480,acodec:m4a ytdl_options: format_sort: - vcodec:h264 - res:480 - acodec:m4a ``` --- docs/source/usage.rst | 8 ++++ src/ytdl_sub/cli/entrypoint.py | 7 ++- src/ytdl_sub/cli/parsers/cli_to_sub.py | 64 ++++++++++++++++++++++++++ src/ytdl_sub/cli/parsers/main.py | 4 ++ tests/unit/main/test_main.py | 19 +++++++- 5 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/ytdl_sub/cli/parsers/cli_to_sub.py diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 113e14f8..0db65064 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -105,3 +105,11 @@ Preview the source variables for a given URL. Helpful to create new subscription -sc, --split-chapters View source variables after splitting by chapters + +CLI to SUB Options +------------------ +Convert yt-dlp cli arguments to ytdl-sub `ytdl_options` arguments. + +.. code-block:: + + ytdl-sub cli-to-sub [YT-DLP ARGS] diff --git a/src/ytdl_sub/cli/entrypoint.py b/src/ytdl_sub/cli/entrypoint.py index eace6173..b825d45e 100644 --- a/src/ytdl_sub/cli/entrypoint.py +++ b/src/ytdl_sub/cli/entrypoint.py @@ -12,6 +12,7 @@ from yt_dlp.utils import sanitize_filename from ytdl_sub.cli.output_summary import output_summary from ytdl_sub.cli.output_transaction_log import _maybe_validate_transaction_log_file from ytdl_sub.cli.output_transaction_log import output_transaction_log +from ytdl_sub.cli.parsers.cli_to_sub import print_cli_to_sub from ytdl_sub.cli.parsers.dl import DownloadArgsParser from ytdl_sub.cli.parsers.main import DEFAULT_CONFIG_FILE_NAME from ytdl_sub.cli.parsers.main import parser @@ -206,6 +207,10 @@ def main() -> List[Subscription]: args, extra_args = parser.parse_known_args() + if args.subparser == "cli-to-sub": + print_cli_to_sub(args=extra_args) + return [] + # Load the config if args.config: config = ConfigFile.from_file_path(args.config) @@ -263,7 +268,7 @@ def main() -> List[Subscription]: _view_url_from_cli(config=config, url=args.url, split_chapters=args.split_chapters) ) else: - raise ValidationException("Must provide one of the commands: sub, dl, view") + raise ValidationException("Must provide one of the commands: sub, dl, view, cli-to-sub") if not args.suppress_transaction_log: output_transaction_log( diff --git a/src/ytdl_sub/cli/parsers/cli_to_sub.py b/src/ytdl_sub/cli/parsers/cli_to_sub.py new file mode 100644 index 00000000..d4190042 --- /dev/null +++ b/src/ytdl_sub/cli/parsers/cli_to_sub.py @@ -0,0 +1,64 @@ +from typing import List + +import yt_dlp +import yt_dlp.options + +from ytdl_sub.utils.logger import Logger +from ytdl_sub.utils.yaml import dump_yaml + +logger = Logger.get() + +# pylint: disable=missing-function-docstring + +############################################################## +# --- BEGIN ---- +# Copy of https://github.com/yt-dlp/yt-dlp/blob/master/devscripts/cli_to_api.py + +create_parser = yt_dlp.options.create_parser + + +def parse_patched_options(opts): + + patched_parser = create_parser() + patched_parser.defaults.update( + { + "ignoreerrors": False, + "retries": 0, + "fragment_retries": 0, + "extract_flat": False, + "concat_playlist": "never", + "update_self": False, + } + ) + yt_dlp.options.create_parser = lambda: patched_parser + try: + return yt_dlp.parse_options(opts) + finally: + yt_dlp.options.create_parser = create_parser + + +default_opts = parse_patched_options([]).ydl_opts + + +def cli_to_api(opts, cli_defaults=False): + opts = (yt_dlp.parse_options if cli_defaults else parse_patched_options)(opts).ydl_opts + + diff = {k: v for k, v in opts.items() if default_opts[k] != v} + if "postprocessors" in diff: + diff["postprocessors"] = [ + pp for pp in diff["postprocessors"] if pp not in default_opts["postprocessors"] + ] + return diff + + +# --- END ---- +############################################################## + + +def print_cli_to_sub(args: List[str]) -> None: + api_args = cli_to_api(args) + if not api_args: + logger.info("Does not resolve to any yt-dlp args") + return + + print(dump_yaml({"ytdl_options": api_args})) diff --git a/src/ytdl_sub/cli/parsers/main.py b/src/ytdl_sub/cli/parsers/main.py index daa4d5ea..aa26b591 100644 --- a/src/ytdl_sub/cli/parsers/main.py +++ b/src/ytdl_sub/cli/parsers/main.py @@ -221,3 +221,7 @@ view_parser.add_argument( help="View source variables after splitting by chapters", ) view_parser.add_argument("url", help="URL to view source variables for") + +################################################################################################### +# CLI-TO-SUB PARSER +cli_to_sub_parser = subparsers.add_parser("cli-to-sub") diff --git a/tests/unit/main/test_main.py b/tests/unit/main/test_main.py index b9a6b07d..32ff6d57 100644 --- a/tests/unit/main/test_main.py +++ b/tests/unit/main/test_main.py @@ -244,7 +244,9 @@ def test_no_positional_arg_command(mock_sys_exit, tv_show_config_path): main() assert mock_error.call_count == 1 - assert mock_error.call_args.args[0] == "Must provide one of the commands: sub, dl, view" + assert mock_error.call_args.args[0] == ( + "Must provide one of the commands: sub, dl, view, cli-to-sub" + ) def test_bad_config_path(mock_sys_exit): @@ -264,3 +266,18 @@ def test_bad_config_path(mock_sys_exit): "The config file 'does_not_exist.yaml' could not be found. " "Did you set --config correctly?" ) + + +def test_cli_to_sub(mock_sys_exit, capsys): + with ( + mock_sys_exit(expected_exit_code=0), + patch.object( + sys, + "argv", + ["ytdl-sub", "cli-to-sub", "--mark-watched", "--mtime"], + ), + ): + main() + + captured = capsys.readouterr() + assert captured.out == ("ytdl_options:\n mark_watched: true\n updatetime: true\n\n") From e507e6ed4604ae5e8c3ada28a02ee73cb7fc3fef Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Fri, 7 Nov 2025 00:08:10 -0500 Subject: [PATCH 06/46] [DOCKER] Disable pip break-system-packages (#1373) Fixes `UPDATE_YT_DLP_ON_START` when set to 'nightly' as the pip command is missing --break-system-packages. Simplifies dockerfiles by setting this globally via pip config file. Thanks @Snuffy2 for the contribution! --- docker/Dockerfile | 4 +++- docker/Dockerfile.gui | 4 +++- docker/Dockerfile.ubuntu | 4 +++- docker/root/custom-cont-init.d/defaults | 8 ++++---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 558f75dc..53f04090 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -46,8 +46,10 @@ RUN mkdir -pv "${DEFAULT_WORKSPACE}" && \ phantomjs --version && \ cd -; \ fi && \ + # Configure pip globally + echo -e "[global]\nbreak-system-packages = true\nroot-user-action = ignore\nno-cache-dir = true" > /etc/pip.conf && \ # Install ytdl-sub, ensure it is installed properly - python3 -m pip install --break-system-packages --no-cache-dir ytdl_sub-*.whl && \ + python3 -m pip install ytdl_sub-*.whl && \ ytdl-sub -h && \ # Delete unneeded packages after install rm ytdl_sub-*.whl && \ diff --git a/docker/Dockerfile.gui b/docker/Dockerfile.gui index 11a31d6c..3b89252b 100644 --- a/docker/Dockerfile.gui +++ b/docker/Dockerfile.gui @@ -65,8 +65,10 @@ RUN mkdir -p /config && \ # Install Deno, required for YouTube downloads curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh -s -- -y --no-modify-path && \ deno --help && \ + # Configure pip globally + echo -e "[global]\nbreak-system-packages = true\nroot-user-action = ignore\nno-cache-dir = true" > /etc/pip.conf && \ # Install ytdl-sub, ensure it is installed properly - python3 -m pip install --no-cache-dir --break-system-packages ytdl_sub-*.whl && \ + python3 -m pip install ytdl_sub-*.whl && \ ytdl-sub -h && \ # Delete unneeded packages after install rm ytdl_sub-*.whl && \ diff --git a/docker/Dockerfile.ubuntu b/docker/Dockerfile.ubuntu index b2de4019..5199c8ec 100644 --- a/docker/Dockerfile.ubuntu +++ b/docker/Dockerfile.ubuntu @@ -68,8 +68,10 @@ RUN mkdir -pv "${DEFAULT_WORKSPACE}" && \ # Install Deno, required for YouTube downloads curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh -s -- -y --no-modify-path && \ deno --help && \ + # Configure pip globally + echo -e "[global]\nbreak-system-packages = true\nroot-user-action = ignore\nno-cache-dir = true" > /etc/pip.conf && \ # Install ytdl-sub, ensure it is installed properly - python3 -m pip install --no-cache-dir --break-system-packages ytdl_sub-*.whl && \ + python3 -m pip install ytdl_sub-*.whl && \ ytdl-sub -h && \ # Delete unneeded packages after install rm ytdl_sub-*.whl && \ diff --git a/docker/root/custom-cont-init.d/defaults b/docker/root/custom-cont-init.d/defaults index 0dcbc36d..3fd4c22d 100644 --- a/docker/root/custom-cont-init.d/defaults +++ b/docker/root/custom-cont-init.d/defaults @@ -28,14 +28,14 @@ echo "" > "$LOGS_TO_STDOUT" # https://github.com/yt-dlp/yt-dlp/wiki/Installation#with-pip if [ "$UPDATE_YT_DLP_ON_START" == "stable" ] ; then echo "UPDATE_YT_DLP_ON_START is set to stable, attempting to update to a new stable version of yt-dlp if it exists." - python3 -m pip install -U "yt-dlp[default]" --break-system-packages --root-user-action=ignore + python3 -m pip install -U "yt-dlp[default]" elif [ "$UPDATE_YT_DLP_ON_START" == "nightly" ] ; then echo "UPDATE_YT_DLP_ON_START is set to nightly, attempting to update to the latest nightly version of yt-dlp." - python3 -m pip install -U --pre "yt-dlp[default]" --root-user-action=ignore + python3 -m pip install -U --pre "yt-dlp[default]" elif [ "$UPDATE_YT_DLP_ON_START" == "master" ] ; then echo "UPDATE_YT_DLP_ON_START is set to master, pulling yt-dlp's latest commit for install." - python3 -m pip install -U pip hatchling wheel --root-user-action=ignore - python3 -m pip install --force-reinstall "yt-dlp[default] @ https://github.com/yt-dlp/yt-dlp/archive/master.tar.gz" --root-user-action=ignore + python3 -m pip install -U pip hatchling wheel + python3 -m pip install --force-reinstall "yt-dlp[default] @ https://github.com/yt-dlp/yt-dlp/archive/master.tar.gz" else echo "UPDATE_YT_DLP_ON_START is not set, using packaged version." fi From 2c33c3b49b0f7d43015352d2f5a94995ca371b9c Mon Sep 17 00:00:00 2001 From: Colin Davis <401330+e1ven@users.noreply.github.com> Date: Sat, 8 Nov 2025 01:01:48 +0000 Subject: [PATCH 07/46] [DOCKER] Add curl-cffi and yt-dlp-ejs dependencies Adds recommended deps from yt-dlp README --- docker/Dockerfile | 4 ++-- docker/Dockerfile.gui | 4 ++-- docker/Dockerfile.ubuntu | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 53f04090..adc3e443 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -48,8 +48,8 @@ RUN mkdir -pv "${DEFAULT_WORKSPACE}" && \ fi && \ # Configure pip globally echo -e "[global]\nbreak-system-packages = true\nroot-user-action = ignore\nno-cache-dir = true" > /etc/pip.conf && \ - # Install ytdl-sub, ensure it is installed properly - python3 -m pip install ytdl_sub-*.whl && \ + # Install ytdl-sub and yt-dlp dependencies, ensure they are installed properly + python3 -m pip install ytdl_sub-*.whl curl-cffi yt-dlp-ejs && \ ytdl-sub -h && \ # Delete unneeded packages after install rm ytdl_sub-*.whl && \ diff --git a/docker/Dockerfile.gui b/docker/Dockerfile.gui index 3b89252b..e78266ce 100644 --- a/docker/Dockerfile.gui +++ b/docker/Dockerfile.gui @@ -67,8 +67,8 @@ RUN mkdir -p /config && \ deno --help && \ # Configure pip globally echo -e "[global]\nbreak-system-packages = true\nroot-user-action = ignore\nno-cache-dir = true" > /etc/pip.conf && \ - # Install ytdl-sub, ensure it is installed properly - python3 -m pip install ytdl_sub-*.whl && \ + # Install ytdl-sub and yt-dlp dependencies, ensure they are installed properly + python3 -m pip install ytdl_sub-*.whl curl-cffi yt-dlp-ejs && \ ytdl-sub -h && \ # Delete unneeded packages after install rm ytdl_sub-*.whl && \ diff --git a/docker/Dockerfile.ubuntu b/docker/Dockerfile.ubuntu index 5199c8ec..5ba76866 100644 --- a/docker/Dockerfile.ubuntu +++ b/docker/Dockerfile.ubuntu @@ -70,8 +70,8 @@ RUN mkdir -pv "${DEFAULT_WORKSPACE}" && \ deno --help && \ # Configure pip globally echo -e "[global]\nbreak-system-packages = true\nroot-user-action = ignore\nno-cache-dir = true" > /etc/pip.conf && \ - # Install ytdl-sub, ensure it is installed properly - python3 -m pip install ytdl_sub-*.whl && \ + # Install ytdl-sub and yt-dlp dependencies, ensure they are installed properly + python3 -m pip install ytdl_sub-*.whl curl-cffi yt-dlp-ejs && \ ytdl-sub -h && \ # Delete unneeded packages after install rm ytdl_sub-*.whl && \ From a74c79d014bcc0ea220e0750211f1b50a128e8d2 Mon Sep 17 00:00:00 2001 From: Maxim Borisov Date: Sun, 9 Nov 2025 18:51:57 +0100 Subject: [PATCH 08/46] [DOCKER] Ensure correct cron log file permissions (#1378) Co-authored-by: bedlamzd --- docker/root/custom-cont-init.d/defaults | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/root/custom-cont-init.d/defaults b/docker/root/custom-cont-init.d/defaults index 3fd4c22d..f3bfd62e 100644 --- a/docker/root/custom-cont-init.d/defaults +++ b/docker/root/custom-cont-init.d/defaults @@ -17,13 +17,13 @@ echo "Starting ytdl-sub..." echo "alias ls='ls --color=auto'" > /config/.bashrc && \ echo "cd ." >> /config/.bashrc +# always create empty cron log file on start +echo "" > "$LOGS_TO_STDOUT" + # permissions chown -R ${PUID:-abc}:${PGID:-abc} \ /config -# always create empty cron log file on start -echo "" > "$LOGS_TO_STDOUT" - # update command reference: # https://github.com/yt-dlp/yt-dlp/wiki/Installation#with-pip if [ "$UPDATE_YT_DLP_ON_START" == "stable" ] ; then From 08180c0412d3436eb284c85bfb5e9921f8a3b38d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:37:17 -0800 Subject: [PATCH 09/46] Bump yt-dlp[default] from 2025.10.22 to 2025.11.12 (#1381) Bumps [yt-dlp[default]](https://github.com/yt-dlp/yt-dlp) from 2025.10.22 to 2025.11.12. - [Release notes](https://github.com/yt-dlp/yt-dlp/releases) - [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md) - [Commits](https://github.com/yt-dlp/yt-dlp/compare/2025.10.22...2025.11.12) --- updated-dependencies: - dependency-name: yt-dlp[default] dependency-version: 2025.11.12 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ef02e11d..ac8b121c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ - "yt-dlp[default]==2025.10.22", + "yt-dlp[default]==2025.11.12", "colorama~=0.4", "mergedeep~=1.3", "mediafile~=0.12", From 63753c564959e819d77af65018949d2620c5e929 Mon Sep 17 00:00:00 2001 From: Colin Davis <401330+e1ven@users.noreply.github.com> Date: Tue, 18 Nov 2025 06:42:13 +0000 Subject: [PATCH 10/46] [RFC] Add preserve_mtime config (#1382) Closes https://github.com/jmbannon/ytdl-sub/issues/386 Adds the ability to preserve mtime via Output Options. Usage: ``` output_options: preserve_mtime: True ``` Thanks @e1ven for the contribution! --- docs/source/config_reference/plugins.rst | 9 +++++++++ src/ytdl_sub/config/preset_options.py | 16 +++++++++++++++ .../subscriptions/subscription_download.py | 3 +++ src/ytdl_sub/utils/file_handler.py | 19 ++++++++++++++++++ .../enhanced_download_archive.py | 20 +++++++++++++++++++ tests/unit/config/test_preset.py | 20 +++++++++++++++++++ 6 files changed, 87 insertions(+) diff --git a/docs/source/config_reference/plugins.rst b/docs/source/config_reference/plugins.rst index 74fbe988..6b8229e4 100644 --- a/docs/source/config_reference/plugins.rst +++ b/docs/source/config_reference/plugins.rst @@ -763,6 +763,15 @@ Defines where to output files and thumbnails after all post-processing has compl The output directory to store all media files downloaded. +``preserve_mtime`` + +:expected type: Optional[Boolean] +:description: + Preserve the video's original upload time as the file modification time. + When True, sets the file's mtime to match the video's upload_date from + yt-dlp metadata. Defaults to False. + + ``thumbnail_name`` :expected type: Optional[EntryFormatter] diff --git a/src/ytdl_sub/config/preset_options.py b/src/ytdl_sub/config/preset_options.py index a16476ed..2f9e75df 100644 --- a/src/ytdl_sub/config/preset_options.py +++ b/src/ytdl_sub/config/preset_options.py @@ -107,6 +107,7 @@ class OutputOptions(OptionsDictValidator): "keep_max_files", "download_archive_standardized_date", "keep_files_date_eval", + "preserve_mtime", } @classmethod @@ -170,6 +171,10 @@ class OutputOptions(OptionsDictValidator): default=f"{{{v.upload_date_standardized.variable_name}}}", ) + self._preserve_mtime = self._validate_key_if_present( + key="preserve_mtime", validator=BoolValidator, default=False + ) + if ( self._keep_files_before or self._keep_files_after or self._keep_max_files ) and not self.maintain_download_archive: @@ -309,6 +314,17 @@ class OutputOptions(OptionsDictValidator): """ return self._keep_max_files + @property + def preserve_mtime(self) -> bool: + """ + :expected type: Optional[Boolean] + :description: + Preserve the video's original upload time as the file modification time. + When True, sets the file's mtime to match the video's upload_date from + yt-dlp metadata. Defaults to False. + """ + return self._preserve_mtime.value + def added_variables(self, unresolved_variables: Set[str]) -> Dict[PluginOperation, Set[str]]: return { # PluginOperation.MODIFY_ENTRY_METADATA: { diff --git a/src/ytdl_sub/subscriptions/subscription_download.py b/src/ytdl_sub/subscriptions/subscription_download.py index 43b811a4..c9a8b6f7 100644 --- a/src/ytdl_sub/subscriptions/subscription_download.py +++ b/src/ytdl_sub/subscriptions/subscription_download.py @@ -73,6 +73,7 @@ class SubscriptionDownload(BaseSubscription, ABC): file_metadata=entry_metadata, output_file_name=output_file_name, entry=entry, + preserve_mtime=self.output_options.preserve_mtime, ) # Always pretend to include the thumbnail in a dry-run @@ -87,6 +88,7 @@ class SubscriptionDownload(BaseSubscription, ABC): output_file_name=output_thumbnail_name, entry=entry, copy_file=True, + preserve_mtime=self.output_options.preserve_mtime, ) elif not entry.is_thumbnail_downloaded(): logger.warning( @@ -106,6 +108,7 @@ class SubscriptionDownload(BaseSubscription, ABC): file_name=entry.get_download_info_json_name(), output_file_name=output_info_json_name, entry=entry, + preserve_mtime=self.output_options.preserve_mtime, ) def _delete_working_directory(self, is_error: bool = False) -> None: diff --git a/src/ytdl_sub/utils/file_handler.py b/src/ytdl_sub/utils/file_handler.py index f7c6e44f..cdf9c099 100644 --- a/src/ytdl_sub/utils/file_handler.py +++ b/src/ytdl_sub/utils/file_handler.py @@ -430,6 +430,25 @@ class FileHandler: if os.path.isfile(file_path): os.remove(file_path) + @classmethod + def set_mtime(cls, file_path: Union[str, Path], mtime: float): + """ + Set the modification time of a file + + Parameters + ---------- + file_path + Path to the file to modify + mtime + Modification time as a Unix timestamp + """ + try: + # Set both access time and modification time + os.utime(file_path, (mtime, mtime)) + except OSError: + # If file operation fails, silently continue + pass + def move_file_to_output_directory( self, file_name: str, diff --git a/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py b/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py index 7a565412..50b12f42 100644 --- a/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +++ b/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py @@ -1,6 +1,7 @@ import copy import json import os.path +import time from dataclasses import dataclass from datetime import datetime from pathlib import Path @@ -642,6 +643,7 @@ class EnhancedDownloadArchive: output_file_name: Optional[str] = None, entry: Optional[Entry] = None, copy_file: bool = False, + preserve_mtime: bool = False, ): """ Saves a file from the working directory to the output directory and record it in the @@ -660,6 +662,8 @@ class EnhancedDownloadArchive: Optional. Entry that this file belongs to copy_file Optional. If True, copy the file. Move otherwise + preserve_mtime + Optional. If True and entry has upload_date, set file mtime to upload date """ if output_file_name is None: output_file_name = file_name @@ -674,6 +678,22 @@ class EnhancedDownloadArchive: copy_file=copy_file, ) + # Set mtime if preserve_mtime is enabled and we have an entry with upload_date + if preserve_mtime and entry and not self._file_handler.dry_run: + upload_date = entry.get(v.ytdl_sub_keep_files_date_eval, str) + if upload_date: + try: + # Convert YYYY-mm-dd to timestamp + upload_datetime = datetime.strptime(upload_date, "%Y-%m-%d") + upload_timestamp = time.mktime(upload_datetime.timetuple()) + + # Set mtime on the output file + output_file_path = Path(self._file_handler.output_directory) / output_file_name + FileHandler.set_mtime(output_file_path, upload_timestamp) + except (ValueError, OSError): + # If date parsing or file operation fails, silently continue + pass + # Determine if it's the entry file by seeing if the file_name to move matches the entry # download file name is_entry_file = entry and entry.get_download_file_name() == file_name diff --git a/tests/unit/config/test_preset.py b/tests/unit/config/test_preset.py index cb3c0b81..939eab92 100644 --- a/tests/unit/config/test_preset.py +++ b/tests/unit/config/test_preset.py @@ -120,6 +120,26 @@ class TestPreset: == "today-2months" ) + def test_preset_preserve_mtime_option(self, config_file, youtube_video, output_options): + # Test preserve_mtime defaults to False + preset_default = Preset( + config=config_file, + name="test_default", + value={"download": youtube_video, "output_options": output_options}, + ) + assert preset_default.output_options.preserve_mtime is False + + # Test preserve_mtime can be set to True + preset_enabled = Preset( + config=config_file, + name="test_enabled", + value={ + "download": youtube_video, + "output_options": dict(output_options, **{"preserve_mtime": True}), + }, + ) + assert preset_enabled.output_options.preserve_mtime is True + @pytest.mark.parametrize( "parent_preset", ["preset_self_loop", "preset_loop_0", "preset_loop_1"] ) From 24e71ce73334be3dd9e92c3fb0a90507c6305911 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Wed, 26 Nov 2025 20:22:22 -0800 Subject: [PATCH 11/46] [DOCKER] Make defaults script executable (#1388) Fixes https://github.com/jmbannon/ytdl-sub/issues/1387 and some potential docker issues by not having this file ran on start. --- docker/root/custom-cont-init.d/defaults | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 docker/root/custom-cont-init.d/defaults diff --git a/docker/root/custom-cont-init.d/defaults b/docker/root/custom-cont-init.d/defaults old mode 100644 new mode 100755 From 19f47cd9141531247a6eca567057a8ac9202c810 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Thu, 27 Nov 2025 18:43:27 -0800 Subject: [PATCH 12/46] [BACKEND] Optimize script interpreter (#1390) Makes scripting runtime much faster. --- src/ytdl_sub/config/overrides.py | 5 ++++ src/ytdl_sub/script/parser.py | 2 +- src/ytdl_sub/script/script.py | 11 +++++-- src/ytdl_sub/script/types/function.py | 17 +++++++---- src/ytdl_sub/script/types/syntax_tree.py | 30 +++++++++++++++++-- .../script/types/variable_dependency.py | 8 ++--- .../validators/string_formatter_validators.py | 1 + tests/unit/script/test_parser.py | 3 +- 8 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index 8ed6990c..06ca8fce 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -158,10 +158,15 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): script = entry.script unresolvable = entry.unresolvable + # Update the script internally so long as we are not supplying overrides + # that could alter the script with one-off state + update = function_overrides is None + try: return script.resolve_once( dict({"tmp_var": formatter.format_string}, **(function_overrides or {})), unresolvable=unresolvable, + update=update, )["tmp_var"] except ScriptVariableNotResolved as exc: raise StringFormattingException( diff --git a/src/ytdl_sub/script/parser.py b/src/ytdl_sub/script/parser.py index 737d6ffa..9dd3f186 100644 --- a/src/ytdl_sub/script/parser.py +++ b/src/ytdl_sub/script/parser.py @@ -605,7 +605,7 @@ def parse( name=name, custom_function_names=custom_function_names, variable_names=variable_names, - ).ast + ).ast.maybe_resolvable_casted() # pylint: enable=invalid-name diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index ab506318..e92e7fdd 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -10,6 +10,7 @@ from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.resolvable import BuiltInFunctionType from ytdl_sub.script.types.resolvable import Lambda from ytdl_sub.script.types.resolvable import Resolvable +from ytdl_sub.script.types.syntax_tree import ResolvedSyntaxTree from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable @@ -273,7 +274,7 @@ class Script: def _update_internally(self, resolved_variables: Dict[str, Resolvable]) -> None: for variable_name, resolved in resolved_variables.items(): - self._variables[variable_name] = SyntaxTree(ast=[resolved]) + self._variables[variable_name] = ResolvedSyntaxTree(ast=[resolved]) def _recursive_get_unresolved_output_filter_variables( self, current_var: SyntaxTree, subset_to_resolve: Set[str], unresolvable: Set[Variable] @@ -398,8 +399,8 @@ class Script: # Otherwise, if it has dependencies that are all resolved, then # resolve the definition - elif not definition.is_subset_of( - variables=resolved.keys(), custom_function_definitions=self._functions + elif definition.is_subset_of( + variables=resolved, custom_function_definitions=self._functions ): resolved[variable] = unresolved[variable].resolve( resolved_variables=resolved, @@ -522,6 +523,7 @@ class Script: variable_definitions: Dict[str, str], resolved: Optional[Dict[str, Resolvable]] = None, unresolvable: Optional[Set[str]] = None, + update: bool = False, ) -> Dict[str, Resolvable]: """ Given a new set of variable definitions, resolve them using the Script, but do not @@ -536,6 +538,8 @@ class Script: unresolvable Optional. Unresolvable variables that will be ignored in resolution, including all variables with a dependency to them. + update + Whether to update the script's state with resolved variables. Defaults to False. Returns ------- @@ -548,6 +552,7 @@ class Script: pre_resolved=resolved, unresolvable=unresolvable, output_filter=set(list(variable_definitions.keys())), + update=update, ).output finally: for name in variable_definitions.keys(): diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index 7ecd1999..5f33006e 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -1,4 +1,3 @@ -import copy import functools from abc import ABC from dataclasses import dataclass @@ -58,23 +57,29 @@ class CustomFunction(Function, NamedCustomFunction): # Should be validated in the Script raise UNREACHABLE - resolved_variables_with_args = copy.deepcopy(resolved_variables) + function_args: List[FunctionArgument] = [] for i, arg in enumerate(resolved_args): function_arg = FunctionArgument.from_idx(idx=i, custom_function_name=self.name) - if function_arg in resolved_variables_with_args: + if function_arg in resolved_variables: # function args should always be unique since they are only defined once # in the custom function as %custom_function_name___idx # and returned as a set from each custom function. raise UNREACHABLE - resolved_variables_with_args[function_arg] = arg + resolved_variables[function_arg] = arg + function_args.append(function_arg) - return custom_functions[self.name].resolve( - resolved_variables=resolved_variables_with_args, + out = custom_functions[self.name].resolve( + resolved_variables=resolved_variables, custom_functions=custom_functions, ) + for function_arg in function_args: + del resolved_variables[function_arg] + + return out + # Implies the custom function does not exist. This should have # been checked in the parser with raise UNREACHABLE diff --git a/src/ytdl_sub/script/types/syntax_tree.py b/src/ytdl_sub/script/types/syntax_tree.py index d058852c..eff39990 100644 --- a/src/ytdl_sub/script/types/syntax_tree.py +++ b/src/ytdl_sub/script/types/syntax_tree.py @@ -47,6 +47,32 @@ class SyntaxTree(VariableDependency): ------- A resolvable if the AST contains a single type that is resolvable. None otherwise. """ - if len(self.ast) == 1 and isinstance(self.ast[0], Resolvable): - return self.ast[0] return None + + def maybe_resolvable_casted(self) -> "SyntaxTree": + """ + Returns + ------- + Optimized SyntaxTree if its deemed resolvable + """ + if len(self.ast) == 1 and isinstance(self.ast[0], Resolvable): + return ResolvedSyntaxTree(self.ast) + return self + + +@dataclass(frozen=True) +class ResolvedSyntaxTree(SyntaxTree): + """ + SyntaxTree with optimized helper functions if it's known to be resolved. + """ + + def resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + custom_functions: Dict[str, VariableDependency], + ) -> Resolvable: + return self.ast[0] + + @property + def maybe_resolvable(self) -> Optional[Resolvable]: + return self.ast[0] diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index eb5f646e..21716124 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -162,7 +162,7 @@ class VariableDependency(ABC): @final def is_subset_of( self, - variables: Iterable[Variable], + variables: Dict[Variable, Resolvable], custom_function_definitions: Dict[str, "VariableDependency"], ) -> bool: """ @@ -171,12 +171,12 @@ class VariableDependency(ABC): True if it contains all input variables as a dependency. False otherwise. """ for custom_function in self.custom_functions: - if custom_function_definitions[custom_function.name].is_subset_of( + if not custom_function_definitions[custom_function.name].is_subset_of( variables=variables, custom_function_definitions=custom_function_definitions ): - return True + return False - return not self.variables.issubset(variables) + return all(var in variables for var in self.variables) @final def contains( diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 17436700..567f0f8d 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -246,6 +246,7 @@ def _validate_formatter( ) }, unresolvable=unresolvable, + update=True, ) except RuntimeException as exc: if isinstance(exc, ScriptVariableNotResolved) and is_static_formatter: diff --git a/tests/unit/script/test_parser.py b/tests/unit/script/test_parser.py index b40e13ca..c6694562 100644 --- a/tests/unit/script/test_parser.py +++ b/tests/unit/script/test_parser.py @@ -16,6 +16,7 @@ from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.types.resolvable import Lambda from ytdl_sub.script.types.resolvable import String +from ytdl_sub.script.types.syntax_tree import ResolvedSyntaxTree from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.utils.exceptions import InvalidSyntaxException @@ -24,7 +25,7 @@ from ytdl_sub.script.utils.exceptions import InvalidSyntaxException class TestParser: def test_simple(self): parsed = parse("hello world") - assert parsed == SyntaxTree([String(value="hello world")]) + assert parsed == ResolvedSyntaxTree([String(value="hello world")]) assert parsed.variables == set() def test_single_function_one_arg(self): From 8d5d2be6a7e8839e8316d85c6fd9690cddf53c50 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 28 Nov 2025 10:14:18 -0800 Subject: [PATCH 13/46] [FEATURE] TV Show Collection multi-url, s00 support (#1391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/jmbannon/ytdl-sub/issues/1251, https://github.com/jmbannon/ytdl-sub/issues/1290 Required for https://github.com/jmbannon/ytdl-sub/issues/1383 Adds both s00 support and multi-url support per season, up to 11 URLs currently. This can and should be extended, but with a more principled approach instead of the current method: AI generating boiler-plate YAML 🙂 ``` Plex TV Show Collection: = Music: "~Beyond the Guitar": s00_name: "Specials" s00_url: - "https://www.youtube.com/watch?v=vXzguOdulAI" - "https://www.youtube.com/watch?v=IGwYDvaGAz0" s01_name: "Videos" s01_url: - "https://www.youtube.com/c/BeyondTheGuitar" - "https://www.youtube.com/@BeyondTheGuitarAcademy" s02_name: "Covers" s02_url: "https://www.youtube.com/playlist?list=PLE62gWlWZk5NWVAVuf0Lm9jdv_-_KXs0W" ``` --- docs/source/prebuilt_presets/tv_shows.rst | 25 + .../tv_show/tv_show_collection.yaml | 4722 ++++++++++++++++- .../test_tv_show_collection.py | 51 + 3 files changed, 4718 insertions(+), 80 deletions(-) diff --git a/docs/source/prebuilt_presets/tv_shows.rst b/docs/source/prebuilt_presets/tv_shows.rst index 0d4a038c..6dfce04a 100644 --- a/docs/source/prebuilt_presets/tv_shows.rst +++ b/docs/source/prebuilt_presets/tv_shows.rst @@ -160,6 +160,8 @@ Two main use cases of a collection are: 2. Organize one or more YouTube channels/playlists, where each season represents a separate channel/playlist. +Today, ytdl-supports up to 40 seasons with 11 URLs per season. + Example ~~~~~~~ @@ -185,6 +187,29 @@ Must define ``tv_show_directory``. Available presets: s02_name: "Covers" s02_url: "https://www.youtube.com/playlist?list=PLE62gWlWZk5NWVAVuf0Lm9jdv_-_KXs0W" +Other notable features include: + +* TV show poster info is pulled from the first URL in s01. +* Duplicate videos in different URLs (channel /videos vs playlist) will not download twice. + + * The video will attributed to the season with the highest number. +* Individual seasons support both single and multi URL. +* s00 is supported for specials. + +.. code-block:: yaml + + "~Beyond the Guitar": + s00_name: "Specials" + s00_url: + - "https://www.youtube.com/watch?v=vXzguOdulAI" + - "https://www.youtube.com/watch?v=IGwYDvaGAz0" + s01_name: "Videos" + s01_url: + - "https://www.youtube.com/c/BeyondTheGuitar" + - "https://www.youtube.com/@BeyondTheGuitarAcademy" + s02_name: "Covers" + s02_url: "https://www.youtube.com/playlist?list=PLE62gWlWZk5NWVAVuf0Lm9jdv_-_KXs0W" + Advanced Usage ~~~~~~~~~~~~~~ diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml index 70c0fd9d..29019cb2 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +++ b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml @@ -124,7 +124,7 @@ presets: - "_tv_show_collection_asserts" download: - - url: "{collection_season_1_url}" + - url: "{ %get_season_url(collection_season_1_url, 0) }" variables: collection_season_number: "1" collection_season_name: "{collection_season_1_name}" @@ -142,318 +142,1970 @@ presets: uid: "avatar_uncropped" - name: "{tv_show_fanart_file_name}" uid: "banner_uncropped" + - url: "{ %get_season_url(collection_season_1_url, 1) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + - url: "{ %get_season_url(collection_season_1_url, 2) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + - url: "{ %get_season_url(collection_season_1_url, 3) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + - url: "{ %get_season_url(collection_season_1_url, 4) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + - url: "{ %get_season_url(collection_season_1_url, 5) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + - url: "{ %get_season_url(collection_season_1_url, 6) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + - url: "{ %get_season_url(collection_season_1_url, 7) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + - url: "{ %get_season_url(collection_season_1_url, 8) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + - url: "{ %get_season_url(collection_season_1_url, 9) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" + - url: "{ %get_season_url(collection_season_1_url, 10) }" + variables: + collection_season_number: "1" + collection_season_name: "{collection_season_1_name}" - - url: "{collection_season_2_url}" + - url: "{ %get_season_url(collection_season_2_url, 0) }" variables: collection_season_number: "2" collection_season_name: "{collection_season_2_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_2_url, 1) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + - url: "{ %get_season_url(collection_season_2_url, 2) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + - url: "{ %get_season_url(collection_season_2_url, 3) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + - url: "{ %get_season_url(collection_season_2_url, 4) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + - url: "{ %get_season_url(collection_season_2_url, 5) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + - url: "{ %get_season_url(collection_season_2_url, 6) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + - url: "{ %get_season_url(collection_season_2_url, 7) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + - url: "{ %get_season_url(collection_season_2_url, 8) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + - url: "{ %get_season_url(collection_season_2_url, 9) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" + - url: "{ %get_season_url(collection_season_2_url, 10) }" + variables: + collection_season_number: "2" + collection_season_name: "{collection_season_2_name}" - - url: "{collection_season_3_url}" + - url: "{ %get_season_url(collection_season_3_url, 0) }" variables: collection_season_number: "3" collection_season_name: "{collection_season_3_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_3_url, 1) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + - url: "{ %get_season_url(collection_season_3_url, 2) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + - url: "{ %get_season_url(collection_season_3_url, 3) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + - url: "{ %get_season_url(collection_season_3_url, 4) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + - url: "{ %get_season_url(collection_season_3_url, 5) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + - url: "{ %get_season_url(collection_season_3_url, 6) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + - url: "{ %get_season_url(collection_season_3_url, 7) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + - url: "{ %get_season_url(collection_season_3_url, 8) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + - url: "{ %get_season_url(collection_season_3_url, 9) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" + - url: "{ %get_season_url(collection_season_3_url, 10) }" + variables: + collection_season_number: "3" + collection_season_name: "{collection_season_3_name}" - - url: "{collection_season_4_url}" + - url: "{ %get_season_url(collection_season_4_url, 0) }" variables: collection_season_number: "4" collection_season_name: "{collection_season_4_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_4_url, 1) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + - url: "{ %get_season_url(collection_season_4_url, 2) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + - url: "{ %get_season_url(collection_season_4_url, 3) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + - url: "{ %get_season_url(collection_season_4_url, 4) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + - url: "{ %get_season_url(collection_season_4_url, 5) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + - url: "{ %get_season_url(collection_season_4_url, 6) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + - url: "{ %get_season_url(collection_season_4_url, 7) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + - url: "{ %get_season_url(collection_season_4_url, 8) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + - url: "{ %get_season_url(collection_season_4_url, 9) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" + - url: "{ %get_season_url(collection_season_4_url, 10) }" + variables: + collection_season_number: "4" + collection_season_name: "{collection_season_4_name}" - - url: "{collection_season_5_url}" + - url: "{ %get_season_url(collection_season_5_url, 0) }" variables: collection_season_number: "5" collection_season_name: "{collection_season_5_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_5_url, 1) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + - url: "{ %get_season_url(collection_season_5_url, 2) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + - url: "{ %get_season_url(collection_season_5_url, 3) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + - url: "{ %get_season_url(collection_season_5_url, 4) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + - url: "{ %get_season_url(collection_season_5_url, 5) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + - url: "{ %get_season_url(collection_season_5_url, 6) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + - url: "{ %get_season_url(collection_season_5_url, 7) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + - url: "{ %get_season_url(collection_season_5_url, 8) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + - url: "{ %get_season_url(collection_season_5_url, 9) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" + - url: "{ %get_season_url(collection_season_5_url, 10) }" + variables: + collection_season_number: "5" + collection_season_name: "{collection_season_5_name}" - - url: "{collection_season_6_url}" + - url: "{ %get_season_url(collection_season_6_url, 0) }" variables: collection_season_number: "6" collection_season_name: "{collection_season_6_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_6_url, 1) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + - url: "{ %get_season_url(collection_season_6_url, 2) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + - url: "{ %get_season_url(collection_season_6_url, 3) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + - url: "{ %get_season_url(collection_season_6_url, 4) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + - url: "{ %get_season_url(collection_season_6_url, 5) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + - url: "{ %get_season_url(collection_season_6_url, 6) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + - url: "{ %get_season_url(collection_season_6_url, 7) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + - url: "{ %get_season_url(collection_season_6_url, 8) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + - url: "{ %get_season_url(collection_season_6_url, 9) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" + - url: "{ %get_season_url(collection_season_6_url, 10) }" + variables: + collection_season_number: "6" + collection_season_name: "{collection_season_6_name}" - - url: "{collection_season_7_url}" + - url: "{ %get_season_url(collection_season_7_url, 0) }" variables: collection_season_number: "7" collection_season_name: "{collection_season_7_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_7_url, 1) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + - url: "{ %get_season_url(collection_season_7_url, 2) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + - url: "{ %get_season_url(collection_season_7_url, 3) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + - url: "{ %get_season_url(collection_season_7_url, 4) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + - url: "{ %get_season_url(collection_season_7_url, 5) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + - url: "{ %get_season_url(collection_season_7_url, 6) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + - url: "{ %get_season_url(collection_season_7_url, 7) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + - url: "{ %get_season_url(collection_season_7_url, 8) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + - url: "{ %get_season_url(collection_season_7_url, 9) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" + - url: "{ %get_season_url(collection_season_7_url, 10) }" + variables: + collection_season_number: "7" + collection_season_name: "{collection_season_7_name}" - - url: "{collection_season_8_url}" + - url: "{ %get_season_url(collection_season_8_url, 0) }" variables: collection_season_number: "8" collection_season_name: "{collection_season_8_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_8_url, 1) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + - url: "{ %get_season_url(collection_season_8_url, 2) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + - url: "{ %get_season_url(collection_season_8_url, 3) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + - url: "{ %get_season_url(collection_season_8_url, 4) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + - url: "{ %get_season_url(collection_season_8_url, 5) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + - url: "{ %get_season_url(collection_season_8_url, 6) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + - url: "{ %get_season_url(collection_season_8_url, 7) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + - url: "{ %get_season_url(collection_season_8_url, 8) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + - url: "{ %get_season_url(collection_season_8_url, 9) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" + - url: "{ %get_season_url(collection_season_8_url, 10) }" + variables: + collection_season_number: "8" + collection_season_name: "{collection_season_8_name}" - - url: "{collection_season_9_url}" + - url: "{ %get_season_url(collection_season_9_url, 0) }" variables: collection_season_number: "9" collection_season_name: "{collection_season_9_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_9_url, 1) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + - url: "{ %get_season_url(collection_season_9_url, 2) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + - url: "{ %get_season_url(collection_season_9_url, 3) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + - url: "{ %get_season_url(collection_season_9_url, 4) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + - url: "{ %get_season_url(collection_season_9_url, 5) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + - url: "{ %get_season_url(collection_season_9_url, 6) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + - url: "{ %get_season_url(collection_season_9_url, 7) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + - url: "{ %get_season_url(collection_season_9_url, 8) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + - url: "{ %get_season_url(collection_season_9_url, 9) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" + - url: "{ %get_season_url(collection_season_9_url, 10) }" + variables: + collection_season_number: "9" + collection_season_name: "{collection_season_9_name}" - - url: "{collection_season_10_url}" + - url: "{%get_season_url(collection_season_10_url, 0)}" variables: collection_season_number: "10" collection_season_name: "{collection_season_10_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_10_url, 1) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + - url: "{ %get_season_url(collection_season_10_url, 2) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + - url: "{ %get_season_url(collection_season_10_url, 3) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + - url: "{ %get_season_url(collection_season_10_url, 4) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + - url: "{ %get_season_url(collection_season_10_url, 5) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + - url: "{ %get_season_url(collection_season_10_url, 6) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + - url: "{ %get_season_url(collection_season_10_url, 7) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + - url: "{ %get_season_url(collection_season_10_url, 8) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + - url: "{ %get_season_url(collection_season_10_url, 9) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" + - url: "{ %get_season_url(collection_season_10_url, 10) }" + variables: + collection_season_number: "10" + collection_season_name: "{collection_season_10_name}" - - url: "{collection_season_11_url}" + - url: "{%get_season_url(collection_season_11_url, 0)}" variables: collection_season_number: "11" collection_season_name: "{collection_season_11_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_11_url, 1) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + - url: "{ %get_season_url(collection_season_11_url, 2) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + - url: "{ %get_season_url(collection_season_11_url, 3) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + - url: "{ %get_season_url(collection_season_11_url, 4) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + - url: "{ %get_season_url(collection_season_11_url, 5) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + - url: "{ %get_season_url(collection_season_11_url, 6) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + - url: "{ %get_season_url(collection_season_11_url, 7) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + - url: "{ %get_season_url(collection_season_11_url, 8) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + - url: "{ %get_season_url(collection_season_11_url, 9) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" + - url: "{ %get_season_url(collection_season_11_url, 10) }" + variables: + collection_season_number: "11" + collection_season_name: "{collection_season_11_name}" - - url: "{collection_season_12_url}" + - url: "{%get_season_url(collection_season_12_url, 0)}" variables: collection_season_number: "12" collection_season_name: "{collection_season_12_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_12_url, 1) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + - url: "{ %get_season_url(collection_season_12_url, 2) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + - url: "{ %get_season_url(collection_season_12_url, 3) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + - url: "{ %get_season_url(collection_season_12_url, 4) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + - url: "{ %get_season_url(collection_season_12_url, 5) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + - url: "{ %get_season_url(collection_season_12_url, 6) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + - url: "{ %get_season_url(collection_season_12_url, 7) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + - url: "{ %get_season_url(collection_season_12_url, 8) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + - url: "{ %get_season_url(collection_season_12_url, 9) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" + - url: "{ %get_season_url(collection_season_12_url, 10) }" + variables: + collection_season_number: "12" + collection_season_name: "{collection_season_12_name}" - - url: "{collection_season_13_url}" + - url: "{%get_season_url(collection_season_13_url, 0)}" variables: collection_season_number: "13" collection_season_name: "{collection_season_13_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_13_url, 1) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + - url: "{ %get_season_url(collection_season_13_url, 2) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + - url: "{ %get_season_url(collection_season_13_url, 3) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + - url: "{ %get_season_url(collection_season_13_url, 4) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + - url: "{ %get_season_url(collection_season_13_url, 5) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + - url: "{ %get_season_url(collection_season_13_url, 6) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + - url: "{ %get_season_url(collection_season_13_url, 7) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + - url: "{ %get_season_url(collection_season_13_url, 8) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + - url: "{ %get_season_url(collection_season_13_url, 9) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" + - url: "{ %get_season_url(collection_season_13_url, 10) }" + variables: + collection_season_number: "13" + collection_season_name: "{collection_season_13_name}" - - url: "{collection_season_14_url}" + - url: "{%get_season_url(collection_season_14_url, 0)}" variables: collection_season_number: "14" collection_season_name: "{collection_season_14_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_14_url, 1) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + - url: "{ %get_season_url(collection_season_14_url, 2) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + - url: "{ %get_season_url(collection_season_14_url, 3) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + - url: "{ %get_season_url(collection_season_14_url, 4) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + - url: "{ %get_season_url(collection_season_14_url, 5) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + - url: "{ %get_season_url(collection_season_14_url, 6) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + - url: "{ %get_season_url(collection_season_14_url, 7) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + - url: "{ %get_season_url(collection_season_14_url, 8) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + - url: "{ %get_season_url(collection_season_14_url, 9) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" + - url: "{ %get_season_url(collection_season_14_url, 10) }" + variables: + collection_season_number: "14" + collection_season_name: "{collection_season_14_name}" - - url: "{collection_season_15_url}" + - url: "{%get_season_url(collection_season_15_url, 0)}" variables: collection_season_number: "15" collection_season_name: "{collection_season_15_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_15_url, 1) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + - url: "{ %get_season_url(collection_season_15_url, 2) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + - url: "{ %get_season_url(collection_season_15_url, 3) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + - url: "{ %get_season_url(collection_season_15_url, 4) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + - url: "{ %get_season_url(collection_season_15_url, 5) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + - url: "{ %get_season_url(collection_season_15_url, 6) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + - url: "{ %get_season_url(collection_season_15_url, 7) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + - url: "{ %get_season_url(collection_season_15_url, 8) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + - url: "{ %get_season_url(collection_season_15_url, 9) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" + - url: "{ %get_season_url(collection_season_15_url, 10) }" + variables: + collection_season_number: "15" + collection_season_name: "{collection_season_15_name}" - - url: "{collection_season_16_url}" + - url: "{%get_season_url(collection_season_16_url, 0)}" variables: collection_season_number: "16" collection_season_name: "{collection_season_16_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_16_url, 1) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + - url: "{ %get_season_url(collection_season_16_url, 2) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + - url: "{ %get_season_url(collection_season_16_url, 3) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + - url: "{ %get_season_url(collection_season_16_url, 4) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + - url: "{ %get_season_url(collection_season_16_url, 5) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + - url: "{ %get_season_url(collection_season_16_url, 6) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + - url: "{ %get_season_url(collection_season_16_url, 7) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + - url: "{ %get_season_url(collection_season_16_url, 8) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + - url: "{ %get_season_url(collection_season_16_url, 9) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" + - url: "{ %get_season_url(collection_season_16_url, 10) }" + variables: + collection_season_number: "16" + collection_season_name: "{collection_season_16_name}" - - url: "{collection_season_17_url}" + - url: "{%get_season_url(collection_season_17_url, 0)}" variables: collection_season_number: "17" collection_season_name: "{collection_season_17_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_17_url, 1) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + - url: "{ %get_season_url(collection_season_17_url, 2) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + - url: "{ %get_season_url(collection_season_17_url, 3) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + - url: "{ %get_season_url(collection_season_17_url, 4) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + - url: "{ %get_season_url(collection_season_17_url, 5) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + - url: "{ %get_season_url(collection_season_17_url, 6) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + - url: "{ %get_season_url(collection_season_17_url, 7) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + - url: "{ %get_season_url(collection_season_17_url, 8) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + - url: "{ %get_season_url(collection_season_17_url, 9) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" + - url: "{ %get_season_url(collection_season_17_url, 10) }" + variables: + collection_season_number: "17" + collection_season_name: "{collection_season_17_name}" - - url: "{collection_season_18_url}" + - url: "{%get_season_url(collection_season_18_url, 0)}" variables: collection_season_number: "18" collection_season_name: "{collection_season_18_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_18_url, 1) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + - url: "{ %get_season_url(collection_season_18_url, 2) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + - url: "{ %get_season_url(collection_season_18_url, 3) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + - url: "{ %get_season_url(collection_season_18_url, 4) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + - url: "{ %get_season_url(collection_season_18_url, 5) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + - url: "{ %get_season_url(collection_season_18_url, 6) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + - url: "{ %get_season_url(collection_season_18_url, 7) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + - url: "{ %get_season_url(collection_season_18_url, 8) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + - url: "{ %get_season_url(collection_season_18_url, 9) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" + - url: "{ %get_season_url(collection_season_18_url, 10) }" + variables: + collection_season_number: "18" + collection_season_name: "{collection_season_18_name}" - - url: "{collection_season_19_url}" + - url: "{%get_season_url(collection_season_19_url, 0)}" variables: collection_season_number: "19" collection_season_name: "{collection_season_19_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_19_url, 1) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + - url: "{ %get_season_url(collection_season_19_url, 2) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + - url: "{ %get_season_url(collection_season_19_url, 3) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + - url: "{ %get_season_url(collection_season_19_url, 4) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + - url: "{ %get_season_url(collection_season_19_url, 5) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + - url: "{ %get_season_url(collection_season_19_url, 6) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + - url: "{ %get_season_url(collection_season_19_url, 7) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + - url: "{ %get_season_url(collection_season_19_url, 8) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + - url: "{ %get_season_url(collection_season_19_url, 9) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" + - url: "{ %get_season_url(collection_season_19_url, 10) }" + variables: + collection_season_number: "19" + collection_season_name: "{collection_season_19_name}" - - url: "{collection_season_20_url}" + - url: "{%get_season_url(collection_season_20_url, 0)}" variables: collection_season_number: "20" collection_season_name: "{collection_season_20_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_20_url, 1) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + - url: "{ %get_season_url(collection_season_20_url, 2) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + - url: "{ %get_season_url(collection_season_20_url, 3) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + - url: "{ %get_season_url(collection_season_20_url, 4) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + - url: "{ %get_season_url(collection_season_20_url, 5) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + - url: "{ %get_season_url(collection_season_20_url, 6) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + - url: "{ %get_season_url(collection_season_20_url, 7) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + - url: "{ %get_season_url(collection_season_20_url, 8) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + - url: "{ %get_season_url(collection_season_20_url, 9) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" + - url: "{ %get_season_url(collection_season_20_url, 10) }" + variables: + collection_season_number: "20" + collection_season_name: "{collection_season_20_name}" - - url: "{collection_season_21_url}" + - url: "{%get_season_url(collection_season_21_url, 0)}" variables: collection_season_number: "21" collection_season_name: "{collection_season_21_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_21_url, 1) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + - url: "{ %get_season_url(collection_season_21_url, 2) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + - url: "{ %get_season_url(collection_season_21_url, 3) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + - url: "{ %get_season_url(collection_season_21_url, 4) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + - url: "{ %get_season_url(collection_season_21_url, 5) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + - url: "{ %get_season_url(collection_season_21_url, 6) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + - url: "{ %get_season_url(collection_season_21_url, 7) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + - url: "{ %get_season_url(collection_season_21_url, 8) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + - url: "{ %get_season_url(collection_season_21_url, 9) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" + - url: "{ %get_season_url(collection_season_21_url, 10) }" + variables: + collection_season_number: "21" + collection_season_name: "{collection_season_21_name}" - - url: "{collection_season_22_url}" + - url: "{%get_season_url(collection_season_22_url, 0)}" variables: collection_season_number: "22" collection_season_name: "{collection_season_22_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_22_url, 1) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + - url: "{ %get_season_url(collection_season_22_url, 2) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + - url: "{ %get_season_url(collection_season_22_url, 3) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + - url: "{ %get_season_url(collection_season_22_url, 4) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + - url: "{ %get_season_url(collection_season_22_url, 5) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + - url: "{ %get_season_url(collection_season_22_url, 6) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + - url: "{ %get_season_url(collection_season_22_url, 7) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + - url: "{ %get_season_url(collection_season_22_url, 8) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + - url: "{ %get_season_url(collection_season_22_url, 9) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" + - url: "{ %get_season_url(collection_season_22_url, 10) }" + variables: + collection_season_number: "22" + collection_season_name: "{collection_season_22_name}" - - url: "{collection_season_23_url}" + - url: "{%get_season_url(collection_season_23_url, 0)}" variables: collection_season_number: "23" collection_season_name: "{collection_season_23_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_23_url, 1) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + - url: "{ %get_season_url(collection_season_23_url, 2) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + - url: "{ %get_season_url(collection_season_23_url, 3) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + - url: "{ %get_season_url(collection_season_23_url, 4) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + - url: "{ %get_season_url(collection_season_23_url, 5) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + - url: "{ %get_season_url(collection_season_23_url, 6) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + - url: "{ %get_season_url(collection_season_23_url, 7) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + - url: "{ %get_season_url(collection_season_23_url, 8) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + - url: "{ %get_season_url(collection_season_23_url, 9) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" + - url: "{ %get_season_url(collection_season_23_url, 10) }" + variables: + collection_season_number: "23" + collection_season_name: "{collection_season_23_name}" - - url: "{collection_season_24_url}" + - url: "{%get_season_url(collection_season_24_url, 0)}" variables: collection_season_number: "24" collection_season_name: "{collection_season_24_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_24_url, 1) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + - url: "{ %get_season_url(collection_season_24_url, 2) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + - url: "{ %get_season_url(collection_season_24_url, 3) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + - url: "{ %get_season_url(collection_season_24_url, 4) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + - url: "{ %get_season_url(collection_season_24_url, 5) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + - url: "{ %get_season_url(collection_season_24_url, 6) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + - url: "{ %get_season_url(collection_season_24_url, 7) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + - url: "{ %get_season_url(collection_season_24_url, 8) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + - url: "{ %get_season_url(collection_season_24_url, 9) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" + - url: "{ %get_season_url(collection_season_24_url, 10) }" + variables: + collection_season_number: "24" + collection_season_name: "{collection_season_24_name}" - - url: "{collection_season_25_url}" + - url: "{%get_season_url(collection_season_25_url, 0)}" variables: collection_season_number: "25" collection_season_name: "{collection_season_25_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_25_url, 1) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + - url: "{ %get_season_url(collection_season_25_url, 2) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + - url: "{ %get_season_url(collection_season_25_url, 3) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + - url: "{ %get_season_url(collection_season_25_url, 4) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + - url: "{ %get_season_url(collection_season_25_url, 5) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + - url: "{ %get_season_url(collection_season_25_url, 6) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + - url: "{ %get_season_url(collection_season_25_url, 7) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + - url: "{ %get_season_url(collection_season_25_url, 8) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + - url: "{ %get_season_url(collection_season_25_url, 9) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" + - url: "{ %get_season_url(collection_season_25_url, 10) }" + variables: + collection_season_number: "25" + collection_season_name: "{collection_season_25_name}" - - url: "{collection_season_26_url}" + - url: "{%get_season_url(collection_season_26_url, 0)}" variables: collection_season_number: "26" collection_season_name: "{collection_season_26_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_26_url, 1) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + - url: "{ %get_season_url(collection_season_26_url, 2) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + - url: "{ %get_season_url(collection_season_26_url, 3) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + - url: "{ %get_season_url(collection_season_26_url, 4) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + - url: "{ %get_season_url(collection_season_26_url, 5) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + - url: "{ %get_season_url(collection_season_26_url, 6) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + - url: "{ %get_season_url(collection_season_26_url, 7) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + - url: "{ %get_season_url(collection_season_26_url, 8) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + - url: "{ %get_season_url(collection_season_26_url, 9) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" + - url: "{ %get_season_url(collection_season_26_url, 10) }" + variables: + collection_season_number: "26" + collection_season_name: "{collection_season_26_name}" - - url: "{collection_season_27_url}" + - url: "{%get_season_url(collection_season_27_url, 0)}" variables: collection_season_number: "27" collection_season_name: "{collection_season_27_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_27_url, 1) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + - url: "{ %get_season_url(collection_season_27_url, 2) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + - url: "{ %get_season_url(collection_season_27_url, 3) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + - url: "{ %get_season_url(collection_season_27_url, 4) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + - url: "{ %get_season_url(collection_season_27_url, 5) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + - url: "{ %get_season_url(collection_season_27_url, 6) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + - url: "{ %get_season_url(collection_season_27_url, 7) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + - url: "{ %get_season_url(collection_season_27_url, 8) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + - url: "{ %get_season_url(collection_season_27_url, 9) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" + - url: "{ %get_season_url(collection_season_27_url, 10) }" + variables: + collection_season_number: "27" + collection_season_name: "{collection_season_27_name}" - - url: "{collection_season_28_url}" + - url: "{%get_season_url(collection_season_28_url, 0)}" variables: collection_season_number: "28" collection_season_name: "{collection_season_28_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_28_url, 1) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + - url: "{ %get_season_url(collection_season_28_url, 2) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + - url: "{ %get_season_url(collection_season_28_url, 3) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + - url: "{ %get_season_url(collection_season_28_url, 4) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + - url: "{ %get_season_url(collection_season_28_url, 5) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + - url: "{ %get_season_url(collection_season_28_url, 6) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + - url: "{ %get_season_url(collection_season_28_url, 7) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + - url: "{ %get_season_url(collection_season_28_url, 8) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + - url: "{ %get_season_url(collection_season_28_url, 9) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" + - url: "{ %get_season_url(collection_season_28_url, 10) }" + variables: + collection_season_number: "28" + collection_season_name: "{collection_season_28_name}" - - url: "{collection_season_29_url}" + - url: "{%get_season_url(collection_season_29_url, 0)}" variables: collection_season_number: "29" collection_season_name: "{collection_season_29_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_29_url, 1) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + - url: "{ %get_season_url(collection_season_29_url, 2) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + - url: "{ %get_season_url(collection_season_29_url, 3) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + - url: "{ %get_season_url(collection_season_29_url, 4) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + - url: "{ %get_season_url(collection_season_29_url, 5) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + - url: "{ %get_season_url(collection_season_29_url, 6) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + - url: "{ %get_season_url(collection_season_29_url, 7) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + - url: "{ %get_season_url(collection_season_29_url, 8) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + - url: "{ %get_season_url(collection_season_29_url, 9) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" + - url: "{ %get_season_url(collection_season_29_url, 10) }" + variables: + collection_season_number: "29" + collection_season_name: "{collection_season_29_name}" - - url: "{collection_season_30_url}" + - url: "{%get_season_url(collection_season_30_url, 0)}" variables: collection_season_number: "30" collection_season_name: "{collection_season_30_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_30_url, 1) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + - url: "{ %get_season_url(collection_season_30_url, 2) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + - url: "{ %get_season_url(collection_season_30_url, 3) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + - url: "{ %get_season_url(collection_season_30_url, 4) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + - url: "{ %get_season_url(collection_season_30_url, 5) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + - url: "{ %get_season_url(collection_season_30_url, 6) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + - url: "{ %get_season_url(collection_season_30_url, 7) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + - url: "{ %get_season_url(collection_season_30_url, 8) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + - url: "{ %get_season_url(collection_season_30_url, 9) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" + - url: "{ %get_season_url(collection_season_30_url, 10) }" + variables: + collection_season_number: "30" + collection_season_name: "{collection_season_30_name}" - - url: "{collection_season_31_url}" + - url: "{%get_season_url(collection_season_31_url, 0)}" variables: collection_season_number: "31" collection_season_name: "{collection_season_31_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_31_url, 1) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + - url: "{ %get_season_url(collection_season_31_url, 2) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + - url: "{ %get_season_url(collection_season_31_url, 3) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + - url: "{ %get_season_url(collection_season_31_url, 4) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + - url: "{ %get_season_url(collection_season_31_url, 5) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + - url: "{ %get_season_url(collection_season_31_url, 6) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + - url: "{ %get_season_url(collection_season_31_url, 7) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + - url: "{ %get_season_url(collection_season_31_url, 8) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + - url: "{ %get_season_url(collection_season_31_url, 9) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" + - url: "{ %get_season_url(collection_season_31_url, 10) }" + variables: + collection_season_number: "31" + collection_season_name: "{collection_season_31_name}" - - url: "{collection_season_32_url}" + - url: "{%get_season_url(collection_season_32_url, 0)}" variables: collection_season_number: "32" collection_season_name: "{collection_season_32_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_32_url, 1) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + - url: "{ %get_season_url(collection_season_32_url, 2) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + - url: "{ %get_season_url(collection_season_32_url, 3) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + - url: "{ %get_season_url(collection_season_32_url, 4) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + - url: "{ %get_season_url(collection_season_32_url, 5) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + - url: "{ %get_season_url(collection_season_32_url, 6) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + - url: "{ %get_season_url(collection_season_32_url, 7) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + - url: "{ %get_season_url(collection_season_32_url, 8) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + - url: "{ %get_season_url(collection_season_32_url, 9) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" + - url: "{ %get_season_url(collection_season_32_url, 10) }" + variables: + collection_season_number: "32" + collection_season_name: "{collection_season_32_name}" - - url: "{collection_season_33_url}" + - url: "{%get_season_url(collection_season_33_url, 0)}" variables: collection_season_number: "33" collection_season_name: "{collection_season_33_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_33_url, 1) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + - url: "{ %get_season_url(collection_season_33_url, 2) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + - url: "{ %get_season_url(collection_season_33_url, 3) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + - url: "{ %get_season_url(collection_season_33_url, 4) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + - url: "{ %get_season_url(collection_season_33_url, 5) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + - url: "{ %get_season_url(collection_season_33_url, 6) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + - url: "{ %get_season_url(collection_season_33_url, 7) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + - url: "{ %get_season_url(collection_season_33_url, 8) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + - url: "{ %get_season_url(collection_season_33_url, 9) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" + - url: "{ %get_season_url(collection_season_33_url, 10) }" + variables: + collection_season_number: "33" + collection_season_name: "{collection_season_33_name}" - - url: "{collection_season_34_url}" + - url: "{%get_season_url(collection_season_34_url, 0)}" variables: collection_season_number: "34" collection_season_name: "{collection_season_34_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_34_url, 1) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + - url: "{ %get_season_url(collection_season_34_url, 2) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + - url: "{ %get_season_url(collection_season_34_url, 3) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + - url: "{ %get_season_url(collection_season_34_url, 4) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + - url: "{ %get_season_url(collection_season_34_url, 5) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + - url: "{ %get_season_url(collection_season_34_url, 6) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + - url: "{ %get_season_url(collection_season_34_url, 7) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + - url: "{ %get_season_url(collection_season_34_url, 8) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + - url: "{ %get_season_url(collection_season_34_url, 9) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" + - url: "{ %get_season_url(collection_season_34_url, 10) }" + variables: + collection_season_number: "34" + collection_season_name: "{collection_season_34_name}" - - url: "{collection_season_35_url}" + - url: "{%get_season_url(collection_season_35_url, 0)}" variables: collection_season_number: "35" collection_season_name: "{collection_season_35_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_35_url, 1) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + - url: "{ %get_season_url(collection_season_35_url, 2) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + - url: "{ %get_season_url(collection_season_35_url, 3) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + - url: "{ %get_season_url(collection_season_35_url, 4) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + - url: "{ %get_season_url(collection_season_35_url, 5) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + - url: "{ %get_season_url(collection_season_35_url, 6) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + - url: "{ %get_season_url(collection_season_35_url, 7) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + - url: "{ %get_season_url(collection_season_35_url, 8) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + - url: "{ %get_season_url(collection_season_35_url, 9) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" + - url: "{ %get_season_url(collection_season_35_url, 10) }" + variables: + collection_season_number: "35" + collection_season_name: "{collection_season_35_name}" - - url: "{collection_season_36_url}" + + - url: "{%get_season_url(collection_season_36_url, 0)}" variables: collection_season_number: "36" collection_season_name: "{collection_season_36_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_36_url, 1) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + - url: "{ %get_season_url(collection_season_36_url, 2) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + - url: "{ %get_season_url(collection_season_36_url, 3) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + - url: "{ %get_season_url(collection_season_36_url, 4) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + - url: "{ %get_season_url(collection_season_36_url, 5) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + - url: "{ %get_season_url(collection_season_36_url, 6) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + - url: "{ %get_season_url(collection_season_36_url, 7) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + - url: "{ %get_season_url(collection_season_36_url, 8) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + - url: "{ %get_season_url(collection_season_36_url, 9) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" + - url: "{ %get_season_url(collection_season_36_url, 10) }" + variables: + collection_season_number: "36" + collection_season_name: "{collection_season_36_name}" - - url: "{collection_season_37_url}" + - url: "{%get_season_url(collection_season_37_url, 0)}" variables: collection_season_number: "37" collection_season_name: "{collection_season_37_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_37_url, 1) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + - url: "{ %get_season_url(collection_season_37_url, 2) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + - url: "{ %get_season_url(collection_season_37_url, 3) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + - url: "{ %get_season_url(collection_season_37_url, 4) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + - url: "{ %get_season_url(collection_season_37_url, 5) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + - url: "{ %get_season_url(collection_season_37_url, 6) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + - url: "{ %get_season_url(collection_season_37_url, 7) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + - url: "{ %get_season_url(collection_season_37_url, 8) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + - url: "{ %get_season_url(collection_season_37_url, 9) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" + - url: "{ %get_season_url(collection_season_37_url, 10) }" + variables: + collection_season_number: "37" + collection_season_name: "{collection_season_37_name}" - - url: "{collection_season_38_url}" + - url: "{%get_season_url(collection_season_38_url, 0)}" variables: collection_season_number: "38" collection_season_name: "{collection_season_38_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_38_url, 1) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + - url: "{ %get_season_url(collection_season_38_url, 2) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + - url: "{ %get_season_url(collection_season_38_url, 3) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + - url: "{ %get_season_url(collection_season_38_url, 4) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + - url: "{ %get_season_url(collection_season_38_url, 5) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + - url: "{ %get_season_url(collection_season_38_url, 6) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + - url: "{ %get_season_url(collection_season_38_url, 7) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + - url: "{ %get_season_url(collection_season_38_url, 8) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + - url: "{ %get_season_url(collection_season_38_url, 9) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" + - url: "{ %get_season_url(collection_season_38_url, 10) }" + variables: + collection_season_number: "38" + collection_season_name: "{collection_season_38_name}" - - url: "{collection_season_39_url}" + - url: "{%get_season_url(collection_season_39_url, 0)}" variables: collection_season_number: "39" collection_season_name: "{collection_season_39_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_39_url, 1) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + - url: "{ %get_season_url(collection_season_39_url, 2) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + - url: "{ %get_season_url(collection_season_39_url, 3) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + - url: "{ %get_season_url(collection_season_39_url, 4) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + - url: "{ %get_season_url(collection_season_39_url, 5) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + - url: "{ %get_season_url(collection_season_39_url, 6) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + - url: "{ %get_season_url(collection_season_39_url, 7) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + - url: "{ %get_season_url(collection_season_39_url, 8) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + - url: "{ %get_season_url(collection_season_39_url, 9) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" + - url: "{ %get_season_url(collection_season_39_url, 10) }" + variables: + collection_season_number: "39" + collection_season_name: "{collection_season_39_name}" - - url: "{collection_season_40_url}" + - url: "{%get_season_url(collection_season_40_url, 0)}" variables: collection_season_number: "40" collection_season_name: "{collection_season_40_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" + - url: "{ %get_season_url(collection_season_40_url, 1) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + - url: "{ %get_season_url(collection_season_40_url, 2) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + - url: "{ %get_season_url(collection_season_40_url, 3) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + - url: "{ %get_season_url(collection_season_40_url, 4) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + - url: "{ %get_season_url(collection_season_40_url, 5) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + - url: "{ %get_season_url(collection_season_40_url, 6) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + - url: "{ %get_season_url(collection_season_40_url, 7) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + - url: "{ %get_season_url(collection_season_40_url, 8) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + - url: "{ %get_season_url(collection_season_40_url, 9) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + - url: "{ %get_season_url(collection_season_40_url, 10) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + + # Place season 0 at end + - url: "{ %get_season_url(collection_season_0_url, 0) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + playlist_thumbnails: + - name: "{season_poster_file_name}" + uid: "latest_entry" + - url: "{ %get_season_url(collection_season_0_url, 1) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + - url: "{ %get_season_url(collection_season_0_url, 2) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + - url: "{ %get_season_url(collection_season_0_url, 3) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + - url: "{ %get_season_url(collection_season_0_url, 4) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + - url: "{ %get_season_url(collection_season_0_url, 5) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + - url: "{ %get_season_url(collection_season_0_url, 6) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + - url: "{ %get_season_url(collection_season_0_url, 7) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + - url: "{ %get_season_url(collection_season_0_url, 8) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + - url: "{ %get_season_url(collection_season_0_url, 9) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + - url: "{ %get_season_url(collection_season_0_url, 10) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + + output_directory_nfo_tags: tags: @@ -625,6 +2277,7 @@ presets: collection_season_38_name: "{s38_name}" collection_season_39_name: "{s39_name}" collection_season_40_name: "{s40_name}" + collection_season_0_name: "{s00_name}" # Legacy url variable collection_season_1_url: "{s01_url}" @@ -667,6 +2320,7 @@ presets: collection_season_38_url: "{s38_url}" collection_season_39_url: "{s39_url}" collection_season_40_url: "{s40_url}" + collection_season_0_url: "{s00_url}" s01_name: "" s02_name: "" @@ -708,6 +2362,7 @@ presets: s38_name: "" s39_name: "" s40_name: "" + s00_name: "" s01_url: "" s02_url: "" @@ -749,13 +2404,99 @@ presets: s38_url: "" s39_url: "" s40_url: "" + s00_url: "" + + # $0 - season url variable + # $1 - get the i'th url from the array + "%get_season_url": >- + { + %array_at( + %if( + %is_array( $0 ), + $0, + [ $0 ] + ), + $1, + "" + ) + } _tv_show_collection_bilateral: preset: - "_url_bilateral_overrides" download: - - url: "{ %bilateral_url(collection_season_1_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_1_url, 0) ) }" + 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( %get_season_url(collection_season_1_url, 1) ) }" + 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( %get_season_url(collection_season_1_url, 2) ) }" + 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( %get_season_url(collection_season_1_url, 3) ) }" + 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( %get_season_url(collection_season_1_url, 4) ) }" + 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( %get_season_url(collection_season_1_url, 5) ) }" + 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( %get_season_url(collection_season_1_url, 6) ) }" + 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( %get_season_url(collection_season_1_url, 7) ) }" + 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( %get_season_url(collection_season_1_url, 8) ) }" + 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( %get_season_url(collection_season_1_url, 9) ) }" + 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( %get_season_url(collection_season_1_url, 10) ) }" variables: collection_season_number: "1" collection_season_name: "{collection_season_1_name}" @@ -763,7 +2504,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_2_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_2_url, 0) ) }" + 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( %get_season_url(collection_season_2_url, 1) ) }" + 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( %get_season_url(collection_season_2_url, 2) ) }" + 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( %get_season_url(collection_season_2_url, 3) ) }" + 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( %get_season_url(collection_season_2_url, 4) ) }" + 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( %get_season_url(collection_season_2_url, 5) ) }" + 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( %get_season_url(collection_season_2_url, 6) ) }" + 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( %get_season_url(collection_season_2_url, 7) ) }" + 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( %get_season_url(collection_season_2_url, 8) ) }" + 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( %get_season_url(collection_season_2_url, 9) ) }" + 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( %get_season_url(collection_season_2_url, 10) ) }" variables: collection_season_number: "2" collection_season_name: "{collection_season_2_name}" @@ -771,7 +2582,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_3_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_3_url, 0) ) }" + 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( %get_season_url(collection_season_3_url, 1) ) }" + 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( %get_season_url(collection_season_3_url, 2) ) }" + 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( %get_season_url(collection_season_3_url, 3) ) }" + 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( %get_season_url(collection_season_3_url, 4) ) }" + 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( %get_season_url(collection_season_3_url, 5) ) }" + 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( %get_season_url(collection_season_3_url, 6) ) }" + 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( %get_season_url(collection_season_3_url, 7) ) }" + 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( %get_season_url(collection_season_3_url, 8) ) }" + 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( %get_season_url(collection_season_3_url, 9) ) }" + 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( %get_season_url(collection_season_3_url, 10) ) }" variables: collection_season_number: "3" collection_season_name: "{collection_season_3_name}" @@ -779,7 +2660,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_4_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_4_url, 0) ) }" + 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( %get_season_url(collection_season_4_url, 1) ) }" + 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( %get_season_url(collection_season_4_url, 2) ) }" + 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( %get_season_url(collection_season_4_url, 3) ) }" + 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( %get_season_url(collection_season_4_url, 4) ) }" + 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( %get_season_url(collection_season_4_url, 5) ) }" + 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( %get_season_url(collection_season_4_url, 6) ) }" + 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( %get_season_url(collection_season_4_url, 7) ) }" + 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( %get_season_url(collection_season_4_url, 8) ) }" + 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( %get_season_url(collection_season_4_url, 9) ) }" + 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( %get_season_url(collection_season_4_url, 10) ) }" variables: collection_season_number: "4" collection_season_name: "{collection_season_4_name}" @@ -787,7 +2738,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_5_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_5_url, 0) ) }" + 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( %get_season_url(collection_season_5_url, 1) ) }" + 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( %get_season_url(collection_season_5_url, 2) ) }" + 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( %get_season_url(collection_season_5_url, 3) ) }" + 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( %get_season_url(collection_season_5_url, 4) ) }" + 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( %get_season_url(collection_season_5_url, 5) ) }" + 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( %get_season_url(collection_season_5_url, 6) ) }" + 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( %get_season_url(collection_season_5_url, 7) ) }" + 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( %get_season_url(collection_season_5_url, 8) ) }" + 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( %get_season_url(collection_season_5_url, 9) ) }" + 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( %get_season_url(collection_season_5_url, 10) ) }" variables: collection_season_number: "5" collection_season_name: "{collection_season_5_name}" @@ -795,7 +2816,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_6_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_6_url, 0) ) }" + 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( %get_season_url(collection_season_6_url, 1) ) }" + 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( %get_season_url(collection_season_6_url, 2) ) }" + 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( %get_season_url(collection_season_6_url, 3) ) }" + 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( %get_season_url(collection_season_6_url, 4) ) }" + 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( %get_season_url(collection_season_6_url, 5) ) }" + 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( %get_season_url(collection_season_6_url, 6) ) }" + 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( %get_season_url(collection_season_6_url, 7) ) }" + 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( %get_season_url(collection_season_6_url, 8) ) }" + 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( %get_season_url(collection_season_6_url, 9) ) }" + 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( %get_season_url(collection_season_6_url, 10) ) }" variables: collection_season_number: "6" collection_season_name: "{collection_season_6_name}" @@ -803,7 +2894,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_7_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_7_url, 0) ) }" + 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( %get_season_url(collection_season_7_url, 1) ) }" + 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( %get_season_url(collection_season_7_url, 2) ) }" + 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( %get_season_url(collection_season_7_url, 3) ) }" + 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( %get_season_url(collection_season_7_url, 4) ) }" + 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( %get_season_url(collection_season_7_url, 5) ) }" + 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( %get_season_url(collection_season_7_url, 6) ) }" + 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( %get_season_url(collection_season_7_url, 7) ) }" + 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( %get_season_url(collection_season_7_url, 8) ) }" + 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( %get_season_url(collection_season_7_url, 9) ) }" + 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( %get_season_url(collection_season_7_url, 10) ) }" variables: collection_season_number: "7" collection_season_name: "{collection_season_7_name}" @@ -811,7 +2972,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_8_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_8_url, 0) ) }" + 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( %get_season_url(collection_season_8_url, 1) ) }" + 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( %get_season_url(collection_season_8_url, 2) ) }" + 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( %get_season_url(collection_season_8_url, 3) ) }" + 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( %get_season_url(collection_season_8_url, 4) ) }" + 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( %get_season_url(collection_season_8_url, 5) ) }" + 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( %get_season_url(collection_season_8_url, 6) ) }" + 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( %get_season_url(collection_season_8_url, 7) ) }" + 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( %get_season_url(collection_season_8_url, 8) ) }" + 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( %get_season_url(collection_season_8_url, 9) ) }" + 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( %get_season_url(collection_season_8_url, 10) ) }" variables: collection_season_number: "8" collection_season_name: "{collection_season_8_name}" @@ -819,7 +3050,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_9_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_9_url, 0) ) }" + 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( %get_season_url(collection_season_9_url, 1) ) }" + 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( %get_season_url(collection_season_9_url, 2) ) }" + 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( %get_season_url(collection_season_9_url, 3) ) }" + 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( %get_season_url(collection_season_9_url, 4) ) }" + 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( %get_season_url(collection_season_9_url, 5) ) }" + 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( %get_season_url(collection_season_9_url, 6) ) }" + 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( %get_season_url(collection_season_9_url, 7) ) }" + 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( %get_season_url(collection_season_9_url, 8) ) }" + 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( %get_season_url(collection_season_9_url, 9) ) }" + 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( %get_season_url(collection_season_9_url, 10) ) }" variables: collection_season_number: "9" collection_season_name: "{collection_season_9_name}" @@ -827,7 +3128,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_10_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_10_url, 0) ) }" + 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( %get_season_url(collection_season_10_url, 1) ) }" + 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( %get_season_url(collection_season_10_url, 2) ) }" + 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( %get_season_url(collection_season_10_url, 3) ) }" + 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( %get_season_url(collection_season_10_url, 4) ) }" + 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( %get_season_url(collection_season_10_url, 5) ) }" + 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( %get_season_url(collection_season_10_url, 6) ) }" + 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( %get_season_url(collection_season_10_url, 7) ) }" + 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( %get_season_url(collection_season_10_url, 8) ) }" + 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( %get_season_url(collection_season_10_url, 9) ) }" + 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( %get_season_url(collection_season_10_url, 10) ) }" variables: collection_season_number: "10" collection_season_name: "{collection_season_10_name}" @@ -835,7 +3206,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_11_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_11_url, 0) ) }" + 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( %get_season_url(collection_season_11_url, 1) ) }" + 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( %get_season_url(collection_season_11_url, 2) ) }" + 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( %get_season_url(collection_season_11_url, 3) ) }" + 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( %get_season_url(collection_season_11_url, 4) ) }" + 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( %get_season_url(collection_season_11_url, 5) ) }" + 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( %get_season_url(collection_season_11_url, 6) ) }" + 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( %get_season_url(collection_season_11_url, 7) ) }" + 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( %get_season_url(collection_season_11_url, 8) ) }" + 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( %get_season_url(collection_season_11_url, 9) ) }" + 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( %get_season_url(collection_season_11_url, 10) ) }" variables: collection_season_number: "11" collection_season_name: "{collection_season_11_name}" @@ -843,7 +3284,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_12_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_12_url, 0) ) }" + 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( %get_season_url(collection_season_12_url, 1) ) }" + 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( %get_season_url(collection_season_12_url, 2) ) }" + 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( %get_season_url(collection_season_12_url, 3) ) }" + 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( %get_season_url(collection_season_12_url, 4) ) }" + 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( %get_season_url(collection_season_12_url, 5) ) }" + 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( %get_season_url(collection_season_12_url, 6) ) }" + 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( %get_season_url(collection_season_12_url, 7) ) }" + 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( %get_season_url(collection_season_12_url, 8) ) }" + 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( %get_season_url(collection_season_12_url, 9) ) }" + 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( %get_season_url(collection_season_12_url, 10) ) }" variables: collection_season_number: "12" collection_season_name: "{collection_season_12_name}" @@ -851,7 +3362,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_13_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_13_url, 0) ) }" + 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( %get_season_url(collection_season_13_url, 1) ) }" + 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( %get_season_url(collection_season_13_url, 2) ) }" + 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( %get_season_url(collection_season_13_url, 3) ) }" + 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( %get_season_url(collection_season_13_url, 4) ) }" + 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( %get_season_url(collection_season_13_url, 5) ) }" + 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( %get_season_url(collection_season_13_url, 6) ) }" + 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( %get_season_url(collection_season_13_url, 7) ) }" + 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( %get_season_url(collection_season_13_url, 8) ) }" + 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( %get_season_url(collection_season_13_url, 9) ) }" + 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( %get_season_url(collection_season_13_url, 10) ) }" variables: collection_season_number: "13" collection_season_name: "{collection_season_13_name}" @@ -859,7 +3440,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_14_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_14_url, 0) ) }" + 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( %get_season_url(collection_season_14_url, 1) ) }" + 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( %get_season_url(collection_season_14_url, 2) ) }" + 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( %get_season_url(collection_season_14_url, 3) ) }" + 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( %get_season_url(collection_season_14_url, 4) ) }" + 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( %get_season_url(collection_season_14_url, 5) ) }" + 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( %get_season_url(collection_season_14_url, 6) ) }" + 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( %get_season_url(collection_season_14_url, 7) ) }" + 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( %get_season_url(collection_season_14_url, 8) ) }" + 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( %get_season_url(collection_season_14_url, 9) ) }" + 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( %get_season_url(collection_season_14_url, 10) ) }" variables: collection_season_number: "14" collection_season_name: "{collection_season_14_name}" @@ -867,7 +3518,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_15_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_15_url, 0) ) }" + 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( %get_season_url(collection_season_15_url, 1) ) }" + 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( %get_season_url(collection_season_15_url, 2) ) }" + 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( %get_season_url(collection_season_15_url, 3) ) }" + 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( %get_season_url(collection_season_15_url, 4) ) }" + 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( %get_season_url(collection_season_15_url, 5) ) }" + 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( %get_season_url(collection_season_15_url, 6) ) }" + 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( %get_season_url(collection_season_15_url, 7) ) }" + 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( %get_season_url(collection_season_15_url, 8) ) }" + 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( %get_season_url(collection_season_15_url, 9) ) }" + 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( %get_season_url(collection_season_15_url, 10) ) }" variables: collection_season_number: "15" collection_season_name: "{collection_season_15_name}" @@ -875,7 +3596,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_16_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_16_url, 0) ) }" + 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( %get_season_url(collection_season_16_url, 1) ) }" + 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( %get_season_url(collection_season_16_url, 2) ) }" + 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( %get_season_url(collection_season_16_url, 3) ) }" + 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( %get_season_url(collection_season_16_url, 4) ) }" + 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( %get_season_url(collection_season_16_url, 5) ) }" + 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( %get_season_url(collection_season_16_url, 6) ) }" + 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( %get_season_url(collection_season_16_url, 7) ) }" + 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( %get_season_url(collection_season_16_url, 8) ) }" + 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( %get_season_url(collection_season_16_url, 9) ) }" + 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( %get_season_url(collection_season_16_url, 10) ) }" variables: collection_season_number: "16" collection_season_name: "{collection_season_16_name}" @@ -883,7 +3675,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_17_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_17_url, 0) ) }" + 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( %get_season_url(collection_season_17_url, 1) ) }" + 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( %get_season_url(collection_season_17_url, 2) ) }" + 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( %get_season_url(collection_season_17_url, 3) ) }" + 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( %get_season_url(collection_season_17_url, 4) ) }" + 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( %get_season_url(collection_season_17_url, 5) ) }" + 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( %get_season_url(collection_season_17_url, 6) ) }" + 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( %get_season_url(collection_season_17_url, 7) ) }" + 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( %get_season_url(collection_season_17_url, 8) ) }" + 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( %get_season_url(collection_season_17_url, 9) ) }" + 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( %get_season_url(collection_season_17_url, 10) ) }" variables: collection_season_number: "17" collection_season_name: "{collection_season_17_name}" @@ -891,7 +3753,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_18_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_18_url, 0) ) }" + 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( %get_season_url(collection_season_18_url, 1) ) }" + 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( %get_season_url(collection_season_18_url, 2) ) }" + 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( %get_season_url(collection_season_18_url, 3) ) }" + 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( %get_season_url(collection_season_18_url, 4) ) }" + 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( %get_season_url(collection_season_18_url, 5) ) }" + 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( %get_season_url(collection_season_18_url, 6) ) }" + 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( %get_season_url(collection_season_18_url, 7) ) }" + 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( %get_season_url(collection_season_18_url, 8) ) }" + 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( %get_season_url(collection_season_18_url, 9) ) }" + 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( %get_season_url(collection_season_18_url, 10) ) }" variables: collection_season_number: "18" collection_season_name: "{collection_season_18_name}" @@ -899,7 +3831,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_19_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_19_url, 0) ) }" + 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( %get_season_url(collection_season_19_url, 1) ) }" + 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( %get_season_url(collection_season_19_url, 2) ) }" + 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( %get_season_url(collection_season_19_url, 3) ) }" + 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( %get_season_url(collection_season_19_url, 4) ) }" + 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( %get_season_url(collection_season_19_url, 5) ) }" + 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( %get_season_url(collection_season_19_url, 6) ) }" + 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( %get_season_url(collection_season_19_url, 7) ) }" + 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( %get_season_url(collection_season_19_url, 8) ) }" + 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( %get_season_url(collection_season_19_url, 9) ) }" + 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( %get_season_url(collection_season_19_url, 10) ) }" variables: collection_season_number: "19" collection_season_name: "{collection_season_19_name}" @@ -907,7 +3909,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_20_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_20_url, 0) ) }" + 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( %get_season_url(collection_season_20_url, 1) ) }" + 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( %get_season_url(collection_season_20_url, 2) ) }" + 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( %get_season_url(collection_season_20_url, 3) ) }" + 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( %get_season_url(collection_season_20_url, 4) ) }" + 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( %get_season_url(collection_season_20_url, 5) ) }" + 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( %get_season_url(collection_season_20_url, 6) ) }" + 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( %get_season_url(collection_season_20_url, 7) ) }" + 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( %get_season_url(collection_season_20_url, 8) ) }" + 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( %get_season_url(collection_season_20_url, 9) ) }" + 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( %get_season_url(collection_season_20_url, 10) ) }" variables: collection_season_number: "20" collection_season_name: "{collection_season_20_name}" @@ -915,7 +3987,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_21_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_21_url, 0) ) }" + 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( %get_season_url(collection_season_21_url, 1) ) }" + 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( %get_season_url(collection_season_21_url, 2) ) }" + 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( %get_season_url(collection_season_21_url, 3) ) }" + 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( %get_season_url(collection_season_21_url, 4) ) }" + 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( %get_season_url(collection_season_21_url, 5) ) }" + 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( %get_season_url(collection_season_21_url, 6) ) }" + 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( %get_season_url(collection_season_21_url, 7) ) }" + 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( %get_season_url(collection_season_21_url, 8) ) }" + 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( %get_season_url(collection_season_21_url, 9) ) }" + 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( %get_season_url(collection_season_21_url, 10) ) }" variables: collection_season_number: "21" collection_season_name: "{collection_season_21_name}" @@ -923,7 +4065,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_22_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_22_url, 0) ) }" + 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( %get_season_url(collection_season_22_url, 1) ) }" + 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( %get_season_url(collection_season_22_url, 2) ) }" + 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( %get_season_url(collection_season_22_url, 3) ) }" + 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( %get_season_url(collection_season_22_url, 4) ) }" + 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( %get_season_url(collection_season_22_url, 5) ) }" + 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( %get_season_url(collection_season_22_url, 6) ) }" + 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( %get_season_url(collection_season_22_url, 7) ) }" + 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( %get_season_url(collection_season_22_url, 8) ) }" + 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( %get_season_url(collection_season_22_url, 9) ) }" + 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( %get_season_url(collection_season_22_url, 10) ) }" variables: collection_season_number: "22" collection_season_name: "{collection_season_22_name}" @@ -931,7 +4143,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_23_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_23_url, 0) ) }" + 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( %get_season_url(collection_season_23_url, 1) ) }" + 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( %get_season_url(collection_season_23_url, 2) ) }" + 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( %get_season_url(collection_season_23_url, 3) ) }" + 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( %get_season_url(collection_season_23_url, 4) ) }" + 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( %get_season_url(collection_season_23_url, 5) ) }" + 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( %get_season_url(collection_season_23_url, 6) ) }" + 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( %get_season_url(collection_season_23_url, 7) ) }" + 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( %get_season_url(collection_season_23_url, 8) ) }" + 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( %get_season_url(collection_season_23_url, 9) ) }" + 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( %get_season_url(collection_season_23_url, 10) ) }" variables: collection_season_number: "23" collection_season_name: "{collection_season_23_name}" @@ -939,7 +4221,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_24_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_24_url, 0) ) }" + 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( %get_season_url(collection_season_24_url, 1) ) }" + 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( %get_season_url(collection_season_24_url, 2) ) }" + 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( %get_season_url(collection_season_24_url, 3) ) }" + 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( %get_season_url(collection_season_24_url, 4) ) }" + 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( %get_season_url(collection_season_24_url, 5) ) }" + 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( %get_season_url(collection_season_24_url, 6) ) }" + 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( %get_season_url(collection_season_24_url, 7) ) }" + 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( %get_season_url(collection_season_24_url, 8) ) }" + 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( %get_season_url(collection_season_24_url, 9) ) }" + 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( %get_season_url(collection_season_24_url, 10) ) }" variables: collection_season_number: "24" collection_season_name: "{collection_season_24_name}" @@ -947,7 +4299,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_25_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_25_url, 0) ) }" + 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( %get_season_url(collection_season_25_url, 1) ) }" + 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( %get_season_url(collection_season_25_url, 2) ) }" + 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( %get_season_url(collection_season_25_url, 3) ) }" + 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( %get_season_url(collection_season_25_url, 4) ) }" + 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( %get_season_url(collection_season_25_url, 5) ) }" + 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( %get_season_url(collection_season_25_url, 6) ) }" + 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( %get_season_url(collection_season_25_url, 7) ) }" + 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( %get_season_url(collection_season_25_url, 8) ) }" + 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( %get_season_url(collection_season_25_url, 9) ) }" + 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( %get_season_url(collection_season_25_url, 10) ) }" variables: collection_season_number: "25" collection_season_name: "{collection_season_25_name}" @@ -955,7 +4377,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_26_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_26_url, 0) ) }" + 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( %get_season_url(collection_season_26_url, 1) ) }" + 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( %get_season_url(collection_season_26_url, 2) ) }" + 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( %get_season_url(collection_season_26_url, 3) ) }" + 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( %get_season_url(collection_season_26_url, 4) ) }" + 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( %get_season_url(collection_season_26_url, 5) ) }" + 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( %get_season_url(collection_season_26_url, 6) ) }" + 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( %get_season_url(collection_season_26_url, 7) ) }" + 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( %get_season_url(collection_season_26_url, 8) ) }" + 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( %get_season_url(collection_season_26_url, 9) ) }" + 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( %get_season_url(collection_season_26_url, 10) ) }" variables: collection_season_number: "26" collection_season_name: "{collection_season_26_name}" @@ -963,7 +4455,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_27_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_27_url, 0) ) }" + 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( %get_season_url(collection_season_27_url, 1) ) }" + 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( %get_season_url(collection_season_27_url, 2) ) }" + 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( %get_season_url(collection_season_27_url, 3) ) }" + 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( %get_season_url(collection_season_27_url, 4) ) }" + 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( %get_season_url(collection_season_27_url, 5) ) }" + 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( %get_season_url(collection_season_27_url, 6) ) }" + 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( %get_season_url(collection_season_27_url, 7) ) }" + 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( %get_season_url(collection_season_27_url, 8) ) }" + 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( %get_season_url(collection_season_27_url, 9) ) }" + 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( %get_season_url(collection_season_27_url, 10) ) }" variables: collection_season_number: "27" collection_season_name: "{collection_season_27_name}" @@ -971,7 +4534,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_28_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_28_url, 0) ) }" + 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( %get_season_url(collection_season_28_url, 1) ) }" + 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( %get_season_url(collection_season_28_url, 2) ) }" + 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( %get_season_url(collection_season_28_url, 3) ) }" + 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( %get_season_url(collection_season_28_url, 4) ) }" + 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( %get_season_url(collection_season_28_url, 5) ) }" + 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( %get_season_url(collection_season_28_url, 6) ) }" + 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( %get_season_url(collection_season_28_url, 7) ) }" + 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( %get_season_url(collection_season_28_url, 8) ) }" + 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( %get_season_url(collection_season_28_url, 9) ) }" + 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( %get_season_url(collection_season_28_url, 10) ) }" variables: collection_season_number: "28" collection_season_name: "{collection_season_28_name}" @@ -979,7 +4613,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_29_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_29_url, 0) ) }" + 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( %get_season_url(collection_season_29_url, 1) ) }" + 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( %get_season_url(collection_season_29_url, 2) ) }" + 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( %get_season_url(collection_season_29_url, 3) ) }" + 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( %get_season_url(collection_season_29_url, 4) ) }" + 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( %get_season_url(collection_season_29_url, 5) ) }" + 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( %get_season_url(collection_season_29_url, 6) ) }" + 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( %get_season_url(collection_season_29_url, 7) ) }" + 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( %get_season_url(collection_season_29_url, 8) ) }" + 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( %get_season_url(collection_season_29_url, 9) ) }" + 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( %get_season_url(collection_season_29_url, 10) ) }" variables: collection_season_number: "29" collection_season_name: "{collection_season_29_name}" @@ -987,7 +4692,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_30_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_30_url, 0) ) }" + 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( %get_season_url(collection_season_30_url, 1) ) }" + 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( %get_season_url(collection_season_30_url, 2) ) }" + 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( %get_season_url(collection_season_30_url, 3) ) }" + 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( %get_season_url(collection_season_30_url, 4) ) }" + 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( %get_season_url(collection_season_30_url, 5) ) }" + 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( %get_season_url(collection_season_30_url, 6) ) }" + 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( %get_season_url(collection_season_30_url, 7) ) }" + 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( %get_season_url(collection_season_30_url, 8) ) }" + 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( %get_season_url(collection_season_30_url, 9) ) }" + 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( %get_season_url(collection_season_30_url, 10) ) }" variables: collection_season_number: "30" collection_season_name: "{collection_season_30_name}" @@ -995,7 +4771,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_31_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_31_url, 0) ) }" + 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( %get_season_url(collection_season_31_url, 1) ) }" + 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( %get_season_url(collection_season_31_url, 2) ) }" + 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( %get_season_url(collection_season_31_url, 3) ) }" + 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( %get_season_url(collection_season_31_url, 4) ) }" + 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( %get_season_url(collection_season_31_url, 5) ) }" + 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( %get_season_url(collection_season_31_url, 6) ) }" + 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( %get_season_url(collection_season_31_url, 7) ) }" + 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( %get_season_url(collection_season_31_url, 8) ) }" + 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( %get_season_url(collection_season_31_url, 9) ) }" + 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( %get_season_url(collection_season_31_url, 10) ) }" variables: collection_season_number: "31" collection_season_name: "{collection_season_31_name}" @@ -1003,7 +4850,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_32_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_32_url, 0) ) }" + 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( %get_season_url(collection_season_32_url, 1) ) }" + 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( %get_season_url(collection_season_32_url, 2) ) }" + 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( %get_season_url(collection_season_32_url, 3) ) }" + 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( %get_season_url(collection_season_32_url, 4) ) }" + 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( %get_season_url(collection_season_32_url, 5) ) }" + 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( %get_season_url(collection_season_32_url, 6) ) }" + 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( %get_season_url(collection_season_32_url, 7) ) }" + 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( %get_season_url(collection_season_32_url, 8) ) }" + 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( %get_season_url(collection_season_32_url, 9) ) }" + 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( %get_season_url(collection_season_32_url, 10) ) }" variables: collection_season_number: "32" collection_season_name: "{collection_season_32_name}" @@ -1011,7 +4929,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_33_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_33_url, 0) ) }" + 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( %get_season_url(collection_season_33_url, 1) ) }" + 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( %get_season_url(collection_season_33_url, 2) ) }" + 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( %get_season_url(collection_season_33_url, 3) ) }" + 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( %get_season_url(collection_season_33_url, 4) ) }" + 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( %get_season_url(collection_season_33_url, 5) ) }" + 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( %get_season_url(collection_season_33_url, 6) ) }" + 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( %get_season_url(collection_season_33_url, 7) ) }" + 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( %get_season_url(collection_season_33_url, 8) ) }" + 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( %get_season_url(collection_season_33_url, 9) ) }" + 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( %get_season_url(collection_season_33_url, 10) ) }" variables: collection_season_number: "33" collection_season_name: "{collection_season_33_name}" @@ -1019,7 +5008,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_34_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_34_url, 0) ) }" + 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( %get_season_url(collection_season_34_url, 1) ) }" + 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( %get_season_url(collection_season_34_url, 2) ) }" + 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( %get_season_url(collection_season_34_url, 3) ) }" + 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( %get_season_url(collection_season_34_url, 4) ) }" + 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( %get_season_url(collection_season_34_url, 5) ) }" + 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( %get_season_url(collection_season_34_url, 6) ) }" + 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( %get_season_url(collection_season_34_url, 7) ) }" + 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( %get_season_url(collection_season_34_url, 8) ) }" + 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( %get_season_url(collection_season_34_url, 9) ) }" + 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( %get_season_url(collection_season_34_url, 10) ) }" variables: collection_season_number: "34" collection_season_name: "{collection_season_34_name}" @@ -1027,7 +5087,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_35_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_35_url, 0) ) }" + 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( %get_season_url(collection_season_35_url, 1) ) }" + 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( %get_season_url(collection_season_35_url, 2) ) }" + 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( %get_season_url(collection_season_35_url, 3) ) }" + 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( %get_season_url(collection_season_35_url, 4) ) }" + 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( %get_season_url(collection_season_35_url, 5) ) }" + 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( %get_season_url(collection_season_35_url, 6) ) }" + 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( %get_season_url(collection_season_35_url, 7) ) }" + 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( %get_season_url(collection_season_35_url, 8) ) }" + 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( %get_season_url(collection_season_35_url, 9) ) }" + 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( %get_season_url(collection_season_35_url, 10) ) }" variables: collection_season_number: "35" collection_season_name: "{collection_season_35_name}" @@ -1035,7 +5165,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_36_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_36_url, 0) ) }" + 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( %get_season_url(collection_season_36_url, 1) ) }" + 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( %get_season_url(collection_season_36_url, 2) ) }" + 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( %get_season_url(collection_season_36_url, 3) ) }" + 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( %get_season_url(collection_season_36_url, 4) ) }" + 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( %get_season_url(collection_season_36_url, 5) ) }" + 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( %get_season_url(collection_season_36_url, 6) ) }" + 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( %get_season_url(collection_season_36_url, 7) ) }" + 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( %get_season_url(collection_season_36_url, 8) ) }" + 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( %get_season_url(collection_season_36_url, 9) ) }" + 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( %get_season_url(collection_season_36_url, 10) ) }" variables: collection_season_number: "36" collection_season_name: "{collection_season_36_name}" @@ -1043,7 +5244,77 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_37_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_37_url, 0) ) }" + 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( %get_season_url(collection_season_37_url, 1) ) }" + 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( %get_season_url(collection_season_37_url, 2) ) }" + 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( %get_season_url(collection_season_37_url, 3) ) }" + 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( %get_season_url(collection_season_37_url, 4) ) }" + 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( %get_season_url(collection_season_37_url, 5) ) }" + 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( %get_season_url(collection_season_37_url, 6) ) }" + 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( %get_season_url(collection_season_37_url, 7) ) }" + 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( %get_season_url(collection_season_37_url, 8) ) }" + 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( %get_season_url(collection_season_37_url, 9) ) }" + 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( %get_season_url(collection_season_37_url, 10) ) }" variables: collection_season_number: "37" collection_season_name: "{collection_season_37_name}" @@ -1051,7 +5322,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_38_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_38_url, 0) ) }" + 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( %get_season_url(collection_season_38_url, 1) ) }" + 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( %get_season_url(collection_season_38_url, 2) ) }" + 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( %get_season_url(collection_season_38_url, 3) ) }" + 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( %get_season_url(collection_season_38_url, 4) ) }" + 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( %get_season_url(collection_season_38_url, 5) ) }" + 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( %get_season_url(collection_season_38_url, 6) ) }" + 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( %get_season_url(collection_season_38_url, 7) ) }" + 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( %get_season_url(collection_season_38_url, 8) ) }" + 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( %get_season_url(collection_season_38_url, 9) ) }" + 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( %get_season_url(collection_season_38_url, 10) ) }" variables: collection_season_number: "38" collection_season_name: "{collection_season_38_name}" @@ -1059,7 +5401,78 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_39_url) }" + + - url: "{ %bilateral_url( %get_season_url(collection_season_39_url, 0) ) }" + 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( %get_season_url(collection_season_39_url, 1) ) }" + 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( %get_season_url(collection_season_39_url, 2) ) }" + 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( %get_season_url(collection_season_39_url, 3) ) }" + 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( %get_season_url(collection_season_39_url, 4) ) }" + 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( %get_season_url(collection_season_39_url, 5) ) }" + 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( %get_season_url(collection_season_39_url, 6) ) }" + 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( %get_season_url(collection_season_39_url, 7) ) }" + 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( %get_season_url(collection_season_39_url, 8) ) }" + 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( %get_season_url(collection_season_39_url, 9) ) }" + 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( %get_season_url(collection_season_39_url, 10) ) }" variables: collection_season_number: "39" collection_season_name: "{collection_season_39_name}" @@ -1067,13 +5480,162 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url(collection_season_40_url) }" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 0) ) }" variables: collection_season_number: "40" collection_season_name: "{collection_season_40_name}" download_reverse: False ytdl_options: playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 1) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 2) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 3) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 4) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 5) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 6) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 7) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 8) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 9) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 10) ) }" + variables: + collection_season_number: "40" + collection_season_name: "{collection_season_40_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 0) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 1) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 2) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 3) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 4) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 5) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 6) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 7) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 8) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 9) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 10) ) }" + variables: + collection_season_number: "0" + collection_season_name: "{collection_season_0_name}" + download_reverse: False + ytdl_options: + playlist_items: "-1:0:-1" + _tv_show_collection_asserts: overrides: diff --git a/tests/unit/prebuilt_presets/test_tv_show_collection.py b/tests/unit/prebuilt_presets/test_tv_show_collection.py index 3621f3cb..f56755b8 100644 --- a/tests/unit/prebuilt_presets/test_tv_show_collection.py +++ b/tests/unit/prebuilt_presets/test_tv_show_collection.py @@ -24,3 +24,54 @@ class TestTvShowCollectionPreset: "overrides": {"tv_show_directory": "abc", "url": "test"}, }, ) + + def test_multi_url(self, default_config): + num_seasons = 40 # excluding season 0 + num_urls_per_season = 11 + + overrides = { + "tv_show_directory": "abc", + } + for season_num in range(0, num_seasons + 1): + overrides[f"s{season_num:02d}_name"] = f"The Season {season_num}" + overrides[f"s{season_num:02d}_url"] = [ + f"youtube.com/playlist?url_{season_num}_{i}" for i in range(num_urls_per_season) + ] + + sub = Subscription.from_dict( + config=default_config, + preset_name="test", + preset_dict={"preset": "Jellyfin TV Show Collection", "overrides": overrides}, + ) + + assert len(sub.downloader_options.urls.list) == (num_seasons + 1) * num_urls_per_season * 2 + url_list = sub.downloader_options.urls.list + itr = 0 + + # loop twice for bilateral + for i in range(2): + for season_num in range(1, num_seasons + 2): + # Season 0 is placed last, adjust here + if season_num == num_seasons + 1: + season_num = 0 + + for i in range(num_urls_per_season): + url = sub.overrides.apply_formatter( + url_list[itr].url, + function_overrides={ + # mock so bilateral url gets enabled + "subscription_has_download_archive": "True" + }, + ) + variables = url_list[itr].variables.dict + assert url == f"youtube.com/playlist?url_{season_num}_{i}" + assert ( + sub.overrides.apply_formatter(variables["collection_season_number"]) + == f"{season_num}" + ) + assert ( + sub.overrides.apply_formatter(variables["collection_season_name"]) + == f"The Season {season_num}" + ) + + itr += 1 From a5ae69550ecf9bcec2aff276162d53df9b64e84a Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sat, 6 Dec 2025 15:54:41 -0800 Subject: [PATCH 14/46] [BACKEND] Validate write permissions on working directory and output directory (#1395) Closes https://github.com/jmbannon/ytdl-sub/issues/1394 Should help debug user issues like https://github.com/jmbannon/ytdl-sub/issues/1148 Checks permissions on the following: - working directory - output directory - cookiefile (if specified) And will fail immediately with a verbose message. --- src/ytdl_sub/config/config_validator.py | 8 +++ src/ytdl_sub/config/preset_options.py | 19 +++++- .../subscriptions/base_subscription.py | 8 +++ src/ytdl_sub/utils/exceptions.py | 4 ++ src/ytdl_sub/utils/file_handler.py | 30 +++++++++ tests/conftest.py | 61 +++++++++++++++---- tests/unit/config/test_subscription.py | 4 +- tests/unit/plugins/test_ytdl_options.py | 38 ++++++++++++ tests/unit/utils/test_file_handler.py | 20 ++++++ 9 files changed, 175 insertions(+), 17 deletions(-) create mode 100644 tests/unit/plugins/test_ytdl_options.py create mode 100644 tests/unit/utils/test_file_handler.py diff --git a/src/ytdl_sub/config/config_validator.py b/src/ytdl_sub/config/config_validator.py index b5d234a9..073eb23a 100644 --- a/src/ytdl_sub/config/config_validator.py +++ b/src/ytdl_sub/config/config_validator.py @@ -12,6 +12,8 @@ from ytdl_sub.config.defaults import DEFAULT_FFPROBE_PATH from ytdl_sub.config.defaults import DEFAULT_LOCK_DIRECTORY from ytdl_sub.config.defaults import MAX_FILE_NAME_BYTES from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS +from ytdl_sub.utils.exceptions import SubscriptionPermissionError +from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.validators.file_path_validators import FFmpegFileValidator from ytdl_sub.validators.file_path_validators import FFprobeFileValidator from ytdl_sub.validators.strict_dict_validator import StrictDictValidator @@ -147,6 +149,12 @@ class ConfigOptions(StrictDictValidator): key="file_name_max_bytes", validator=IntValidator, default=MAX_FILE_NAME_BYTES ) + if not FileHandler.is_path_writable(self.working_directory): + raise SubscriptionPermissionError( + "ytdl-sub does not have permissions to the working directory: " + f"{self.working_directory}" + ) + @property def working_directory(self) -> str: """ diff --git a/src/ytdl_sub/config/preset_options.py b/src/ytdl_sub/config/preset_options.py index 2f9e75df..93f36453 100644 --- a/src/ytdl_sub/config/preset_options.py +++ b/src/ytdl_sub/config/preset_options.py @@ -8,6 +8,9 @@ from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.plugin.plugin_operation import PluginOperation from ytdl_sub.config.validators.options import OptionsDictValidator from ytdl_sub.entries.script.variable_definitions import VARIABLES as v +from ytdl_sub.utils.exceptions import SubscriptionPermissionError +from ytdl_sub.utils.exceptions import ValidationException +from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.validators.file_path_validators import OverridesStringFormatterFilePathValidator from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator from ytdl_sub.validators.string_datetime import StringDatetimeValidator @@ -57,12 +60,24 @@ class YTDLOptions(UnstructuredOverridesDictFormatterValidator): def to_native_dict(self, overrides: Overrides) -> Dict: """ Materializes the entire ytdl-options dict from OverrideStringFormatters into - native python + native python. """ - return { + out = { key: overrides.apply_overrides_formatter_to_native(val) for key, val in self.dict.items() } + if "cookiefile" in out: + if not FileHandler.is_file_existent(out["cookiefile"]): + raise ValidationException( + f"Specified cookiefile {out['cookiefile']} but it does not exist as a file." + ) + + if not FileHandler.is_file_readable(out["cookiefile"]): + raise SubscriptionPermissionError( + f"Cannot read cookiefile {out['cookiefile']} due to permissions issue." + ) + + return out # Disable for proper docstring formatting diff --git a/src/ytdl_sub/subscriptions/base_subscription.py b/src/ytdl_sub/subscriptions/base_subscription.py index 970f142d..39a8a70a 100644 --- a/src/ytdl_sub/subscriptions/base_subscription.py +++ b/src/ytdl_sub/subscriptions/base_subscription.py @@ -10,6 +10,8 @@ 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.exceptions import SubscriptionPermissionError +from ytdl_sub.utils.file_handler import FileHandler 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 @@ -94,6 +96,12 @@ class BaseSubscription(ABC): self._exception: Optional[Exception] = None + if not FileHandler.is_path_writable(self.output_directory): + raise SubscriptionPermissionError( + "ytdl-sub does not have write permissions to the output directory: " + f"{self.output_directory}" + ) + @property def download_archive(self) -> EnhancedDownloadArchive: """ diff --git a/src/ytdl_sub/utils/exceptions.py b/src/ytdl_sub/utils/exceptions.py index bfc222ec..76d80780 100644 --- a/src/ytdl_sub/utils/exceptions.py +++ b/src/ytdl_sub/utils/exceptions.py @@ -40,3 +40,7 @@ class FileNotDownloadedException(ValueError): class ExperimentalFeatureNotEnabled(ValidationException): """Feature is not enabled for usage""" + + +class SubscriptionPermissionError(ValidationException): + """Early-caught permission error""" diff --git a/src/ytdl_sub/utils/file_handler.py b/src/ytdl_sub/utils/file_handler.py index cdf9c099..1db4bb6c 100644 --- a/src/ytdl_sub/utils/file_handler.py +++ b/src/ytdl_sub/utils/file_handler.py @@ -379,6 +379,36 @@ class FileHandler: """ return self._file_handler_transaction_log + @classmethod + def is_path_writable(cls, src_file_path: Union[str, Path]) -> bool: + """ + Check whether a path is writable. If it does not exist, try to find the base directory + and check permissions on that. + """ + path = os.path.abspath(src_file_path) + + while not os.path.exists(path): + new_path = os.path.dirname(path) + if new_path == path: # reached root + break + path = new_path + + return os.access(path, os.W_OK) + + @classmethod + def is_file_existent(cls, file_path: Union[str, Path]) -> bool: + """ + Check whether a file exists. + """ + return os.path.isfile(file_path) + + @classmethod + def is_file_readable(cls, file_path: Union[str, Path]) -> bool: + """ + Check whether a file exists and is readable. + """ + return cls.is_file_existent(file_path) and os.access(file_path, os.R_OK) + @classmethod def copy(cls, src_file_path: Union[str, Path], dst_file_path: Union[str, Path]): """ diff --git a/tests/conftest.py b/tests/conftest.py index aabbba8d..449163c8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,7 @@ from typing import Dict from typing import List from typing import Optional from typing import Tuple +from typing import Union from unittest.mock import patch import pytest @@ -165,19 +166,18 @@ def preset_dict_to_dl_args(preset_dict: Dict) -> str: @pytest.fixture -def preset_dict_to_subscription_yaml_generator() -> Callable: +def subscription_yaml_file_generator() -> Callable: @contextlib.contextmanager - def _preset_dict_to_subscription_yaml_generator(subscription_name: str, preset_dict: Dict): - subscription_dict = {subscription_name: preset_dict} + def _subscription_yaml_file_generator(yaml_dict: Dict): with tempfile.NamedTemporaryFile(suffix=".yaml", delete=False) as tmp_file: - tmp_file.write(json.dumps(subscription_dict).encode("utf-8")) + tmp_file.write(json.dumps(yaml_dict).encode("utf-8")) try: yield tmp_file.name finally: FileHandler.delete(tmp_file.name) - return _preset_dict_to_subscription_yaml_generator + return _subscription_yaml_file_generator ################################################################################################### @@ -224,8 +224,14 @@ def _load_config(config_path: Path, working_directory: str) -> ConfigFile: @pytest.fixture() -def music_video_subscription_path() -> Path: - return Path("examples/music_video_subscriptions.yaml") +def music_video_subscription_path( + output_directory: str, subscription_yaml_file_generator: Callable +) -> Path: + yaml = load_yaml("examples/music_video_subscriptions.yaml") + yaml["__preset__"]["overrides"]["music_video_directory"] = output_directory + + with subscription_yaml_file_generator(yaml) as mock_filename: + yield mock_filename @pytest.fixture() @@ -239,13 +245,25 @@ def tv_show_config(working_directory, tv_show_config_path) -> ConfigFile: @pytest.fixture() -def tv_show_subscriptions_path() -> Path: - return Path("examples/tv_show_subscriptions.yaml") +def tv_show_subscriptions_path( + output_directory: str, subscription_yaml_file_generator: Callable +) -> Path: + yaml = load_yaml("examples/tv_show_subscriptions.yaml") + yaml["__preset__"]["overrides"]["tv_show_directory"] = output_directory + + with subscription_yaml_file_generator(yaml) as mock_filename: + yield mock_filename @pytest.fixture() -def advanced_tv_show_subscriptions_path() -> Path: - return Path("examples/advanced/tv_show_subscriptions.yaml") +def advanced_tv_show_subscriptions_path( + output_directory: str, subscription_yaml_file_generator: Callable +) -> Path: + yaml = load_yaml("examples/advanced/tv_show_subscriptions.yaml") + yaml["__preset__"] = {"overrides": {"tv_show_directory": output_directory}} + + with subscription_yaml_file_generator(yaml) as mock_filename: + yield mock_filename @pytest.fixture() @@ -265,8 +283,25 @@ def default_config_path(default_config) -> str: @pytest.fixture() -def music_subscriptions_path() -> Path: - return Path("examples/music_subscriptions.yaml") +def music_subscriptions_path( + output_directory: str, subscription_yaml_file_generator: Callable +) -> Path: + yaml = load_yaml("examples/music_subscriptions.yaml") + yaml["__preset__"]["overrides"]["music_directory"] = output_directory + + with subscription_yaml_file_generator(yaml) as mock_filename: + yield mock_filename + + +@pytest.fixture() +def docker_default_subscription_path( + output_directory: str, subscription_yaml_file_generator: Callable +) -> Path: + yaml = load_yaml("docker/root/defaults/subscriptions.yaml") + yaml["__preset__"]["overrides"]["tv_show_directory"] = output_directory + + with subscription_yaml_file_generator(yaml) as mock_filename: + yield mock_filename def mock_run_from_cli(args: str) -> List[Subscription]: diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index 044509bf..423ff8b0 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -540,9 +540,9 @@ def test_music_video_subscriptions(default_config: ConfigFile, music_video_subsc assert gnr.get("url2").native == "https://www.youtube.com/watch?v=OldpIhHPsbs" -def test_default_docker_config_and_subscriptions(): +def test_default_docker_config_and_subscriptions(docker_default_subscription_path: Path): default_config = ConfigFile.from_file_path("docker/root/defaults/config.yaml") default_subs = Subscription.from_file_path( - config=default_config, subscription_path=Path("docker/root/defaults/subscriptions.yaml") + config=default_config, subscription_path=docker_default_subscription_path ) assert len(default_subs) == 1 diff --git a/tests/unit/plugins/test_ytdl_options.py b/tests/unit/plugins/test_ytdl_options.py new file mode 100644 index 00000000..bfeb0ef1 --- /dev/null +++ b/tests/unit/plugins/test_ytdl_options.py @@ -0,0 +1,38 @@ +import re +from typing import Any +from typing import Dict + +import pytest +from conftest import get_match_filters + +from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.subscriptions.subscription import Subscription +from ytdl_sub.utils.exceptions import ValidationException + + +@pytest.fixture +def preset_dict(output_directory) -> Dict[str, Any]: + return { + "download": "https://your.name.here", + "output_options": {"output_directory": output_directory, "file_name": "will_error.mp4"}, + } + + +class TestYtdlOptions: + def test_cookiefile_does_not_exist( + self, + default_config: ConfigFile, + preset_dict: Dict[str, Any], + ): + preset_dict["ytdl_options"] = { + "cookiefile": "/path/to/nowhere", + } + + error_msg = "Specified cookiefile /path/to/nowhere but it does not exist as a file." + + with pytest.raises(ValidationException, match=re.escape(error_msg)): + Subscription.from_dict( + config=default_config, + preset_name="test_ytdl_options", + preset_dict=preset_dict, + ).download(dry_run=False) diff --git a/tests/unit/utils/test_file_handler.py b/tests/unit/utils/test_file_handler.py new file mode 100644 index 00000000..80505e24 --- /dev/null +++ b/tests/unit/utils/test_file_handler.py @@ -0,0 +1,20 @@ +import pytest + +from ytdl_sub.utils.file_handler import FileHandler +from ytdl_sub.utils.system import IS_WINDOWS + + +class TestFileHandler: + + def test_directory_exists(self): + if IS_WINDOWS: + return + + assert FileHandler.is_path_writable("/tmp") + assert FileHandler.is_path_writable("/tmp/") + assert FileHandler.is_path_writable("/tmp/non-existent") + assert FileHandler.is_path_writable("/tmp/non-existent/") + assert FileHandler.is_path_writable("/tmp/non-existent/nested") + + assert not FileHandler.is_path_writable("/lol-in-root") + assert not FileHandler.is_path_writable("/") From 157a0b59c4923a04effd89da99f2db4449d04c57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:41:54 -0800 Subject: [PATCH 15/46] Bump yt-dlp[default] from 2025.11.12 to 2025.12.8 (#1396) Bumps [yt-dlp[default]](https://github.com/yt-dlp/yt-dlp) from 2025.11.12 to 2025.12.8. - [Release notes](https://github.com/yt-dlp/yt-dlp/releases) - [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md) - [Commits](https://github.com/yt-dlp/yt-dlp/compare/2025.11.12...2025.12.08) --- updated-dependencies: - dependency-name: yt-dlp[default] dependency-version: 2025.12.8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ac8b121c..343e1a0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ - "yt-dlp[default]==2025.11.12", + "yt-dlp[default]==2025.12.8", "colorama~=0.4", "mergedeep~=1.3", "mediafile~=0.12", From 0f96d8e24e6523894ac6953a5fde669a007917dd Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 26 Dec 2025 15:30:37 -0800 Subject: [PATCH 16/46] [BACKEND] Disallow override names conflicting with plugin names (#1399) Disallow override variable names conflicting with plugin names. May cause backward incompatible issues from `date_range` being an old override variable name for the `Only Recent` preset. Update this variable name to `only_recent_date_range` to resolve. More info in https://ytdl-sub.readthedocs.io/en/latest/deprecation_notices.html --- docs/source/deprecation_notices.rst | 9 +++++++ examples/advanced/tv_show_subscriptions.yaml | 2 +- src/ytdl_sub/config/overrides.py | 19 +++++++++++++++ src/ytdl_sub/config/preset.py | 2 ++ .../entries/variables/override_variables.py | 5 +--- .../helpers/download_deletion_options.yaml | 3 +-- tests/unit/config/test_preset.py | 24 +++++++++++++++++++ 7 files changed, 57 insertions(+), 7 deletions(-) diff --git a/docs/source/deprecation_notices.rst b/docs/source/deprecation_notices.rst index 9fc4b48d..a75240c2 100644 --- a/docs/source/deprecation_notices.rst +++ b/docs/source/deprecation_notices.rst @@ -1,6 +1,15 @@ Deprecation Notices =================== +Dec 2025 +-------- + +Override variables names can no longer be plugin names, to avoid the common pitfall of +defining a plugin underneath ``overrides``. + +In the past, there was usage of a ``date_range`` override variable in a few example configs +that complimented the ``Only Recent`` preset. This overrride variable usage needs to be +replaced with ``only_recent_date_range``. Sep 2024 -------- diff --git a/examples/advanced/tv_show_subscriptions.yaml b/examples/advanced/tv_show_subscriptions.yaml index 52e1728c..f0b18dfa 100644 --- a/examples/advanced/tv_show_subscriptions.yaml +++ b/examples/advanced/tv_show_subscriptions.yaml @@ -24,6 +24,6 @@ TV Show Only Recent: # to set only for that subscriptions "~BBC News": url: "https://www.youtube.com/@BBCNews" # use url2, url3, ... for multi-url in this form - date_range: "2weeks" + only_recent_date_range: "2weeks" "Frontline PBS": "https://www.youtube.com/@frontline" "Whitehouse": "https://www.bitchute.com/channel/zWsYVmCOu4JA/" # Supports non-YT sites \ No newline at end of file diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index 06ca8fce..c3b791b7 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -1,5 +1,6 @@ from typing import Any from typing import Dict +from typing import Iterable from typing import Optional from typing import Set @@ -88,6 +89,24 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): return True + def ensure_variable_names_not_a_plugin(self, plugin_names: Iterable[str]) -> None: + """ + Throws an error if an override variable or function has the same name as a + preset key. This is to avoid confusion when accidentally defining things in + overrides that are meant to be in the preset. + """ + for name in self.keys: + if name.startswith("%"): + name = name[1:] + + if name in plugin_names: + raise self._validation_exception( + f"Override variable with name {name} cannot be used since it is" + " the name of a plugin. Perhaps you meant to define it as a plugin? If so," + " indent it left to make it at the same level as overrides.", + exception_class=InvalidVariableNameException, + ) + def ensure_variable_name_valid(self, name: str) -> None: """ Ensures the variable name does not collide with any entry variables or built-in functions. diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index 914ebc77..31ac34a8 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -194,6 +194,8 @@ class Preset(_PresetShell): self.plugins: PresetPlugins = self._validate_and_get_plugins() self.overrides = self._validate_key(key="overrides", validator=Overrides, default={}) + self.overrides.ensure_variable_names_not_a_plugin(plugin_names=PRESET_KEYS) + VariableValidation( downloader_options=self.downloader_options, output_options=self.output_options, diff --git a/src/ytdl_sub/entries/variables/override_variables.py b/src/ytdl_sub/entries/variables/override_variables.py index e9b3da07..8dd2081c 100644 --- a/src/ytdl_sub/entries/variables/override_variables.py +++ b/src/ytdl_sub/entries/variables/override_variables.py @@ -11,9 +11,6 @@ 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 -# TODO: use this -SUBSCRIPTION_ARRAY = "subscription_array" - class SubscriptionVariables: @staticmethod @@ -163,7 +160,7 @@ class OverrideHelpers: True if the override name itself is valid. False otherwise. """ if name.startswith("%"): - return is_valid_name(name=name[1:]) + name = name[1:] return is_valid_name(name=name) diff --git a/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml b/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml index 676862a3..0bf420c3 100644 --- a/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml +++ b/src/ytdl_sub/prebuilt_presets/helpers/download_deletion_options.yaml @@ -11,8 +11,7 @@ presets: # Set the default date_range to 2 months overrides: - date_range: "2months" # keep for legacy-reasons - only_recent_date_range: "{date_range}" + only_recent_date_range: "2months" ############################################################################# # Only Recent diff --git a/tests/unit/config/test_preset.py b/tests/unit/config/test_preset.py index 939eab92..8e9b4c89 100644 --- a/tests/unit/config/test_preset.py +++ b/tests/unit/config/test_preset.py @@ -435,3 +435,27 @@ class TestPreset: "output_options": {"output_directory": "dir", "file_name": "acjk"}, }, ) + + def test_preset_error_override_name_conflicts_with_plugin(self, config_file, output_options): + with pytest.raises( + ValidationException, + match=re.escape( + "Override variable with name throttle_protection cannot be used since it is the " + "name of a plugin. Perhaps you meant to define it as a plugin? If so, indent it " + "left to make it at the same level as overrides." + ), + ): + _ = Preset( + config=config_file, + name="test", + value={ + "download": { + "url": "youtube.com/watch?v=123abc", + }, + "subtitles": { + "embed_subtitles": True, + }, + "output_options": {"output_directory": "dir", "file_name": "acjk"}, + "overrides": {"throttle_protection": "nope"}, + }, + ) From 344753cc63ce45e15a3e0e462cc5172cbc8f6b56 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Mon, 29 Dec 2025 17:38:34 -0800 Subject: [PATCH 17/46] [BACKEND] Optimize script resolution (#1400) Speeds up scripting in the backend. --- src/ytdl_sub/script/script.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index e92e7fdd..d6e482fe 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -307,10 +307,9 @@ class Script: def _get_unresolved_output_filter( self, - unresolved: Dict[Variable, SyntaxTree], output_filter: Set[str], unresolvable: Set[Variable], - ) -> Dict[Variable, SyntaxTree]: + ) -> Set[str]: """ When an output filter is applied, only a subset of variables that the filter depends on need to be resolved. @@ -331,7 +330,7 @@ class Script: unresolvable=unresolvable, ) - return {var: syntax for var, syntax in unresolved.items() if var.name in subset_to_resolve} + return subset_to_resolve def _resolve( self, @@ -367,18 +366,21 @@ class Script: unresolvable: Set[Variable] = {Variable(name) for name in (unresolvable or {})} unresolved_filter = set(resolved.keys()).union(unresolvable) - unresolved: Dict[Variable, SyntaxTree] = { - Variable(name): ast - for name, ast in self._variables.items() - if Variable(name) not in unresolved_filter - } if output_filter: - unresolved = self._get_unresolved_output_filter( - unresolved=unresolved, - output_filter=output_filter, - unresolvable=unresolvable, - ) + unresolved = { + Variable(name): self._variables[name] + for name in self._get_unresolved_output_filter( + output_filter=output_filter, + unresolvable=unresolvable, + ) + } + else: + unresolved = { + Variable(name): ast + for name, ast in self._variables.items() + if Variable(name) not in unresolved_filter + } while unresolved: unresolved_count: int = len(unresolved) @@ -556,8 +558,7 @@ class Script: ).output finally: for name in variable_definitions.keys(): - if name in self._variables: - del self._variables[name] + self._variables.pop(name, None) def get(self, variable_name: str) -> Resolvable: """ From 1abe2a44f5ab4286ae194fa16accc24c17b48de4 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Tue, 30 Dec 2025 17:03:59 -0800 Subject: [PATCH 18/46] [BACKEND] Refactor validation, preparation for subscription dissect (#1398) Completely rewrite how subscription validation is performed. Optimizes it quite a bit while also adding backend support for the upcoming `dissect` sub-command, where you can resolve any subscription into its 'raw' form for easier debugging when making scripting changes. --- .gitignore | 5 +- src/ytdl_sub/config/overrides.py | 33 ++-- src/ytdl_sub/config/plugin/preset_plugins.py | 33 ++++ src/ytdl_sub/config/preset.py | 44 ++++- .../config/validators/variable_validation.py | 170 +++-------------- src/ytdl_sub/downloaders/url/validators.py | 4 +- src/ytdl_sub/script/parser.py | 5 + src/ytdl_sub/script/script.py | 121 +++++++++--- src/ytdl_sub/script/utils/name_validation.py | 21 +++ .../subscriptions/base_subscription.py | 24 ++- src/ytdl_sub/utils/script.py | 21 ++- .../validators/string_formatter_validators.py | 92 +++++++--- src/ytdl_sub/validators/validators.py | 16 +- tests/unit/config/test_preset.py | 142 --------------- tests/unit/config/test_subscription.py | 61 ++++++- .../config/test_subscription_validation.py | 172 ++++++++++++++++++ tools/docgen/plugins.py | 2 + 17 files changed, 575 insertions(+), 391 deletions(-) create mode 100644 tests/unit/config/test_subscription_validation.py diff --git a/.gitignore b/.gitignore index e18f90d3..5e9f1aad 100644 --- a/.gitignore +++ b/.gitignore @@ -146,8 +146,11 @@ docker/testing/volumes .local/ .ytdl-sub-working-directory +.ytdl-sub-lock ffmpeg.exe ffprobe.exe -tools/docgen/out \ No newline at end of file +tools/docgen/out + +prof/ diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index c3b791b7..8f790022 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -4,15 +4,16 @@ from typing import Iterable from typing import Optional from typing import Set -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.script.parser import parse from ytdl_sub.script.script import Script +from ytdl_sub.script.types.function import BuiltInFunction from ytdl_sub.script.types.resolvable import Resolvable +from ytdl_sub.script.types.resolvable import String +from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved from ytdl_sub.utils.exceptions import InvalidVariableNameException from ytdl_sub.utils.exceptions import StringFormattingException @@ -134,29 +135,35 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): ) def initial_variables( - self, unresolved_variables: Optional[Dict[str, str]] = None - ) -> Dict[str, str]: + self, unresolved_variables: Optional[Dict[str, SyntaxTree]] = None + ) -> Dict[str, SyntaxTree]: """ Returns ------- Variables and format strings for all Override variables + additional variables (Optional) """ - initial_variables: Dict[str, str] = {} - mergedeep.merge( - initial_variables, - self.dict_with_format_strings, - unresolved_variables if unresolved_variables else {}, - ) - return ScriptUtils.add_sanitized_variables(initial_variables) + initial_variables: Dict[str, SyntaxTree] = self.dict_with_parsed_format_strings + if unresolved_variables: + initial_variables |= unresolved_variables + return ScriptUtils.add_sanitized_parsed_variables(initial_variables) def initialize_script(self, unresolved_variables: Set[str]) -> "Overrides": """ Initialize the override script with any unresolved variables """ - self.script.add( + self.script.add_parsed( self.initial_variables( unresolved_variables={ - var_name: f"{{%throw('Plugin variable {var_name} has not been created yet')}}" + var_name: SyntaxTree( + ast=[ + BuiltInFunction( + name="throw", + args=[ + String(f"Plugin variable {var_name} has not been created yet") + ], + ) + ] + ) for var_name in unresolved_variables } ) diff --git a/src/ytdl_sub/config/plugin/preset_plugins.py b/src/ytdl_sub/config/plugin/preset_plugins.py index 7eda8a9d..61305a37 100644 --- a/src/ytdl_sub/config/plugin/preset_plugins.py +++ b/src/ytdl_sub/config/plugin/preset_plugins.py @@ -1,5 +1,7 @@ +from typing import Iterable from typing import List from typing import Optional +from typing import Set from typing import Tuple from typing import Type @@ -44,3 +46,34 @@ class PresetPlugins: if plugin_type in plugin_option_types: return self.plugin_options[plugin_option_types.index(plugin_type)] return None + + def get_added_and_modified_variables( + self, additional_options: List[OptionsValidator] + ) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]: + """ + Iterates and returns the plugin options, added variables, modified variables + """ + for plugin_options in self.plugin_options + additional_options: + added_variables: Set[str] = set() + modified_variables: Set[str] = set() + + for plugin_added_variables in plugin_options.added_variables( + unresolved_variables=set(), + ).values(): + added_variables |= set(plugin_added_variables) + + for plugin_modified_variables in plugin_options.modified_variables().values(): + modified_variables = plugin_modified_variables + + yield plugin_options, added_variables, modified_variables + + def get_all_variables(self, additional_options: List[OptionsValidator]) -> Set[str]: + """ + Returns set of all added and modified variables' names. + """ + all_variables: Set[str] = set() + for _, added, modified in self.get_added_and_modified_variables(additional_options): + all_variables.update(added) + all_variables.update(modified) + + return all_variables diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index 31ac34a8..d8380839 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -2,6 +2,7 @@ import copy from typing import Any from typing import Dict from typing import List +from typing import Set from mergedeep import mergedeep @@ -11,7 +12,6 @@ from ytdl_sub.config.plugin.plugin_mapping import PluginMapping from ytdl_sub.config.plugin.preset_plugins import PresetPlugins from ytdl_sub.config.preset_options import OutputOptions from ytdl_sub.config.preset_options import YTDLOptions -from ytdl_sub.config.validators.variable_validation import VariableValidation from ytdl_sub.downloaders.url.validators import MultiUrlValidator from ytdl_sub.prebuilt_presets import PREBUILT_PRESET_NAMES from ytdl_sub.prebuilt_presets import PUBLISHED_PRESET_NAMES @@ -172,6 +172,37 @@ class Preset(_PresetShell): mergedeep.merge({}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE) ) + def _initialize_overrides_script(self, overrides: Overrides) -> Overrides: + """ + Do some gymnastics to initialize the Overrides script. + """ + unresolved_variables: Set[str] = set() + + for ( + plugin_options, + added_variables, + modified_variables, + ) in self.plugins.get_added_and_modified_variables( + additional_options=[self.downloader_options, self.output_options] + ): + for added_variable in added_variables: + if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable): + # pylint: disable=protected-access + raise plugin_options._validation_exception( + f"Cannot use the variable name {added_variable} because it exists as a" + " built-in ytdl-sub variable name." + ) + # pylint: enable=protected-access + + # Set unresolved as variables that are added but do not exist as + # entry/override variables since they are created at run-time + unresolved_variables |= added_variables | modified_variables + + # 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 + return overrides.initialize_script(unresolved_variables=unresolved_variables) + def __init__(self, config: ConfigValidator, name: str, value: Any): super().__init__(name=name, value=value) @@ -192,16 +223,11 @@ class Preset(_PresetShell): ) self.plugins: PresetPlugins = self._validate_and_get_plugins() - self.overrides = self._validate_key(key="overrides", validator=Overrides, default={}) - + self.overrides = self._initialize_overrides_script( + overrides=self._validate_key(key="overrides", validator=Overrides, default={}) + ) self.overrides.ensure_variable_names_not_a_plugin(plugin_names=PRESET_KEYS) - VariableValidation( - downloader_options=self.downloader_options, - output_options=self.output_options, - plugins=self.plugins, - ).initialize_preset_overrides(overrides=self.overrides).ensure_proper_usage() - @property def name(self) -> str: """ diff --git a/src/ytdl_sub/config/validators/variable_validation.py b/src/ytdl_sub/config/validators/variable_validation.py index e2d5e85d..bb8ea705 100644 --- a/src/ytdl_sub/config/validators/variable_validation.py +++ b/src/ytdl_sub/config/validators/variable_validation.py @@ -1,10 +1,4 @@ -import copy from typing import Dict -from typing import Iterable -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.plugin.plugin_mapping import PluginMapping @@ -13,155 +7,27 @@ 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 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] = {} - for var in variables: - dummy_variables[var] = "" - dummy_variables[f"{var}_sanitized"] = "" - - return dummy_variables - - -def _add_dummy_overrides(overrides: Overrides) -> Dict[str, str]: - # Have the dummy override variable contain all variable deps that it uses in the string - dummy_overrides: Dict[str, str] = {} - for override_name in _override_variables(overrides): - if _is_function(override_name): - continue - - # pylint: disable=protected-access - dummy_overrides[override_name] = to_variable_dependency_format_string( - script=overrides.script, parsed_format_string=overrides.script._variables[override_name] - ) - # pylint: enable=protected-access - return dummy_overrides - - -def _get_added_and_modified_variables( - plugins: PresetPlugins, downloader_options: MultiUrlValidator, output_options: OutputOptions -) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]: - """ - Iterates and returns the plugin options, added variables, modified variables - """ - options: List[OptionsValidator] = plugins.plugin_options - options.append(downloader_options) - options.append(output_options) - - for plugin_options in options: - added_variables: Set[str] = set() - modified_variables: Set[str] = set() - - for plugin_added_variables in plugin_options.added_variables( - unresolved_variables=set(), - ).values(): - added_variables |= set(plugin_added_variables) - - for plugin_modified_variables in plugin_options.modified_variables().values(): - modified_variables = plugin_modified_variables - - yield plugin_options, added_variables, modified_variables - - -def _override_variables(overrides: Overrides) -> Set[str]: - return set(list(overrides.initial_variables().keys())) - class VariableValidation: def __init__( self, + overrides: Overrides, downloader_options: MultiUrlValidator, output_options: OutputOptions, plugins: PresetPlugins, ): + self.overrides = overrides self.downloader_options = downloader_options self.output_options = output_options self.plugins = plugins - self.script: Optional[Script] = None - self.resolved_variables: Set[str] = set() - self.unresolved_variables: Set[str] = set() - - def initialize_preset_overrides(self, overrides: Overrides) -> "VariableValidation": - """ - Do some gymnastics to initialize the Overrides script. - """ - 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 - self.resolved_variables = set(_DUMMY_ENTRY_VARIABLES.keys()) | override_variables - plugin_variables: Set[str] = set() - - for ( - plugin_options, - added_variables, - modified_variables, - ) in _get_added_and_modified_variables( - plugins=self.plugins, - downloader_options=self.downloader_options, - output_options=self.output_options, - ): - - for added_variable in added_variables: - if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable): - # pylint: disable=protected-access - raise plugin_options._validation_exception( - f"Cannot use the variable name {added_variable} because it exists as a" - " built-in ytdl-sub variable name." - ) - # pylint: enable=protected-access - - # Set unresolved as variables that are added but do not exist as - # entry/override variables since they are created at run-time - self.unresolved_variables |= added_variables | modified_variables - plugin_variables |= added_variables | modified_variables - - # Then update resolved variables to reflect that - self.resolved_variables -= self.unresolved_variables - - # 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(unresolved_variables=self.unresolved_variables) - - # copy the script and mock entry variables - self.script = copy.deepcopy(overrides.script) - self.script.add( - variables=_add_dummy_overrides(overrides=overrides) - | _add_dummy_variables(variables=plugin_variables) - | _DUMMY_ENTRY_VARIABLES + self.script = self.overrides.script + self.unresolved_variables = self.plugins.get_all_variables( + additional_options=[self.output_options, self.downloader_options] ) - return self - - def _update_script(self) -> None: - _ = self.script.resolve(unresolvable=self.unresolved_variables, update=True) - - 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 @@ -171,19 +37,17 @@ class VariableValidation: ).get(plugin_op, set()) modified_variables = options.modified_variables().get(plugin_op, set()) - resolved_variables = added_variables | modified_variables + self.unresolved_variables -= added_variables | modified_variables - self.resolved_variables |= resolved_variables - self.unresolved_variables -= resolved_variables - - def ensure_proper_usage(self) -> None: + def ensure_proper_usage(self) -> Dict: """ Validate variables resolve as plugins are executed, and return a mock script which contains actualized added variables from the plugins """ + resolved_subscription: Dict = {} + self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options) - self._add_subscription_override_variables() # Always add output options first self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options) @@ -200,16 +64,28 @@ class VariableValidation: self._add_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options) # Validate that any formatter in the plugin options can resolve - validate_formatters( + resolved_subscription |= validate_formatters( script=self.script, unresolved_variables=self.unresolved_variables, validator=plugin_options, ) - validate_formatters( + resolved_subscription |= validate_formatters( script=self.script, unresolved_variables=self.unresolved_variables, validator=self.output_options, ) + # TODO: make this a function + raw_download_output = validate_formatters( + script=self.script, + unresolved_variables=self.unresolved_variables, + validator=self.downloader_options.urls, + ) + resolved_subscription["download"] = [] + for url_output in raw_download_output["download"]: + if url_output["url"]: + resolved_subscription["download"].append(url_output) + assert not self.unresolved_variables + return resolved_subscription diff --git a/src/ytdl_sub/downloaders/url/validators.py b/src/ytdl_sub/downloaders/url/validators.py index 4504eccf..e518cdf8 100644 --- a/src/ytdl_sub/downloaders/url/validators.py +++ b/src/ytdl_sub/downloaders/url/validators.py @@ -21,7 +21,7 @@ class UrlThumbnailValidator(StrictDictValidator): def __init__(self, name, value): super().__init__(name, value) - self._name = self._validate_key(key="name", validator=StringFormatterValidator) + self._thumb_name = self._validate_key(key="name", validator=StringFormatterValidator) self._uid = self._validate_key(key="uid", validator=OverridesStringFormatterValidator) @property @@ -29,7 +29,7 @@ class UrlThumbnailValidator(StrictDictValidator): """ File name for the thumbnail """ - return self._name + return self._thumb_name @property def uid(self) -> OverridesStringFormatterValidator: diff --git a/src/ytdl_sub/script/parser.py b/src/ytdl_sub/script/parser.py index 9dd3f186..73dbb3f4 100644 --- a/src/ytdl_sub/script/parser.py +++ b/src/ytdl_sub/script/parser.py @@ -31,6 +31,8 @@ from ytdl_sub.script.utils.exceptions import InvalidSyntaxException from ytdl_sub.script.utils.exceptions import InvalidVariableName from ytdl_sub.script.utils.exceptions import UserException from ytdl_sub.script.utils.exceptions import VariableDoesNotExist +from ytdl_sub.script.utils.name_validation import is_function +from ytdl_sub.script.utils.name_validation import to_function_name from ytdl_sub.script.utils.name_validation import validate_variable_name # pylint: disable=invalid-name @@ -144,6 +146,9 @@ class _Parser: ): self._text = text self._name = name + if name and is_function(name): + self._name = to_function_name(name) + self._custom_function_names = custom_function_names self._variable_names = variable_names self._pos = 0 diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index d6e482fe..67eeeaf1 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -20,28 +20,13 @@ from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments from ytdl_sub.script.utils.exceptions import InvalidCustomFunctionArguments from ytdl_sub.script.utils.exceptions import RuntimeException from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved +from ytdl_sub.script.utils.name_validation import is_function +from ytdl_sub.script.utils.name_validation import to_function_definition_name +from ytdl_sub.script.utils.name_validation import to_function_name from ytdl_sub.script.utils.name_validation import validate_variable_name from ytdl_sub.script.utils.type_checking import FunctionSpec -def _is_function(override_name: str): - return override_name.startswith("%") - - -def _function_name(function_key: str) -> str: - """ - Drop the % in %custom_function - """ - return function_key[1:] - - -def _to_function_definition_name(function_key: str) -> str: - """ - Add % in %custom_function - """ - return f"%{function_key}" - - class Script: """ Takes a dictionary of both @@ -241,23 +226,23 @@ class Script: def __init__(self, script: Dict[str, str]): function_names: Set[str] = { - _function_name(name) for name in script.keys() if _is_function(name) + to_function_name(name) for name in script.keys() if is_function(name) } variable_names: Set[str] = { - validate_variable_name(name) for name in script.keys() if not _is_function(name) + validate_variable_name(name) for name in script.keys() if not is_function(name) } self._functions: Dict[str, SyntaxTree] = { # custom_function_name must be passed to properly type custom function # arguments uniquely if they're nested (i.e. $0 to $custom_func___0) - _function_name(function_key): parse( + to_function_name(function_key): parse( text=function_value, - name=_function_name(function_key), + name=to_function_name(function_key), custom_function_names=function_names, variable_names=variable_names, ) for function_key, function_value in script.items() - if _is_function(function_key) + if is_function(function_key) } self._variables: Dict[str, SyntaxTree] = { @@ -268,7 +253,7 @@ class Script: variable_names=variable_names, ) for variable_key, variable_value in script.items() - if not _is_function(variable_key) + if not is_function(variable_key) } self._validate() @@ -485,12 +470,12 @@ class Script: added_variables_to_validate: Set[str] = set() functions_to_add = { - _function_name(name): definition + to_function_name(name): definition for name, definition in variables.items() - if _is_function(name) + if is_function(name) } variables_to_add = { - name: definition for name, definition in variables.items() if not _is_function(name) + name: definition for name, definition in variables.items() if not is_function(name) } custom_function_names = set(self._functions.keys()) | functions_to_add.keys() @@ -520,6 +505,46 @@ class Script: return self + def add_parsed(self, variables: Dict[str, SyntaxTree]) -> "Script": + """ + Adds already parsed, new variables to the script. + + Parameters + ---------- + variables + Mapping containing variable name to definition. + + Returns + ------- + Script + self + """ + added_variables_to_validate: Set[str] = set() + + functions_to_add = { + to_function_name(name): definition + for name, definition in variables.items() + if is_function(name) + } + variables_to_add = { + name: definition for name, definition in variables.items() if not is_function(name) + } + + for definitions in [functions_to_add, variables_to_add]: + for name, parsed in definitions.items(): + if parsed.maybe_resolvable is None: + added_variables_to_validate.add(name) + + if name in functions_to_add: + self._functions[name] = parsed + else: + self._variables[name] = parsed + + if added_variables_to_validate: + self._validate(added_variables=added_variables_to_validate) + + return self + def resolve_once( self, variable_definitions: Dict[str, str], @@ -560,6 +585,46 @@ class Script: for name in variable_definitions.keys(): self._variables.pop(name, None) + def resolve_once_parsed( + self, + variable_definitions: Dict[str, SyntaxTree], + resolved: Optional[Dict[str, Resolvable]] = None, + unresolvable: Optional[Set[str]] = None, + update: bool = False, + ) -> Dict[str, Resolvable]: + """ + Given a new set of variable definitions, resolve them using the Script, but do not + add them to the Script itself. + + Parameters + ---------- + variable_definitions + Variables to resolve, but not store in the Script + resolved + Optional. Pre-resolved variables that should be used instead of what is in the script. + unresolvable + Optional. Unresolvable variables that will be ignored in resolution, including all + variables with a dependency to them. + update + Whether to update the script's state with resolved variables. Defaults to False. + + Returns + ------- + Dict[str, Resolvable] + Dict containing the variable names to their resolved values. + """ + try: + self.add_parsed(variable_definitions) + return self._resolve( + pre_resolved=resolved, + unresolvable=unresolvable, + output_filter=set(list(variable_definitions.keys())), + update=update, + ).output + finally: + for name in variable_definitions.keys(): + self._variables.pop(name, None) + def get(self, variable_name: str) -> Resolvable: """ Parameters @@ -605,4 +670,4 @@ class Script: Set[str] Names of all functions within the Script. """ - return set(_to_function_definition_name(name) for name in self._functions.keys()) + return set(to_function_definition_name(name) for name in self._functions.keys()) diff --git a/src/ytdl_sub/script/utils/name_validation.py b/src/ytdl_sub/script/utils/name_validation.py index de187ec8..5b2597ce 100644 --- a/src/ytdl_sub/script/utils/name_validation.py +++ b/src/ytdl_sub/script/utils/name_validation.py @@ -58,3 +58,24 @@ def validate_custom_function_name(custom_function_name: str) -> None: f"Custom function name '%{custom_function_name}' is invalid:" " The name is used by a built-in function and cannot be overwritten." ) + + +def is_function(override_name: str): + """ + Whether the definition is a function or not. + """ + return override_name.startswith("%") + + +def to_function_name(function_key: str) -> str: + """ + Drop the % in %custom_function + """ + return function_key[1:] + + +def to_function_definition_name(function_key: str) -> str: + """ + Add % in %custom_function + """ + return f"%{function_key}" diff --git a/src/ytdl_sub/subscriptions/base_subscription.py b/src/ytdl_sub/subscriptions/base_subscription.py index 39a8a70a..656e98ed 100644 --- a/src/ytdl_sub/subscriptions/base_subscription.py +++ b/src/ytdl_sub/subscriptions/base_subscription.py @@ -8,12 +8,14 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins 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.config.validators.variable_validation import VariableValidation from ytdl_sub.downloaders.url.validators import MultiUrlValidator from ytdl_sub.entries.variables.override_variables import SubscriptionVariables from ytdl_sub.utils.exceptions import SubscriptionPermissionError from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_handler import FileHandlerTransactionLog from ytdl_sub.utils.logger import Logger +from ytdl_sub.utils.yaml import dump_yaml from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive logger = Logger.get("subscription") @@ -76,6 +78,14 @@ class BaseSubscription(ABC): } ) + # Validate after adding the subscription name + self._validated_dict = VariableValidation( + overrides=self.overrides, + downloader_options=self.downloader_options, + output_options=self.output_options, + plugins=self.plugins, + ).ensure_proper_usage() + self._enhanced_download_archive: Optional[EnhancedDownloadArchive] = ( _initialize_download_archive( output_options=self.output_options, @@ -88,9 +98,9 @@ class BaseSubscription(ABC): # Add post-archive variables self.overrides.add( { - SubscriptionVariables.subscription_has_download_archive(): f"""{{ - %bool({self.download_archive.num_entries > 0}) - }}""", + SubscriptionVariables.subscription_has_download_archive(): ( + f"{{%bool({self.download_archive.num_entries > 0})}}" + ), } ) @@ -245,3 +255,11 @@ class BaseSubscription(ABC): Subscription in yaml format """ return self._preset_options.yaml + + def resolved_yaml(self) -> str: + """ + Returns + ------- + Human-readable, condensed YAML definition of the subscription. + """ + return dump_yaml(self._validated_dict) diff --git a/src/ytdl_sub/utils/script.py b/src/ytdl_sub/utils/script.py index e3a17b2f..027a0bd8 100644 --- a/src/ytdl_sub/utils/script.py +++ b/src/ytdl_sub/utils/script.py @@ -4,7 +4,6 @@ from typing import Any from typing import Dict from ytdl_sub.script.parser import parse -from ytdl_sub.script.script import _is_function from ytdl_sub.script.types.array import UnresolvedArray from ytdl_sub.script.types.function import BuiltInFunction from ytdl_sub.script.types.function import Function @@ -15,8 +14,10 @@ from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.types.resolvable import Lambda from ytdl_sub.script.types.resolvable import String +from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.utils.exceptions import UNREACHABLE +from ytdl_sub.script.utils.name_validation import is_function # pylint: disable=too-many-return-statements @@ -30,10 +31,26 @@ class ScriptUtils: sanitized_variables = { f"{name}_sanitized": f"{{%sanitize({name})}}" for name in variables.keys() - if not _is_function(name) + if not is_function(name) } return dict(variables, **sanitized_variables) + @classmethod + def add_sanitized_parsed_variables( + cls, variables: Dict[str, SyntaxTree] + ) -> Dict[str, SyntaxTree]: + """ + Helper to add sanitized variables to a Script + """ + sanitized_variables = { + f"{name}_sanitized": SyntaxTree( + ast=[BuiltInFunction(name="sanitize", args=[Variable(name)])] + ) + for name in variables.keys() + if not is_function(name) + } + return variables | sanitized_variables + @classmethod def to_script(cls, value: Any, sort_keys: bool = True) -> str: """ diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 567f0f8d..19a59344 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -11,6 +11,7 @@ from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.utils.exceptions import RuntimeException from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved from ytdl_sub.script.utils.exceptions import UserException +from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException from ytdl_sub.utils.script import ScriptUtils from ytdl_sub.validators.validators import DictValidator @@ -19,6 +20,8 @@ from ytdl_sub.validators.validators import LiteralDictValidator from ytdl_sub.validators.validators import StringValidator from ytdl_sub.validators.validators import Validator +# pylint: disable=protected-access + class StringFormatterValidator(StringValidator): """ @@ -51,7 +54,10 @@ class StringFormatterValidator(StringValidator): def __init__(self, name, value: str): super().__init__(name=name, value=value) try: - _ = parse(str(value)) + self._parsed = parse( + text=str(value), + name=self.leaf_name, + ) except UserException as exc: raise self._validation_exception(exc) from exc @@ -65,6 +71,16 @@ class StringFormatterValidator(StringValidator): """ return self._value + @property + @final + def parsed(self) -> SyntaxTree: + """ + Returns + ------- + The parsed format string. + """ + return self._parsed + def post_process(self, resolved: str) -> str: """ Returns @@ -167,9 +183,14 @@ class DictFormatterValidator(LiteralDictValidator): @property def dict_with_format_strings(self) -> Dict[str, str]: - """Returns dict with the format strings themselves""" + """Returns dict with the format strings themselves.""" return {key: string_formatter.format_string for key, string_formatter in self.dict.items()} + @property + def dict_with_parsed_format_strings(self) -> Dict[str, SyntaxTree]: + """Returns dict with the parsed format strings.""" + return {key: string_formatter.parsed for key, string_formatter in self.dict.items()} + class OverridesDictFormatterValidator(DictFormatterValidator): """ @@ -199,10 +220,8 @@ def to_variable_dependency_format_string(script: Script, parsed_format_string: S dummy_format_string = "" for var in parsed_format_string.variables: dummy_format_string += f"{{ {var.name} }}" - # pylint: disable=protected-access for variable_dependency in script._variables[var.name].variables: dummy_format_string += f"{{ {variable_dependency.name} }}" - # pylint: enable=protected-access return dummy_format_string @@ -210,16 +229,15 @@ def _validate_formatter( mock_script: Script, unresolved_variables: Set[str], formatter_validator: Union[StringFormatterValidator, OverridesStringFormatterValidator], -) -> None: - is_static_formatter = False - unresolvable = unresolved_variables - if isinstance(formatter_validator, OverridesStringFormatterValidator): - is_static_formatter = True - unresolvable = unresolved_variables.union({VARIABLES.entry_metadata.variable_name}) +) -> str: + parsed = formatter_validator.parsed + if resolved := parsed.maybe_resolvable: + return resolved.native + + is_static_formatter = isinstance(formatter_validator, OverridesStringFormatterValidator) + if is_static_formatter: + unresolved_variables = unresolved_variables.union({VARIABLES.entry_metadata.variable_name}) - parsed = parse( - text=formatter_validator.format_string, - ) variable_names = {var.name for var in parsed.variables} custom_function_names = {f"%{func.name}" for func in parsed.custom_functions} @@ -233,21 +251,20 @@ def _validate_formatter( "contains the following custom functions that do not exist: " f"{', '.join(sorted(custom_function_names - mock_script.function_names))}" ) - if unresolved := variable_names.intersection(unresolvable): + if unresolved := variable_names.intersection(unresolved_variables): raise StringFormattingVariableNotFoundException( "contains the following variables that are unresolved when executing this " f"formatter: {', '.join(sorted(unresolved))}" ) try: - mock_script.resolve_once( - { - "tmp_var": to_variable_dependency_format_string( - script=mock_script, parsed_format_string=parsed - ) - }, - unresolvable=unresolvable, - update=True, - ) + if is_static_formatter: + return mock_script.resolve_once_parsed( + {"tmp_var": formatter_validator.parsed}, + unresolvable=unresolved_variables, + update=True, + )["tmp_var"].native + + return formatter_validator.format_string except RuntimeException as exc: if isinstance(exc, ScriptVariableNotResolved) and is_static_formatter: raise StringFormattingVariableNotFoundException( @@ -255,45 +272,60 @@ def _validate_formatter( "entry variables" ) from exc raise StringFormattingVariableNotFoundException(exc) from exc + except UserThrownRuntimeError as exc: + # Errors are expected for non-static formatters due to missing entry + # data. Raise otherwise. + if not is_static_formatter: + return formatter_validator.format_string + raise exc def validate_formatters( script: Script, unresolved_variables: Set[str], validator: Validator, -) -> None: +) -> Dict: """ Ensure all OverridesStringFormatterValidator's only contain variables from the overrides and resolve. """ + resolved_dict: Dict = {} + if isinstance(validator, DictValidator): - # pylint: disable=protected-access + resolved_dict[validator.leaf_name] = {} # Usage of protected variables in other validators is fine. The reason to keep # them protected is for readability when using them in subscriptions. for validator_value in validator._validator_dict.values(): - validate_formatters( + resolved_dict[validator.leaf_name] |= validate_formatters( script=script, unresolved_variables=unresolved_variables, validator=validator_value, ) - # pylint: enable=protected-access elif isinstance(validator, ListValidator): + resolved_dict[validator.leaf_name] = [] for list_value in validator.list: - validate_formatters( + list_output = validate_formatters( script=script, unresolved_variables=unresolved_variables, validator=list_value, ) + assert len(list_output) == 1 + resolved_dict[validator.leaf_name].append(list(list_output.values())[0]) elif isinstance(validator, (StringFormatterValidator, OverridesStringFormatterValidator)): - _validate_formatter( + resolved_dict[validator.leaf_name] = _validate_formatter( mock_script=script, unresolved_variables=unresolved_variables, formatter_validator=validator, ) elif isinstance(validator, (DictFormatterValidator, OverridesDictFormatterValidator)): + resolved_dict[validator.leaf_name] = {} for validator_value in validator.dict.values(): - _validate_formatter( + resolved_dict[validator.leaf_name] |= _validate_formatter( mock_script=script, unresolved_variables=unresolved_variables, formatter_validator=validator_value, ) + else: + resolved_dict[validator.leaf_name] = validator._value + + return resolved_dict diff --git a/src/ytdl_sub/validators/validators.py b/src/ytdl_sub/validators/validators.py index fbb13c95..6c1356ae 100644 --- a/src/ytdl_sub/validators/validators.py +++ b/src/ytdl_sub/validators/validators.py @@ -95,21 +95,11 @@ class Validator(ABC): @final @property - def _root_name(self) -> str: + def leaf_name(self) -> str: """ Returns ------- - "first" from the first.element.of.the.name - """ - return self._name.split(".")[0] - - @final - @property - def _leaf_name(self) -> str: - """ - Returns - ------- - "first" from the first.element.of.the.name + "name" from the first.element.of.the.name """ return self._name.split(".")[-1] @@ -274,7 +264,7 @@ class DictValidator(Validator): value=self._dict.get(key, default), ) - self.__validator_dict[validator_name] = validator_instance + self.__validator_dict[key] = validator_instance return validator_instance @final diff --git a/tests/unit/config/test_preset.py b/tests/unit/config/test_preset.py index 8e9b4c89..a42439be 100644 --- a/tests/unit/config/test_preset.py +++ b/tests/unit/config/test_preset.py @@ -6,7 +6,6 @@ import pytest from ytdl_sub.config.preset import Preset from ytdl_sub.plugins.nfo_tags import NfoTagsOptions -from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException from ytdl_sub.utils.exceptions import ValidationException @@ -157,100 +156,6 @@ class TestPreset: }, ) - def test_preset_error__source_variable_does_not_exist( - self, config_file, output_options, youtube_video - ): - with pytest.raises( - StringFormattingVariableNotFoundException, - match="contains the following variables that do not exist: dne_var", - ): - _ = Preset( - config=config_file, - name="test", - value={ - "download": youtube_video, - "output_options": {"output_directory": "dir", "file_name": "{dne_var}"}, - }, - ) - - def test_preset_error__override_variable_does_not_exist( - self, config_file, output_options, youtube_video - ): - with pytest.raises( - StringFormattingVariableNotFoundException, - match="contains the following variables that do not exist: dne_var", - ): - _ = Preset( - config=config_file, - name="test", - value={ - "download": youtube_video, - "output_options": {"output_directory": "{dne_var}", "file_name": "file"}, - }, - ) - - def test_preset_error__dict_source_variable_does_not_exist( - self, config_file, output_options, youtube_video - ): - with pytest.raises( - StringFormattingVariableNotFoundException, - match="contains the following variables that do not exist: dne_var", - ): - _ = Preset( - config=config_file, - name="test", - value={ - "download": youtube_video, - "output_options": {"output_directory": "dir", "file_name": "file"}, - "nfo_tags": { - "nfo_name": "the nfo name", - "nfo_root": "the root", - "tags": {"tag_a": "{dne_var}"}, - }, - }, - ) - - def test_preset_error__dict_override_variable_does_not_exist( - self, config_file, output_options, youtube_video - ): - with pytest.raises( - StringFormattingVariableNotFoundException, - match="contains the following variables that do not exist: dne_var", - ): - _ = Preset( - config=config_file, - name="test", - value={ - "download": youtube_video, - "output_options": output_options, - "output_directory_nfo_tags": { - "nfo_name": "the nfo name", - "nfo_root": "the root", - "tags": {"tag_a": "{dne_var}"}, - }, - }, - ) - - def test_preset_error__dict_override_variable_not_static( - self, config_file, output_options, youtube_video - ): - with pytest.raises( - StringFormattingVariableNotFoundException, - match="static formatters must contain variables that " - "have no dependency to entry variables", - ): - _ = Preset( - config=config_file, - name="test", - value={ - "download": youtube_video, - "output_options": { - "output_directory": "{title}", - "file_name": "{uid}", - }, - }, - ) - def test_preset_with_multi_url__contains_empty_url(self, config_file, output_options): _ = Preset( config=config_file, @@ -412,50 +317,3 @@ class TestPreset: "overrides": {name: "ack"}, }, ) - - def test_preset_error_added_url_variable_cannot_resolve(self, config_file, output_options): - with pytest.raises( - ValidationException, - match=re.escape( - "variable the_bad_one cannot use the variables subtitles_ext because it " - "depends on other variables that are computed later in execution" - ), - ): - _ = Preset( - config=config_file, - name="test", - value={ - "download": { - "url": "youtube.com/watch?v=123abc", - "variables": {"the_bad_one": "{subtitles_ext}"}, - }, - "subtitles": { - "embed_subtitles": True, - }, - "output_options": {"output_directory": "dir", "file_name": "acjk"}, - }, - ) - - def test_preset_error_override_name_conflicts_with_plugin(self, config_file, output_options): - with pytest.raises( - ValidationException, - match=re.escape( - "Override variable with name throttle_protection cannot be used since it is the " - "name of a plugin. Perhaps you meant to define it as a plugin? If so, indent it " - "left to make it at the same level as overrides." - ), - ): - _ = Preset( - config=config_file, - name="test", - value={ - "download": { - "url": "youtube.com/watch?v=123abc", - }, - "subtitles": { - "embed_subtitles": True, - }, - "output_options": {"output_directory": "dir", "file_name": "acjk"}, - "overrides": {"throttle_protection": "nope"}, - }, - ) diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index 423ff8b0..7e14fe05 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -6,6 +6,7 @@ from typing import Dict from unittest.mock import patch import pytest +import yaml from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.plugins.nfo_tags import NfoTagsOptions @@ -540,9 +541,67 @@ def test_music_video_subscriptions(default_config: ConfigFile, music_video_subsc assert gnr.get("url2").native == "https://www.youtube.com/watch?v=OldpIhHPsbs" -def test_default_docker_config_and_subscriptions(docker_default_subscription_path: Path): +def test_default_docker_config_and_subscriptions( + docker_default_subscription_path: Path, output_directory: str +): default_config = ConfigFile.from_file_path("docker/root/defaults/config.yaml") default_subs = Subscription.from_file_path( config=default_config, subscription_path=docker_default_subscription_path ) assert len(default_subs) == 1 + + resolved_yaml_as_json = yaml.safe_load(default_subs[0].resolved_yaml()) + + # Since this creates random values, ignore it for this test + assert "throttle_protection" in resolved_yaml_as_json + del resolved_yaml_as_json["throttle_protection"] + + assert resolved_yaml_as_json == { + "chapters": { + "allow_chapters_from_comments": False, + "embed_chapters": True, + "enable": "True", + "force_key_frames": False, + }, + "date_range": {"breaks": "True", "enable": "True", "type": "upload_date"}, + "download": [ + { + "download_reverse": "True", + "include_sibling_metadata": False, + "playlist_thumbnails": [ + {"name": "{avatar_uncropped_thumbnail_file_name}", "uid": "avatar_uncropped"}, + {"name": "{banner_uncropped_thumbnail_file_name}", "uid": "banner_uncropped"}, + ], + "source_thumbnails": [ + {"name": "{avatar_uncropped_thumbnail_file_name}", "uid": "avatar_uncropped"}, + {"name": "{banner_uncropped_thumbnail_file_name}", "uid": "banner_uncropped"}, + ], + "url": "https://www.youtube.com/@novapbs", + "variables": {}, + "webpage_url": "{modified_webpage_url}", + "ytdl_options": {}, + } + ], + "file_convert": {"convert_to": "mp4", "convert_with": "yt-dlp", "enable": "True"}, + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-NOVA PBS-download-archive.json", + "file_name": "{episode_file_path}.{ext}", + "info_json_name": "{episode_file_path}.{info_json_ext}", + "keep_files_date_eval": "{episode_date_standardized}", + "maintain_download_archive": True, + "output_directory": f"{output_directory}/NOVA PBS", + "preserve_mtime": False, + "thumbnail_name": "{thumbnail_file_name}", + }, + "video_tags": { + "contentRating": "{episode_content_rating}", + "date": "{episode_date_standardized}", + "episode_id": "{episode_number}", + "genre": "{tv_show_genre}", + "show": "{tv_show_name}", + "synopsis": "{episode_plot}", + "title": "{episode_title}", + "year": "{episode_year}", + }, + } diff --git a/tests/unit/config/test_subscription_validation.py b/tests/unit/config/test_subscription_validation.py new file mode 100644 index 00000000..3a6fd13d --- /dev/null +++ b/tests/unit/config/test_subscription_validation.py @@ -0,0 +1,172 @@ +import re + +import pytest + +from ytdl_sub.config.preset import Preset +from ytdl_sub.subscriptions.subscription import Subscription +from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException +from ytdl_sub.utils.exceptions import ValidationException + + +class TestSubscriptionValidation: + def test_preset_error__source_variable_does_not_exist( + self, config_file, output_options, youtube_video + ): + with pytest.raises( + StringFormattingVariableNotFoundException, + match="contains the following variables that do not exist: dne_var", + ): + _ = Subscription.from_preset( + preset=Preset( + config=config_file, + name="test", + value={ + "download": youtube_video, + "output_options": {"output_directory": "dir", "file_name": "{dne_var}"}, + }, + ), + config=config_file, + ) + + def test_preset_error__override_variable_does_not_exist( + self, config_file, output_options, youtube_video + ): + with pytest.raises( + StringFormattingVariableNotFoundException, + match="contains the following variables that do not exist: dne_var", + ): + _ = Subscription.from_preset( + preset=Preset( + config=config_file, + name="test", + value={ + "download": youtube_video, + "output_options": {"output_directory": "{dne_var}", "file_name": "file"}, + }, + ), + config=config_file, + ) + + def test_preset_error__dict_source_variable_does_not_exist( + self, config_file, output_options, youtube_video + ): + with pytest.raises( + StringFormattingVariableNotFoundException, + match="contains the following variables that do not exist: dne_var", + ): + _ = Subscription.from_preset( + preset=Preset( + config=config_file, + name="test", + value={ + "download": youtube_video, + "output_options": {"output_directory": "dir", "file_name": "file"}, + "nfo_tags": { + "nfo_name": "the nfo name", + "nfo_root": "the root", + "tags": {"tag_a": "{dne_var}"}, + }, + }, + ), + config=config_file, + ) + + def test_preset_error__dict_override_variable_does_not_exist( + self, config_file, output_options, youtube_video + ): + with pytest.raises( + StringFormattingVariableNotFoundException, + match="contains the following variables that do not exist: dne_var", + ): + _ = Subscription.from_preset( + preset=Preset( + config=config_file, + name="test", + value={ + "download": youtube_video, + "output_options": output_options, + "output_directory_nfo_tags": { + "nfo_name": "the nfo name", + "nfo_root": "the root", + "tags": {"tag_a": "{dne_var}"}, + }, + }, + ), + config=config_file, + ) + + def test_preset_error__dict_override_variable_not_static( + self, config_file, output_options, youtube_video + ): + with pytest.raises( + StringFormattingVariableNotFoundException, + match="static formatters must contain variables that " + "have no dependency to entry variables", + ): + _ = Subscription.from_preset( + preset=Preset( + config=config_file, + name="test", + value={ + "download": youtube_video, + "output_options": { + "output_directory": "{title}", + "file_name": "{uid}", + }, + }, + ), + config=config_file, + ) + + def test_preset_error_added_url_variable_cannot_resolve(self, config_file, output_options): + with pytest.raises( + ValidationException, + match=re.escape( + "variable the_bad_one cannot use the variables subtitles_ext because it " + "depends on other variables that are computed later in execution" + ), + ): + _ = Subscription.from_preset( + preset=Preset( + config=config_file, + name="test", + value={ + "download": { + "url": "youtube.com/watch?v=123abc", + "variables": {"the_bad_one": "{subtitles_ext}"}, + }, + "subtitles": { + "embed_subtitles": True, + }, + "output_options": {"output_directory": "dir", "file_name": "acjk"}, + }, + ), + config=config_file, + ) + + def test_preset_error_override_name_conflicts_with_plugin(self, config_file, output_options): + with pytest.raises( + ValidationException, + match=re.escape( + "Override variable with name throttle_protection cannot be used since it is the " + "name of a plugin. Perhaps you meant to define it as a plugin? If so, indent it " + "left to make it at the same level as overrides." + ), + ): + _ = Subscription.from_preset( + preset=Preset( + config=config_file, + name="test", + value={ + "download": { + "url": "youtube.com/watch?v=123abc", + }, + "subtitles": { + "embed_subtitles": True, + }, + "output_options": {"output_directory": "dir", "file_name": "acjk"}, + "overrides": {"throttle_protection": "nope"}, + }, + ), + config=config_file, + ) diff --git a/tools/docgen/plugins.py b/tools/docgen/plugins.py index a7f7bed7..d2be80d3 100644 --- a/tools/docgen/plugins.py +++ b/tools/docgen/plugins.py @@ -37,10 +37,12 @@ def should_filter_property(property_name: str) -> bool: "dict", "keys", "dict_with_format_strings", + "dict_with_parsed_format_strings", "subscription_name", "list", "script", "unresolvable", + "leaf_name", ) From d373935fff9f53617a691e3c9af4a9b4d576b570 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Tue, 30 Dec 2025 19:53:15 -0800 Subject: [PATCH 19/46] [BUGFIX] Fix custom function lambda variable dependency issue (#1402) There was a bug that did not properly check variable dependency when using lambda functions. It could still work depending on order of definitions. This fix should make it work no matter the order. --- .../script/types/variable_dependency.py | 13 ++++++++++- .../unit/script/types/test_lambda_function.py | 22 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index 21716124..3e8bfdb4 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -114,6 +114,7 @@ class VariableDependency(ABC): output.add(ParsedCustomFunction(name=arg.name, num_input_args=len(arg.args))) if isinstance(arg, VariableDependency): output.update(arg.custom_functions) + # if isinstance(arg, Lambda) return output @@ -189,7 +190,17 @@ class VariableDependency(ABC): ------- True if it contains any of the input variables. False otherwise. """ - for custom_function in self.custom_functions: + # If there are lambdas, see if they are custom functions. If so, check them + custom_functions_to_check = self.custom_functions + for lambda_func in self.lambdas: + if lambda_func.value in custom_function_definitions: + custom_functions_to_check.add( + ParsedCustomFunction( + name=lambda_func.value, num_input_args=lambda_func.num_input_args() + ) + ) + + for custom_function in custom_functions_to_check: if custom_function_definitions[custom_function.name].contains( variables=variables, custom_function_definitions=custom_function_definitions ): diff --git a/tests/unit/script/types/test_lambda_function.py b/tests/unit/script/types/test_lambda_function.py index 07105477..1112afc4 100644 --- a/tests/unit/script/types/test_lambda_function.py +++ b/tests/unit/script/types/test_lambda_function.py @@ -76,6 +76,28 @@ class TestLambdaFunction: assert script.resolve().get("category_url_map").native == {1: 1, 2: 2, 3: 3} + def test_array_apply_custom_function(self): + output = ( + Script( + { + "the_array": "{ ['a', 'B', 'c', 'D'] }", + "output": "{ %array_apply(the_array, %lower) }", + "should_lower": "{%bool(True)}", + "%custom_cap": """{ + %if( + %bool(should_lower), + %lower($0), + %upper($0) + ) + }""", + } + ) + .resolve(update=True) + .get("output") + .native + ) + assert output == ["a", "b", "c", "d"] + class TestLambdaFunctionIncompatibleNumArguments: @pytest.mark.parametrize( From 7ac525f8753c91ba4d12f3ef57e8f818dc556564 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Tue, 30 Dec 2025 23:46:07 -0800 Subject: [PATCH 20/46] [BUGFIX] Fix custom function lambda variable dependency issue, part 2 (#1403) Part two of https://github.com/jmbannon/ytdl-sub/pull/1402 which actually completes the fix. --- src/ytdl_sub/script/script.py | 8 +++++ .../script/types/variable_dependency.py | 29 +++++++++++-------- .../validators/string_formatter_validators.py | 5 ++++ .../unit/script/types/test_lambda_function.py | 2 +- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index 67eeeaf1..b4c431df 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -288,6 +288,14 @@ class Script: unresolvable=unresolvable, ) + for lambda_func in current_var.lambdas: + if lambda_func.value in self._functions: + subset_to_resolve |= self._recursive_get_unresolved_output_filter_variables( + current_var=self._functions[lambda_func.value], + subset_to_resolve=subset_to_resolve, + unresolvable=unresolvable, + ) + return subset_to_resolve def _get_unresolved_output_filter( diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index 3e8bfdb4..18f2dab2 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -114,7 +114,6 @@ class VariableDependency(ABC): output.add(ParsedCustomFunction(name=arg.name, num_input_args=len(arg.args))) if isinstance(arg, VariableDependency): output.update(arg.custom_functions) - # if isinstance(arg, Lambda) return output @@ -160,6 +159,20 @@ class VariableDependency(ABC): raise UNREACHABLE + @final + def _custom_function_dependencies( + self, custom_function_definitions: Dict[str, "VariableDependency"] + ) -> Set[ParsedCustomFunction]: + custom_functions = self.custom_functions + for lambda_func in self.lambdas: + if lambda_func.value in custom_function_definitions: + custom_functions.add( + ParsedCustomFunction( + name=lambda_func.value, num_input_args=lambda_func.num_input_args() + ) + ) + return custom_functions + @final def is_subset_of( self, @@ -171,7 +184,8 @@ class VariableDependency(ABC): ------- True if it contains all input variables as a dependency. False otherwise. """ - for custom_function in self.custom_functions: + # If there are lambdas, see if they are custom functions. If so, check them + for custom_function in self._custom_function_dependencies(custom_function_definitions): if not custom_function_definitions[custom_function.name].is_subset_of( variables=variables, custom_function_definitions=custom_function_definitions ): @@ -191,16 +205,7 @@ class VariableDependency(ABC): True if it contains any of the input variables. False otherwise. """ # If there are lambdas, see if they are custom functions. If so, check them - custom_functions_to_check = self.custom_functions - for lambda_func in self.lambdas: - if lambda_func.value in custom_function_definitions: - custom_functions_to_check.add( - ParsedCustomFunction( - name=lambda_func.value, num_input_args=lambda_func.num_input_args() - ) - ) - - for custom_function in custom_functions_to_check: + for custom_function in self._custom_function_dependencies(custom_function_definitions): if custom_function_definitions[custom_function.name].contains( variables=variables, custom_function_definitions=custom_function_definitions ): diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 19a59344..5a44066e 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -241,6 +241,11 @@ def _validate_formatter( variable_names = {var.name for var in parsed.variables} custom_function_names = {f"%{func.name}" for func in parsed.custom_functions} + # Add lambda functions to custom function names, if it's custom + for lambda_func in parsed.lambdas: + if lambda_func in mock_script.function_names: + custom_function_names.add(lambda_func.value) + if not variable_names.issubset(mock_script.variable_names): raise StringFormattingVariableNotFoundException( "contains the following variables that do not exist: " diff --git a/tests/unit/script/types/test_lambda_function.py b/tests/unit/script/types/test_lambda_function.py index 1112afc4..aac78101 100644 --- a/tests/unit/script/types/test_lambda_function.py +++ b/tests/unit/script/types/test_lambda_function.py @@ -81,7 +81,7 @@ class TestLambdaFunction: Script( { "the_array": "{ ['a', 'B', 'c', 'D'] }", - "output": "{ %array_apply(the_array, %lower) }", + "output": "{ %array_apply(the_array, %custom_cap) }", "should_lower": "{%bool(True)}", "%custom_cap": """{ %if( From 869fc0b836a7bb9ad3e790d29209e7f5d72d8796 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Wed, 31 Dec 2025 08:22:26 -0800 Subject: [PATCH 21/46] [BUGFIX] Fix custom function lambda cycle detection (#1404) Cycles detection did not take into account custom functions' variable usage. This is now fixed. --- src/ytdl_sub/script/script.py | 16 ++++++++++++++++ src/ytdl_sub/script/types/resolvable.py | 8 ++++++++ .../script/types/variable_dependency.py | 17 ++++++++++++++--- tests/unit/script/types/test_lambda_function.py | 8 ++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index b4c431df..38e0346e 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -66,6 +66,22 @@ class Script: deps=deps + [dep.name], ) + for custom_func in variable_dependency.custom_function_dependencies( + custom_function_definitions=self._functions + ): + for dep in self._functions[custom_func.name].variables: + self._ensure_no_cycle( + name=variable_name, + dep=dep.name, + deps=deps + [custom_func.definition_name()], + definitions=self._variables, + ) + self._traverse_variable_dependencies( + variable_name=variable_name, + variable_dependency=self._variables[dep.name], + deps=deps + [custom_func.definition_name(), dep.name], + ) + def _ensure_no_variable_cycles(self, variables: Dict[str, SyntaxTree]): for variable_name, variable_definition in variables.items(): self._traverse_variable_dependencies( diff --git a/src/ytdl_sub/script/types/resolvable.py b/src/ytdl_sub/script/types/resolvable.py index c042b1f5..ad9ff300 100644 --- a/src/ytdl_sub/script/types/resolvable.py +++ b/src/ytdl_sub/script/types/resolvable.py @@ -193,6 +193,14 @@ class NamedCustomFunction(NamedArgument, ABC): class ParsedCustomFunction(NamedCustomFunction): num_input_args: int + def definition_name(self) -> str: + """ + Returns + ------- + The function definition name, including the % + """ + return f"%{self.name}" + @dataclass(frozen=True) class FunctionType(NamedArgument, ABC): diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index 18f2dab2..37130b7a 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -160,9 +160,20 @@ class VariableDependency(ABC): raise UNREACHABLE @final - def _custom_function_dependencies( + def custom_function_dependencies( self, custom_function_definitions: Dict[str, "VariableDependency"] ) -> Set[ParsedCustomFunction]: + """ + Parameters + ---------- + custom_function_definitions + Definition of all currently existing custom functions. Needed to check whether + a lambda function's input function is custom or not. + + Returns + ------- + All custom function dependencies + """ custom_functions = self.custom_functions for lambda_func in self.lambdas: if lambda_func.value in custom_function_definitions: @@ -185,7 +196,7 @@ class VariableDependency(ABC): True if it contains all input variables as a dependency. False otherwise. """ # If there are lambdas, see if they are custom functions. If so, check them - for custom_function in self._custom_function_dependencies(custom_function_definitions): + for custom_function in self.custom_function_dependencies(custom_function_definitions): if not custom_function_definitions[custom_function.name].is_subset_of( variables=variables, custom_function_definitions=custom_function_definitions ): @@ -205,7 +216,7 @@ class VariableDependency(ABC): True if it contains any of the input variables. False otherwise. """ # If there are lambdas, see if they are custom functions. If so, check them - for custom_function in self._custom_function_dependencies(custom_function_definitions): + for custom_function in self.custom_function_dependencies(custom_function_definitions): if custom_function_definitions[custom_function.name].contains( variables=variables, custom_function_definitions=custom_function_definitions ): diff --git a/tests/unit/script/types/test_lambda_function.py b/tests/unit/script/types/test_lambda_function.py index aac78101..cd054811 100644 --- a/tests/unit/script/types/test_lambda_function.py +++ b/tests/unit/script/types/test_lambda_function.py @@ -6,6 +6,7 @@ from ytdl_sub.script.script import Script from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.resolvable import Integer +from ytdl_sub.script.utils.exceptions import CycleDetected from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments @@ -169,3 +170,10 @@ class TestLambdaFunctionIncompatibleNumArguments: "%output": f"{{%array_enumerate(array1, {lambda_value})}}", } ) + + def test_lambda_with_custom_function_cycle(self): + with pytest.raises( + CycleDetected, + match=re.escape("Cycle detected within these variables: two -> %times_two -> two"), + ): + Script({"%times_two": "{%mul($0, two)}", "two": "{%times_two(2)}"}) From c46de048ca107a192f3e1fa3e35bb6d5093f68e7 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Wed, 31 Dec 2025 09:06:08 -0800 Subject: [PATCH 22/46] [FEATURE] %range function (#1405) Add `%range` function, equivalent to Python's `range`. --- .../scripting/scripting_functions.rst | 7 +++++++ .../script/functions/numeric_functions.py | 17 +++++++++++++++++ .../script/functions/test_numeric_functions.py | 12 ++++++++++++ 3 files changed, 36 insertions(+) diff --git a/docs/source/config_reference/scripting/scripting_functions.rst b/docs/source/config_reference/scripting/scripting_functions.rst index d6064e0f..e2a200ef 100644 --- a/docs/source/config_reference/scripting/scripting_functions.rst +++ b/docs/source/config_reference/scripting/scripting_functions.rst @@ -521,6 +521,13 @@ pow :description: ``**`` operator. Returns the exponential of the base and exponent value. +range +~~~~~ +:spec: ``range(end: Integer, start: Optional[Integer], step: Optional[Integer]) -> Array`` + +:description: + Returns the desired range of Integers in the form of an Array. + sub ~~~ :spec: ``sub(values: Numeric, ...) -> Numeric`` diff --git a/src/ytdl_sub/script/functions/numeric_functions.py b/src/ytdl_sub/script/functions/numeric_functions.py index b25374f9..003c8837 100644 --- a/src/ytdl_sub/script/functions/numeric_functions.py +++ b/src/ytdl_sub/script/functions/numeric_functions.py @@ -1,5 +1,7 @@ import math +from typing import Optional +from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.resolvable import AnyArgument from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import Integer @@ -96,3 +98,18 @@ class NumericFunctions: Returns min of all values. """ return _to_numeric(min(val.value for val in values)) + + @staticmethod + def range( + end: Integer, start: Optional[Integer] = None, step: Optional[Integer] = None + ) -> Array: + """ + :description: + Returns the desired range of Integers in the form of an Array. + """ + if start is None: + start = Integer(0) + if step is None: + step = Integer(1) + + return Array(value=[Integer(idx) for idx in range(start.value, end.value, step.value)]) diff --git a/tests/unit/script/functions/test_numeric_functions.py b/tests/unit/script/functions/test_numeric_functions.py index c41f4c59..b05dbb70 100644 --- a/tests/unit/script/functions/test_numeric_functions.py +++ b/tests/unit/script/functions/test_numeric_functions.py @@ -76,3 +76,15 @@ class TestNumericFunctions: def test_pow(self, values: str, expected_output: float): output = single_variable_output(f"{{ %pow({values}) }}") assert output == expected_output + + @pytest.mark.parametrize( + "values, expected_output", + [ + ("5", [0, 1, 2, 3, 4]), + ("5, 1", [1, 2, 3, 4]), + ("5, 1, 2", [1, 3]), + ], + ) + def test_range(self, values: str, expected_output: float): + output = single_variable_output(f"{{ %range({values}) }}") + assert output == expected_output From b2056bec5da334e2ce7144905b0618666ca89af1 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Wed, 31 Dec 2025 15:24:44 -0800 Subject: [PATCH 23/46] [BACKEND] Array support for URLs (#1406) Allows for the `download` plugin to take in a script-based array of URLs. Prebuilt presets are now greatly reduced in size by constructing urls as an array versus 100+ separate URL variables. Eventually, we can completely remove the limit of the number of URLs in a subscription. --- src/ytdl_sub/config/overrides.py | 11 +- .../config/validators/variable_validation.py | 3 + src/ytdl_sub/downloaders/url/downloader.py | 32 +- src/ytdl_sub/downloaders/url/validators.py | 15 +- .../prebuilt_presets/helpers/url.yaml | 1112 +--- .../helpers/url_categorized.yaml | 101 +- .../tv_show/tv_show_collection.yaml | 4624 +---------------- .../validators/string_formatter_validators.py | 9 + tests/unit/config/test_subscription.py | 39 +- .../prebuilt_presets/test_tv_show_by_date.py | 55 + .../test_tv_show_collection.py | 44 +- 11 files changed, 414 insertions(+), 5631 deletions(-) diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index 8f790022..623fc06d 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -238,20 +238,25 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): def apply_overrides_formatter_to_native( self, formatter: OverridesStringFormatterValidator, + function_overrides: Optional[Dict[str, str]] = None, ) -> Any: """ Parameters ---------- formatter Overrides formatter to apply + function_overrides + Optional. Explicit values to override the overrides themselves and source variables Returns ------- The native python form of the resolved variable """ - return self._apply_to_resolvable( - formatter=formatter, entry=None, function_overrides=None - ).native + return formatter.post_process_native( + self._apply_to_resolvable( + formatter=formatter, entry=None, function_overrides=function_overrides + ).native + ) def evaluate_boolean( self, formatter: StringFormatterValidator, entry: Optional[Entry] = None diff --git a/src/ytdl_sub/config/validators/variable_validation.py b/src/ytdl_sub/config/validators/variable_validation.py index bb8ea705..82069d7c 100644 --- a/src/ytdl_sub/config/validators/variable_validation.py +++ b/src/ytdl_sub/config/validators/variable_validation.py @@ -84,6 +84,9 @@ class VariableValidation: ) resolved_subscription["download"] = [] for url_output in raw_download_output["download"]: + if isinstance(url_output["url"], list): + url_output["url"] = [url for url in url_output["url"] if bool(url)] + if url_output["url"]: resolved_subscription["download"].append(url_output) diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index ef5fbd5a..acb35895 100644 --- a/src/ytdl_sub/downloaders/url/downloader.py +++ b/src/ytdl_sub/downloaders/url/downloader.py @@ -52,12 +52,12 @@ class UrlDownloaderBasePluginExtension(SourcePluginExtension[MultiUrlValidator]) 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: + if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.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: + if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.url): return validator # Return the first validator if none exist @@ -487,19 +487,27 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): # download the bottom-most urls first since they are top-priority 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(url_validator.url)): + if not (urls := self.overrides.apply_overrides_formatter_to_native(url_validator.url)): continue - for entry in self._download_metadata(url=url, validator=url_validator): - entry.initialize_script(self.overrides).add( - { - v.ytdl_sub_input_url: url, - v.ytdl_sub_input_url_index: idx, - v.ytdl_sub_input_url_count: len(self.collection.urls.list), - } - ) + assert isinstance(urls, list) - yield entry + for url in reversed(urls): + assert isinstance(url, str) + + if not url: + continue + + for entry in self._download_metadata(url=url, validator=url_validator): + entry.initialize_script(self.overrides).add( + { + 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]: """ diff --git a/src/ytdl_sub/downloaders/url/validators.py b/src/ytdl_sub/downloaders/url/validators.py index e518cdf8..551d7169 100644 --- a/src/ytdl_sub/downloaders/url/validators.py +++ b/src/ytdl_sub/downloaders/url/validators.py @@ -43,6 +43,19 @@ class UrlThumbnailListValidator(ListValidator[UrlThumbnailValidator]): _inner_list_type = UrlThumbnailValidator +class OverridesOneOrManyUrlValidator(OverridesStringFormatterValidator): + def post_process_native(self, resolved: Any) -> Any: + if isinstance(resolved, str): + return [resolved] + if isinstance(resolved, list): + for value in resolved: + if not isinstance(value, str): + raise self._validation_exception("Must be a string or an array of strings.") + return resolved + + raise self._validation_exception("Must be a string or an array of strings.") + + class UrlValidator(StrictDictValidator): _required_keys = {"url"} _optional_keys = { @@ -68,7 +81,7 @@ class UrlValidator(StrictDictValidator): super().__init__(name, value) # TODO: url validate using yt-dlp IE - self._url = self._validate_key(key="url", validator=OverridesStringFormatterValidator) + self._url = self._validate_key(key="url", validator=OverridesOneOrManyUrlValidator) self._variables = self._validate_key_if_present( key="variables", validator=DictFormatterValidator, default={} ) diff --git a/src/ytdl_sub/prebuilt_presets/helpers/url.yaml b/src/ytdl_sub/prebuilt_presets/helpers/url.yaml index 5c4439e7..7bd4d3f5 100644 --- a/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +++ b/src/ytdl_sub/prebuilt_presets/helpers/url.yaml @@ -14,7 +14,7 @@ presets: # _multi_url: download: - - url: "{url}" + - url: "{ %array_at(urls, 0) }" playlist_thumbnails: - name: "{avatar_uncropped_thumbnail_file_name}" uid: "avatar_uncropped" @@ -27,301 +27,8 @@ presets: uid: "banner_uncropped" include_sibling_metadata: "{include_sibling_metadata}" webpage_url: "{modified_webpage_url}" - - url: "{url2}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url3}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url4}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url5}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url6}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url7}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url8}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url9}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url10}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url11}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url12}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url13}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url14}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url15}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url16}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url17}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url18}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url19}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url20}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url21}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url22}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url23}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url24}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url25}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url26}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url27}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url28}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url29}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url30}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url31}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url32}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url33}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url34}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url35}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url36}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url37}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url38}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url39}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url40}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url41}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url42}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url43}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url44}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url45}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url46}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url47}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url48}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url49}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url50}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url51}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url52}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url53}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url54}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url55}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url56}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url57}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url58}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url59}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url60}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url61}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url62}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url63}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url64}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url65}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url66}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url67}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url68}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url69}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url70}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url71}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url72}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url73}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url74}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url75}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url76}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url77}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url78}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url79}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url80}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url81}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url82}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url83}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url84}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url85}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url86}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url87}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url88}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url89}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url90}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url91}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url92}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url93}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url94}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url95}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url96}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url97}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url98}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url99}" - include_sibling_metadata: "{include_sibling_metadata}" - webpage_url: "{modified_webpage_url}" - - url: "{url100}" + + - url: "{ %array_slice(urls, 1) }" include_sibling_metadata: "{include_sibling_metadata}" webpage_url: "{modified_webpage_url}" overrides: @@ -330,208 +37,124 @@ presets: include_sibling_metadata: False modified_webpage_url: "{webpage_url}" - subscription_array: "{ [] }" + subscription_array: >- + { + [ + url, url2, url3, url4, url5, url6, url7, url8, url9, url10, + url11, url12, url13, url14, url15, url16, url17, url18, url19, url20, + url21, url22, url23, url24, url25, url26, url27, url28, url29, url30, + url31, url32, url33, url34, url35, url36, url37, url38, url39, url40, + url41, url42, url43, url44, url45, url46, url47, url48, url49, url50, + url51, url52, url53, url54, url55, url56, url57, url58, url59, url60, + url61, url62, url63, url64, url65, url66, url67, url68, url69, url70, + url71, url72, url73, url74, url75, url76, url77, url78, url79, url80, + url81, url82, url83, url84, url85, url86, url87, url88, url89, url90, + url91, url92, url93, url94, url95, url96, url97, url98, url99, url100 + ] + } + urls: "{subscription_array}" + subscription_value: "" - subscription_value_2: "" - subscription_value_3: "" - subscription_value_4: "" - subscription_value_5: "" - subscription_value_6: "" - subscription_value_7: "" - subscription_value_8: "" - subscription_value_9: "" - subscription_value_10: "" - subscription_value_11: "" - subscription_value_12: "" - subscription_value_13: "" - subscription_value_14: "" - subscription_value_15: "" - subscription_value_16: "" - subscription_value_17: "" - subscription_value_18: "" - subscription_value_19: "" - subscription_value_20: "" - subscription_value_21: "" - subscription_value_22: "" - subscription_value_23: "" - subscription_value_24: "" - subscription_value_25: "" - subscription_value_26: "" - subscription_value_27: "" - subscription_value_28: "" - subscription_value_29: "" - subscription_value_30: "" - subscription_value_31: "" - subscription_value_32: "" - subscription_value_33: "" - subscription_value_34: "" - subscription_value_35: "" - subscription_value_36: "" - subscription_value_37: "" - subscription_value_38: "" - subscription_value_39: "" - subscription_value_40: "" - subscription_value_41: "" - subscription_value_42: "" - subscription_value_43: "" - subscription_value_44: "" - subscription_value_45: "" - subscription_value_46: "" - subscription_value_47: "" - subscription_value_48: "" - subscription_value_49: "" - subscription_value_50: "" - subscription_value_51: "" - subscription_value_52: "" - subscription_value_53: "" - subscription_value_54: "" - subscription_value_55: "" - subscription_value_56: "" - subscription_value_57: "" - subscription_value_58: "" - subscription_value_59: "" - subscription_value_60: "" - subscription_value_61: "" - subscription_value_62: "" - subscription_value_63: "" - subscription_value_64: "" - subscription_value_65: "" - subscription_value_66: "" - subscription_value_67: "" - subscription_value_68: "" - subscription_value_69: "" - subscription_value_70: "" - subscription_value_71: "" - subscription_value_72: "" - subscription_value_73: "" - subscription_value_74: "" - subscription_value_75: "" - subscription_value_76: "" - subscription_value_77: "" - subscription_value_78: "" - subscription_value_79: "" - subscription_value_80: "" - subscription_value_81: "" - subscription_value_82: "" - subscription_value_83: "" - subscription_value_84: "" - subscription_value_85: "" - subscription_value_86: "" - subscription_value_87: "" - subscription_value_88: "" - subscription_value_89: "" - subscription_value_90: "" - subscription_value_91: "" - subscription_value_92: "" - subscription_value_93: "" - subscription_value_94: "" - subscription_value_95: "" - subscription_value_96: "" - subscription_value_97: "" - subscription_value_98: "" - subscription_value_99: "" - subscription_value_100: "" - url: "{subscription_value}" - url2: "{subscription_value_2}" - url3: "{subscription_value_3}" - url4: "{subscription_value_4}" - url5: "{subscription_value_5}" - url6: "{subscription_value_6}" - url7: "{subscription_value_7}" - url8: "{subscription_value_8}" - url9: "{subscription_value_9}" - url10: "{subscription_value_10}" - url11: "{subscription_value_11}" - url12: "{subscription_value_12}" - url13: "{subscription_value_13}" - url14: "{subscription_value_14}" - url15: "{subscription_value_15}" - url16: "{subscription_value_16}" - url17: "{subscription_value_17}" - url18: "{subscription_value_18}" - url19: "{subscription_value_19}" - url20: "{subscription_value_20}" - url21: "{subscription_value_21}" - url22: "{subscription_value_22}" - url23: "{subscription_value_23}" - url24: "{subscription_value_24}" - url25: "{subscription_value_25}" - url26: "{subscription_value_26}" - url27: "{subscription_value_27}" - url28: "{subscription_value_28}" - url29: "{subscription_value_29}" - url30: "{subscription_value_30}" - url31: "{subscription_value_31}" - url32: "{subscription_value_32}" - url33: "{subscription_value_33}" - url34: "{subscription_value_34}" - url35: "{subscription_value_35}" - url36: "{subscription_value_36}" - url37: "{subscription_value_37}" - url38: "{subscription_value_38}" - url39: "{subscription_value_39}" - url40: "{subscription_value_40}" - url41: "{subscription_value_41}" - url42: "{subscription_value_42}" - url43: "{subscription_value_43}" - url44: "{subscription_value_44}" - url45: "{subscription_value_45}" - url46: "{subscription_value_46}" - url47: "{subscription_value_47}" - url48: "{subscription_value_48}" - url49: "{subscription_value_49}" - url50: "{subscription_value_50}" - url51: "{subscription_value_51}" - url52: "{subscription_value_52}" - url53: "{subscription_value_53}" - url54: "{subscription_value_54}" - url55: "{subscription_value_55}" - url56: "{subscription_value_56}" - url57: "{subscription_value_57}" - url58: "{subscription_value_58}" - url59: "{subscription_value_59}" - url60: "{subscription_value_60}" - url61: "{subscription_value_61}" - url62: "{subscription_value_62}" - url63: "{subscription_value_63}" - url64: "{subscription_value_64}" - url65: "{subscription_value_65}" - url66: "{subscription_value_66}" - url67: "{subscription_value_67}" - url68: "{subscription_value_68}" - url69: "{subscription_value_69}" - url70: "{subscription_value_70}" - url71: "{subscription_value_71}" - url72: "{subscription_value_72}" - url73: "{subscription_value_73}" - url74: "{subscription_value_74}" - url75: "{subscription_value_75}" - url76: "{subscription_value_76}" - url77: "{subscription_value_77}" - url78: "{subscription_value_78}" - url79: "{subscription_value_79}" - url80: "{subscription_value_80}" - url81: "{subscription_value_81}" - url82: "{subscription_value_82}" - url83: "{subscription_value_83}" - url84: "{subscription_value_84}" - url85: "{subscription_value_85}" - url86: "{subscription_value_86}" - url87: "{subscription_value_87}" - url88: "{subscription_value_88}" - url89: "{subscription_value_89}" - url90: "{subscription_value_90}" - url91: "{subscription_value_91}" - url92: "{subscription_value_92}" - url93: "{subscription_value_93}" - url94: "{subscription_value_94}" - url95: "{subscription_value_95}" - url96: "{subscription_value_96}" - url97: "{subscription_value_97}" - url98: "{subscription_value_98}" - url99: "{subscription_value_99}" - url100: "{subscription_value_100}" + url2: "" + url3: "" + url4: "" + url5: "" + url6: "" + url7: "" + url8: "" + url9: "" + url10: "" + url11: "" + url12: "" + url13: "" + url14: "" + url15: "" + url16: "" + url17: "" + url18: "" + url19: "" + url20: "" + url21: "" + url22: "" + url23: "" + url24: "" + url25: "" + url26: "" + url27: "" + url28: "" + url29: "" + url30: "" + url31: "" + url32: "" + url33: "" + url34: "" + url35: "" + url36: "" + url37: "" + url38: "" + url39: "" + url40: "" + url41: "" + url42: "" + url43: "" + url44: "" + url45: "" + url46: "" + url47: "" + url48: "" + url49: "" + url50: "" + url51: "" + url52: "" + url53: "" + url54: "" + url55: "" + url56: "" + url57: "" + url58: "" + url59: "" + url60: "" + url61: "" + url62: "" + url63: "" + url64: "" + url65: "" + url66: "" + url67: "" + url68: "" + url69: "" + url70: "" + url71: "" + url72: "" + url73: "" + url74: "" + url75: "" + url76: "" + url77: "" + url78: "" + url79: "" + url80: "" + url81: "" + url82: "" + url83: "" + url84: "" + url85: "" + url86: "" + url87: "" + url88: "" + url89: "" + url90: "" + url91: "" + url92: "" + url93: "" + url94: "" + url95: "" + url96: "" + url97: "" + url98: "" + url99: "" + url100: "" # multi-url with bilateral scraping built into it via @@ -542,502 +165,7 @@ presets: - "_url_bilateral_overrides" download: - - url: "{%bilateral_url(url) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url2) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url3) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url4) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url5) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url6) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url7) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url8) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url9) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url10) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url11) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url12) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url13) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url14) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url15) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url16) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url17) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url18) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url19) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url20) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url21) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url22) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url23) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url24) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url25) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url26) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url27) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url28) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url29) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url30) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url31) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url32) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url33) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url34) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url35) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url36) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url37) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url38) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url39) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url40) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url41) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url42) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url43) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url44) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url45) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url46) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url47) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url48) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url49) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url50) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url51) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url52) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url53) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url54) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url55) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url56) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url57) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url58) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url59) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url60) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url61) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url62) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url63) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url64) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url65) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url66) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url67) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url68) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url69) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url70) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url71) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url72) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url73) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url74) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url75) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url76) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url77) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url78) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url79) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url80) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url81) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url82) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url83) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url84) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url85) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url86) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url87) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url88) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url89) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url90) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url91) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url92) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url93) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url94) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url95) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url96) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url97) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url98) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{ %bilateral_url(url99) }" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - webpage_url: "{modified_webpage_url}" - - url: "{%bilateral_url(url100) }" + - url: "{ %array_apply(urls, %bilateral_url) }" download_reverse: False ytdl_options: playlist_items: "-1:0:-1" diff --git a/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml b/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml index b212f640..80e041a7 100644 --- a/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml +++ b/src/ytdl_sub/prebuilt_presets/helpers/url_categorized.yaml @@ -108,103 +108,4 @@ presets: } subscription_map: "{ {} }" - url: "{ %get_url_i(1) }" - url2: "{ %get_url_i(2) }" - url3: "{ %get_url_i(3) }" - url4: "{ %get_url_i(4) }" - url5: "{ %get_url_i(5) }" - url6: "{ %get_url_i(6) }" - url7: "{ %get_url_i(7) }" - url8: "{ %get_url_i(8) }" - url9: "{ %get_url_i(9) }" - url10: "{ %get_url_i(10) }" - url11: "{ %get_url_i(11) }" - url12: "{ %get_url_i(12) }" - url13: "{ %get_url_i(13) }" - url14: "{ %get_url_i(14) }" - url15: "{ %get_url_i(15) }" - url16: "{ %get_url_i(16) }" - url17: "{ %get_url_i(17) }" - url18: "{ %get_url_i(18) }" - url19: "{ %get_url_i(19) }" - url20: "{ %get_url_i(20) }" - url21: "{ %get_url_i(21) }" - url22: "{ %get_url_i(22) }" - url23: "{ %get_url_i(23) }" - url24: "{ %get_url_i(24) }" - url25: "{ %get_url_i(25) }" - url26: "{ %get_url_i(26) }" - url27: "{ %get_url_i(27) }" - url28: "{ %get_url_i(28) }" - url29: "{ %get_url_i(29) }" - url30: "{ %get_url_i(30) }" - url31: "{ %get_url_i(31) }" - url32: "{ %get_url_i(32) }" - url33: "{ %get_url_i(33) }" - url34: "{ %get_url_i(34) }" - url35: "{ %get_url_i(35) }" - url36: "{ %get_url_i(36) }" - url37: "{ %get_url_i(37) }" - url38: "{ %get_url_i(38) }" - url39: "{ %get_url_i(39) }" - url40: "{ %get_url_i(40) }" - url41: "{ %get_url_i(41) }" - url42: "{ %get_url_i(42) }" - url43: "{ %get_url_i(43) }" - url44: "{ %get_url_i(44) }" - url45: "{ %get_url_i(45) }" - url46: "{ %get_url_i(46) }" - url47: "{ %get_url_i(47) }" - url48: "{ %get_url_i(48) }" - url49: "{ %get_url_i(49) }" - url50: "{ %get_url_i(50) }" - url51: "{ %get_url_i(51) }" - url52: "{ %get_url_i(52) }" - url53: "{ %get_url_i(53) }" - url54: "{ %get_url_i(54) }" - url55: "{ %get_url_i(55) }" - url56: "{ %get_url_i(56) }" - url57: "{ %get_url_i(57) }" - url58: "{ %get_url_i(58) }" - url59: "{ %get_url_i(59) }" - url60: "{ %get_url_i(60) }" - url61: "{ %get_url_i(61) }" - url62: "{ %get_url_i(62) }" - url63: "{ %get_url_i(63) }" - url64: "{ %get_url_i(64) }" - url65: "{ %get_url_i(65) }" - url66: "{ %get_url_i(66) }" - url67: "{ %get_url_i(67) }" - url68: "{ %get_url_i(68) }" - url69: "{ %get_url_i(69) }" - url70: "{ %get_url_i(70) }" - url71: "{ %get_url_i(71) }" - url72: "{ %get_url_i(72) }" - url73: "{ %get_url_i(73) }" - url74: "{ %get_url_i(74) }" - url75: "{ %get_url_i(75) }" - url76: "{ %get_url_i(76) }" - url77: "{ %get_url_i(77) }" - url78: "{ %get_url_i(78) }" - url79: "{ %get_url_i(79) }" - url80: "{ %get_url_i(80) }" - url81: "{ %get_url_i(81) }" - url82: "{ %get_url_i(82) }" - url83: "{ %get_url_i(83) }" - url84: "{ %get_url_i(84) }" - url85: "{ %get_url_i(85) }" - url86: "{ %get_url_i(86) }" - url87: "{ %get_url_i(87) }" - url88: "{ %get_url_i(88) }" - url89: "{ %get_url_i(89) }" - url90: "{ %get_url_i(90) }" - url91: "{ %get_url_i(91) }" - url92: "{ %get_url_i(92) }" - url93: "{ %get_url_i(93) }" - url94: "{ %get_url_i(94) }" - url95: "{ %get_url_i(95) }" - url96: "{ %get_url_i(96) }" - url97: "{ %get_url_i(97) }" - url98: "{ %get_url_i(98) }" - url99: "{ %get_url_i(99) }" - url100: "{ %get_url_i(100) }" \ No newline at end of file + urls: "{ %array_apply(%range(100, 1), %get_url_i) }" \ No newline at end of file diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml index 29019cb2..c8a78a7a 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +++ b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml @@ -124,7 +124,7 @@ presets: - "_tv_show_collection_asserts" download: - - url: "{ %get_season_url(collection_season_1_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_1_url), 0) }" variables: collection_season_number: "1" collection_season_name: "{collection_season_1_name}" @@ -142,1969 +142,492 @@ presets: uid: "avatar_uncropped" - name: "{tv_show_fanart_file_name}" uid: "banner_uncropped" - - url: "{ %get_season_url(collection_season_1_url, 1) }" - variables: - collection_season_number: "1" - collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_1_url, 2) }" - variables: - collection_season_number: "1" - collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_1_url, 3) }" - variables: - collection_season_number: "1" - collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_1_url, 4) }" - variables: - collection_season_number: "1" - collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_1_url, 5) }" - variables: - collection_season_number: "1" - collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_1_url, 6) }" - variables: - collection_season_number: "1" - collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_1_url, 7) }" - variables: - collection_season_number: "1" - collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_1_url, 8) }" - variables: - collection_season_number: "1" - collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_1_url, 9) }" - variables: - collection_season_number: "1" - collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_1_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_1_url), 1) }" variables: collection_season_number: "1" collection_season_name: "{collection_season_1_name}" - - url: "{ %get_season_url(collection_season_2_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_2_url), 0) }" variables: collection_season_number: "2" collection_season_name: "{collection_season_2_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_2_url, 1) }" - variables: - collection_season_number: "2" - collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_2_url, 2) }" - variables: - collection_season_number: "2" - collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_2_url, 3) }" - variables: - collection_season_number: "2" - collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_2_url, 4) }" - variables: - collection_season_number: "2" - collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_2_url, 5) }" - variables: - collection_season_number: "2" - collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_2_url, 6) }" - variables: - collection_season_number: "2" - collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_2_url, 7) }" - variables: - collection_season_number: "2" - collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_2_url, 8) }" - variables: - collection_season_number: "2" - collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_2_url, 9) }" - variables: - collection_season_number: "2" - collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_2_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_2_url), 1) }" variables: collection_season_number: "2" collection_season_name: "{collection_season_2_name}" - - url: "{ %get_season_url(collection_season_3_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_3_url), 0) }" variables: collection_season_number: "3" collection_season_name: "{collection_season_3_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_3_url, 1) }" - variables: - collection_season_number: "3" - collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_3_url, 2) }" - variables: - collection_season_number: "3" - collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_3_url, 3) }" - variables: - collection_season_number: "3" - collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_3_url, 4) }" - variables: - collection_season_number: "3" - collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_3_url, 5) }" - variables: - collection_season_number: "3" - collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_3_url, 6) }" - variables: - collection_season_number: "3" - collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_3_url, 7) }" - variables: - collection_season_number: "3" - collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_3_url, 8) }" - variables: - collection_season_number: "3" - collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_3_url, 9) }" - variables: - collection_season_number: "3" - collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_3_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_3_url), 1) }" variables: collection_season_number: "3" collection_season_name: "{collection_season_3_name}" - - url: "{ %get_season_url(collection_season_4_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_4_url), 0) }" variables: collection_season_number: "4" collection_season_name: "{collection_season_4_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_4_url, 1) }" - variables: - collection_season_number: "4" - collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_4_url, 2) }" - variables: - collection_season_number: "4" - collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_4_url, 3) }" - variables: - collection_season_number: "4" - collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_4_url, 4) }" - variables: - collection_season_number: "4" - collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_4_url, 5) }" - variables: - collection_season_number: "4" - collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_4_url, 6) }" - variables: - collection_season_number: "4" - collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_4_url, 7) }" - variables: - collection_season_number: "4" - collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_4_url, 8) }" - variables: - collection_season_number: "4" - collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_4_url, 9) }" - variables: - collection_season_number: "4" - collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_4_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_4_url), 1) }" variables: collection_season_number: "4" collection_season_name: "{collection_season_4_name}" - - url: "{ %get_season_url(collection_season_5_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_5_url), 0) }" variables: collection_season_number: "5" collection_season_name: "{collection_season_5_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_5_url, 1) }" - variables: - collection_season_number: "5" - collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_5_url, 2) }" - variables: - collection_season_number: "5" - collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_5_url, 3) }" - variables: - collection_season_number: "5" - collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_5_url, 4) }" - variables: - collection_season_number: "5" - collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_5_url, 5) }" - variables: - collection_season_number: "5" - collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_5_url, 6) }" - variables: - collection_season_number: "5" - collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_5_url, 7) }" - variables: - collection_season_number: "5" - collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_5_url, 8) }" - variables: - collection_season_number: "5" - collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_5_url, 9) }" - variables: - collection_season_number: "5" - collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_5_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_5_url), 1) }" variables: collection_season_number: "5" collection_season_name: "{collection_season_5_name}" - - url: "{ %get_season_url(collection_season_6_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_6_url), 0) }" variables: collection_season_number: "6" collection_season_name: "{collection_season_6_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_6_url, 1) }" - variables: - collection_season_number: "6" - collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_6_url, 2) }" - variables: - collection_season_number: "6" - collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_6_url, 3) }" - variables: - collection_season_number: "6" - collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_6_url, 4) }" - variables: - collection_season_number: "6" - collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_6_url, 5) }" - variables: - collection_season_number: "6" - collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_6_url, 6) }" - variables: - collection_season_number: "6" - collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_6_url, 7) }" - variables: - collection_season_number: "6" - collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_6_url, 8) }" - variables: - collection_season_number: "6" - collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_6_url, 9) }" - variables: - collection_season_number: "6" - collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_6_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_6_url), 1) }" variables: collection_season_number: "6" collection_season_name: "{collection_season_6_name}" - - url: "{ %get_season_url(collection_season_7_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_7_url), 0) }" variables: collection_season_number: "7" collection_season_name: "{collection_season_7_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_7_url, 1) }" - variables: - collection_season_number: "7" - collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_7_url, 2) }" - variables: - collection_season_number: "7" - collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_7_url, 3) }" - variables: - collection_season_number: "7" - collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_7_url, 4) }" - variables: - collection_season_number: "7" - collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_7_url, 5) }" - variables: - collection_season_number: "7" - collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_7_url, 6) }" - variables: - collection_season_number: "7" - collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_7_url, 7) }" - variables: - collection_season_number: "7" - collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_7_url, 8) }" - variables: - collection_season_number: "7" - collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_7_url, 9) }" - variables: - collection_season_number: "7" - collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_7_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_7_url), 1) }" variables: collection_season_number: "7" collection_season_name: "{collection_season_7_name}" - - url: "{ %get_season_url(collection_season_8_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_8_url), 0) }" variables: collection_season_number: "8" collection_season_name: "{collection_season_8_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_8_url, 1) }" - variables: - collection_season_number: "8" - collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_8_url, 2) }" - variables: - collection_season_number: "8" - collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_8_url, 3) }" - variables: - collection_season_number: "8" - collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_8_url, 4) }" - variables: - collection_season_number: "8" - collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_8_url, 5) }" - variables: - collection_season_number: "8" - collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_8_url, 6) }" - variables: - collection_season_number: "8" - collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_8_url, 7) }" - variables: - collection_season_number: "8" - collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_8_url, 8) }" - variables: - collection_season_number: "8" - collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_8_url, 9) }" - variables: - collection_season_number: "8" - collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_8_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_8_url), 1) }" variables: collection_season_number: "8" collection_season_name: "{collection_season_8_name}" - - url: "{ %get_season_url(collection_season_9_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_9_url), 0) }" variables: collection_season_number: "9" collection_season_name: "{collection_season_9_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_9_url, 1) }" - variables: - collection_season_number: "9" - collection_season_name: "{collection_season_9_name}" - - url: "{ %get_season_url(collection_season_9_url, 2) }" - variables: - collection_season_number: "9" - collection_season_name: "{collection_season_9_name}" - - url: "{ %get_season_url(collection_season_9_url, 3) }" - variables: - collection_season_number: "9" - collection_season_name: "{collection_season_9_name}" - - url: "{ %get_season_url(collection_season_9_url, 4) }" - variables: - collection_season_number: "9" - collection_season_name: "{collection_season_9_name}" - - url: "{ %get_season_url(collection_season_9_url, 5) }" - variables: - collection_season_number: "9" - collection_season_name: "{collection_season_9_name}" - - url: "{ %get_season_url(collection_season_9_url, 6) }" - variables: - collection_season_number: "9" - collection_season_name: "{collection_season_9_name}" - - url: "{ %get_season_url(collection_season_9_url, 7) }" - variables: - collection_season_number: "9" - collection_season_name: "{collection_season_9_name}" - - url: "{ %get_season_url(collection_season_9_url, 8) }" - variables: - collection_season_number: "9" - collection_season_name: "{collection_season_9_name}" - - url: "{ %get_season_url(collection_season_9_url, 9) }" - variables: - collection_season_number: "9" - collection_season_name: "{collection_season_9_name}" - - url: "{ %get_season_url(collection_season_9_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_9_url), 1) }" variables: collection_season_number: "9" collection_season_name: "{collection_season_9_name}" - - url: "{%get_season_url(collection_season_10_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_10_url), 0) }" variables: collection_season_number: "10" collection_season_name: "{collection_season_10_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_10_url, 1) }" - variables: - collection_season_number: "10" - collection_season_name: "{collection_season_10_name}" - - url: "{ %get_season_url(collection_season_10_url, 2) }" - variables: - collection_season_number: "10" - collection_season_name: "{collection_season_10_name}" - - url: "{ %get_season_url(collection_season_10_url, 3) }" - variables: - collection_season_number: "10" - collection_season_name: "{collection_season_10_name}" - - url: "{ %get_season_url(collection_season_10_url, 4) }" - variables: - collection_season_number: "10" - collection_season_name: "{collection_season_10_name}" - - url: "{ %get_season_url(collection_season_10_url, 5) }" - variables: - collection_season_number: "10" - collection_season_name: "{collection_season_10_name}" - - url: "{ %get_season_url(collection_season_10_url, 6) }" - variables: - collection_season_number: "10" - collection_season_name: "{collection_season_10_name}" - - url: "{ %get_season_url(collection_season_10_url, 7) }" - variables: - collection_season_number: "10" - collection_season_name: "{collection_season_10_name}" - - url: "{ %get_season_url(collection_season_10_url, 8) }" - variables: - collection_season_number: "10" - collection_season_name: "{collection_season_10_name}" - - url: "{ %get_season_url(collection_season_10_url, 9) }" - variables: - collection_season_number: "10" - collection_season_name: "{collection_season_10_name}" - - url: "{ %get_season_url(collection_season_10_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_10_url), 1) }" variables: collection_season_number: "10" collection_season_name: "{collection_season_10_name}" - - url: "{%get_season_url(collection_season_11_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_11_url), 0) }" variables: collection_season_number: "11" collection_season_name: "{collection_season_11_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_11_url, 1) }" - variables: - collection_season_number: "11" - collection_season_name: "{collection_season_11_name}" - - url: "{ %get_season_url(collection_season_11_url, 2) }" - variables: - collection_season_number: "11" - collection_season_name: "{collection_season_11_name}" - - url: "{ %get_season_url(collection_season_11_url, 3) }" - variables: - collection_season_number: "11" - collection_season_name: "{collection_season_11_name}" - - url: "{ %get_season_url(collection_season_11_url, 4) }" - variables: - collection_season_number: "11" - collection_season_name: "{collection_season_11_name}" - - url: "{ %get_season_url(collection_season_11_url, 5) }" - variables: - collection_season_number: "11" - collection_season_name: "{collection_season_11_name}" - - url: "{ %get_season_url(collection_season_11_url, 6) }" - variables: - collection_season_number: "11" - collection_season_name: "{collection_season_11_name}" - - url: "{ %get_season_url(collection_season_11_url, 7) }" - variables: - collection_season_number: "11" - collection_season_name: "{collection_season_11_name}" - - url: "{ %get_season_url(collection_season_11_url, 8) }" - variables: - collection_season_number: "11" - collection_season_name: "{collection_season_11_name}" - - url: "{ %get_season_url(collection_season_11_url, 9) }" - variables: - collection_season_number: "11" - collection_season_name: "{collection_season_11_name}" - - url: "{ %get_season_url(collection_season_11_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_11_url), 1) }" variables: collection_season_number: "11" collection_season_name: "{collection_season_11_name}" - - url: "{%get_season_url(collection_season_12_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_12_url), 0) }" variables: collection_season_number: "12" collection_season_name: "{collection_season_12_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_12_url, 1) }" - variables: - collection_season_number: "12" - collection_season_name: "{collection_season_12_name}" - - url: "{ %get_season_url(collection_season_12_url, 2) }" - variables: - collection_season_number: "12" - collection_season_name: "{collection_season_12_name}" - - url: "{ %get_season_url(collection_season_12_url, 3) }" - variables: - collection_season_number: "12" - collection_season_name: "{collection_season_12_name}" - - url: "{ %get_season_url(collection_season_12_url, 4) }" - variables: - collection_season_number: "12" - collection_season_name: "{collection_season_12_name}" - - url: "{ %get_season_url(collection_season_12_url, 5) }" - variables: - collection_season_number: "12" - collection_season_name: "{collection_season_12_name}" - - url: "{ %get_season_url(collection_season_12_url, 6) }" - variables: - collection_season_number: "12" - collection_season_name: "{collection_season_12_name}" - - url: "{ %get_season_url(collection_season_12_url, 7) }" - variables: - collection_season_number: "12" - collection_season_name: "{collection_season_12_name}" - - url: "{ %get_season_url(collection_season_12_url, 8) }" - variables: - collection_season_number: "12" - collection_season_name: "{collection_season_12_name}" - - url: "{ %get_season_url(collection_season_12_url, 9) }" - variables: - collection_season_number: "12" - collection_season_name: "{collection_season_12_name}" - - url: "{ %get_season_url(collection_season_12_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_12_url), 1) }" variables: collection_season_number: "12" collection_season_name: "{collection_season_12_name}" - - url: "{%get_season_url(collection_season_13_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_13_url), 0) }" variables: collection_season_number: "13" collection_season_name: "{collection_season_13_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_13_url, 1) }" - variables: - collection_season_number: "13" - collection_season_name: "{collection_season_13_name}" - - url: "{ %get_season_url(collection_season_13_url, 2) }" - variables: - collection_season_number: "13" - collection_season_name: "{collection_season_13_name}" - - url: "{ %get_season_url(collection_season_13_url, 3) }" - variables: - collection_season_number: "13" - collection_season_name: "{collection_season_13_name}" - - url: "{ %get_season_url(collection_season_13_url, 4) }" - variables: - collection_season_number: "13" - collection_season_name: "{collection_season_13_name}" - - url: "{ %get_season_url(collection_season_13_url, 5) }" - variables: - collection_season_number: "13" - collection_season_name: "{collection_season_13_name}" - - url: "{ %get_season_url(collection_season_13_url, 6) }" - variables: - collection_season_number: "13" - collection_season_name: "{collection_season_13_name}" - - url: "{ %get_season_url(collection_season_13_url, 7) }" - variables: - collection_season_number: "13" - collection_season_name: "{collection_season_13_name}" - - url: "{ %get_season_url(collection_season_13_url, 8) }" - variables: - collection_season_number: "13" - collection_season_name: "{collection_season_13_name}" - - url: "{ %get_season_url(collection_season_13_url, 9) }" - variables: - collection_season_number: "13" - collection_season_name: "{collection_season_13_name}" - - url: "{ %get_season_url(collection_season_13_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_13_url), 1) }" variables: collection_season_number: "13" collection_season_name: "{collection_season_13_name}" - - url: "{%get_season_url(collection_season_14_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_14_url), 0) }" variables: collection_season_number: "14" collection_season_name: "{collection_season_14_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_14_url, 1) }" - variables: - collection_season_number: "14" - collection_season_name: "{collection_season_14_name}" - - url: "{ %get_season_url(collection_season_14_url, 2) }" - variables: - collection_season_number: "14" - collection_season_name: "{collection_season_14_name}" - - url: "{ %get_season_url(collection_season_14_url, 3) }" - variables: - collection_season_number: "14" - collection_season_name: "{collection_season_14_name}" - - url: "{ %get_season_url(collection_season_14_url, 4) }" - variables: - collection_season_number: "14" - collection_season_name: "{collection_season_14_name}" - - url: "{ %get_season_url(collection_season_14_url, 5) }" - variables: - collection_season_number: "14" - collection_season_name: "{collection_season_14_name}" - - url: "{ %get_season_url(collection_season_14_url, 6) }" - variables: - collection_season_number: "14" - collection_season_name: "{collection_season_14_name}" - - url: "{ %get_season_url(collection_season_14_url, 7) }" - variables: - collection_season_number: "14" - collection_season_name: "{collection_season_14_name}" - - url: "{ %get_season_url(collection_season_14_url, 8) }" - variables: - collection_season_number: "14" - collection_season_name: "{collection_season_14_name}" - - url: "{ %get_season_url(collection_season_14_url, 9) }" - variables: - collection_season_number: "14" - collection_season_name: "{collection_season_14_name}" - - url: "{ %get_season_url(collection_season_14_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_14_url), 1) }" variables: collection_season_number: "14" collection_season_name: "{collection_season_14_name}" - - url: "{%get_season_url(collection_season_15_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_15_url), 0) }" variables: collection_season_number: "15" collection_season_name: "{collection_season_15_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_15_url, 1) }" - variables: - collection_season_number: "15" - collection_season_name: "{collection_season_15_name}" - - url: "{ %get_season_url(collection_season_15_url, 2) }" - variables: - collection_season_number: "15" - collection_season_name: "{collection_season_15_name}" - - url: "{ %get_season_url(collection_season_15_url, 3) }" - variables: - collection_season_number: "15" - collection_season_name: "{collection_season_15_name}" - - url: "{ %get_season_url(collection_season_15_url, 4) }" - variables: - collection_season_number: "15" - collection_season_name: "{collection_season_15_name}" - - url: "{ %get_season_url(collection_season_15_url, 5) }" - variables: - collection_season_number: "15" - collection_season_name: "{collection_season_15_name}" - - url: "{ %get_season_url(collection_season_15_url, 6) }" - variables: - collection_season_number: "15" - collection_season_name: "{collection_season_15_name}" - - url: "{ %get_season_url(collection_season_15_url, 7) }" - variables: - collection_season_number: "15" - collection_season_name: "{collection_season_15_name}" - - url: "{ %get_season_url(collection_season_15_url, 8) }" - variables: - collection_season_number: "15" - collection_season_name: "{collection_season_15_name}" - - url: "{ %get_season_url(collection_season_15_url, 9) }" - variables: - collection_season_number: "15" - collection_season_name: "{collection_season_15_name}" - - url: "{ %get_season_url(collection_season_15_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_15_url), 1) }" variables: collection_season_number: "15" collection_season_name: "{collection_season_15_name}" - - url: "{%get_season_url(collection_season_16_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_16_url), 0) }" variables: collection_season_number: "16" collection_season_name: "{collection_season_16_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_16_url, 1) }" - variables: - collection_season_number: "16" - collection_season_name: "{collection_season_16_name}" - - url: "{ %get_season_url(collection_season_16_url, 2) }" - variables: - collection_season_number: "16" - collection_season_name: "{collection_season_16_name}" - - url: "{ %get_season_url(collection_season_16_url, 3) }" - variables: - collection_season_number: "16" - collection_season_name: "{collection_season_16_name}" - - url: "{ %get_season_url(collection_season_16_url, 4) }" - variables: - collection_season_number: "16" - collection_season_name: "{collection_season_16_name}" - - url: "{ %get_season_url(collection_season_16_url, 5) }" - variables: - collection_season_number: "16" - collection_season_name: "{collection_season_16_name}" - - url: "{ %get_season_url(collection_season_16_url, 6) }" - variables: - collection_season_number: "16" - collection_season_name: "{collection_season_16_name}" - - url: "{ %get_season_url(collection_season_16_url, 7) }" - variables: - collection_season_number: "16" - collection_season_name: "{collection_season_16_name}" - - url: "{ %get_season_url(collection_season_16_url, 8) }" - variables: - collection_season_number: "16" - collection_season_name: "{collection_season_16_name}" - - url: "{ %get_season_url(collection_season_16_url, 9) }" - variables: - collection_season_number: "16" - collection_season_name: "{collection_season_16_name}" - - url: "{ %get_season_url(collection_season_16_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_16_url), 1) }" variables: collection_season_number: "16" collection_season_name: "{collection_season_16_name}" - - url: "{%get_season_url(collection_season_17_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_17_url), 0) }" variables: collection_season_number: "17" collection_season_name: "{collection_season_17_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_17_url, 1) }" - variables: - collection_season_number: "17" - collection_season_name: "{collection_season_17_name}" - - url: "{ %get_season_url(collection_season_17_url, 2) }" - variables: - collection_season_number: "17" - collection_season_name: "{collection_season_17_name}" - - url: "{ %get_season_url(collection_season_17_url, 3) }" - variables: - collection_season_number: "17" - collection_season_name: "{collection_season_17_name}" - - url: "{ %get_season_url(collection_season_17_url, 4) }" - variables: - collection_season_number: "17" - collection_season_name: "{collection_season_17_name}" - - url: "{ %get_season_url(collection_season_17_url, 5) }" - variables: - collection_season_number: "17" - collection_season_name: "{collection_season_17_name}" - - url: "{ %get_season_url(collection_season_17_url, 6) }" - variables: - collection_season_number: "17" - collection_season_name: "{collection_season_17_name}" - - url: "{ %get_season_url(collection_season_17_url, 7) }" - variables: - collection_season_number: "17" - collection_season_name: "{collection_season_17_name}" - - url: "{ %get_season_url(collection_season_17_url, 8) }" - variables: - collection_season_number: "17" - collection_season_name: "{collection_season_17_name}" - - url: "{ %get_season_url(collection_season_17_url, 9) }" - variables: - collection_season_number: "17" - collection_season_name: "{collection_season_17_name}" - - url: "{ %get_season_url(collection_season_17_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_17_url), 1) }" variables: collection_season_number: "17" collection_season_name: "{collection_season_17_name}" - - url: "{%get_season_url(collection_season_18_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_18_url), 0) }" variables: collection_season_number: "18" collection_season_name: "{collection_season_18_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_18_url, 1) }" - variables: - collection_season_number: "18" - collection_season_name: "{collection_season_18_name}" - - url: "{ %get_season_url(collection_season_18_url, 2) }" - variables: - collection_season_number: "18" - collection_season_name: "{collection_season_18_name}" - - url: "{ %get_season_url(collection_season_18_url, 3) }" - variables: - collection_season_number: "18" - collection_season_name: "{collection_season_18_name}" - - url: "{ %get_season_url(collection_season_18_url, 4) }" - variables: - collection_season_number: "18" - collection_season_name: "{collection_season_18_name}" - - url: "{ %get_season_url(collection_season_18_url, 5) }" - variables: - collection_season_number: "18" - collection_season_name: "{collection_season_18_name}" - - url: "{ %get_season_url(collection_season_18_url, 6) }" - variables: - collection_season_number: "18" - collection_season_name: "{collection_season_18_name}" - - url: "{ %get_season_url(collection_season_18_url, 7) }" - variables: - collection_season_number: "18" - collection_season_name: "{collection_season_18_name}" - - url: "{ %get_season_url(collection_season_18_url, 8) }" - variables: - collection_season_number: "18" - collection_season_name: "{collection_season_18_name}" - - url: "{ %get_season_url(collection_season_18_url, 9) }" - variables: - collection_season_number: "18" - collection_season_name: "{collection_season_18_name}" - - url: "{ %get_season_url(collection_season_18_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_18_url), 1) }" variables: collection_season_number: "18" collection_season_name: "{collection_season_18_name}" - - url: "{%get_season_url(collection_season_19_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_19_url), 0) }" variables: collection_season_number: "19" collection_season_name: "{collection_season_19_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_19_url, 1) }" - variables: - collection_season_number: "19" - collection_season_name: "{collection_season_19_name}" - - url: "{ %get_season_url(collection_season_19_url, 2) }" - variables: - collection_season_number: "19" - collection_season_name: "{collection_season_19_name}" - - url: "{ %get_season_url(collection_season_19_url, 3) }" - variables: - collection_season_number: "19" - collection_season_name: "{collection_season_19_name}" - - url: "{ %get_season_url(collection_season_19_url, 4) }" - variables: - collection_season_number: "19" - collection_season_name: "{collection_season_19_name}" - - url: "{ %get_season_url(collection_season_19_url, 5) }" - variables: - collection_season_number: "19" - collection_season_name: "{collection_season_19_name}" - - url: "{ %get_season_url(collection_season_19_url, 6) }" - variables: - collection_season_number: "19" - collection_season_name: "{collection_season_19_name}" - - url: "{ %get_season_url(collection_season_19_url, 7) }" - variables: - collection_season_number: "19" - collection_season_name: "{collection_season_19_name}" - - url: "{ %get_season_url(collection_season_19_url, 8) }" - variables: - collection_season_number: "19" - collection_season_name: "{collection_season_19_name}" - - url: "{ %get_season_url(collection_season_19_url, 9) }" - variables: - collection_season_number: "19" - collection_season_name: "{collection_season_19_name}" - - url: "{ %get_season_url(collection_season_19_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_19_url), 1) }" variables: collection_season_number: "19" collection_season_name: "{collection_season_19_name}" - - url: "{%get_season_url(collection_season_20_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_20_url), 0) }" variables: collection_season_number: "20" collection_season_name: "{collection_season_20_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_20_url, 1) }" - variables: - collection_season_number: "20" - collection_season_name: "{collection_season_20_name}" - - url: "{ %get_season_url(collection_season_20_url, 2) }" - variables: - collection_season_number: "20" - collection_season_name: "{collection_season_20_name}" - - url: "{ %get_season_url(collection_season_20_url, 3) }" - variables: - collection_season_number: "20" - collection_season_name: "{collection_season_20_name}" - - url: "{ %get_season_url(collection_season_20_url, 4) }" - variables: - collection_season_number: "20" - collection_season_name: "{collection_season_20_name}" - - url: "{ %get_season_url(collection_season_20_url, 5) }" - variables: - collection_season_number: "20" - collection_season_name: "{collection_season_20_name}" - - url: "{ %get_season_url(collection_season_20_url, 6) }" - variables: - collection_season_number: "20" - collection_season_name: "{collection_season_20_name}" - - url: "{ %get_season_url(collection_season_20_url, 7) }" - variables: - collection_season_number: "20" - collection_season_name: "{collection_season_20_name}" - - url: "{ %get_season_url(collection_season_20_url, 8) }" - variables: - collection_season_number: "20" - collection_season_name: "{collection_season_20_name}" - - url: "{ %get_season_url(collection_season_20_url, 9) }" - variables: - collection_season_number: "20" - collection_season_name: "{collection_season_20_name}" - - url: "{ %get_season_url(collection_season_20_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_20_url), 1) }" variables: collection_season_number: "20" collection_season_name: "{collection_season_20_name}" - - url: "{%get_season_url(collection_season_21_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_21_url), 0) }" variables: collection_season_number: "21" collection_season_name: "{collection_season_21_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_21_url, 1) }" - variables: - collection_season_number: "21" - collection_season_name: "{collection_season_21_name}" - - url: "{ %get_season_url(collection_season_21_url, 2) }" - variables: - collection_season_number: "21" - collection_season_name: "{collection_season_21_name}" - - url: "{ %get_season_url(collection_season_21_url, 3) }" - variables: - collection_season_number: "21" - collection_season_name: "{collection_season_21_name}" - - url: "{ %get_season_url(collection_season_21_url, 4) }" - variables: - collection_season_number: "21" - collection_season_name: "{collection_season_21_name}" - - url: "{ %get_season_url(collection_season_21_url, 5) }" - variables: - collection_season_number: "21" - collection_season_name: "{collection_season_21_name}" - - url: "{ %get_season_url(collection_season_21_url, 6) }" - variables: - collection_season_number: "21" - collection_season_name: "{collection_season_21_name}" - - url: "{ %get_season_url(collection_season_21_url, 7) }" - variables: - collection_season_number: "21" - collection_season_name: "{collection_season_21_name}" - - url: "{ %get_season_url(collection_season_21_url, 8) }" - variables: - collection_season_number: "21" - collection_season_name: "{collection_season_21_name}" - - url: "{ %get_season_url(collection_season_21_url, 9) }" - variables: - collection_season_number: "21" - collection_season_name: "{collection_season_21_name}" - - url: "{ %get_season_url(collection_season_21_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_21_url), 1) }" variables: collection_season_number: "21" collection_season_name: "{collection_season_21_name}" - - url: "{%get_season_url(collection_season_22_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_22_url), 0) }" variables: collection_season_number: "22" collection_season_name: "{collection_season_22_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_22_url, 1) }" - variables: - collection_season_number: "22" - collection_season_name: "{collection_season_22_name}" - - url: "{ %get_season_url(collection_season_22_url, 2) }" - variables: - collection_season_number: "22" - collection_season_name: "{collection_season_22_name}" - - url: "{ %get_season_url(collection_season_22_url, 3) }" - variables: - collection_season_number: "22" - collection_season_name: "{collection_season_22_name}" - - url: "{ %get_season_url(collection_season_22_url, 4) }" - variables: - collection_season_number: "22" - collection_season_name: "{collection_season_22_name}" - - url: "{ %get_season_url(collection_season_22_url, 5) }" - variables: - collection_season_number: "22" - collection_season_name: "{collection_season_22_name}" - - url: "{ %get_season_url(collection_season_22_url, 6) }" - variables: - collection_season_number: "22" - collection_season_name: "{collection_season_22_name}" - - url: "{ %get_season_url(collection_season_22_url, 7) }" - variables: - collection_season_number: "22" - collection_season_name: "{collection_season_22_name}" - - url: "{ %get_season_url(collection_season_22_url, 8) }" - variables: - collection_season_number: "22" - collection_season_name: "{collection_season_22_name}" - - url: "{ %get_season_url(collection_season_22_url, 9) }" - variables: - collection_season_number: "22" - collection_season_name: "{collection_season_22_name}" - - url: "{ %get_season_url(collection_season_22_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_22_url), 1) }" variables: collection_season_number: "22" collection_season_name: "{collection_season_22_name}" - - url: "{%get_season_url(collection_season_23_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_23_url), 0) }" variables: collection_season_number: "23" collection_season_name: "{collection_season_23_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_23_url, 1) }" - variables: - collection_season_number: "23" - collection_season_name: "{collection_season_23_name}" - - url: "{ %get_season_url(collection_season_23_url, 2) }" - variables: - collection_season_number: "23" - collection_season_name: "{collection_season_23_name}" - - url: "{ %get_season_url(collection_season_23_url, 3) }" - variables: - collection_season_number: "23" - collection_season_name: "{collection_season_23_name}" - - url: "{ %get_season_url(collection_season_23_url, 4) }" - variables: - collection_season_number: "23" - collection_season_name: "{collection_season_23_name}" - - url: "{ %get_season_url(collection_season_23_url, 5) }" - variables: - collection_season_number: "23" - collection_season_name: "{collection_season_23_name}" - - url: "{ %get_season_url(collection_season_23_url, 6) }" - variables: - collection_season_number: "23" - collection_season_name: "{collection_season_23_name}" - - url: "{ %get_season_url(collection_season_23_url, 7) }" - variables: - collection_season_number: "23" - collection_season_name: "{collection_season_23_name}" - - url: "{ %get_season_url(collection_season_23_url, 8) }" - variables: - collection_season_number: "23" - collection_season_name: "{collection_season_23_name}" - - url: "{ %get_season_url(collection_season_23_url, 9) }" - variables: - collection_season_number: "23" - collection_season_name: "{collection_season_23_name}" - - url: "{ %get_season_url(collection_season_23_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_23_url), 1) }" variables: collection_season_number: "23" collection_season_name: "{collection_season_23_name}" - - url: "{%get_season_url(collection_season_24_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_24_url), 0) }" variables: collection_season_number: "24" collection_season_name: "{collection_season_24_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_24_url, 1) }" - variables: - collection_season_number: "24" - collection_season_name: "{collection_season_24_name}" - - url: "{ %get_season_url(collection_season_24_url, 2) }" - variables: - collection_season_number: "24" - collection_season_name: "{collection_season_24_name}" - - url: "{ %get_season_url(collection_season_24_url, 3) }" - variables: - collection_season_number: "24" - collection_season_name: "{collection_season_24_name}" - - url: "{ %get_season_url(collection_season_24_url, 4) }" - variables: - collection_season_number: "24" - collection_season_name: "{collection_season_24_name}" - - url: "{ %get_season_url(collection_season_24_url, 5) }" - variables: - collection_season_number: "24" - collection_season_name: "{collection_season_24_name}" - - url: "{ %get_season_url(collection_season_24_url, 6) }" - variables: - collection_season_number: "24" - collection_season_name: "{collection_season_24_name}" - - url: "{ %get_season_url(collection_season_24_url, 7) }" - variables: - collection_season_number: "24" - collection_season_name: "{collection_season_24_name}" - - url: "{ %get_season_url(collection_season_24_url, 8) }" - variables: - collection_season_number: "24" - collection_season_name: "{collection_season_24_name}" - - url: "{ %get_season_url(collection_season_24_url, 9) }" - variables: - collection_season_number: "24" - collection_season_name: "{collection_season_24_name}" - - url: "{ %get_season_url(collection_season_24_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_24_url), 1) }" variables: collection_season_number: "24" collection_season_name: "{collection_season_24_name}" - - url: "{%get_season_url(collection_season_25_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_25_url), 0) }" variables: collection_season_number: "25" collection_season_name: "{collection_season_25_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_25_url, 1) }" - variables: - collection_season_number: "25" - collection_season_name: "{collection_season_25_name}" - - url: "{ %get_season_url(collection_season_25_url, 2) }" - variables: - collection_season_number: "25" - collection_season_name: "{collection_season_25_name}" - - url: "{ %get_season_url(collection_season_25_url, 3) }" - variables: - collection_season_number: "25" - collection_season_name: "{collection_season_25_name}" - - url: "{ %get_season_url(collection_season_25_url, 4) }" - variables: - collection_season_number: "25" - collection_season_name: "{collection_season_25_name}" - - url: "{ %get_season_url(collection_season_25_url, 5) }" - variables: - collection_season_number: "25" - collection_season_name: "{collection_season_25_name}" - - url: "{ %get_season_url(collection_season_25_url, 6) }" - variables: - collection_season_number: "25" - collection_season_name: "{collection_season_25_name}" - - url: "{ %get_season_url(collection_season_25_url, 7) }" - variables: - collection_season_number: "25" - collection_season_name: "{collection_season_25_name}" - - url: "{ %get_season_url(collection_season_25_url, 8) }" - variables: - collection_season_number: "25" - collection_season_name: "{collection_season_25_name}" - - url: "{ %get_season_url(collection_season_25_url, 9) }" - variables: - collection_season_number: "25" - collection_season_name: "{collection_season_25_name}" - - url: "{ %get_season_url(collection_season_25_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_25_url), 1) }" variables: collection_season_number: "25" collection_season_name: "{collection_season_25_name}" - - url: "{%get_season_url(collection_season_26_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_26_url), 0) }" variables: collection_season_number: "26" collection_season_name: "{collection_season_26_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_26_url, 1) }" - variables: - collection_season_number: "26" - collection_season_name: "{collection_season_26_name}" - - url: "{ %get_season_url(collection_season_26_url, 2) }" - variables: - collection_season_number: "26" - collection_season_name: "{collection_season_26_name}" - - url: "{ %get_season_url(collection_season_26_url, 3) }" - variables: - collection_season_number: "26" - collection_season_name: "{collection_season_26_name}" - - url: "{ %get_season_url(collection_season_26_url, 4) }" - variables: - collection_season_number: "26" - collection_season_name: "{collection_season_26_name}" - - url: "{ %get_season_url(collection_season_26_url, 5) }" - variables: - collection_season_number: "26" - collection_season_name: "{collection_season_26_name}" - - url: "{ %get_season_url(collection_season_26_url, 6) }" - variables: - collection_season_number: "26" - collection_season_name: "{collection_season_26_name}" - - url: "{ %get_season_url(collection_season_26_url, 7) }" - variables: - collection_season_number: "26" - collection_season_name: "{collection_season_26_name}" - - url: "{ %get_season_url(collection_season_26_url, 8) }" - variables: - collection_season_number: "26" - collection_season_name: "{collection_season_26_name}" - - url: "{ %get_season_url(collection_season_26_url, 9) }" - variables: - collection_season_number: "26" - collection_season_name: "{collection_season_26_name}" - - url: "{ %get_season_url(collection_season_26_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_26_url), 1) }" variables: collection_season_number: "26" collection_season_name: "{collection_season_26_name}" - - url: "{%get_season_url(collection_season_27_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_27_url), 0) }" variables: collection_season_number: "27" collection_season_name: "{collection_season_27_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_27_url, 1) }" - variables: - collection_season_number: "27" - collection_season_name: "{collection_season_27_name}" - - url: "{ %get_season_url(collection_season_27_url, 2) }" - variables: - collection_season_number: "27" - collection_season_name: "{collection_season_27_name}" - - url: "{ %get_season_url(collection_season_27_url, 3) }" - variables: - collection_season_number: "27" - collection_season_name: "{collection_season_27_name}" - - url: "{ %get_season_url(collection_season_27_url, 4) }" - variables: - collection_season_number: "27" - collection_season_name: "{collection_season_27_name}" - - url: "{ %get_season_url(collection_season_27_url, 5) }" - variables: - collection_season_number: "27" - collection_season_name: "{collection_season_27_name}" - - url: "{ %get_season_url(collection_season_27_url, 6) }" - variables: - collection_season_number: "27" - collection_season_name: "{collection_season_27_name}" - - url: "{ %get_season_url(collection_season_27_url, 7) }" - variables: - collection_season_number: "27" - collection_season_name: "{collection_season_27_name}" - - url: "{ %get_season_url(collection_season_27_url, 8) }" - variables: - collection_season_number: "27" - collection_season_name: "{collection_season_27_name}" - - url: "{ %get_season_url(collection_season_27_url, 9) }" - variables: - collection_season_number: "27" - collection_season_name: "{collection_season_27_name}" - - url: "{ %get_season_url(collection_season_27_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_27_url), 1) }" variables: collection_season_number: "27" collection_season_name: "{collection_season_27_name}" - - url: "{%get_season_url(collection_season_28_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_28_url), 0) }" variables: collection_season_number: "28" collection_season_name: "{collection_season_28_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_28_url, 1) }" - variables: - collection_season_number: "28" - collection_season_name: "{collection_season_28_name}" - - url: "{ %get_season_url(collection_season_28_url, 2) }" - variables: - collection_season_number: "28" - collection_season_name: "{collection_season_28_name}" - - url: "{ %get_season_url(collection_season_28_url, 3) }" - variables: - collection_season_number: "28" - collection_season_name: "{collection_season_28_name}" - - url: "{ %get_season_url(collection_season_28_url, 4) }" - variables: - collection_season_number: "28" - collection_season_name: "{collection_season_28_name}" - - url: "{ %get_season_url(collection_season_28_url, 5) }" - variables: - collection_season_number: "28" - collection_season_name: "{collection_season_28_name}" - - url: "{ %get_season_url(collection_season_28_url, 6) }" - variables: - collection_season_number: "28" - collection_season_name: "{collection_season_28_name}" - - url: "{ %get_season_url(collection_season_28_url, 7) }" - variables: - collection_season_number: "28" - collection_season_name: "{collection_season_28_name}" - - url: "{ %get_season_url(collection_season_28_url, 8) }" - variables: - collection_season_number: "28" - collection_season_name: "{collection_season_28_name}" - - url: "{ %get_season_url(collection_season_28_url, 9) }" - variables: - collection_season_number: "28" - collection_season_name: "{collection_season_28_name}" - - url: "{ %get_season_url(collection_season_28_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_28_url), 1) }" variables: collection_season_number: "28" collection_season_name: "{collection_season_28_name}" - - url: "{%get_season_url(collection_season_29_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_29_url), 0) }" variables: collection_season_number: "29" collection_season_name: "{collection_season_29_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_29_url, 1) }" - variables: - collection_season_number: "29" - collection_season_name: "{collection_season_29_name}" - - url: "{ %get_season_url(collection_season_29_url, 2) }" - variables: - collection_season_number: "29" - collection_season_name: "{collection_season_29_name}" - - url: "{ %get_season_url(collection_season_29_url, 3) }" - variables: - collection_season_number: "29" - collection_season_name: "{collection_season_29_name}" - - url: "{ %get_season_url(collection_season_29_url, 4) }" - variables: - collection_season_number: "29" - collection_season_name: "{collection_season_29_name}" - - url: "{ %get_season_url(collection_season_29_url, 5) }" - variables: - collection_season_number: "29" - collection_season_name: "{collection_season_29_name}" - - url: "{ %get_season_url(collection_season_29_url, 6) }" - variables: - collection_season_number: "29" - collection_season_name: "{collection_season_29_name}" - - url: "{ %get_season_url(collection_season_29_url, 7) }" - variables: - collection_season_number: "29" - collection_season_name: "{collection_season_29_name}" - - url: "{ %get_season_url(collection_season_29_url, 8) }" - variables: - collection_season_number: "29" - collection_season_name: "{collection_season_29_name}" - - url: "{ %get_season_url(collection_season_29_url, 9) }" - variables: - collection_season_number: "29" - collection_season_name: "{collection_season_29_name}" - - url: "{ %get_season_url(collection_season_29_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_29_url), 1) }" variables: collection_season_number: "29" collection_season_name: "{collection_season_29_name}" - - url: "{%get_season_url(collection_season_30_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_30_url), 0) }" variables: collection_season_number: "30" collection_season_name: "{collection_season_30_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_30_url, 1) }" - variables: - collection_season_number: "30" - collection_season_name: "{collection_season_30_name}" - - url: "{ %get_season_url(collection_season_30_url, 2) }" - variables: - collection_season_number: "30" - collection_season_name: "{collection_season_30_name}" - - url: "{ %get_season_url(collection_season_30_url, 3) }" - variables: - collection_season_number: "30" - collection_season_name: "{collection_season_30_name}" - - url: "{ %get_season_url(collection_season_30_url, 4) }" - variables: - collection_season_number: "30" - collection_season_name: "{collection_season_30_name}" - - url: "{ %get_season_url(collection_season_30_url, 5) }" - variables: - collection_season_number: "30" - collection_season_name: "{collection_season_30_name}" - - url: "{ %get_season_url(collection_season_30_url, 6) }" - variables: - collection_season_number: "30" - collection_season_name: "{collection_season_30_name}" - - url: "{ %get_season_url(collection_season_30_url, 7) }" - variables: - collection_season_number: "30" - collection_season_name: "{collection_season_30_name}" - - url: "{ %get_season_url(collection_season_30_url, 8) }" - variables: - collection_season_number: "30" - collection_season_name: "{collection_season_30_name}" - - url: "{ %get_season_url(collection_season_30_url, 9) }" - variables: - collection_season_number: "30" - collection_season_name: "{collection_season_30_name}" - - url: "{ %get_season_url(collection_season_30_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_30_url), 1) }" variables: collection_season_number: "30" collection_season_name: "{collection_season_30_name}" - - url: "{%get_season_url(collection_season_31_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_31_url), 0) }" variables: collection_season_number: "31" collection_season_name: "{collection_season_31_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_31_url, 1) }" - variables: - collection_season_number: "31" - collection_season_name: "{collection_season_31_name}" - - url: "{ %get_season_url(collection_season_31_url, 2) }" - variables: - collection_season_number: "31" - collection_season_name: "{collection_season_31_name}" - - url: "{ %get_season_url(collection_season_31_url, 3) }" - variables: - collection_season_number: "31" - collection_season_name: "{collection_season_31_name}" - - url: "{ %get_season_url(collection_season_31_url, 4) }" - variables: - collection_season_number: "31" - collection_season_name: "{collection_season_31_name}" - - url: "{ %get_season_url(collection_season_31_url, 5) }" - variables: - collection_season_number: "31" - collection_season_name: "{collection_season_31_name}" - - url: "{ %get_season_url(collection_season_31_url, 6) }" - variables: - collection_season_number: "31" - collection_season_name: "{collection_season_31_name}" - - url: "{ %get_season_url(collection_season_31_url, 7) }" - variables: - collection_season_number: "31" - collection_season_name: "{collection_season_31_name}" - - url: "{ %get_season_url(collection_season_31_url, 8) }" - variables: - collection_season_number: "31" - collection_season_name: "{collection_season_31_name}" - - url: "{ %get_season_url(collection_season_31_url, 9) }" - variables: - collection_season_number: "31" - collection_season_name: "{collection_season_31_name}" - - url: "{ %get_season_url(collection_season_31_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_31_url), 1) }" variables: collection_season_number: "31" collection_season_name: "{collection_season_31_name}" - - url: "{%get_season_url(collection_season_32_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_32_url), 0) }" variables: collection_season_number: "32" collection_season_name: "{collection_season_32_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_32_url, 1) }" - variables: - collection_season_number: "32" - collection_season_name: "{collection_season_32_name}" - - url: "{ %get_season_url(collection_season_32_url, 2) }" - variables: - collection_season_number: "32" - collection_season_name: "{collection_season_32_name}" - - url: "{ %get_season_url(collection_season_32_url, 3) }" - variables: - collection_season_number: "32" - collection_season_name: "{collection_season_32_name}" - - url: "{ %get_season_url(collection_season_32_url, 4) }" - variables: - collection_season_number: "32" - collection_season_name: "{collection_season_32_name}" - - url: "{ %get_season_url(collection_season_32_url, 5) }" - variables: - collection_season_number: "32" - collection_season_name: "{collection_season_32_name}" - - url: "{ %get_season_url(collection_season_32_url, 6) }" - variables: - collection_season_number: "32" - collection_season_name: "{collection_season_32_name}" - - url: "{ %get_season_url(collection_season_32_url, 7) }" - variables: - collection_season_number: "32" - collection_season_name: "{collection_season_32_name}" - - url: "{ %get_season_url(collection_season_32_url, 8) }" - variables: - collection_season_number: "32" - collection_season_name: "{collection_season_32_name}" - - url: "{ %get_season_url(collection_season_32_url, 9) }" - variables: - collection_season_number: "32" - collection_season_name: "{collection_season_32_name}" - - url: "{ %get_season_url(collection_season_32_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_32_url), 1) }" variables: collection_season_number: "32" collection_season_name: "{collection_season_32_name}" - - url: "{%get_season_url(collection_season_33_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_33_url), 0) }" variables: collection_season_number: "33" collection_season_name: "{collection_season_33_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_33_url, 1) }" - variables: - collection_season_number: "33" - collection_season_name: "{collection_season_33_name}" - - url: "{ %get_season_url(collection_season_33_url, 2) }" - variables: - collection_season_number: "33" - collection_season_name: "{collection_season_33_name}" - - url: "{ %get_season_url(collection_season_33_url, 3) }" - variables: - collection_season_number: "33" - collection_season_name: "{collection_season_33_name}" - - url: "{ %get_season_url(collection_season_33_url, 4) }" - variables: - collection_season_number: "33" - collection_season_name: "{collection_season_33_name}" - - url: "{ %get_season_url(collection_season_33_url, 5) }" - variables: - collection_season_number: "33" - collection_season_name: "{collection_season_33_name}" - - url: "{ %get_season_url(collection_season_33_url, 6) }" - variables: - collection_season_number: "33" - collection_season_name: "{collection_season_33_name}" - - url: "{ %get_season_url(collection_season_33_url, 7) }" - variables: - collection_season_number: "33" - collection_season_name: "{collection_season_33_name}" - - url: "{ %get_season_url(collection_season_33_url, 8) }" - variables: - collection_season_number: "33" - collection_season_name: "{collection_season_33_name}" - - url: "{ %get_season_url(collection_season_33_url, 9) }" - variables: - collection_season_number: "33" - collection_season_name: "{collection_season_33_name}" - - url: "{ %get_season_url(collection_season_33_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_33_url), 1) }" variables: collection_season_number: "33" collection_season_name: "{collection_season_33_name}" - - url: "{%get_season_url(collection_season_34_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_34_url), 0) }" variables: collection_season_number: "34" collection_season_name: "{collection_season_34_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_34_url, 1) }" - variables: - collection_season_number: "34" - collection_season_name: "{collection_season_34_name}" - - url: "{ %get_season_url(collection_season_34_url, 2) }" - variables: - collection_season_number: "34" - collection_season_name: "{collection_season_34_name}" - - url: "{ %get_season_url(collection_season_34_url, 3) }" - variables: - collection_season_number: "34" - collection_season_name: "{collection_season_34_name}" - - url: "{ %get_season_url(collection_season_34_url, 4) }" - variables: - collection_season_number: "34" - collection_season_name: "{collection_season_34_name}" - - url: "{ %get_season_url(collection_season_34_url, 5) }" - variables: - collection_season_number: "34" - collection_season_name: "{collection_season_34_name}" - - url: "{ %get_season_url(collection_season_34_url, 6) }" - variables: - collection_season_number: "34" - collection_season_name: "{collection_season_34_name}" - - url: "{ %get_season_url(collection_season_34_url, 7) }" - variables: - collection_season_number: "34" - collection_season_name: "{collection_season_34_name}" - - url: "{ %get_season_url(collection_season_34_url, 8) }" - variables: - collection_season_number: "34" - collection_season_name: "{collection_season_34_name}" - - url: "{ %get_season_url(collection_season_34_url, 9) }" - variables: - collection_season_number: "34" - collection_season_name: "{collection_season_34_name}" - - url: "{ %get_season_url(collection_season_34_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_34_url), 1) }" variables: collection_season_number: "34" collection_season_name: "{collection_season_34_name}" - - url: "{%get_season_url(collection_season_35_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_35_url), 0) }" variables: collection_season_number: "35" collection_season_name: "{collection_season_35_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_35_url, 1) }" - variables: - collection_season_number: "35" - collection_season_name: "{collection_season_35_name}" - - url: "{ %get_season_url(collection_season_35_url, 2) }" - variables: - collection_season_number: "35" - collection_season_name: "{collection_season_35_name}" - - url: "{ %get_season_url(collection_season_35_url, 3) }" - variables: - collection_season_number: "35" - collection_season_name: "{collection_season_35_name}" - - url: "{ %get_season_url(collection_season_35_url, 4) }" - variables: - collection_season_number: "35" - collection_season_name: "{collection_season_35_name}" - - url: "{ %get_season_url(collection_season_35_url, 5) }" - variables: - collection_season_number: "35" - collection_season_name: "{collection_season_35_name}" - - url: "{ %get_season_url(collection_season_35_url, 6) }" - variables: - collection_season_number: "35" - collection_season_name: "{collection_season_35_name}" - - url: "{ %get_season_url(collection_season_35_url, 7) }" - variables: - collection_season_number: "35" - collection_season_name: "{collection_season_35_name}" - - url: "{ %get_season_url(collection_season_35_url, 8) }" - variables: - collection_season_number: "35" - collection_season_name: "{collection_season_35_name}" - - url: "{ %get_season_url(collection_season_35_url, 9) }" - variables: - collection_season_number: "35" - collection_season_name: "{collection_season_35_name}" - - url: "{ %get_season_url(collection_season_35_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_35_url), 1) }" variables: collection_season_number: "35" collection_season_name: "{collection_season_35_name}" - - - url: "{%get_season_url(collection_season_36_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_36_url), 0) }" variables: collection_season_number: "36" collection_season_name: "{collection_season_36_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_36_url, 1) }" - variables: - collection_season_number: "36" - collection_season_name: "{collection_season_36_name}" - - url: "{ %get_season_url(collection_season_36_url, 2) }" - variables: - collection_season_number: "36" - collection_season_name: "{collection_season_36_name}" - - url: "{ %get_season_url(collection_season_36_url, 3) }" - variables: - collection_season_number: "36" - collection_season_name: "{collection_season_36_name}" - - url: "{ %get_season_url(collection_season_36_url, 4) }" - variables: - collection_season_number: "36" - collection_season_name: "{collection_season_36_name}" - - url: "{ %get_season_url(collection_season_36_url, 5) }" - variables: - collection_season_number: "36" - collection_season_name: "{collection_season_36_name}" - - url: "{ %get_season_url(collection_season_36_url, 6) }" - variables: - collection_season_number: "36" - collection_season_name: "{collection_season_36_name}" - - url: "{ %get_season_url(collection_season_36_url, 7) }" - variables: - collection_season_number: "36" - collection_season_name: "{collection_season_36_name}" - - url: "{ %get_season_url(collection_season_36_url, 8) }" - variables: - collection_season_number: "36" - collection_season_name: "{collection_season_36_name}" - - url: "{ %get_season_url(collection_season_36_url, 9) }" - variables: - collection_season_number: "36" - collection_season_name: "{collection_season_36_name}" - - url: "{ %get_season_url(collection_season_36_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_36_url), 1) }" variables: collection_season_number: "36" collection_season_name: "{collection_season_36_name}" - - url: "{%get_season_url(collection_season_37_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_37_url), 0) }" variables: collection_season_number: "37" collection_season_name: "{collection_season_37_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_37_url, 1) }" - variables: - collection_season_number: "37" - collection_season_name: "{collection_season_37_name}" - - url: "{ %get_season_url(collection_season_37_url, 2) }" - variables: - collection_season_number: "37" - collection_season_name: "{collection_season_37_name}" - - url: "{ %get_season_url(collection_season_37_url, 3) }" - variables: - collection_season_number: "37" - collection_season_name: "{collection_season_37_name}" - - url: "{ %get_season_url(collection_season_37_url, 4) }" - variables: - collection_season_number: "37" - collection_season_name: "{collection_season_37_name}" - - url: "{ %get_season_url(collection_season_37_url, 5) }" - variables: - collection_season_number: "37" - collection_season_name: "{collection_season_37_name}" - - url: "{ %get_season_url(collection_season_37_url, 6) }" - variables: - collection_season_number: "37" - collection_season_name: "{collection_season_37_name}" - - url: "{ %get_season_url(collection_season_37_url, 7) }" - variables: - collection_season_number: "37" - collection_season_name: "{collection_season_37_name}" - - url: "{ %get_season_url(collection_season_37_url, 8) }" - variables: - collection_season_number: "37" - collection_season_name: "{collection_season_37_name}" - - url: "{ %get_season_url(collection_season_37_url, 9) }" - variables: - collection_season_number: "37" - collection_season_name: "{collection_season_37_name}" - - url: "{ %get_season_url(collection_season_37_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_37_url), 1) }" variables: collection_season_number: "37" collection_season_name: "{collection_season_37_name}" - - url: "{%get_season_url(collection_season_38_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_38_url), 0) }" variables: collection_season_number: "38" collection_season_name: "{collection_season_38_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_38_url, 1) }" - variables: - collection_season_number: "38" - collection_season_name: "{collection_season_38_name}" - - url: "{ %get_season_url(collection_season_38_url, 2) }" - variables: - collection_season_number: "38" - collection_season_name: "{collection_season_38_name}" - - url: "{ %get_season_url(collection_season_38_url, 3) }" - variables: - collection_season_number: "38" - collection_season_name: "{collection_season_38_name}" - - url: "{ %get_season_url(collection_season_38_url, 4) }" - variables: - collection_season_number: "38" - collection_season_name: "{collection_season_38_name}" - - url: "{ %get_season_url(collection_season_38_url, 5) }" - variables: - collection_season_number: "38" - collection_season_name: "{collection_season_38_name}" - - url: "{ %get_season_url(collection_season_38_url, 6) }" - variables: - collection_season_number: "38" - collection_season_name: "{collection_season_38_name}" - - url: "{ %get_season_url(collection_season_38_url, 7) }" - variables: - collection_season_number: "38" - collection_season_name: "{collection_season_38_name}" - - url: "{ %get_season_url(collection_season_38_url, 8) }" - variables: - collection_season_number: "38" - collection_season_name: "{collection_season_38_name}" - - url: "{ %get_season_url(collection_season_38_url, 9) }" - variables: - collection_season_number: "38" - collection_season_name: "{collection_season_38_name}" - - url: "{ %get_season_url(collection_season_38_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_38_url), 1) }" variables: collection_season_number: "38" collection_season_name: "{collection_season_38_name}" - - url: "{%get_season_url(collection_season_39_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_39_url), 0) }" variables: collection_season_number: "39" collection_season_name: "{collection_season_39_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_39_url, 1) }" - variables: - collection_season_number: "39" - collection_season_name: "{collection_season_39_name}" - - url: "{ %get_season_url(collection_season_39_url, 2) }" - variables: - collection_season_number: "39" - collection_season_name: "{collection_season_39_name}" - - url: "{ %get_season_url(collection_season_39_url, 3) }" - variables: - collection_season_number: "39" - collection_season_name: "{collection_season_39_name}" - - url: "{ %get_season_url(collection_season_39_url, 4) }" - variables: - collection_season_number: "39" - collection_season_name: "{collection_season_39_name}" - - url: "{ %get_season_url(collection_season_39_url, 5) }" - variables: - collection_season_number: "39" - collection_season_name: "{collection_season_39_name}" - - url: "{ %get_season_url(collection_season_39_url, 6) }" - variables: - collection_season_number: "39" - collection_season_name: "{collection_season_39_name}" - - url: "{ %get_season_url(collection_season_39_url, 7) }" - variables: - collection_season_number: "39" - collection_season_name: "{collection_season_39_name}" - - url: "{ %get_season_url(collection_season_39_url, 8) }" - variables: - collection_season_number: "39" - collection_season_name: "{collection_season_39_name}" - - url: "{ %get_season_url(collection_season_39_url, 9) }" - variables: - collection_season_number: "39" - collection_season_name: "{collection_season_39_name}" - - url: "{ %get_season_url(collection_season_39_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_39_url), 1) }" variables: collection_season_number: "39" collection_season_name: "{collection_season_39_name}" - - url: "{%get_season_url(collection_season_40_url, 0)}" + - url: "{ %array_at( %get_season_urls(collection_season_40_url), 0) }" variables: collection_season_number: "40" collection_season_name: "{collection_season_40_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_40_url, 1) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - - url: "{ %get_season_url(collection_season_40_url, 2) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - - url: "{ %get_season_url(collection_season_40_url, 3) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - - url: "{ %get_season_url(collection_season_40_url, 4) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - - url: "{ %get_season_url(collection_season_40_url, 5) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - - url: "{ %get_season_url(collection_season_40_url, 6) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - - url: "{ %get_season_url(collection_season_40_url, 7) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - - url: "{ %get_season_url(collection_season_40_url, 8) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - - url: "{ %get_season_url(collection_season_40_url, 9) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - - url: "{ %get_season_url(collection_season_40_url, 10) }" + - url: "{ %array_slice( %get_season_urls(collection_season_40_url), 1) }" variables: collection_season_number: "40" collection_season_name: "{collection_season_40_name}" + # Place season 0 at end - - url: "{ %get_season_url(collection_season_0_url, 0) }" + - url: "{ %array_at( %get_season_urls(collection_season_0_url), 0) }" variables: collection_season_number: "0" collection_season_name: "{collection_season_0_name}" playlist_thumbnails: - name: "{season_poster_file_name}" uid: "latest_entry" - - url: "{ %get_season_url(collection_season_0_url, 1) }" + - url: "{ %array_slice( %get_season_urls(collection_season_0_url), 1) }" variables: collection_season_number: "0" collection_season_name: "{collection_season_0_name}" - - url: "{ %get_season_url(collection_season_0_url, 2) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - - url: "{ %get_season_url(collection_season_0_url, 3) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - - url: "{ %get_season_url(collection_season_0_url, 4) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - - url: "{ %get_season_url(collection_season_0_url, 5) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - - url: "{ %get_season_url(collection_season_0_url, 6) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - - url: "{ %get_season_url(collection_season_0_url, 7) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - - url: "{ %get_season_url(collection_season_0_url, 8) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - - url: "{ %get_season_url(collection_season_0_url, 9) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - - url: "{ %get_season_url(collection_season_0_url, 10) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - output_directory_nfo_tags: @@ -2406,97 +929,20 @@ presets: s40_url: "" s00_url: "" + "%get_season_urls": >- + { %if( %is_array( $0 ), $0, [ $0 ] ) } + # $0 - season url variable # $1 - get the i'th url from the array "%get_season_url": >- - { - %array_at( - %if( - %is_array( $0 ), - $0, - [ $0 ] - ), - $1, - "" - ) - } + { %array_at( %get_season_urls($0), $1, "" ) } _tv_show_collection_bilateral: preset: - "_url_bilateral_overrides" download: - - url: "{ %bilateral_url( %get_season_url(collection_season_1_url, 0) ) }" - 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( %get_season_url(collection_season_1_url, 1) ) }" - 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( %get_season_url(collection_season_1_url, 2) ) }" - 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( %get_season_url(collection_season_1_url, 3) ) }" - 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( %get_season_url(collection_season_1_url, 4) ) }" - 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( %get_season_url(collection_season_1_url, 5) ) }" - 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( %get_season_url(collection_season_1_url, 6) ) }" - 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( %get_season_url(collection_season_1_url, 7) ) }" - 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( %get_season_url(collection_season_1_url, 8) ) }" - 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( %get_season_url(collection_season_1_url, 9) ) }" - 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( %get_season_url(collection_season_1_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_1_url), %bilateral_url) }" variables: collection_season_number: "1" collection_season_name: "{collection_season_1_name}" @@ -2504,77 +950,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_2_url, 0) ) }" - 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( %get_season_url(collection_season_2_url, 1) ) }" - 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( %get_season_url(collection_season_2_url, 2) ) }" - 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( %get_season_url(collection_season_2_url, 3) ) }" - 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( %get_season_url(collection_season_2_url, 4) ) }" - 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( %get_season_url(collection_season_2_url, 5) ) }" - 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( %get_season_url(collection_season_2_url, 6) ) }" - 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( %get_season_url(collection_season_2_url, 7) ) }" - 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( %get_season_url(collection_season_2_url, 8) ) }" - 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( %get_season_url(collection_season_2_url, 9) ) }" - 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( %get_season_url(collection_season_2_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_2_url), %bilateral_url) }" variables: collection_season_number: "2" collection_season_name: "{collection_season_2_name}" @@ -2582,77 +958,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_3_url, 0) ) }" - 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( %get_season_url(collection_season_3_url, 1) ) }" - 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( %get_season_url(collection_season_3_url, 2) ) }" - 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( %get_season_url(collection_season_3_url, 3) ) }" - 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( %get_season_url(collection_season_3_url, 4) ) }" - 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( %get_season_url(collection_season_3_url, 5) ) }" - 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( %get_season_url(collection_season_3_url, 6) ) }" - 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( %get_season_url(collection_season_3_url, 7) ) }" - 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( %get_season_url(collection_season_3_url, 8) ) }" - 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( %get_season_url(collection_season_3_url, 9) ) }" - 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( %get_season_url(collection_season_3_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_3_url), %bilateral_url) }" variables: collection_season_number: "3" collection_season_name: "{collection_season_3_name}" @@ -2660,77 +966,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_4_url, 0) ) }" - 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( %get_season_url(collection_season_4_url, 1) ) }" - 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( %get_season_url(collection_season_4_url, 2) ) }" - 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( %get_season_url(collection_season_4_url, 3) ) }" - 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( %get_season_url(collection_season_4_url, 4) ) }" - 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( %get_season_url(collection_season_4_url, 5) ) }" - 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( %get_season_url(collection_season_4_url, 6) ) }" - 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( %get_season_url(collection_season_4_url, 7) ) }" - 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( %get_season_url(collection_season_4_url, 8) ) }" - 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( %get_season_url(collection_season_4_url, 9) ) }" - 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( %get_season_url(collection_season_4_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_4_url), %bilateral_url) }" variables: collection_season_number: "4" collection_season_name: "{collection_season_4_name}" @@ -2738,77 +974,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_5_url, 0) ) }" - 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( %get_season_url(collection_season_5_url, 1) ) }" - 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( %get_season_url(collection_season_5_url, 2) ) }" - 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( %get_season_url(collection_season_5_url, 3) ) }" - 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( %get_season_url(collection_season_5_url, 4) ) }" - 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( %get_season_url(collection_season_5_url, 5) ) }" - 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( %get_season_url(collection_season_5_url, 6) ) }" - 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( %get_season_url(collection_season_5_url, 7) ) }" - 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( %get_season_url(collection_season_5_url, 8) ) }" - 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( %get_season_url(collection_season_5_url, 9) ) }" - 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( %get_season_url(collection_season_5_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_5_url), %bilateral_url) }" variables: collection_season_number: "5" collection_season_name: "{collection_season_5_name}" @@ -2816,77 +982,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_6_url, 0) ) }" - 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( %get_season_url(collection_season_6_url, 1) ) }" - 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( %get_season_url(collection_season_6_url, 2) ) }" - 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( %get_season_url(collection_season_6_url, 3) ) }" - 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( %get_season_url(collection_season_6_url, 4) ) }" - 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( %get_season_url(collection_season_6_url, 5) ) }" - 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( %get_season_url(collection_season_6_url, 6) ) }" - 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( %get_season_url(collection_season_6_url, 7) ) }" - 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( %get_season_url(collection_season_6_url, 8) ) }" - 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( %get_season_url(collection_season_6_url, 9) ) }" - 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( %get_season_url(collection_season_6_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_6_url), %bilateral_url) }" variables: collection_season_number: "6" collection_season_name: "{collection_season_6_name}" @@ -2894,77 +990,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_7_url, 0) ) }" - 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( %get_season_url(collection_season_7_url, 1) ) }" - 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( %get_season_url(collection_season_7_url, 2) ) }" - 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( %get_season_url(collection_season_7_url, 3) ) }" - 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( %get_season_url(collection_season_7_url, 4) ) }" - 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( %get_season_url(collection_season_7_url, 5) ) }" - 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( %get_season_url(collection_season_7_url, 6) ) }" - 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( %get_season_url(collection_season_7_url, 7) ) }" - 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( %get_season_url(collection_season_7_url, 8) ) }" - 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( %get_season_url(collection_season_7_url, 9) ) }" - 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( %get_season_url(collection_season_7_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_7_url), %bilateral_url) }" variables: collection_season_number: "7" collection_season_name: "{collection_season_7_name}" @@ -2972,77 +998,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_8_url, 0) ) }" - 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( %get_season_url(collection_season_8_url, 1) ) }" - 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( %get_season_url(collection_season_8_url, 2) ) }" - 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( %get_season_url(collection_season_8_url, 3) ) }" - 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( %get_season_url(collection_season_8_url, 4) ) }" - 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( %get_season_url(collection_season_8_url, 5) ) }" - 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( %get_season_url(collection_season_8_url, 6) ) }" - 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( %get_season_url(collection_season_8_url, 7) ) }" - 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( %get_season_url(collection_season_8_url, 8) ) }" - 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( %get_season_url(collection_season_8_url, 9) ) }" - 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( %get_season_url(collection_season_8_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_8_url), %bilateral_url) }" variables: collection_season_number: "8" collection_season_name: "{collection_season_8_name}" @@ -3050,77 +1006,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_9_url, 0) ) }" - 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( %get_season_url(collection_season_9_url, 1) ) }" - 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( %get_season_url(collection_season_9_url, 2) ) }" - 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( %get_season_url(collection_season_9_url, 3) ) }" - 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( %get_season_url(collection_season_9_url, 4) ) }" - 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( %get_season_url(collection_season_9_url, 5) ) }" - 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( %get_season_url(collection_season_9_url, 6) ) }" - 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( %get_season_url(collection_season_9_url, 7) ) }" - 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( %get_season_url(collection_season_9_url, 8) ) }" - 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( %get_season_url(collection_season_9_url, 9) ) }" - 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( %get_season_url(collection_season_9_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_9_url), %bilateral_url) }" variables: collection_season_number: "9" collection_season_name: "{collection_season_9_name}" @@ -3128,77 +1014,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_10_url, 0) ) }" - 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( %get_season_url(collection_season_10_url, 1) ) }" - 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( %get_season_url(collection_season_10_url, 2) ) }" - 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( %get_season_url(collection_season_10_url, 3) ) }" - 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( %get_season_url(collection_season_10_url, 4) ) }" - 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( %get_season_url(collection_season_10_url, 5) ) }" - 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( %get_season_url(collection_season_10_url, 6) ) }" - 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( %get_season_url(collection_season_10_url, 7) ) }" - 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( %get_season_url(collection_season_10_url, 8) ) }" - 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( %get_season_url(collection_season_10_url, 9) ) }" - 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( %get_season_url(collection_season_10_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_10_url), %bilateral_url) }" variables: collection_season_number: "10" collection_season_name: "{collection_season_10_name}" @@ -3206,77 +1022,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_11_url, 0) ) }" - 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( %get_season_url(collection_season_11_url, 1) ) }" - 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( %get_season_url(collection_season_11_url, 2) ) }" - 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( %get_season_url(collection_season_11_url, 3) ) }" - 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( %get_season_url(collection_season_11_url, 4) ) }" - 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( %get_season_url(collection_season_11_url, 5) ) }" - 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( %get_season_url(collection_season_11_url, 6) ) }" - 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( %get_season_url(collection_season_11_url, 7) ) }" - 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( %get_season_url(collection_season_11_url, 8) ) }" - 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( %get_season_url(collection_season_11_url, 9) ) }" - 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( %get_season_url(collection_season_11_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_11_url), %bilateral_url) }" variables: collection_season_number: "11" collection_season_name: "{collection_season_11_name}" @@ -3284,77 +1030,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_12_url, 0) ) }" - 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( %get_season_url(collection_season_12_url, 1) ) }" - 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( %get_season_url(collection_season_12_url, 2) ) }" - 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( %get_season_url(collection_season_12_url, 3) ) }" - 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( %get_season_url(collection_season_12_url, 4) ) }" - 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( %get_season_url(collection_season_12_url, 5) ) }" - 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( %get_season_url(collection_season_12_url, 6) ) }" - 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( %get_season_url(collection_season_12_url, 7) ) }" - 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( %get_season_url(collection_season_12_url, 8) ) }" - 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( %get_season_url(collection_season_12_url, 9) ) }" - 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( %get_season_url(collection_season_12_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_12_url), %bilateral_url) }" variables: collection_season_number: "12" collection_season_name: "{collection_season_12_name}" @@ -3362,77 +1038,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_13_url, 0) ) }" - 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( %get_season_url(collection_season_13_url, 1) ) }" - 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( %get_season_url(collection_season_13_url, 2) ) }" - 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( %get_season_url(collection_season_13_url, 3) ) }" - 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( %get_season_url(collection_season_13_url, 4) ) }" - 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( %get_season_url(collection_season_13_url, 5) ) }" - 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( %get_season_url(collection_season_13_url, 6) ) }" - 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( %get_season_url(collection_season_13_url, 7) ) }" - 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( %get_season_url(collection_season_13_url, 8) ) }" - 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( %get_season_url(collection_season_13_url, 9) ) }" - 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( %get_season_url(collection_season_13_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_13_url), %bilateral_url) }" variables: collection_season_number: "13" collection_season_name: "{collection_season_13_name}" @@ -3440,77 +1046,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_14_url, 0) ) }" - 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( %get_season_url(collection_season_14_url, 1) ) }" - 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( %get_season_url(collection_season_14_url, 2) ) }" - 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( %get_season_url(collection_season_14_url, 3) ) }" - 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( %get_season_url(collection_season_14_url, 4) ) }" - 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( %get_season_url(collection_season_14_url, 5) ) }" - 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( %get_season_url(collection_season_14_url, 6) ) }" - 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( %get_season_url(collection_season_14_url, 7) ) }" - 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( %get_season_url(collection_season_14_url, 8) ) }" - 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( %get_season_url(collection_season_14_url, 9) ) }" - 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( %get_season_url(collection_season_14_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_14_url), %bilateral_url) }" variables: collection_season_number: "14" collection_season_name: "{collection_season_14_name}" @@ -3518,77 +1054,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_15_url, 0) ) }" - 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( %get_season_url(collection_season_15_url, 1) ) }" - 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( %get_season_url(collection_season_15_url, 2) ) }" - 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( %get_season_url(collection_season_15_url, 3) ) }" - 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( %get_season_url(collection_season_15_url, 4) ) }" - 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( %get_season_url(collection_season_15_url, 5) ) }" - 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( %get_season_url(collection_season_15_url, 6) ) }" - 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( %get_season_url(collection_season_15_url, 7) ) }" - 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( %get_season_url(collection_season_15_url, 8) ) }" - 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( %get_season_url(collection_season_15_url, 9) ) }" - 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( %get_season_url(collection_season_15_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_15_url), %bilateral_url) }" variables: collection_season_number: "15" collection_season_name: "{collection_season_15_name}" @@ -3596,78 +1062,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_16_url, 0) ) }" - 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( %get_season_url(collection_season_16_url, 1) ) }" - 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( %get_season_url(collection_season_16_url, 2) ) }" - 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( %get_season_url(collection_season_16_url, 3) ) }" - 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( %get_season_url(collection_season_16_url, 4) ) }" - 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( %get_season_url(collection_season_16_url, 5) ) }" - 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( %get_season_url(collection_season_16_url, 6) ) }" - 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( %get_season_url(collection_season_16_url, 7) ) }" - 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( %get_season_url(collection_season_16_url, 8) ) }" - 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( %get_season_url(collection_season_16_url, 9) ) }" - 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( %get_season_url(collection_season_16_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_16_url), %bilateral_url) }" variables: collection_season_number: "16" collection_season_name: "{collection_season_16_name}" @@ -3675,77 +1070,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_17_url, 0) ) }" - 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( %get_season_url(collection_season_17_url, 1) ) }" - 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( %get_season_url(collection_season_17_url, 2) ) }" - 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( %get_season_url(collection_season_17_url, 3) ) }" - 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( %get_season_url(collection_season_17_url, 4) ) }" - 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( %get_season_url(collection_season_17_url, 5) ) }" - 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( %get_season_url(collection_season_17_url, 6) ) }" - 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( %get_season_url(collection_season_17_url, 7) ) }" - 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( %get_season_url(collection_season_17_url, 8) ) }" - 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( %get_season_url(collection_season_17_url, 9) ) }" - 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( %get_season_url(collection_season_17_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_17_url), %bilateral_url) }" variables: collection_season_number: "17" collection_season_name: "{collection_season_17_name}" @@ -3753,77 +1078,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_18_url, 0) ) }" - 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( %get_season_url(collection_season_18_url, 1) ) }" - 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( %get_season_url(collection_season_18_url, 2) ) }" - 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( %get_season_url(collection_season_18_url, 3) ) }" - 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( %get_season_url(collection_season_18_url, 4) ) }" - 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( %get_season_url(collection_season_18_url, 5) ) }" - 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( %get_season_url(collection_season_18_url, 6) ) }" - 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( %get_season_url(collection_season_18_url, 7) ) }" - 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( %get_season_url(collection_season_18_url, 8) ) }" - 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( %get_season_url(collection_season_18_url, 9) ) }" - 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( %get_season_url(collection_season_18_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_18_url), %bilateral_url) }" variables: collection_season_number: "18" collection_season_name: "{collection_season_18_name}" @@ -3831,77 +1086,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_19_url, 0) ) }" - 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( %get_season_url(collection_season_19_url, 1) ) }" - 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( %get_season_url(collection_season_19_url, 2) ) }" - 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( %get_season_url(collection_season_19_url, 3) ) }" - 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( %get_season_url(collection_season_19_url, 4) ) }" - 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( %get_season_url(collection_season_19_url, 5) ) }" - 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( %get_season_url(collection_season_19_url, 6) ) }" - 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( %get_season_url(collection_season_19_url, 7) ) }" - 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( %get_season_url(collection_season_19_url, 8) ) }" - 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( %get_season_url(collection_season_19_url, 9) ) }" - 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( %get_season_url(collection_season_19_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_19_url), %bilateral_url) }" variables: collection_season_number: "19" collection_season_name: "{collection_season_19_name}" @@ -3909,77 +1094,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_20_url, 0) ) }" - 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( %get_season_url(collection_season_20_url, 1) ) }" - 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( %get_season_url(collection_season_20_url, 2) ) }" - 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( %get_season_url(collection_season_20_url, 3) ) }" - 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( %get_season_url(collection_season_20_url, 4) ) }" - 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( %get_season_url(collection_season_20_url, 5) ) }" - 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( %get_season_url(collection_season_20_url, 6) ) }" - 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( %get_season_url(collection_season_20_url, 7) ) }" - 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( %get_season_url(collection_season_20_url, 8) ) }" - 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( %get_season_url(collection_season_20_url, 9) ) }" - 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( %get_season_url(collection_season_20_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_20_url), %bilateral_url) }" variables: collection_season_number: "20" collection_season_name: "{collection_season_20_name}" @@ -3987,77 +1102,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_21_url, 0) ) }" - 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( %get_season_url(collection_season_21_url, 1) ) }" - 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( %get_season_url(collection_season_21_url, 2) ) }" - 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( %get_season_url(collection_season_21_url, 3) ) }" - 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( %get_season_url(collection_season_21_url, 4) ) }" - 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( %get_season_url(collection_season_21_url, 5) ) }" - 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( %get_season_url(collection_season_21_url, 6) ) }" - 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( %get_season_url(collection_season_21_url, 7) ) }" - 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( %get_season_url(collection_season_21_url, 8) ) }" - 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( %get_season_url(collection_season_21_url, 9) ) }" - 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( %get_season_url(collection_season_21_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_21_url), %bilateral_url) }" variables: collection_season_number: "21" collection_season_name: "{collection_season_21_name}" @@ -4065,77 +1110,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_22_url, 0) ) }" - 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( %get_season_url(collection_season_22_url, 1) ) }" - 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( %get_season_url(collection_season_22_url, 2) ) }" - 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( %get_season_url(collection_season_22_url, 3) ) }" - 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( %get_season_url(collection_season_22_url, 4) ) }" - 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( %get_season_url(collection_season_22_url, 5) ) }" - 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( %get_season_url(collection_season_22_url, 6) ) }" - 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( %get_season_url(collection_season_22_url, 7) ) }" - 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( %get_season_url(collection_season_22_url, 8) ) }" - 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( %get_season_url(collection_season_22_url, 9) ) }" - 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( %get_season_url(collection_season_22_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_22_url), %bilateral_url) }" variables: collection_season_number: "22" collection_season_name: "{collection_season_22_name}" @@ -4143,77 +1118,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_23_url, 0) ) }" - 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( %get_season_url(collection_season_23_url, 1) ) }" - 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( %get_season_url(collection_season_23_url, 2) ) }" - 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( %get_season_url(collection_season_23_url, 3) ) }" - 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( %get_season_url(collection_season_23_url, 4) ) }" - 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( %get_season_url(collection_season_23_url, 5) ) }" - 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( %get_season_url(collection_season_23_url, 6) ) }" - 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( %get_season_url(collection_season_23_url, 7) ) }" - 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( %get_season_url(collection_season_23_url, 8) ) }" - 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( %get_season_url(collection_season_23_url, 9) ) }" - 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( %get_season_url(collection_season_23_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_23_url), %bilateral_url) }" variables: collection_season_number: "23" collection_season_name: "{collection_season_23_name}" @@ -4221,77 +1126,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_24_url, 0) ) }" - 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( %get_season_url(collection_season_24_url, 1) ) }" - 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( %get_season_url(collection_season_24_url, 2) ) }" - 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( %get_season_url(collection_season_24_url, 3) ) }" - 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( %get_season_url(collection_season_24_url, 4) ) }" - 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( %get_season_url(collection_season_24_url, 5) ) }" - 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( %get_season_url(collection_season_24_url, 6) ) }" - 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( %get_season_url(collection_season_24_url, 7) ) }" - 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( %get_season_url(collection_season_24_url, 8) ) }" - 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( %get_season_url(collection_season_24_url, 9) ) }" - 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( %get_season_url(collection_season_24_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_24_url), %bilateral_url) }" variables: collection_season_number: "24" collection_season_name: "{collection_season_24_name}" @@ -4299,77 +1134,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_25_url, 0) ) }" - 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( %get_season_url(collection_season_25_url, 1) ) }" - 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( %get_season_url(collection_season_25_url, 2) ) }" - 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( %get_season_url(collection_season_25_url, 3) ) }" - 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( %get_season_url(collection_season_25_url, 4) ) }" - 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( %get_season_url(collection_season_25_url, 5) ) }" - 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( %get_season_url(collection_season_25_url, 6) ) }" - 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( %get_season_url(collection_season_25_url, 7) ) }" - 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( %get_season_url(collection_season_25_url, 8) ) }" - 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( %get_season_url(collection_season_25_url, 9) ) }" - 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( %get_season_url(collection_season_25_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_25_url), %bilateral_url) }" variables: collection_season_number: "25" collection_season_name: "{collection_season_25_name}" @@ -4377,77 +1142,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_26_url, 0) ) }" - 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( %get_season_url(collection_season_26_url, 1) ) }" - 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( %get_season_url(collection_season_26_url, 2) ) }" - 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( %get_season_url(collection_season_26_url, 3) ) }" - 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( %get_season_url(collection_season_26_url, 4) ) }" - 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( %get_season_url(collection_season_26_url, 5) ) }" - 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( %get_season_url(collection_season_26_url, 6) ) }" - 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( %get_season_url(collection_season_26_url, 7) ) }" - 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( %get_season_url(collection_season_26_url, 8) ) }" - 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( %get_season_url(collection_season_26_url, 9) ) }" - 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( %get_season_url(collection_season_26_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_26_url), %bilateral_url) }" variables: collection_season_number: "26" collection_season_name: "{collection_season_26_name}" @@ -4455,78 +1150,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_27_url, 0) ) }" - 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( %get_season_url(collection_season_27_url, 1) ) }" - 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( %get_season_url(collection_season_27_url, 2) ) }" - 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( %get_season_url(collection_season_27_url, 3) ) }" - 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( %get_season_url(collection_season_27_url, 4) ) }" - 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( %get_season_url(collection_season_27_url, 5) ) }" - 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( %get_season_url(collection_season_27_url, 6) ) }" - 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( %get_season_url(collection_season_27_url, 7) ) }" - 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( %get_season_url(collection_season_27_url, 8) ) }" - 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( %get_season_url(collection_season_27_url, 9) ) }" - 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( %get_season_url(collection_season_27_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_27_url), %bilateral_url) }" variables: collection_season_number: "27" collection_season_name: "{collection_season_27_name}" @@ -4534,78 +1158,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_28_url, 0) ) }" - 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( %get_season_url(collection_season_28_url, 1) ) }" - 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( %get_season_url(collection_season_28_url, 2) ) }" - 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( %get_season_url(collection_season_28_url, 3) ) }" - 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( %get_season_url(collection_season_28_url, 4) ) }" - 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( %get_season_url(collection_season_28_url, 5) ) }" - 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( %get_season_url(collection_season_28_url, 6) ) }" - 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( %get_season_url(collection_season_28_url, 7) ) }" - 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( %get_season_url(collection_season_28_url, 8) ) }" - 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( %get_season_url(collection_season_28_url, 9) ) }" - 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( %get_season_url(collection_season_28_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_28_url), %bilateral_url) }" variables: collection_season_number: "28" collection_season_name: "{collection_season_28_name}" @@ -4613,78 +1166,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_29_url, 0) ) }" - 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( %get_season_url(collection_season_29_url, 1) ) }" - 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( %get_season_url(collection_season_29_url, 2) ) }" - 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( %get_season_url(collection_season_29_url, 3) ) }" - 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( %get_season_url(collection_season_29_url, 4) ) }" - 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( %get_season_url(collection_season_29_url, 5) ) }" - 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( %get_season_url(collection_season_29_url, 6) ) }" - 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( %get_season_url(collection_season_29_url, 7) ) }" - 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( %get_season_url(collection_season_29_url, 8) ) }" - 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( %get_season_url(collection_season_29_url, 9) ) }" - 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( %get_season_url(collection_season_29_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_29_url), %bilateral_url) }" variables: collection_season_number: "29" collection_season_name: "{collection_season_29_name}" @@ -4692,78 +1174,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_30_url, 0) ) }" - 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( %get_season_url(collection_season_30_url, 1) ) }" - 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( %get_season_url(collection_season_30_url, 2) ) }" - 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( %get_season_url(collection_season_30_url, 3) ) }" - 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( %get_season_url(collection_season_30_url, 4) ) }" - 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( %get_season_url(collection_season_30_url, 5) ) }" - 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( %get_season_url(collection_season_30_url, 6) ) }" - 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( %get_season_url(collection_season_30_url, 7) ) }" - 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( %get_season_url(collection_season_30_url, 8) ) }" - 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( %get_season_url(collection_season_30_url, 9) ) }" - 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( %get_season_url(collection_season_30_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_30_url), %bilateral_url) }" variables: collection_season_number: "30" collection_season_name: "{collection_season_30_name}" @@ -4771,78 +1182,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_31_url, 0) ) }" - 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( %get_season_url(collection_season_31_url, 1) ) }" - 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( %get_season_url(collection_season_31_url, 2) ) }" - 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( %get_season_url(collection_season_31_url, 3) ) }" - 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( %get_season_url(collection_season_31_url, 4) ) }" - 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( %get_season_url(collection_season_31_url, 5) ) }" - 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( %get_season_url(collection_season_31_url, 6) ) }" - 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( %get_season_url(collection_season_31_url, 7) ) }" - 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( %get_season_url(collection_season_31_url, 8) ) }" - 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( %get_season_url(collection_season_31_url, 9) ) }" - 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( %get_season_url(collection_season_31_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_31_url), %bilateral_url) }" variables: collection_season_number: "31" collection_season_name: "{collection_season_31_name}" @@ -4850,78 +1190,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_32_url, 0) ) }" - 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( %get_season_url(collection_season_32_url, 1) ) }" - 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( %get_season_url(collection_season_32_url, 2) ) }" - 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( %get_season_url(collection_season_32_url, 3) ) }" - 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( %get_season_url(collection_season_32_url, 4) ) }" - 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( %get_season_url(collection_season_32_url, 5) ) }" - 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( %get_season_url(collection_season_32_url, 6) ) }" - 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( %get_season_url(collection_season_32_url, 7) ) }" - 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( %get_season_url(collection_season_32_url, 8) ) }" - 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( %get_season_url(collection_season_32_url, 9) ) }" - 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( %get_season_url(collection_season_32_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_32_url), %bilateral_url) }" variables: collection_season_number: "32" collection_season_name: "{collection_season_32_name}" @@ -4929,78 +1198,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_33_url, 0) ) }" - 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( %get_season_url(collection_season_33_url, 1) ) }" - 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( %get_season_url(collection_season_33_url, 2) ) }" - 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( %get_season_url(collection_season_33_url, 3) ) }" - 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( %get_season_url(collection_season_33_url, 4) ) }" - 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( %get_season_url(collection_season_33_url, 5) ) }" - 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( %get_season_url(collection_season_33_url, 6) ) }" - 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( %get_season_url(collection_season_33_url, 7) ) }" - 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( %get_season_url(collection_season_33_url, 8) ) }" - 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( %get_season_url(collection_season_33_url, 9) ) }" - 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( %get_season_url(collection_season_33_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_33_url), %bilateral_url) }" variables: collection_season_number: "33" collection_season_name: "{collection_season_33_name}" @@ -5008,78 +1206,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_34_url, 0) ) }" - 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( %get_season_url(collection_season_34_url, 1) ) }" - 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( %get_season_url(collection_season_34_url, 2) ) }" - 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( %get_season_url(collection_season_34_url, 3) ) }" - 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( %get_season_url(collection_season_34_url, 4) ) }" - 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( %get_season_url(collection_season_34_url, 5) ) }" - 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( %get_season_url(collection_season_34_url, 6) ) }" - 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( %get_season_url(collection_season_34_url, 7) ) }" - 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( %get_season_url(collection_season_34_url, 8) ) }" - 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( %get_season_url(collection_season_34_url, 9) ) }" - 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( %get_season_url(collection_season_34_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_34_url), %bilateral_url) }" variables: collection_season_number: "34" collection_season_name: "{collection_season_34_name}" @@ -5087,77 +1214,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_35_url, 0) ) }" - 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( %get_season_url(collection_season_35_url, 1) ) }" - 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( %get_season_url(collection_season_35_url, 2) ) }" - 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( %get_season_url(collection_season_35_url, 3) ) }" - 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( %get_season_url(collection_season_35_url, 4) ) }" - 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( %get_season_url(collection_season_35_url, 5) ) }" - 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( %get_season_url(collection_season_35_url, 6) ) }" - 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( %get_season_url(collection_season_35_url, 7) ) }" - 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( %get_season_url(collection_season_35_url, 8) ) }" - 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( %get_season_url(collection_season_35_url, 9) ) }" - 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( %get_season_url(collection_season_35_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_35_url), %bilateral_url) }" variables: collection_season_number: "35" collection_season_name: "{collection_season_35_name}" @@ -5165,78 +1222,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_36_url, 0) ) }" - 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( %get_season_url(collection_season_36_url, 1) ) }" - 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( %get_season_url(collection_season_36_url, 2) ) }" - 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( %get_season_url(collection_season_36_url, 3) ) }" - 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( %get_season_url(collection_season_36_url, 4) ) }" - 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( %get_season_url(collection_season_36_url, 5) ) }" - 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( %get_season_url(collection_season_36_url, 6) ) }" - 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( %get_season_url(collection_season_36_url, 7) ) }" - 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( %get_season_url(collection_season_36_url, 8) ) }" - 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( %get_season_url(collection_season_36_url, 9) ) }" - 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( %get_season_url(collection_season_36_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_36_url), %bilateral_url) }" variables: collection_season_number: "36" collection_season_name: "{collection_season_36_name}" @@ -5244,77 +1230,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_37_url, 0) ) }" - 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( %get_season_url(collection_season_37_url, 1) ) }" - 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( %get_season_url(collection_season_37_url, 2) ) }" - 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( %get_season_url(collection_season_37_url, 3) ) }" - 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( %get_season_url(collection_season_37_url, 4) ) }" - 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( %get_season_url(collection_season_37_url, 5) ) }" - 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( %get_season_url(collection_season_37_url, 6) ) }" - 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( %get_season_url(collection_season_37_url, 7) ) }" - 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( %get_season_url(collection_season_37_url, 8) ) }" - 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( %get_season_url(collection_season_37_url, 9) ) }" - 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( %get_season_url(collection_season_37_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_37_url), %bilateral_url) }" variables: collection_season_number: "37" collection_season_name: "{collection_season_37_name}" @@ -5322,78 +1238,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_38_url, 0) ) }" - 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( %get_season_url(collection_season_38_url, 1) ) }" - 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( %get_season_url(collection_season_38_url, 2) ) }" - 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( %get_season_url(collection_season_38_url, 3) ) }" - 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( %get_season_url(collection_season_38_url, 4) ) }" - 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( %get_season_url(collection_season_38_url, 5) ) }" - 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( %get_season_url(collection_season_38_url, 6) ) }" - 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( %get_season_url(collection_season_38_url, 7) ) }" - 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( %get_season_url(collection_season_38_url, 8) ) }" - 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( %get_season_url(collection_season_38_url, 9) ) }" - 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( %get_season_url(collection_season_38_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_38_url), %bilateral_url) }" variables: collection_season_number: "38" collection_season_name: "{collection_season_38_name}" @@ -5401,78 +1246,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - - url: "{ %bilateral_url( %get_season_url(collection_season_39_url, 0) ) }" - 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( %get_season_url(collection_season_39_url, 1) ) }" - 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( %get_season_url(collection_season_39_url, 2) ) }" - 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( %get_season_url(collection_season_39_url, 3) ) }" - 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( %get_season_url(collection_season_39_url, 4) ) }" - 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( %get_season_url(collection_season_39_url, 5) ) }" - 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( %get_season_url(collection_season_39_url, 6) ) }" - 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( %get_season_url(collection_season_39_url, 7) ) }" - 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( %get_season_url(collection_season_39_url, 8) ) }" - 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( %get_season_url(collection_season_39_url, 9) ) }" - 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( %get_season_url(collection_season_39_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_39_url), %bilateral_url) }" variables: collection_season_number: "39" collection_season_name: "{collection_season_39_name}" @@ -5480,77 +1254,7 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 0) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 1) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 2) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 3) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 4) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 5) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 6) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 7) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 8) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 9) ) }" - variables: - collection_season_number: "40" - collection_season_name: "{collection_season_40_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_40_url, 10) ) }" + - url: "{ %array_apply( %get_season_urls(collection_season_40_url), %bilateral_url) }" variables: collection_season_number: "40" collection_season_name: "{collection_season_40_name}" @@ -5558,84 +1262,14 @@ presets: ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 0) ) }" + # Season 0 at end (to download first) + - url: "{ %array_apply( %get_season_urls(collection_season_0_url), %bilateral_url) }" variables: collection_season_number: "0" collection_season_name: "{collection_season_0_name}" download_reverse: False ytdl_options: playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 1) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 2) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 3) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 4) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 5) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 6) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 7) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 8) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 9) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - - url: "{ %bilateral_url( %get_season_url(collection_season_0_url, 10) ) }" - variables: - collection_season_number: "0" - collection_season_name: "{collection_season_0_name}" - download_reverse: False - ytdl_options: - playlist_items: "-1:0:-1" - _tv_show_collection_asserts: overrides: diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 5a44066e..1581c702 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import Any from typing import Dict from typing import Set from typing import Union @@ -89,6 +90,14 @@ class StringFormatterValidator(StringValidator): """ return resolved + def post_process_native(self, resolved: Any) -> Any: + """ + Returns + ------- + Apply any post processing to the resolved native value. + """ + return resolved + class FloatFormatterValidator(StringFormatterValidator): _expected_value_type_name = "float" diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index 7e14fe05..c48f4f0e 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -1,4 +1,3 @@ -import json import re from contextlib import contextmanager from pathlib import Path @@ -473,23 +472,19 @@ def test_advanced_tv_show_subscriptions( overrides = subs[5].overrides assert overrides.script.get("subscription_name").native == "Gardening with Ciscoe" - assert ( - overrides.apply_formatter(overrides.dict["url"]) - == "https://www.youtube.com/@gardeningwithciscoe4430" - ) - assert ( - overrides.apply_formatter(overrides.dict["url2"]) - == "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_" - ) - assert overrides.apply_formatter(overrides.dict["subscription_array"]) == json.dumps( - [ - "https://www.youtube.com/@gardeningwithciscoe4430", - "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_", - "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn", - "https://www.youtube.com/watch?v=2vq-vPubS5I", - ] - ) + assert overrides.apply_overrides_formatter_to_native(overrides.dict["subscription_array"]) == [ + "https://www.youtube.com/@gardeningwithciscoe4430", + "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_", + "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn", + "https://www.youtube.com/watch?v=2vq-vPubS5I", + ] + assert overrides.apply_overrides_formatter_to_native(overrides.dict["urls"]) == [ + "https://www.youtube.com/@gardeningwithciscoe4430", + "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_", + "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn", + "https://www.youtube.com/watch?v=2vq-vPubS5I", + ] def test_music_subscriptions(default_config: ConfigFile, music_subscriptions_path: Path): @@ -525,7 +520,7 @@ def test_music_video_subscriptions(default_config: ConfigFile, music_video_subsc ) assert jackson.get("subscription_indent_1").native == "Pop" assert ( - jackson.get("url").native + jackson.get("urls").native[0] == "https://www.youtube.com/playlist?list=OLAK5uy_mnY03zP6abNWH929q2XhGzWD_2uKJ_n8E" ) @@ -533,12 +528,10 @@ def test_music_video_subscriptions(default_config: ConfigFile, music_video_subsc gnr = subs[3].overrides.script assert gnr.get("subscription_name").native == "Guns N' Roses" - assert ( - gnr.get("url").native - == "https://www.youtube.com/playlist?list=PLOTK54q5K4INNXaHKtmXYr6J7CajWjqeJ" - ) + gnr_urls = gnr.get("urls").native + assert gnr_urls[0] == "https://www.youtube.com/playlist?list=PLOTK54q5K4INNXaHKtmXYr6J7CajWjqeJ" assert gnr.get("subscription_indent_1").native == "Rock" - assert gnr.get("url2").native == "https://www.youtube.com/watch?v=OldpIhHPsbs" + assert gnr_urls[1] == "https://www.youtube.com/watch?v=OldpIhHPsbs" def test_default_docker_config_and_subscriptions( diff --git a/tests/unit/prebuilt_presets/test_tv_show_by_date.py b/tests/unit/prebuilt_presets/test_tv_show_by_date.py index f8da99a7..115a2a61 100644 --- a/tests/unit/prebuilt_presets/test_tv_show_by_date.py +++ b/tests/unit/prebuilt_presets/test_tv_show_by_date.py @@ -24,3 +24,58 @@ class TestTvShowByDatePreset: "overrides": {"tv_show_directory": "abc", "s01_url": "test"}, }, ) + + def test_backward_compatibility_single(self, default_config): + a = Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": {"tv_show_directory": "abc", "url": "test_1"}, + }, + ) + + b = Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": {"tv_show_directory": "abc", "subscription_value": "test_1"}, + }, + ) + + assert a.resolved_yaml() == b.resolved_yaml() + + def test_backward_compatibility_multi(self, default_config): + a = Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": {"tv_show_directory": "abc", "url": "test_1", "url2": "test_2"}, + }, + ) + + b = Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": { + "tv_show_directory": "abc", + "subscription_array": ["test_1", "test_2"], + }, + }, + ) + + c = Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": {"tv_show_directory": "abc", "urls": ["test_1", "test_2"]}, + }, + ) + + assert a.resolved_yaml() == b.resolved_yaml() + assert a.resolved_yaml() == c.resolved_yaml() diff --git a/tests/unit/prebuilt_presets/test_tv_show_collection.py b/tests/unit/prebuilt_presets/test_tv_show_collection.py index f56755b8..07e30006 100644 --- a/tests/unit/prebuilt_presets/test_tv_show_collection.py +++ b/tests/unit/prebuilt_presets/test_tv_show_collection.py @@ -44,7 +44,7 @@ class TestTvShowCollectionPreset: preset_dict={"preset": "Jellyfin TV Show Collection", "overrides": overrides}, ) - assert len(sub.downloader_options.urls.list) == (num_seasons + 1) * num_urls_per_season * 2 + assert len(sub.downloader_options.urls.list) == (num_seasons + 1) * 3 url_list = sub.downloader_options.urls.list itr = 0 @@ -55,16 +55,20 @@ class TestTvShowCollectionPreset: if season_num == num_seasons + 1: season_num = 0 - for i in range(num_urls_per_season): - url = sub.overrides.apply_formatter( + # is_bilateral + if i == 0: + url = sub.overrides.apply_overrides_formatter_to_native( url_list[itr].url, function_overrides={ # mock so bilateral url gets enabled "subscription_has_download_archive": "True" }, ) + assert url == [ + f"youtube.com/playlist?url_{season_num}_{i}" + for i in range(num_urls_per_season) + ] variables = url_list[itr].variables.dict - assert url == f"youtube.com/playlist?url_{season_num}_{i}" assert ( sub.overrides.apply_formatter(variables["collection_season_number"]) == f"{season_num}" @@ -73,5 +77,35 @@ class TestTvShowCollectionPreset: sub.overrides.apply_formatter(variables["collection_season_name"]) == f"The Season {season_num}" ) - itr += 1 + # not bilateral + else: + for j in range(2): + url = sub.overrides.apply_overrides_formatter_to_native( + url_list[itr + j].url, + function_overrides={ + # mock so bilateral url gets enabled + "subscription_has_download_archive": "True" + }, + ) + + # First instance is the first url to get thumbnails + if j == 0: + assert url == [f"youtube.com/playlist?url_{season_num}_0"] + # Next one contains remaining urls + else: + assert url == [ + f"youtube.com/playlist?url_{season_num}_{i}" + for i in range(1, num_urls_per_season) + ] + + variables = url_list[itr].variables.dict + assert ( + sub.overrides.apply_formatter(variables["collection_season_number"]) + == f"{season_num}" + ) + assert ( + sub.overrides.apply_formatter(variables["collection_season_name"]) + == f"The Season {season_num}" + ) + itr += 2 From 6b99c31e45f6c0e2ee5d6acdc8d1d515b4d4e308 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Tue, 6 Jan 2026 12:58:37 -0800 Subject: [PATCH 24/46] [BACKEND] Simplify script quote generation (#1409) Internal script -> string generation used triple-quotes redundantly. Try to use them less to make it more human-readable. --- src/ytdl_sub/utils/script.py | 18 ++++++++++++- tests/unit/utils/test_script_utils.py | 26 ++++++++++++++++++- .../test_string_formatter_validator.py | 8 +++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/ytdl_sub/utils/script.py b/src/ytdl_sub/utils/script.py index 027a0bd8..8a1fc1b1 100644 --- a/src/ytdl_sub/utils/script.py +++ b/src/ytdl_sub/utils/script.py @@ -105,6 +105,19 @@ class ScriptUtils: raise UNREACHABLE + @classmethod + def _get_quote_char(cls, arg: str) -> str: + contains_single_quote = "'" in arg + contains_double_quote = '"' in arg + + if not contains_single_quote and not contains_double_quote: + return '"' + if not contains_single_quote and contains_double_quote: + return "'" + if contains_single_quote and not contains_double_quote: + return '"' + return "'''" + @classmethod def _to_script_code(cls, arg: Argument, top_level: bool = False) -> str: if not top_level and isinstance(arg, (Integer, Boolean, Float)): @@ -113,7 +126,10 @@ class ScriptUtils: if isinstance(arg, String): if arg.native == "": return "" if top_level else "''" - return arg.native if top_level else f"'''{arg.native}'''" + + quote = cls._get_quote_char(arg.native) + + return arg.native if top_level else f"{quote}{arg.native}{quote}" if isinstance(arg, Integer): out = f"%int({arg.native})" diff --git a/tests/unit/utils/test_script_utils.py b/tests/unit/utils/test_script_utils.py index f9d9b395..608066b5 100644 --- a/tests/unit/utils/test_script_utils.py +++ b/tests/unit/utils/test_script_utils.py @@ -18,6 +18,9 @@ class TestScriptUtils: "string": "value", "quotes": "has '' and \"\"", "triple-single-quote": "right here! '''''''''''''''''''''''''''''' ack '''''''", + "has-double-quotes": 'i got "some double quotes" in here', + "has-single-quotes": "i got 'some single quotes' in here", + "has-both-quotes": "i got 'both quotes\" in here", "int": 1, "bool": True, "list": [1, 2, 3], @@ -60,7 +63,15 @@ class TestScriptUtils: def test_to_syntax_tree(self): out = ScriptUtils.to_native_script( - {"{var_a}": "{var_b}", "static_a": "string with {var_c} in it"} + { + "{var_a}": "{var_b}", + "static_a": "string with {var_c} in it", + "quotes": "has '' and \"\"", + "triple-single-quote": "right here! '''''''''''''''''''''''''''''' ack '''''''", + "has-double-quotes": 'i got "some double quotes" in here', + "has-single-quotes": "i got 'some single quotes' in here", + "has-both-quotes": "i got 'both quotes\" in here", + } ) assert parse(out) == SyntaxTree( ast=[ @@ -75,6 +86,19 @@ class TestScriptUtils: BuiltInFunction(name="string", args=[String(value=" in it")]), ], ), + String(value="quotes"): String(value="has '' and \"\""), + String(value="triple-single-quote"): String( + value="right here! '''''''''''''''''''''''''''''' ack '''''''" + ), + String(value="has-double-quotes"): String( + value='i got "some double quotes" in here' + ), + String(value="has-single-quotes"): String( + value="i got 'some single quotes' in here" + ), + String(value="has-both-quotes"): String( + value="i got 'both quotes\" in here" + ), } ) ] diff --git a/tests/unit/validators/test_string_formatter_validator.py b/tests/unit/validators/test_string_formatter_validator.py index 4b2baef6..9bb4d1e0 100644 --- a/tests/unit/validators/test_string_formatter_validator.py +++ b/tests/unit/validators/test_string_formatter_validator.py @@ -115,12 +115,12 @@ class TestUnstructuredDictFormatterValidator(object): assert len(validator.dict) == 8 assert all(isinstance(val, expected_formatter_class) for val in validator.dict.values()) assert validator.dict_with_format_strings == { - "key1": "{ %concat( %string( '''string with ''' ), %string( variable ) ) }", + "key1": '{ %concat( %string( "string with " ), %string( variable ) ) }', "key2": "no variables", "key3": "{ %int(3) }", "key4": "{ %float(4.132) }", "key5": "{ %bool(True) }", - "key6": "{ { %concat( %string( variable ), %string( '''_key''' ) ): '''value''', '''static_key''': %concat( %string( variable ), %string( '''_value''' ) ) } }", - "key7": "{ [ '''list_1''', %concat( %string( '''list_''' ), %string( variable_2 ) ) ] }", - "key8": "{ %concat( %string( '''string ''' ), %string( variable1 ), %string( ''' with multiple ''' ), %string( variable2 ) ) }", + "key6": '{ { %concat( %string( variable ), %string( "_key" ) ): "value", "static_key": %concat( %string( variable ), %string( "_value" ) ) } }', + "key7": '{ [ "list_1", %concat( %string( "list_" ), %string( variable_2 ) ) ] }', + "key8": '{ %concat( %string( "string " ), %string( variable1 ), %string( " with multiple " ), %string( variable2 ) ) }', } From 3a07d058aabdacca57d38d72d6875140dabf0720 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:59:22 -0800 Subject: [PATCH 25/46] [DOCS] Update sphinx requirement from <9,>=7 to >=7,<10 (#1411) Updates the requirements on [sphinx](https://github.com/sphinx-doc/sphinx) to permit the latest version. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v7.0.0...v9.1.0) --- updated-dependencies: - dependency-name: sphinx dependency-version: 9.1.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 343e1a0a..94e18a34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ lint = [ "pylint==4.0.1", ] docs = [ - "sphinx>=7,<9", + "sphinx>=7,<10", "sphinx-rtd-theme>=2,<4", "sphinx-book-theme~=1.0", "sphinx-copybutton~=0.5", From 41a5bf60aa5e56f1d3252e73eba466fc38d749ec Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sun, 11 Jan 2026 15:47:50 -0800 Subject: [PATCH 26/46] [BACKEND] Remove cast and legacy_bracket_safety usage (#1414) Removes unused casts and calls to the legacy_bracket_safety function. --- src/ytdl_sub/entries/script/variable_types.py | 10 +--------- tests/unit/entries/conftest.py | 14 +++++++------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/ytdl_sub/entries/script/variable_types.py b/src/ytdl_sub/entries/script/variable_types.py index f406baf6..bbf994e9 100644 --- a/src/ytdl_sub/entries/script/variable_types.py +++ b/src/ytdl_sub/entries/script/variable_types.py @@ -22,7 +22,6 @@ VariableT = TypeVar("VariableT", bound="Variable") def _get( - cast: str, metadata_variable_name: str, metadata_key: str, variable_name: Optional[str], @@ -47,7 +46,7 @@ def _get( return as_type( variable_name=variable_name or metadata_key, metadata_key=metadata_key, - definition=f"{{ %legacy_bracket_safety(%{cast}({out})) }}", + definition=f"{{ {out} }}", ) @@ -182,7 +181,6 @@ class MapMetadataVariable(MetadataVariable, MapVariable): Creates a map variable from entry metadata """ return _get( - "map", metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME, metadata_key=metadata_key, variable_name=variable_name, @@ -204,7 +202,6 @@ class ArrayMetadataVariable(MetadataVariable, ArrayVariable): Creates an array variable from entry metadata """ return _get( - "array", metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME, metadata_key=metadata_key, variable_name=variable_name, @@ -226,7 +223,6 @@ class StringMetadataVariable(MetadataVariable, StringVariable): Creates a string variable from entry metadata """ return _get( - "string", metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME, metadata_key=metadata_key, variable_name=variable_name, @@ -245,7 +241,6 @@ class StringMetadataVariable(MetadataVariable, StringVariable): Creates a string variable from playlist metadata """ return _get( - "string", metadata_variable_name=PLAYLIST_METADATA_VARIABLE_NAME, metadata_key=metadata_key, variable_name=variable_name, @@ -264,7 +259,6 @@ class StringMetadataVariable(MetadataVariable, StringVariable): Creates a string variable from source metadata """ return _get( - "string", metadata_variable_name=SOURCE_METADATA_VARIABLE_NAME, metadata_key=metadata_key, variable_name=variable_name, @@ -301,7 +295,6 @@ class IntegerMetadataVariable(MetadataVariable, IntegerVariable): Creates an int variable from entry metadata """ return _get( - "int", metadata_variable_name=ENTRY_METADATA_VARIABLE_NAME, metadata_key=metadata_key, variable_name=variable_name, @@ -320,7 +313,6 @@ class IntegerMetadataVariable(MetadataVariable, IntegerVariable): Creates an int variable from playlist metadata """ return _get( - "int", metadata_variable_name=PLAYLIST_METADATA_VARIABLE_NAME, metadata_key=metadata_key, variable_name=variable_name, diff --git a/tests/unit/entries/conftest.py b/tests/unit/entries/conftest.py index 9421d35e..e3302010 100644 --- a/tests/unit/entries/conftest.py +++ b/tests/unit/entries/conftest.py @@ -65,8 +65,8 @@ def mock_entry_to_dict( "epoch": 1596878400, "epoch_date": "20200808", "epoch_hour": "09", - "title": "entry {title}", - "title_sanitized": "entry {title}", + "title": "entry {title}", + "title_sanitized": "entry {title}", "ext": ext, "description": "", "creator": "abc123", @@ -113,8 +113,8 @@ def mock_entry_to_dict( "playlist_count": 1, "playlist_max_upload_year": 2021, "playlist_max_upload_year_truncated": 21, - "playlist_title": "entry {title}", - "playlist_title_sanitized": "entry {title}", + "playlist_title": "entry {title}", + "playlist_title_sanitized": "entry {title}", "playlist_description": "", "playlist_webpage_url": "https://yourname.here", "playlist_uid": "abc123", @@ -126,15 +126,15 @@ def mock_entry_to_dict( "source_description": "", "source_index": 1, "source_index_padded": "01", - "source_title": "entry {title}", - "source_title_sanitized": "entry {title}", + "source_title": "entry {title}", + "source_title_sanitized": "entry {title}", "source_webpage_url": "https://yourname.here", "source_uid": "abc123", "source_uploader": "abc123", "source_uploader_id": "abc123", "source_uploader_url": "https://yourname.here", "uid_sanitized_plex": "abc123", - "title_sanitized_plex": "entry {title}", + "title_sanitized_plex": "entry {title}", "release_date": upload_date, "release_date_standardized": "2021-01-12", "release_year": 2021, From 530d2f7666a2d6f0350b6a0205566ff4c9c8eb3e Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Mon, 12 Jan 2026 17:34:34 -0800 Subject: [PATCH 27/46] [BACKEND] Partial resolve support for Scripts (#1410) Adds the mechanism to partially resolve an entire script (aka every underlying variable in ytdl-sub). This will be used in the upcoming `inspect` sub-command where it will dump a subscription's raw contents at multiple levels of resolution. Potentially useful to optimize script execution as well. --- src/ytdl_sub/script/script.py | 59 ++ src/ytdl_sub/script/types/array.py | 23 +- src/ytdl_sub/script/types/function.py | 179 +++++- src/ytdl_sub/script/types/map.py | 31 +- src/ytdl_sub/script/types/syntax_tree.py | 31 +- .../script/types/variable_dependency.py | 69 ++- src/ytdl_sub/utils/script.py | 39 +- tests/resources.py | 19 + .../music/inspect_full_overrides.json | 137 +++++ .../music/inspect_full_variables.json | 482 ++++++++++++++++ .../music/inspect_overrides.json | 137 +++++ .../music/inspect_variables.json | 482 ++++++++++++++++ .../music_video/inspect_full_overrides.json | 136 +++++ .../music_video/inspect_full_variables.json | 480 ++++++++++++++++ .../music_video/inspect_overrides.json | 136 +++++ .../music_video/inspect_variables.json | 480 ++++++++++++++++ .../tv_show/inspect_full_overrides.json | 155 ++++++ .../tv_show/inspect_full_variables.json | 520 ++++++++++++++++++ .../tv_show/inspect_overrides.json | 155 ++++++ .../tv_show/inspect_variables.json | 520 ++++++++++++++++++ tests/unit/config/test_subscription.py | 2 + .../test_subscription_partial_resolve.py | 118 ++++ 22 files changed, 4381 insertions(+), 9 deletions(-) create mode 100644 tests/resources/expected_json/music/inspect_full_overrides.json create mode 100644 tests/resources/expected_json/music/inspect_full_variables.json create mode 100644 tests/resources/expected_json/music/inspect_overrides.json create mode 100644 tests/resources/expected_json/music/inspect_variables.json create mode 100644 tests/resources/expected_json/music_video/inspect_full_overrides.json create mode 100644 tests/resources/expected_json/music_video/inspect_full_variables.json create mode 100644 tests/resources/expected_json/music_video/inspect_overrides.json create mode 100644 tests/resources/expected_json/music_video/inspect_variables.json create mode 100644 tests/resources/expected_json/tv_show/inspect_full_overrides.json create mode 100644 tests/resources/expected_json/tv_show/inspect_full_variables.json create mode 100644 tests/resources/expected_json/tv_show/inspect_overrides.json create mode 100644 tests/resources/expected_json/tv_show/inspect_variables.json create mode 100644 tests/unit/config/test_subscription_partial_resolve.py diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index 38e0346e..2e436e81 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -1,4 +1,5 @@ # pylint: disable=missing-raises-doc +import copy from typing import Dict from typing import List from typing import Optional @@ -7,6 +8,7 @@ from typing import Set from ytdl_sub.script.functions import Functions from ytdl_sub.script.parser import parse from ytdl_sub.script.script_output import ScriptOutput +from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import BuiltInFunctionType from ytdl_sub.script.types.resolvable import Lambda from ytdl_sub.script.types.resolvable import Resolvable @@ -14,6 +16,7 @@ from ytdl_sub.script.types.syntax_tree import ResolvedSyntaxTree from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable +from ytdl_sub.script.types.variable_dependency import VariableDependency from ytdl_sub.script.utils.exceptions import UNREACHABLE from ytdl_sub.script.utils.exceptions import CycleDetected from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments @@ -695,3 +698,59 @@ class Script: Names of all functions within the Script. """ return set(to_function_definition_name(name) for name in self._functions.keys()) + + def resolve_partial( + self, + unresolvable: Optional[Set[str]] = None, + ) -> "Script": + """ + Returns + ------- + New (deep-copied) script that resolves inner variables as much + as possible. + """ + unresolvable: Set[str] = unresolvable or {} + resolved: Dict[Variable, Resolvable] = {} + unresolved: Dict[Variable, Argument] = { + Variable(name): definition + for name, definition in self._variables.items() + if name not in unresolvable + } + + partially_resolved = True + while partially_resolved: + + partially_resolved = False + + for variable in list(unresolved.keys()): + definition = unresolved[variable] + + maybe_resolved = definition + if isinstance(definition, Variable) and definition.name not in unresolvable: + maybe_resolved = resolved.get(definition, unresolved[definition]) + elif isinstance(definition, VariableDependency): + maybe_resolved = definition.partial_resolve( + resolved_variables=resolved, + unresolved_variables=unresolved, + custom_functions=self._functions, + ) + + if isinstance(maybe_resolved, Resolvable): + resolved[variable] = maybe_resolved + del unresolved[variable] + partially_resolved = True + else: + unresolved[variable] = maybe_resolved + + # If the definition changed, then the script changed + # which means we can iterate again + partially_resolved |= definition != maybe_resolved + + return copy.deepcopy(self).add_parsed( + {var_name: self._variables[var_name] for var_name in unresolvable} + | { + var.name: ResolvedSyntaxTree(ast=[definition]) + for var, definition in resolved.items() + } + | {var.name: SyntaxTree(ast=[definition]) for var, definition in unresolved.items()} + ) diff --git a/src/ytdl_sub/script/types/array.py b/src/ytdl_sub/script/types/array.py index 8adcfe50..204697fb 100644 --- a/src/ytdl_sub/script/types/array.py +++ b/src/ytdl_sub/script/types/array.py @@ -28,7 +28,7 @@ class UnresolvedArray(_Array, VariableDependency, FutureResolvable): value: List[Argument] @property - def _iterable_arguments(self) -> List[Argument]: + def iterable_arguments(self) -> List[Argument]: return self.value def resolve( @@ -47,6 +47,27 @@ class UnresolvedArray(_Array, VariableDependency, FutureResolvable): ] ) + def partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, VariableDependency], + ) -> Argument | Resolvable: + maybe_resolvable_values, is_resolvable = VariableDependency.try_partial_resolve( + args=self.value, + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + out = UnresolvedArray(value=maybe_resolvable_values) + if is_resolvable: + return out.resolve( + resolved_variables=resolved_variables, custom_functions=custom_functions + ) + + return out + def future_resolvable_type(self) -> Type[Resolvable]: return Array diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index 5f33006e..d34c0a47 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from typing import Callable from typing import Dict from typing import List +from typing import Optional from typing import Type from typing import Union @@ -11,9 +12,11 @@ from ytdl_sub.script.functions import Functions from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.array import UnresolvedArray from ytdl_sub.script.types.resolvable import Argument +from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import BuiltInFunctionType from ytdl_sub.script.types.resolvable import FunctionType from ytdl_sub.script.types.resolvable import FutureResolvable +from ytdl_sub.script.types.resolvable import Integer from ytdl_sub.script.types.resolvable import Lambda from ytdl_sub.script.types.resolvable import NamedCustomFunction from ytdl_sub.script.types.resolvable import Resolvable @@ -35,7 +38,7 @@ from ytdl_sub.script.utils.type_checking import is_union @dataclass(frozen=True) class Function(FunctionType, VariableDependency, ABC): @property - def _iterable_arguments(self) -> List[Argument]: + def iterable_arguments(self) -> List[Argument]: return self.args @@ -84,6 +87,52 @@ class CustomFunction(Function, NamedCustomFunction): # been checked in the parser with raise UNREACHABLE + def partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, VariableDependency], + ) -> Argument | Resolvable: + maybe_resolvable_args, _ = VariableDependency.try_partial_resolve( + args=self.args, + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + for i in range(len(self.args)): + function_arg = FunctionArgument.from_idx(idx=i, custom_function_name=self.name) + function_value = maybe_resolvable_args[i] + + if isinstance(function_value, Resolvable): + resolved_variables[function_arg] = function_value + else: + unresolved_variables[function_arg] = function_value + + assert len(custom_functions[self.name].iterable_arguments) == 1 + custom_function_definition = custom_functions[self.name].iterable_arguments[0] + + maybe_resolvable_custom_function, is_resolvable = VariableDependency.try_partial_resolve( + args=[custom_function_definition], + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + for i in range(len(self.args)): + function_arg = FunctionArgument.from_idx(idx=i, custom_function_name=self.name) + + if isinstance(maybe_resolvable_args[i], Resolvable): + del resolved_variables[function_arg] + else: + del unresolved_variables[function_arg] + + if is_resolvable: + return maybe_resolvable_custom_function[0] + + # Did not resolve custom function arguments, do not proceed + return CustomFunction(name=self.name, args=maybe_resolvable_args) + class BuiltInFunction(Function, BuiltInFunctionType): def validate_args(self) -> "BuiltInFunction": @@ -312,5 +361,133 @@ class BuiltInFunction(Function, BuiltInFunctionType): f"Runtime error occurred when executing the function %{self.name}: {str(exc)}" ) from exc + def _partial_resolve_conditional( + self, + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, "VariableDependency"], + ): + """ + If the conditional partially resolvable enough to warrant evaluation, + perform it here. + """ + if self.is_subset_of( + variables=resolved_variables, custom_function_definitions=custom_functions + ): + return self.resolve( + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + if self.name == "if": + maybe_resolvable_arg, is_resolvable = VariableDependency.try_partial_resolve( + args=[self.args[0]], + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + if is_resolvable: + boolean_output = maybe_resolvable_arg[0] + assert isinstance(boolean_output, Boolean) + return self.args[1] if boolean_output.native else self.args[2] + + if self.name == "elif": + for idx in range(0, len(self.args), 2): + maybe_resolvable_arg, is_resolvable = VariableDependency.try_partial_resolve( + args=[self.args[idx]], + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + if is_resolvable: + boolean_output = maybe_resolvable_arg[0] + assert isinstance(boolean_output, Boolean) + if boolean_output.native: + return self.args[idx + 1] + else: + break + + if self.name == "assert_then": + maybe_resolvable_arg, is_resolvable = VariableDependency.try_partial_resolve( + args=[self.args[0]], + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + if is_resolvable: + boolean_output = maybe_resolvable_arg[0] + assert isinstance(boolean_output, Boolean) + if boolean_output.native: + return self.args[1] + + return self + + def _try_optimized_partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, "VariableDependency"], + ) -> Optional[Argument]: + """ + If a function has enough (but not all) resolved parameters to warrant a return, + perform it here. + """ + if self.name == "array_at": + if ( + isinstance(self.args[0], UnresolvedArray) + and isinstance(self.args[1], Integer) + and len(self.args[0].value) >= self.args[1].value + ): + maybe_resolvable_values, _ = VariableDependency.try_partial_resolve( + args=[self.args[0].value[self.args[1].value]], + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + return maybe_resolvable_values[0] + + return None + + def partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, VariableDependency], + ) -> Argument | Resolvable: + conditional_return_args = self.function_spec.conditional_arg_indices( + num_input_args=len(self.args) + ) + + if conditional_return_args: + return self._partial_resolve_conditional( + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + if partial_resolved := self._try_optimized_partial_resolve( + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ): + return partial_resolved + + maybe_resolvable_values, is_resolvable = VariableDependency.try_partial_resolve( + args=self.args, + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + out = BuiltInFunction(name=self.name, args=maybe_resolvable_values) + if is_resolvable: + return out.resolve( + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + return out + def __hash__(self): return hash((self.name, *self.args)) diff --git a/src/ytdl_sub/script/types/map.py b/src/ytdl_sub/script/types/map.py index 0fca1cbb..d5e2eb27 100644 --- a/src/ytdl_sub/script/types/map.py +++ b/src/ytdl_sub/script/types/map.py @@ -31,7 +31,7 @@ class UnresolvedMap(_Map, VariableDependency, FutureResolvable): value: Dict[Argument, Argument] @property - def _iterable_arguments(self) -> List[Argument]: + def iterable_arguments(self) -> List[Argument]: return list(itertools.chain(*self.value.items())) def resolve( @@ -55,6 +55,35 @@ class UnresolvedMap(_Map, VariableDependency, FutureResolvable): return Map(output) + def partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, VariableDependency], + ) -> Argument | Resolvable: + maybe_resolvable_keys, is_keys_resolvable = VariableDependency.try_partial_resolve( + args=self.value.keys(), + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + maybe_resolvable_values, is_values_resolvable = VariableDependency.try_partial_resolve( + args=self.value.values(), + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + out = UnresolvedMap(value=dict(zip(maybe_resolvable_keys, maybe_resolvable_values))) + if is_keys_resolvable and is_values_resolvable: + return out.resolve( + resolved_variables=resolved_variables, + custom_functions=custom_functions, + ) + + return out + def future_resolvable_type(self) -> Type[Resolvable]: return Map diff --git a/src/ytdl_sub/script/types/syntax_tree.py b/src/ytdl_sub/script/types/syntax_tree.py index eff39990..2a970300 100644 --- a/src/ytdl_sub/script/types/syntax_tree.py +++ b/src/ytdl_sub/script/types/syntax_tree.py @@ -3,6 +3,7 @@ from typing import Dict from typing import List from typing import Optional +from ytdl_sub.script.types.function import BuiltInFunction from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import String @@ -15,7 +16,7 @@ class SyntaxTree(VariableDependency): ast: List[Argument] @property - def _iterable_arguments(self) -> List[Argument]: + def iterable_arguments(self) -> List[Argument]: return self.ast def resolve( @@ -40,6 +41,26 @@ class SyntaxTree(VariableDependency): # Otherwise, to concat multiple resolved outputs, we must concat as strings return String("".join([str(res) for res in resolved])) + def partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, VariableDependency], + ) -> Argument | Resolvable: + # Ensure this does not get returned as a SyntaxTree since nesting them is not supported. + maybe_resolvable_values, _ = VariableDependency.try_partial_resolve( + args=self.ast, + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + # Mimic the above resolve behavior + if len(maybe_resolvable_values) > 1: + return BuiltInFunction(name="concat", args=maybe_resolvable_values) + + return maybe_resolvable_values[0] + @property def maybe_resolvable(self) -> Optional[Resolvable]: """ @@ -76,3 +97,11 @@ class ResolvedSyntaxTree(SyntaxTree): @property def maybe_resolvable(self) -> Optional[Resolvable]: return self.ast[0] + + def partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, VariableDependency], + ) -> Argument | Resolvable: + return self.ast[0] diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index 37130b7a..5cfee863 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -5,6 +5,7 @@ from typing import Dict from typing import Iterable from typing import List from typing import Set +from typing import Tuple from typing import Type from typing import TypeVar from typing import final @@ -27,7 +28,7 @@ TypeT = TypeVar("TypeT") class VariableDependency(ABC): @property @abstractmethod - def _iterable_arguments(self) -> List[Argument]: + def iterable_arguments(self) -> List[Argument]: """ Returns ------- @@ -38,7 +39,7 @@ class VariableDependency(ABC): self, ttype: Type[TypeT], subclass: bool = False, instance: bool = True ) -> List[TypeT]: output: List[TypeT] = [] - for arg in self._iterable_arguments: + for arg in self.iterable_arguments: if subclass and issubclass(type(arg), ttype): output.append(arg) elif instance and isinstance(arg, ttype): @@ -104,7 +105,7 @@ class VariableDependency(ABC): All CustomFunctions that this depends on. """ output: Set[ParsedCustomFunction] = set() - for arg in self._iterable_arguments: + for arg in self.iterable_arguments: if isinstance(arg, NamedCustomFunction): if not isinstance(arg, FunctionType): # A NamedCustomFunction should also always be a FunctionType @@ -138,6 +139,28 @@ class VariableDependency(ABC): Resolved value """ + @abstractmethod + def partial_resolve( + self, + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, "VariableDependency"], + ) -> Argument | Resolvable: + """ + Parameters + ---------- + resolved_variables + Lookup of variables that have been resolved + unresolved_variables + Lookup of variables that have not been resolved + custom_functions + Lookup of any custom functions that have been parsed + + Returns + ------- + Either a fully resolved value or partially resolved value of the same type. + """ + @classmethod def _resolve_argument_type( cls, @@ -222,3 +245,43 @@ class VariableDependency(ABC): ): return True return len(self.variables.intersection(variables)) > 0 + + @classmethod + def try_partial_resolve( + cls, + args: Iterable[Argument], + resolved_variables: Dict[Variable, Resolvable], + unresolved_variables: Dict[Variable, Argument], + custom_functions: Dict[str, "VariableDependency"], + ) -> Tuple[List[Argument], bool]: + """ + Attempts to resolve a list of arguments. Returns a tuple of them post partially resolved, + and a boolean indicating whether all of them are fully resolved. + """ + maybe_resolvable_args: List[Argument] = [] + is_resolvable = True + for arg in args: + maybe_resolvable_args.append(arg) + + if isinstance(arg, Lambda) and arg.value in custom_functions: + if not custom_functions[arg.value].is_subset_of( + variables=resolved_variables, + custom_function_definitions=custom_functions, + ): + is_resolvable = False + elif isinstance(arg, VariableDependency): + maybe_resolvable_args[-1] = arg.partial_resolve( + resolved_variables=resolved_variables, + unresolved_variables=unresolved_variables, + custom_functions=custom_functions, + ) + + if not isinstance(maybe_resolvable_args[-1], Resolvable): + is_resolvable = False + elif isinstance(arg, Variable): + if arg not in resolved_variables: + is_resolvable = False + if arg in unresolved_variables: + maybe_resolvable_args[-1] = unresolved_variables[arg] + + return maybe_resolvable_args, is_resolvable diff --git a/src/ytdl_sub/utils/script.py b/src/ytdl_sub/utils/script.py index 8a1fc1b1..90cb3aa3 100644 --- a/src/ytdl_sub/utils/script.py +++ b/src/ytdl_sub/utils/script.py @@ -2,11 +2,14 @@ import json import re from typing import Any from typing import Dict +from typing import Optional from ytdl_sub.script.parser import parse +from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.array import UnresolvedArray from ytdl_sub.script.types.function import BuiltInFunction from ytdl_sub.script.types.function import Function +from ytdl_sub.script.types.map import Map from ytdl_sub.script.types.map import UnresolvedMap from ytdl_sub.script.types.resolvable import Argument from ytdl_sub.script.types.resolvable import Boolean @@ -137,9 +140,9 @@ class ScriptUtils: out = f"%bool({arg.native})" elif isinstance(arg, Float): out = f"%float({arg.native})" - elif isinstance(arg, UnresolvedArray): + elif isinstance(arg, (Array, UnresolvedArray)): out = f"[ {', '.join(cls._to_script_code(val) for val in arg.value)} ]" - elif isinstance(arg, UnresolvedMap): + elif isinstance(arg, (Map, UnresolvedMap)): kv_list = ( f"{cls._to_script_code(key)}: {cls._to_script_code(val)}" for key, val in arg.value.items() @@ -155,11 +158,43 @@ class ScriptUtils: raise UNREACHABLE return f"{{ {out} }}" if top_level else out + @classmethod + def _is_top_level_string(cls, tree: SyntaxTree) -> Optional[str]: + if not ( + len(tree.ast) == 1 + and isinstance(tree.ast[0], BuiltInFunction) + and tree.ast[0].name == "concat" + ): + return None + + output = "" + for arg in tree.ast[0].args: + if isinstance(arg, BuiltInFunction) and arg.name == "string" and len(arg.args) == 1: + output += cls._to_script_code(arg.args[0], top_level=True) + else: + output += cls._to_script_code(arg, top_level=True) + + return output + + @classmethod + def _syntax_tree_to_native_script(cls, tree: SyntaxTree) -> str: + + if (output := cls._is_top_level_string(tree)) is not None: + return output + + output = "" + for arg in tree.ast: + output += cls._to_script_code(arg, top_level=True) + return output + @classmethod def to_native_script(cls, value: Any) -> str: """ Converts any JSON-compatible value into equivalent script syntax """ + if isinstance(value, SyntaxTree): + return cls._syntax_tree_to_native_script(value) + return cls._to_script_code(cls._to_script_argument(value), top_level=True) @classmethod diff --git a/tests/resources.py b/tests/resources.py index c1aa18d4..a327be02 100644 --- a/tests/resources.py +++ b/tests/resources.py @@ -1,18 +1,37 @@ +import json import os import shutil from pathlib import Path +from typing import Dict DISABLE_YOUTUBE_TESTS: bool = True REGENERATE_FIXTURES: bool = False RESOURCE_PATH: Path = Path("tests") / "resources" _FILE_FIXTURE_PATH: Path = RESOURCE_PATH / "file_fixtures" +_EXPECTED_JSON_PATH: Path = RESOURCE_PATH / "expected_json" def file_fixture_path(fixture_name: str) -> Path: return _FILE_FIXTURE_PATH / fixture_name +def expected_json(input_json: Dict, json_name: str) -> Dict: + expected_json_path = _EXPECTED_JSON_PATH / json_name + os.makedirs(os.path.dirname(expected_json_path), exist_ok=True) + + if REGENERATE_FIXTURES: + with open(expected_json_path, "w", encoding="utf-8") as json_file: + json.dump(input_json, json_file, sort_keys=True, indent=2) + + return input_json + + with open(expected_json_path, "r", encoding="utf-8") as json_file: + expected_json = json.load(json_file) + + return expected_json + + def copy_file_fixture(fixture_name: str, output_file_path: Path) -> None: os.makedirs(os.path.dirname(output_file_path), exist_ok=True) shutil.copy(file_fixture_path(fixture_name), output_file_path) diff --git a/tests/resources/expected_json/music/inspect_full_overrides.json b/tests/resources/expected_json/music/inspect_full_overrides.json new file mode 100644 index 00000000..ac17630f --- /dev/null +++ b/tests/resources/expected_json/music/inspect_full_overrides.json @@ -0,0 +1,137 @@ +{ + "album_cover_path": "Lester Young/{ %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }/folder.jpg", + "album_dir": "[{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }] { %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "artist_dir": "Lester Young", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "enable_resolution_assert": "{ %bool(True) }", + "enable_throttle_protection": "{ %bool(True) }", + "include_sibling_metadata": "{ %bool(True) }", + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "music_directory": "tv_show_directory_path", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "subscription_array": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", + "subscription_indent_1": "Jazz", + "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "track_album": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "track_album_artist": "Lester Young", + "track_artist": "Lester Young", + "track_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "track_file_name": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) } - { %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }.{ ext }", + "track_full_path": "Lester Young/{ %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }/{ %concat( %string( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ), \" - \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), \".\", %string( ext ) ) }", + "track_genre": "Jazz", + "track_genre_default": "Unset", + "track_number": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", + "track_number_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", + "track_original_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "track_title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "track_total": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", + "track_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", + "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }" +} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_full_variables.json b/tests/resources/expected_json/music/inspect_full_variables.json new file mode 100644 index 00000000..ca5513bf --- /dev/null +++ b/tests/resources/expected_json/music/inspect_full_variables.json @@ -0,0 +1,482 @@ +{ + "album_cover_path": "Lester Young/{ %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }/folder.jpg", + "album_cover_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %string( %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ), \"/folder.\", \"jpg\" ) ) }", + "album_dir": "[{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }] { %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "album_dir_sanitized": "{ %sanitize( %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) }", + "artist_dir": "Lester Young", + "artist_dir_sanitized": "Lester Young", + "avatar_uncropped_thumbnail_file_name": "", + "avatar_uncropped_thumbnail_file_name_sanitized": "", + "banner_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name_sanitized": "", + "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "channel_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "channel_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "chapters": "{ [ ] }", + "chapters_sanitized": "[]", + "comments": "{ [ ] }", + "comments_sanitized": "[]", + "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "creator_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "description_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", + "download_index": "{ %int(1) }", + "download_index_padded6": "000001", + "download_index_padded6_sanitized": "000001", + "download_index_sanitized": "1", + "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", + "duration_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"duration\", 0 ) ) }", + "enable_resolution_assert": "{ %bool(True) }", + "enable_resolution_assert_sanitized": "true", + "enable_throttle_protection": "{ %bool(True) }", + "enable_throttle_protection_sanitized": "true", + "entry_metadata": "{ { } }", + "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", + "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", + "epoch_date": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) }", + "epoch_date_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", + "epoch_hour": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) }", + "epoch_hour_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) ) }", + "epoch_sanitized": "{ %sanitize( %map_get( entry_metadata, \"epoch\" ) ) }", + "ext": "{ %throw( \"Plugin variable ext has not been created yet\" ) }", + "ext_sanitized": "{ %sanitize( ext ) }", + "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", + "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", + "extractor_key_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor_key\" ) ) }", + "extractor_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor\" ) ) }", + "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "height_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) }", + "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) }", + "ie_key_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) ) }", + "include_sibling_metadata": "{ %bool(True) }", + "include_sibling_metadata_sanitized": "true", + "info_json_ext": "info.json", + "info_json_ext_sanitized": "info.json", + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "modified_webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", + "music_directory": "tv_show_directory_path", + "music_directory_sanitized": "tv_show_directory_path", + "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", + "playlist_count_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) ) }", + "playlist_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", + "playlist_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", + "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", + "playlist_index_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", + "playlist_index_padded6": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) }", + "playlist_index_padded6_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) ) }", + "playlist_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ) }", + "playlist_index_reversed": "{ %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) }", + "playlist_index_reversed_padded": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) }", + "playlist_index_reversed_padded6": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) }", + "playlist_index_reversed_padded6_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) ) }", + "playlist_index_reversed_padded_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) ) }", + "playlist_index_reversed_sanitized": "{ %sanitize( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) ) }", + "playlist_index_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) ) }", + "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", + "playlist_max_upload_date_sanitized": "{ %sanitize( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ) }", + "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", + "playlist_max_upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ) }", + "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) }", + "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) ) }", + "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", + "playlist_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) ) }", + "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "playlist_title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "playlist_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) }", + "playlist_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "playlist_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "playlist_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "playlist_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "playlist_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", + "playlist_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "playlist_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", + "playlist_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", + "release_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ) }", + "release_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) }", + "release_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) ) }", + "release_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) }", + "release_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) }", + "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) }", + "release_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) ) }", + "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) }", + "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) }", + "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", + "release_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) ) }", + "release_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) ) }", + "release_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) }", + "release_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) ) }", + "release_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) }", + "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) }", + "release_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) ) }", + "release_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) ) }", + "release_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) ) }", + "release_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) }", + "release_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) }", + "release_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) ) }", + "release_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) }", + "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) }", + "release_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) ) }", + "release_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) ) }", + "release_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) ) }", + "release_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) }", + "release_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) ) }", + "release_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) }", + "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) }", + "release_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) ) }", + "release_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) ) }", + "requested_subtitles": "{ { } }", + "requested_subtitles_sanitized": "{}", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_height_gte_sanitized": "361", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_ignore_titles_sanitized": "[]", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", + "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_assert_print_sanitized": "true", + "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", + "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "resolution_readable_sanitized": "{ %sanitize( %concat( %string( %map_get_non_empty( entry_metadata, \"width\", 0 ) ), \"x\", %string( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) ) }", + "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", + "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", + "source_count": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) }", + "source_count_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) ) }", + "source_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", + "source_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) ) }", + "source_index": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) }", + "source_index_padded": "{ %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) }", + "source_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) ) }", + "source_index_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) ) }", + "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", + "source_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) ) }", + "source_title": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "source_title_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "source_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "source_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "source_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "source_uploader_id": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "source_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "source_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "source_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", + "source_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) ) }", + "source_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "source_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", + "sponsorblock_chapters": "{ [ ] }", + "sponsorblock_chapters_sanitized": "[]", + "subscription_array": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", + "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists\uff02]", + "subscription_has_download_archive": "{ %bool(False) }", + "subscription_has_download_archive_sanitized": "false", + "subscription_indent_1": "Jazz", + "subscription_indent_1_sanitized": "Jazz", + "subscription_name": "Lester Young", + "subscription_name_sanitized": "Lester Young", + "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", + "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", + "thumbnail_ext": "jpg", + "thumbnail_ext_sanitized": "jpg", + "title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "title_sanitized_plex": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "title_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "track_album": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "track_album_artist": "Lester Young", + "track_album_artist_sanitized": "Lester Young", + "track_album_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "track_artist": "Lester Young", + "track_artist_sanitized": "Lester Young", + "track_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "track_date_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", + "track_file_name": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) } - { %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }.{ ext }", + "track_file_name_sanitized": "{ %sanitize( %concat( %string( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ), \" - \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), \".\", %string( ext ) ) ) }", + "track_full_path": "Lester Young/{ %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }/{ %concat( %string( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ), \" - \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), \".\", %string( ext ) ) }", + "track_full_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %string( %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ), \"/\", %string( %concat( %string( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ), \" - \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), \".\", %string( ext ) ) ) ) ) }", + "track_genre": "Jazz", + "track_genre_default": "Unset", + "track_genre_default_sanitized": "Unset", + "track_genre_sanitized": "Jazz", + "track_number": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", + "track_number_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", + "track_number_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ) }", + "track_number_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) ) }", + "track_original_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "track_original_date_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", + "track_title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "track_title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "track_total": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", + "track_total_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) ) }", + "track_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", + "track_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ) }", + "uid": "{ %map_get( entry_metadata, \"id\" ) }", + "uid_sanitized": "{ %sanitize( %map_get( entry_metadata, \"id\" ) ) }", + "uid_sanitized_plex": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "uid_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", + "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", + "upload_date_index": "{ %int(1) }", + "upload_date_index_padded": "01", + "upload_date_index_padded_sanitized": "01", + "upload_date_index_reversed": "{ %int(99) }", + "upload_date_index_reversed_padded": "99", + "upload_date_index_reversed_padded_sanitized": "99", + "upload_date_index_reversed_sanitized": "99", + "upload_date_index_sanitized": "1", + "upload_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", + "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "upload_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", + "upload_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) }", + "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) }", + "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) }", + "upload_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) ) }", + "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) }", + "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) }", + "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", + "upload_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) ) }", + "upload_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) ) }", + "upload_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) }", + "upload_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) ) }", + "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) }", + "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) }", + "upload_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) ) }", + "upload_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) ) }", + "upload_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) ) }", + "upload_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }", + "upload_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) }", + "upload_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) ) }", + "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) }", + "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) }", + "upload_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) ) }", + "upload_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) ) }", + "upload_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) ) }", + "upload_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", + "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) }", + "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) }", + "upload_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) ) }", + "upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) ) }", + "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) }", + "uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "uploader_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", + "uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "url10": "", + "url100": "", + "url100_sanitized": "", + "url10_sanitized": "", + "url11": "", + "url11_sanitized": "", + "url12": "", + "url12_sanitized": "", + "url13": "", + "url13_sanitized": "", + "url14": "", + "url14_sanitized": "", + "url15": "", + "url15_sanitized": "", + "url16": "", + "url16_sanitized": "", + "url17": "", + "url17_sanitized": "", + "url18": "", + "url18_sanitized": "", + "url19": "", + "url19_sanitized": "", + "url2": "", + "url20": "", + "url20_sanitized": "", + "url21": "", + "url21_sanitized": "", + "url22": "", + "url22_sanitized": "", + "url23": "", + "url23_sanitized": "", + "url24": "", + "url24_sanitized": "", + "url25": "", + "url25_sanitized": "", + "url26": "", + "url26_sanitized": "", + "url27": "", + "url27_sanitized": "", + "url28": "", + "url28_sanitized": "", + "url29": "", + "url29_sanitized": "", + "url2_sanitized": "", + "url3": "", + "url30": "", + "url30_sanitized": "", + "url31": "", + "url31_sanitized": "", + "url32": "", + "url32_sanitized": "", + "url33": "", + "url33_sanitized": "", + "url34": "", + "url34_sanitized": "", + "url35": "", + "url35_sanitized": "", + "url36": "", + "url36_sanitized": "", + "url37": "", + "url37_sanitized": "", + "url38": "", + "url38_sanitized": "", + "url39": "", + "url39_sanitized": "", + "url3_sanitized": "", + "url4": "", + "url40": "", + "url40_sanitized": "", + "url41": "", + "url41_sanitized": "", + "url42": "", + "url42_sanitized": "", + "url43": "", + "url43_sanitized": "", + "url44": "", + "url44_sanitized": "", + "url45": "", + "url45_sanitized": "", + "url46": "", + "url46_sanitized": "", + "url47": "", + "url47_sanitized": "", + "url48": "", + "url48_sanitized": "", + "url49": "", + "url49_sanitized": "", + "url4_sanitized": "", + "url5": "", + "url50": "", + "url50_sanitized": "", + "url51": "", + "url51_sanitized": "", + "url52": "", + "url52_sanitized": "", + "url53": "", + "url53_sanitized": "", + "url54": "", + "url54_sanitized": "", + "url55": "", + "url55_sanitized": "", + "url56": "", + "url56_sanitized": "", + "url57": "", + "url57_sanitized": "", + "url58": "", + "url58_sanitized": "", + "url59": "", + "url59_sanitized": "", + "url5_sanitized": "", + "url6": "", + "url60": "", + "url60_sanitized": "", + "url61": "", + "url61_sanitized": "", + "url62": "", + "url62_sanitized": "", + "url63": "", + "url63_sanitized": "", + "url64": "", + "url64_sanitized": "", + "url65": "", + "url65_sanitized": "", + "url66": "", + "url66_sanitized": "", + "url67": "", + "url67_sanitized": "", + "url68": "", + "url68_sanitized": "", + "url69": "", + "url69_sanitized": "", + "url6_sanitized": "", + "url7": "", + "url70": "", + "url70_sanitized": "", + "url71": "", + "url71_sanitized": "", + "url72": "", + "url72_sanitized": "", + "url73": "", + "url73_sanitized": "", + "url74": "", + "url74_sanitized": "", + "url75": "", + "url75_sanitized": "", + "url76": "", + "url76_sanitized": "", + "url77": "", + "url77_sanitized": "", + "url78": "", + "url78_sanitized": "", + "url79": "", + "url79_sanitized": "", + "url7_sanitized": "", + "url8": "", + "url80": "", + "url80_sanitized": "", + "url81": "", + "url81_sanitized": "", + "url82": "", + "url82_sanitized": "", + "url83": "", + "url83_sanitized": "", + "url84": "", + "url84_sanitized": "", + "url85": "", + "url85_sanitized": "", + "url86": "", + "url86_sanitized": "", + "url87": "", + "url87_sanitized": "", + "url88": "", + "url88_sanitized": "", + "url89": "", + "url89_sanitized": "", + "url8_sanitized": "", + "url9": "", + "url90": "", + "url90_sanitized": "", + "url91": "", + "url91_sanitized": "", + "url92": "", + "url92_sanitized": "", + "url93": "", + "url93_sanitized": "", + "url94": "", + "url94_sanitized": "", + "url95": "", + "url95_sanitized": "", + "url96": "", + "url96_sanitized": "", + "url97": "", + "url97_sanitized": "", + "url98": "", + "url98_sanitized": "", + "url99": "", + "url99_sanitized": "", + "url9_sanitized": "", + "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", + "urls": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", + "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists\uff02]", + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", + "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", + "width_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"width\", 0 ) ) }", + "ytdl_sub_entry_date_eval": "{ %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", + "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) ) }", + "ytdl_sub_input_url": "", + "ytdl_sub_input_url_count": "{ %int(0) }", + "ytdl_sub_input_url_count_sanitized": "0", + "ytdl_sub_input_url_index": "{ %int(-1) }", + "ytdl_sub_input_url_index_sanitized": "-1", + "ytdl_sub_input_url_sanitized": "" +} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_overrides.json b/tests/resources/expected_json/music/inspect_overrides.json new file mode 100644 index 00000000..19490f00 --- /dev/null +++ b/tests/resources/expected_json/music/inspect_overrides.json @@ -0,0 +1,137 @@ +{ + "album_cover_path": "Lester Young/{ %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) }/folder.{ thumbnail_ext }", + "album_dir": "[{ playlist_max_upload_year }] { %sanitize( playlist_title ) }", + "artist_dir": "Lester Young", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "enable_resolution_assert": "{ %bool(True) }", + "enable_throttle_protection": "{ %bool(True) }", + "include_sibling_metadata": "{ %bool(True) }", + "modified_webpage_url": "{ webpage_url }", + "music_directory": "tv_show_directory_path", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_readable": "{ width }x{ height }", + "subscription_array": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", + "subscription_indent_1": "Jazz", + "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "track_album": "{ playlist_title }", + "track_album_artist": "Lester Young", + "track_artist": "Lester Young", + "track_date": "{ upload_date_standardized }", + "track_file_name": "{ playlist_index_padded } - { %sanitize( title ) }.{ ext }", + "track_full_path": "Lester Young/{ %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) }/{ %concat( %string( playlist_index_padded ), \" - \", %string( %sanitize( title ) ), \".\", %string( ext ) ) }", + "track_genre": "Jazz", + "track_genre_default": "Unset", + "track_number": "{ playlist_index }", + "track_number_padded": "{ playlist_index_padded }", + "track_original_date": "{ upload_date_standardized }", + "track_title": "{ title }", + "track_total": "{ playlist_count }", + "track_year": "{ playlist_max_upload_year }", + "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }" +} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_variables.json b/tests/resources/expected_json/music/inspect_variables.json new file mode 100644 index 00000000..131629d8 --- /dev/null +++ b/tests/resources/expected_json/music/inspect_variables.json @@ -0,0 +1,482 @@ +{ + "album_cover_path": "Lester Young/{ %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) }/folder.{ thumbnail_ext }", + "album_cover_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %string( %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) ), \"/folder.\", %string( thumbnail_ext ) ) ) }", + "album_dir": "[{ playlist_max_upload_year }] { %sanitize( playlist_title ) }", + "album_dir_sanitized": "{ %sanitize( %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) ) }", + "artist_dir": "Lester Young", + "artist_dir_sanitized": "Lester Young", + "avatar_uncropped_thumbnail_file_name": "", + "avatar_uncropped_thumbnail_file_name_sanitized": "", + "banner_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name_sanitized": "", + "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", uploader ) }", + "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", uploader_id ) }", + "channel_id_sanitized": "{ %sanitize( channel_id ) }", + "channel_sanitized": "{ %sanitize( channel ) }", + "chapters": "{ [ ] }", + "chapters_sanitized": "{ %sanitize( chapters ) }", + "comments": "{ [ ] }", + "comments_sanitized": "{ %sanitize( comments ) }", + "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", channel ) }", + "creator_sanitized": "{ %sanitize( creator ) }", + "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "description_sanitized": "{ %sanitize( description ) }", + "download_index": "{ %int( 1 ) }", + "download_index_padded6": "{ %pad_zero( download_index, 6 ) }", + "download_index_padded6_sanitized": "{ %sanitize( download_index_padded6 ) }", + "download_index_sanitized": "{ %sanitize( download_index ) }", + "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", + "duration_sanitized": "{ %sanitize( duration ) }", + "enable_resolution_assert": "{ %bool(True) }", + "enable_resolution_assert_sanitized": "true", + "enable_throttle_protection": "{ %bool(True) }", + "enable_throttle_protection_sanitized": "true", + "entry_metadata": "{ { } }", + "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", + "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", + "epoch_date": "{ %datetime_strftime( epoch, \"%Y%m%d\" ) }", + "epoch_date_sanitized": "{ %sanitize( epoch_date ) }", + "epoch_hour": "{ %datetime_strftime( epoch, \"%H\" ) }", + "epoch_hour_sanitized": "{ %sanitize( epoch_hour ) }", + "epoch_sanitized": "{ %sanitize( epoch ) }", + "ext": "{ %throw( \"Plugin variable ext has not been created yet\" ) }", + "ext_sanitized": "{ %sanitize( ext ) }", + "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", + "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", + "extractor_key_sanitized": "{ %sanitize( extractor_key ) }", + "extractor_sanitized": "{ %sanitize( extractor ) }", + "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "height_sanitized": "{ %sanitize( height ) }", + "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", extractor_key ) }", + "ie_key_sanitized": "{ %sanitize( ie_key ) }", + "include_sibling_metadata": "{ %bool(True) }", + "include_sibling_metadata_sanitized": "true", + "info_json_ext": "info.json", + "info_json_ext_sanitized": "info.json", + "modified_webpage_url": "{ webpage_url }", + "modified_webpage_url_sanitized": "{ %sanitize( webpage_url ) }", + "music_directory": "tv_show_directory_path", + "music_directory_sanitized": "tv_show_directory_path", + "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", + "playlist_count_sanitized": "{ %sanitize( playlist_count ) }", + "playlist_description": "{ %map_get_non_empty( playlist_metadata, \"description\", description ) }", + "playlist_description_sanitized": "{ %sanitize( playlist_description ) }", + "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", + "playlist_index_padded": "{ %pad_zero( playlist_index, 2 ) }", + "playlist_index_padded6": "{ %pad_zero( playlist_index, 6 ) }", + "playlist_index_padded6_sanitized": "{ %sanitize( playlist_index_padded6 ) }", + "playlist_index_padded_sanitized": "{ %sanitize( playlist_index_padded ) }", + "playlist_index_reversed": "{ %sub( playlist_count, playlist_index, -1 ) }", + "playlist_index_reversed_padded": "{ %pad_zero( playlist_index_reversed, 2 ) }", + "playlist_index_reversed_padded6": "{ %pad_zero( playlist_index_reversed, 6 ) }", + "playlist_index_reversed_padded6_sanitized": "{ %sanitize( playlist_index_reversed_padded6 ) }", + "playlist_index_reversed_padded_sanitized": "{ %sanitize( playlist_index_reversed_padded ) }", + "playlist_index_reversed_sanitized": "{ %sanitize( playlist_index_reversed ) }", + "playlist_index_sanitized": "{ %sanitize( playlist_index ) }", + "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", + "playlist_max_upload_date_sanitized": "{ %sanitize( playlist_max_upload_date ) }", + "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year\" ) ) }", + "playlist_max_upload_year_sanitized": "{ %sanitize( playlist_max_upload_year ) }", + "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year_truncated\" ) ) }", + "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( playlist_max_upload_year_truncated ) }", + "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", + "playlist_metadata_sanitized": "{ %sanitize( playlist_metadata ) }", + "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", title ) }", + "playlist_title_sanitized": "{ %sanitize( playlist_title ) }", + "playlist_uid": "{ %map_get_non_empty( playlist_metadata, \"playlist_id\", uid ) }", + "playlist_uid_sanitized": "{ %sanitize( playlist_uid ) }", + "playlist_uploader": "{ %map_get_non_empty( playlist_metadata, \"uploader\", uploader ) }", + "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", uploader_id ) }", + "playlist_uploader_id_sanitized": "{ %sanitize( playlist_uploader_id ) }", + "playlist_uploader_sanitized": "{ %sanitize( playlist_uploader ) }", + "playlist_uploader_url": "{ %map_get_non_empty( playlist_metadata, \"uploader_url\", webpage_url ) }", + "playlist_uploader_url_sanitized": "{ %sanitize( playlist_uploader_url ) }", + "playlist_webpage_url": "{ %map_get_non_empty( playlist_metadata, \"webpage_url\", webpage_url ) }", + "playlist_webpage_url_sanitized": "{ %sanitize( playlist_webpage_url ) }", + "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", upload_date ) }", + "release_date_sanitized": "{ %sanitize( release_date ) }", + "release_date_standardized": "{ %string( %map_get( %to_date_metadata( release_date ), \"date_standardized\" ) ) }", + "release_date_standardized_sanitized": "{ %sanitize( release_date_standardized ) }", + "release_day": "{ %int( %map_get( %to_date_metadata( release_date ), \"day\" ) ) }", + "release_day_of_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year\" ) ) }", + "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_padded\" ) ) }", + "release_day_of_year_padded_sanitized": "{ %sanitize( release_day_of_year_padded ) }", + "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed\" ) ) }", + "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed_padded\" ) ) }", + "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( release_day_of_year_reversed_padded ) }", + "release_day_of_year_reversed_sanitized": "{ %sanitize( release_day_of_year_reversed ) }", + "release_day_of_year_sanitized": "{ %sanitize( release_day_of_year ) }", + "release_day_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_padded\" ) ) }", + "release_day_padded_sanitized": "{ %sanitize( release_day_padded ) }", + "release_day_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_reversed\" ) ) }", + "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_reversed_padded\" ) ) }", + "release_day_reversed_padded_sanitized": "{ %sanitize( release_day_reversed_padded ) }", + "release_day_reversed_sanitized": "{ %sanitize( release_day_reversed ) }", + "release_day_sanitized": "{ %sanitize( release_day ) }", + "release_month": "{ %int( %map_get( %to_date_metadata( release_date ), \"month\" ) ) }", + "release_month_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_padded\" ) ) }", + "release_month_padded_sanitized": "{ %sanitize( release_month_padded ) }", + "release_month_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"month_reversed\" ) ) }", + "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_reversed_padded\" ) ) }", + "release_month_reversed_padded_sanitized": "{ %sanitize( release_month_reversed_padded ) }", + "release_month_reversed_sanitized": "{ %sanitize( release_month_reversed ) }", + "release_month_sanitized": "{ %sanitize( release_month ) }", + "release_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"year\" ) ) }", + "release_year_sanitized": "{ %sanitize( release_year ) }", + "release_year_truncated": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated\" ) ) }", + "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated_reversed\" ) ) }", + "release_year_truncated_reversed_sanitized": "{ %sanitize( release_year_truncated_reversed ) }", + "release_year_truncated_sanitized": "{ %sanitize( release_year_truncated ) }", + "requested_subtitles": "{ { } }", + "requested_subtitles_sanitized": "{ %sanitize( requested_subtitles ) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_height_gte_sanitized": "361", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_ignore_titles_sanitized": "[]", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", + "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_assert_print_sanitized": "true", + "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", + "resolution_readable": "{ width }x{ height }", + "resolution_readable_sanitized": "{ %sanitize( %concat( %string( width ), \"x\", %string( height ) ) ) }", + "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", + "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", + "source_count": "{ %map_get_non_empty( playlist_metadata, \"playlist_count\", 1 ) }", + "source_count_sanitized": "{ %sanitize( source_count ) }", + "source_description": "{ %map_get_non_empty( source_metadata, \"description\", playlist_description ) }", + "source_description_sanitized": "{ %sanitize( source_description ) }", + "source_index": "{ %map_get_non_empty( playlist_metadata, \"playlist_index\", 1 ) }", + "source_index_padded": "{ %pad_zero( source_index, 2 ) }", + "source_index_padded_sanitized": "{ %sanitize( source_index_padded ) }", + "source_index_sanitized": "{ %sanitize( source_index ) }", + "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", + "source_metadata_sanitized": "{ %sanitize( source_metadata ) }", + "source_title": "{ %map_get_non_empty( source_metadata, \"title\", playlist_title ) }", + "source_title_sanitized": "{ %sanitize( source_title ) }", + "source_uid": "{ %map_get_non_empty( source_metadata, \"id\", playlist_uid ) }", + "source_uid_sanitized": "{ %sanitize( source_uid ) }", + "source_uploader": "{ %map_get_non_empty( source_metadata, \"uploader\", playlist_uploader ) }", + "source_uploader_id": "{ %map_get_non_empty( source_metadata, \"uploader_id\", playlist_uploader_id ) }", + "source_uploader_id_sanitized": "{ %sanitize( source_uploader_id ) }", + "source_uploader_sanitized": "{ %sanitize( source_uploader ) }", + "source_uploader_url": "{ %map_get_non_empty( source_metadata, \"uploader_url\", source_webpage_url ) }", + "source_uploader_url_sanitized": "{ %sanitize( source_uploader_url ) }", + "source_webpage_url": "{ %map_get_non_empty( source_metadata, \"webpage_url\", playlist_webpage_url ) }", + "source_webpage_url_sanitized": "{ %sanitize( source_webpage_url ) }", + "sponsorblock_chapters": "{ [ ] }", + "sponsorblock_chapters_sanitized": "{ %sanitize( sponsorblock_chapters ) }", + "subscription_array": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", + "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists\uff02]", + "subscription_has_download_archive": "{ %bool(False) }", + "subscription_has_download_archive_sanitized": "false", + "subscription_indent_1": "Jazz", + "subscription_indent_1_sanitized": "Jazz", + "subscription_name": "Lester Young", + "subscription_name_sanitized": "Lester Young", + "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", + "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", + "thumbnail_ext": "jpg", + "thumbnail_ext_sanitized": "jpg", + "title": "{ %map_get_non_empty( entry_metadata, \"title\", uid ) }", + "title_sanitized": "{ %sanitize( title ) }", + "title_sanitized_plex": "{ %sanitize_plex_episode( title ) }", + "title_sanitized_plex_sanitized": "{ %sanitize( title_sanitized_plex ) }", + "track_album": "{ playlist_title }", + "track_album_artist": "Lester Young", + "track_album_artist_sanitized": "Lester Young", + "track_album_sanitized": "{ %sanitize( playlist_title ) }", + "track_artist": "Lester Young", + "track_artist_sanitized": "Lester Young", + "track_date": "{ upload_date_standardized }", + "track_date_sanitized": "{ %sanitize( upload_date_standardized ) }", + "track_file_name": "{ playlist_index_padded } - { %sanitize( title ) }.{ ext }", + "track_file_name_sanitized": "{ %sanitize( %concat( %string( playlist_index_padded ), \" - \", %string( %sanitize( title ) ), \".\", %string( ext ) ) ) }", + "track_full_path": "Lester Young/{ %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) }/{ %concat( %string( playlist_index_padded ), \" - \", %string( %sanitize( title ) ), \".\", %string( ext ) ) }", + "track_full_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %string( %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) ), \"/\", %string( %concat( %string( playlist_index_padded ), \" - \", %string( %sanitize( title ) ), \".\", %string( ext ) ) ) ) ) }", + "track_genre": "Jazz", + "track_genre_default": "Unset", + "track_genre_default_sanitized": "Unset", + "track_genre_sanitized": "Jazz", + "track_number": "{ playlist_index }", + "track_number_padded": "{ playlist_index_padded }", + "track_number_padded_sanitized": "{ %sanitize( playlist_index_padded ) }", + "track_number_sanitized": "{ %sanitize( playlist_index ) }", + "track_original_date": "{ upload_date_standardized }", + "track_original_date_sanitized": "{ %sanitize( upload_date_standardized ) }", + "track_title": "{ title }", + "track_title_sanitized": "{ %sanitize( title ) }", + "track_total": "{ playlist_count }", + "track_total_sanitized": "{ %sanitize( playlist_count ) }", + "track_year": "{ playlist_max_upload_year }", + "track_year_sanitized": "{ %sanitize( playlist_max_upload_year ) }", + "uid": "{ %map_get( entry_metadata, \"id\" ) }", + "uid_sanitized": "{ %sanitize( uid ) }", + "uid_sanitized_plex": "{ %sanitize_plex_episode( uid ) }", + "uid_sanitized_plex_sanitized": "{ %sanitize( uid_sanitized_plex ) }", + "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", epoch_date ) }", + "upload_date_index": "{ %int( 1 ) }", + "upload_date_index_padded": "{ %pad_zero( upload_date_index, 2 ) }", + "upload_date_index_padded_sanitized": "{ %sanitize( upload_date_index_padded ) }", + "upload_date_index_reversed": "{ %sub( 100, upload_date_index ) }", + "upload_date_index_reversed_padded": "{ %pad_zero( upload_date_index_reversed, 2 ) }", + "upload_date_index_reversed_padded_sanitized": "{ %sanitize( upload_date_index_reversed_padded ) }", + "upload_date_index_reversed_sanitized": "{ %sanitize( upload_date_index_reversed ) }", + "upload_date_index_sanitized": "{ %sanitize( upload_date_index ) }", + "upload_date_sanitized": "{ %sanitize( upload_date ) }", + "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( upload_date ), \"date_standardized\" ) ) }", + "upload_date_standardized_sanitized": "{ %sanitize( upload_date_standardized ) }", + "upload_day": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day\" ) ) }", + "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year\" ) ) }", + "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_padded\" ) ) }", + "upload_day_of_year_padded_sanitized": "{ %sanitize( upload_day_of_year_padded ) }", + "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed\" ) ) }", + "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed_padded\" ) ) }", + "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( upload_day_of_year_reversed_padded ) }", + "upload_day_of_year_reversed_sanitized": "{ %sanitize( upload_day_of_year_reversed ) }", + "upload_day_of_year_sanitized": "{ %sanitize( upload_day_of_year ) }", + "upload_day_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_padded\" ) ) }", + "upload_day_padded_sanitized": "{ %sanitize( upload_day_padded ) }", + "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_reversed\" ) ) }", + "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_reversed_padded\" ) ) }", + "upload_day_reversed_padded_sanitized": "{ %sanitize( upload_day_reversed_padded ) }", + "upload_day_reversed_sanitized": "{ %sanitize( upload_day_reversed ) }", + "upload_day_sanitized": "{ %sanitize( upload_day ) }", + "upload_month": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month\" ) ) }", + "upload_month_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_padded\" ) ) }", + "upload_month_padded_sanitized": "{ %sanitize( upload_month_padded ) }", + "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month_reversed\" ) ) }", + "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_reversed_padded\" ) ) }", + "upload_month_reversed_padded_sanitized": "{ %sanitize( upload_month_reversed_padded ) }", + "upload_month_reversed_sanitized": "{ %sanitize( upload_month_reversed ) }", + "upload_month_sanitized": "{ %sanitize( upload_month ) }", + "upload_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year\" ) ) }", + "upload_year_sanitized": "{ %sanitize( upload_year ) }", + "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated\" ) ) }", + "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated_reversed\" ) ) }", + "upload_year_truncated_reversed_sanitized": "{ %sanitize( upload_year_truncated_reversed ) }", + "upload_year_truncated_sanitized": "{ %sanitize( upload_year_truncated ) }", + "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", uploader_id ) }", + "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", uid ) }", + "uploader_id_sanitized": "{ %sanitize( uploader_id ) }", + "uploader_sanitized": "{ %sanitize( uploader ) }", + "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", webpage_url ) }", + "uploader_url_sanitized": "{ %sanitize( uploader_url ) }", + "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "url10": "", + "url100": "", + "url100_sanitized": "", + "url10_sanitized": "", + "url11": "", + "url11_sanitized": "", + "url12": "", + "url12_sanitized": "", + "url13": "", + "url13_sanitized": "", + "url14": "", + "url14_sanitized": "", + "url15": "", + "url15_sanitized": "", + "url16": "", + "url16_sanitized": "", + "url17": "", + "url17_sanitized": "", + "url18": "", + "url18_sanitized": "", + "url19": "", + "url19_sanitized": "", + "url2": "", + "url20": "", + "url20_sanitized": "", + "url21": "", + "url21_sanitized": "", + "url22": "", + "url22_sanitized": "", + "url23": "", + "url23_sanitized": "", + "url24": "", + "url24_sanitized": "", + "url25": "", + "url25_sanitized": "", + "url26": "", + "url26_sanitized": "", + "url27": "", + "url27_sanitized": "", + "url28": "", + "url28_sanitized": "", + "url29": "", + "url29_sanitized": "", + "url2_sanitized": "", + "url3": "", + "url30": "", + "url30_sanitized": "", + "url31": "", + "url31_sanitized": "", + "url32": "", + "url32_sanitized": "", + "url33": "", + "url33_sanitized": "", + "url34": "", + "url34_sanitized": "", + "url35": "", + "url35_sanitized": "", + "url36": "", + "url36_sanitized": "", + "url37": "", + "url37_sanitized": "", + "url38": "", + "url38_sanitized": "", + "url39": "", + "url39_sanitized": "", + "url3_sanitized": "", + "url4": "", + "url40": "", + "url40_sanitized": "", + "url41": "", + "url41_sanitized": "", + "url42": "", + "url42_sanitized": "", + "url43": "", + "url43_sanitized": "", + "url44": "", + "url44_sanitized": "", + "url45": "", + "url45_sanitized": "", + "url46": "", + "url46_sanitized": "", + "url47": "", + "url47_sanitized": "", + "url48": "", + "url48_sanitized": "", + "url49": "", + "url49_sanitized": "", + "url4_sanitized": "", + "url5": "", + "url50": "", + "url50_sanitized": "", + "url51": "", + "url51_sanitized": "", + "url52": "", + "url52_sanitized": "", + "url53": "", + "url53_sanitized": "", + "url54": "", + "url54_sanitized": "", + "url55": "", + "url55_sanitized": "", + "url56": "", + "url56_sanitized": "", + "url57": "", + "url57_sanitized": "", + "url58": "", + "url58_sanitized": "", + "url59": "", + "url59_sanitized": "", + "url5_sanitized": "", + "url6": "", + "url60": "", + "url60_sanitized": "", + "url61": "", + "url61_sanitized": "", + "url62": "", + "url62_sanitized": "", + "url63": "", + "url63_sanitized": "", + "url64": "", + "url64_sanitized": "", + "url65": "", + "url65_sanitized": "", + "url66": "", + "url66_sanitized": "", + "url67": "", + "url67_sanitized": "", + "url68": "", + "url68_sanitized": "", + "url69": "", + "url69_sanitized": "", + "url6_sanitized": "", + "url7": "", + "url70": "", + "url70_sanitized": "", + "url71": "", + "url71_sanitized": "", + "url72": "", + "url72_sanitized": "", + "url73": "", + "url73_sanitized": "", + "url74": "", + "url74_sanitized": "", + "url75": "", + "url75_sanitized": "", + "url76": "", + "url76_sanitized": "", + "url77": "", + "url77_sanitized": "", + "url78": "", + "url78_sanitized": "", + "url79": "", + "url79_sanitized": "", + "url7_sanitized": "", + "url8": "", + "url80": "", + "url80_sanitized": "", + "url81": "", + "url81_sanitized": "", + "url82": "", + "url82_sanitized": "", + "url83": "", + "url83_sanitized": "", + "url84": "", + "url84_sanitized": "", + "url85": "", + "url85_sanitized": "", + "url86": "", + "url86_sanitized": "", + "url87": "", + "url87_sanitized": "", + "url88": "", + "url88_sanitized": "", + "url89": "", + "url89_sanitized": "", + "url8_sanitized": "", + "url9": "", + "url90": "", + "url90_sanitized": "", + "url91": "", + "url91_sanitized": "", + "url92": "", + "url92_sanitized": "", + "url93": "", + "url93_sanitized": "", + "url94": "", + "url94_sanitized": "", + "url95": "", + "url95_sanitized": "", + "url96": "", + "url96_sanitized": "", + "url97": "", + "url97_sanitized": "", + "url98": "", + "url98_sanitized": "", + "url99": "", + "url99_sanitized": "", + "url9_sanitized": "", + "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", + "urls": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", + "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists\uff02]", + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "webpage_url_sanitized": "{ %sanitize( webpage_url ) }", + "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", + "width_sanitized": "{ %sanitize( width ) }", + "ytdl_sub_entry_date_eval": "{ %string( upload_date_standardized ) }", + "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( ytdl_sub_entry_date_eval ) }", + "ytdl_sub_input_url": "{ %string( '' ) }", + "ytdl_sub_input_url_count": "{ %int( 0 ) }", + "ytdl_sub_input_url_count_sanitized": "{ %sanitize( ytdl_sub_input_url_count ) }", + "ytdl_sub_input_url_index": "{ %int( -1 ) }", + "ytdl_sub_input_url_index_sanitized": "{ %sanitize( ytdl_sub_input_url_index ) }", + "ytdl_sub_input_url_sanitized": "{ %sanitize( ytdl_sub_input_url ) }" +} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_full_overrides.json b/tests/resources/expected_json/music_video/inspect_full_overrides.json new file mode 100644 index 00000000..0ad15d9a --- /dev/null +++ b/tests/resources/expected_json/music_video/inspect_full_overrides.json @@ -0,0 +1,136 @@ +{ + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "category_url_array": "{ [ ] }", + "category_url_map": "{ [ ] }", + "enable_bilateral_scraping": "{ %bool(True) }", + "enable_resolution_assert": "{ %bool(True) }", + "enable_throttle_protection": "{ %bool(True) }", + "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "include_sibling_metadata": "{ %bool(False) }", + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "music_video_album": "{ %get_url_field( \"category\", music_video_album_default ) }", + "music_video_album_default": "Music Videos", + "music_video_artist": "Rick Astley", + "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", + "music_video_directory": "tv_show_directory_path", + "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "music_video_file_name_suffix": "", + "music_video_genre": "Pop", + "music_video_genre_default": "ytdl-sub", + "music_video_title": "{ %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "subscription_array": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\" ] }", + "subscription_indent_1": "Pop", + "subscription_map": "{ { } }", + "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] }" +} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_full_variables.json b/tests/resources/expected_json/music_video/inspect_full_variables.json new file mode 100644 index 00000000..fa8b5c85 --- /dev/null +++ b/tests/resources/expected_json/music_video/inspect_full_variables.json @@ -0,0 +1,480 @@ +{ + "avatar_uncropped_thumbnail_file_name": "", + "avatar_uncropped_thumbnail_file_name_sanitized": "", + "banner_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name_sanitized": "", + "category_url_array": "{ [ ] }", + "category_url_array_sanitized": "[]", + "category_url_map": "{ [ ] }", + "category_url_map_sanitized": "[]", + "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "channel_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "channel_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "chapters": "{ [ ] }", + "chapters_sanitized": "[]", + "comments": "{ [ ] }", + "comments_sanitized": "[]", + "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "creator_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "description_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", + "download_index": "{ %int(1) }", + "download_index_padded6": "000001", + "download_index_padded6_sanitized": "000001", + "download_index_sanitized": "1", + "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", + "duration_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"duration\", 0 ) ) }", + "enable_bilateral_scraping": "{ %bool(True) }", + "enable_bilateral_scraping_sanitized": "true", + "enable_resolution_assert": "{ %bool(True) }", + "enable_resolution_assert_sanitized": "true", + "enable_throttle_protection": "{ %bool(True) }", + "enable_throttle_protection_sanitized": "true", + "entry_metadata": "{ { } }", + "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", + "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", + "epoch_date": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) }", + "epoch_date_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", + "epoch_hour": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) }", + "epoch_hour_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) ) }", + "epoch_sanitized": "{ %sanitize( %map_get( entry_metadata, \"epoch\" ) ) }", + "ext": "{ %map_get( entry_metadata, \"ext\" ) }", + "ext_sanitized": "{ %sanitize( %map_get( entry_metadata, \"ext\" ) ) }", + "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", + "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", + "extractor_key_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor_key\" ) ) }", + "extractor_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor\" ) ) }", + "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "file_title_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "file_uid_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", + "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "height_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) }", + "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) }", + "ie_key_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) ) }", + "include_sibling_metadata": "{ %bool(False) }", + "include_sibling_metadata_sanitized": "false", + "info_json_ext": "info.json", + "info_json_ext_sanitized": "info.json", + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "modified_webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", + "music_video_album": "{ %get_url_field( \"category\", music_video_album_default ) }", + "music_video_album_default": "Music Videos", + "music_video_album_default_sanitized": "Music Videos", + "music_video_album_sanitized": "{ %sanitize( %get_url_field( \"category\", music_video_album_default ) ) }", + "music_video_artist": "Rick Astley", + "music_video_artist_sanitized": "Rick Astley", + "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", + "music_video_date_sanitized": "{ %sanitize( %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) ) }", + "music_video_directory": "tv_show_directory_path", + "music_video_directory_sanitized": "tv_show_directory_path", + "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "music_video_file_name_sanitized": "{ %sanitize( %concat( \"Rick Astley\", \"/\", %string( %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ), '' ) ) }", + "music_video_file_name_suffix": "", + "music_video_file_name_suffix_sanitized": "", + "music_video_genre": "Pop", + "music_video_genre_default": "ytdl-sub", + "music_video_genre_default_sanitized": "ytdl-sub", + "music_video_genre_sanitized": "Pop", + "music_video_title": "{ %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "music_video_title_sanitized": "{ %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", + "music_video_year_sanitized": "{ %sanitize( %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) ) }", + "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", + "playlist_count_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) ) }", + "playlist_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", + "playlist_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", + "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", + "playlist_index_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", + "playlist_index_padded6": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) }", + "playlist_index_padded6_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) ) }", + "playlist_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ) }", + "playlist_index_reversed": "{ %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) }", + "playlist_index_reversed_padded": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) }", + "playlist_index_reversed_padded6": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) }", + "playlist_index_reversed_padded6_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) ) }", + "playlist_index_reversed_padded_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) ) }", + "playlist_index_reversed_sanitized": "{ %sanitize( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) ) }", + "playlist_index_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) ) }", + "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", + "playlist_max_upload_date_sanitized": "{ %sanitize( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ) }", + "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", + "playlist_max_upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ) }", + "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) }", + "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) ) }", + "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", + "playlist_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) ) }", + "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "playlist_title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "playlist_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) }", + "playlist_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "playlist_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "playlist_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "playlist_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "playlist_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", + "playlist_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "playlist_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", + "playlist_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", + "release_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ) }", + "release_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) }", + "release_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) ) }", + "release_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) }", + "release_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) }", + "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) }", + "release_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) ) }", + "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) }", + "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) }", + "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", + "release_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) ) }", + "release_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) ) }", + "release_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) }", + "release_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) ) }", + "release_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) }", + "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) }", + "release_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) ) }", + "release_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) ) }", + "release_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) ) }", + "release_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) }", + "release_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) }", + "release_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) ) }", + "release_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) }", + "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) }", + "release_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) ) }", + "release_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) ) }", + "release_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) ) }", + "release_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) }", + "release_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) ) }", + "release_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) }", + "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) }", + "release_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) ) }", + "release_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) ) }", + "requested_subtitles": "{ { } }", + "requested_subtitles_sanitized": "{}", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_height_gte_sanitized": "361", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_ignore_titles_sanitized": "[]", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", + "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_assert_print_sanitized": "true", + "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", + "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "resolution_readable_sanitized": "{ %sanitize( %concat( %string( %map_get_non_empty( entry_metadata, \"width\", 0 ) ), \"x\", %string( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) ) }", + "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", + "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", + "source_count": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) }", + "source_count_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) ) }", + "source_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", + "source_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) ) }", + "source_index": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) }", + "source_index_padded": "{ %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) }", + "source_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) ) }", + "source_index_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) ) }", + "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", + "source_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) ) }", + "source_title": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "source_title_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "source_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "source_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "source_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "source_uploader_id": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "source_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "source_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "source_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", + "source_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) ) }", + "source_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "source_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", + "sponsorblock_chapters": "{ [ ] }", + "sponsorblock_chapters_sanitized": "[]", + "subscription_array": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\" ] }", + "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\uff02]", + "subscription_has_download_archive": "{ %bool(False) }", + "subscription_has_download_archive_sanitized": "false", + "subscription_indent_1": "Pop", + "subscription_indent_1_sanitized": "Pop", + "subscription_map": "{ { } }", + "subscription_map_sanitized": "{}", + "subscription_name": "Rick Astley", + "subscription_name_sanitized": "Rick Astley", + "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "thumbnail_ext": "jpg", + "thumbnail_ext_sanitized": "jpg", + "title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "title_sanitized_plex": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "title_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "uid": "{ %map_get( entry_metadata, \"id\" ) }", + "uid_sanitized": "{ %sanitize( %map_get( entry_metadata, \"id\" ) ) }", + "uid_sanitized_plex": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "uid_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", + "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", + "upload_date_index": "{ %int(1) }", + "upload_date_index_padded": "01", + "upload_date_index_padded_sanitized": "01", + "upload_date_index_reversed": "{ %int(99) }", + "upload_date_index_reversed_padded": "99", + "upload_date_index_reversed_padded_sanitized": "99", + "upload_date_index_reversed_sanitized": "99", + "upload_date_index_sanitized": "1", + "upload_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", + "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "upload_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", + "upload_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) }", + "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) }", + "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) }", + "upload_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) ) }", + "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) }", + "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) }", + "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", + "upload_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) ) }", + "upload_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) ) }", + "upload_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) }", + "upload_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) ) }", + "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) }", + "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) }", + "upload_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) ) }", + "upload_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) ) }", + "upload_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) ) }", + "upload_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }", + "upload_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) }", + "upload_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) ) }", + "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) }", + "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) }", + "upload_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) ) }", + "upload_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) ) }", + "upload_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) ) }", + "upload_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", + "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) }", + "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) }", + "upload_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) ) }", + "upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) ) }", + "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) }", + "uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "uploader_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", + "uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url10": "", + "url100": "", + "url100_sanitized": "", + "url10_sanitized": "", + "url11": "", + "url11_sanitized": "", + "url12": "", + "url12_sanitized": "", + "url13": "", + "url13_sanitized": "", + "url14": "", + "url14_sanitized": "", + "url15": "", + "url15_sanitized": "", + "url16": "", + "url16_sanitized": "", + "url17": "", + "url17_sanitized": "", + "url18": "", + "url18_sanitized": "", + "url19": "", + "url19_sanitized": "", + "url2": "", + "url20": "", + "url20_sanitized": "", + "url21": "", + "url21_sanitized": "", + "url22": "", + "url22_sanitized": "", + "url23": "", + "url23_sanitized": "", + "url24": "", + "url24_sanitized": "", + "url25": "", + "url25_sanitized": "", + "url26": "", + "url26_sanitized": "", + "url27": "", + "url27_sanitized": "", + "url28": "", + "url28_sanitized": "", + "url29": "", + "url29_sanitized": "", + "url2_sanitized": "", + "url3": "", + "url30": "", + "url30_sanitized": "", + "url31": "", + "url31_sanitized": "", + "url32": "", + "url32_sanitized": "", + "url33": "", + "url33_sanitized": "", + "url34": "", + "url34_sanitized": "", + "url35": "", + "url35_sanitized": "", + "url36": "", + "url36_sanitized": "", + "url37": "", + "url37_sanitized": "", + "url38": "", + "url38_sanitized": "", + "url39": "", + "url39_sanitized": "", + "url3_sanitized": "", + "url4": "", + "url40": "", + "url40_sanitized": "", + "url41": "", + "url41_sanitized": "", + "url42": "", + "url42_sanitized": "", + "url43": "", + "url43_sanitized": "", + "url44": "", + "url44_sanitized": "", + "url45": "", + "url45_sanitized": "", + "url46": "", + "url46_sanitized": "", + "url47": "", + "url47_sanitized": "", + "url48": "", + "url48_sanitized": "", + "url49": "", + "url49_sanitized": "", + "url4_sanitized": "", + "url5": "", + "url50": "", + "url50_sanitized": "", + "url51": "", + "url51_sanitized": "", + "url52": "", + "url52_sanitized": "", + "url53": "", + "url53_sanitized": "", + "url54": "", + "url54_sanitized": "", + "url55": "", + "url55_sanitized": "", + "url56": "", + "url56_sanitized": "", + "url57": "", + "url57_sanitized": "", + "url58": "", + "url58_sanitized": "", + "url59": "", + "url59_sanitized": "", + "url5_sanitized": "", + "url6": "", + "url60": "", + "url60_sanitized": "", + "url61": "", + "url61_sanitized": "", + "url62": "", + "url62_sanitized": "", + "url63": "", + "url63_sanitized": "", + "url64": "", + "url64_sanitized": "", + "url65": "", + "url65_sanitized": "", + "url66": "", + "url66_sanitized": "", + "url67": "", + "url67_sanitized": "", + "url68": "", + "url68_sanitized": "", + "url69": "", + "url69_sanitized": "", + "url6_sanitized": "", + "url7": "", + "url70": "", + "url70_sanitized": "", + "url71": "", + "url71_sanitized": "", + "url72": "", + "url72_sanitized": "", + "url73": "", + "url73_sanitized": "", + "url74": "", + "url74_sanitized": "", + "url75": "", + "url75_sanitized": "", + "url76": "", + "url76_sanitized": "", + "url77": "", + "url77_sanitized": "", + "url78": "", + "url78_sanitized": "", + "url79": "", + "url79_sanitized": "", + "url7_sanitized": "", + "url8": "", + "url80": "", + "url80_sanitized": "", + "url81": "", + "url81_sanitized": "", + "url82": "", + "url82_sanitized": "", + "url83": "", + "url83_sanitized": "", + "url84": "", + "url84_sanitized": "", + "url85": "", + "url85_sanitized": "", + "url86": "", + "url86_sanitized": "", + "url87": "", + "url87_sanitized": "", + "url88": "", + "url88_sanitized": "", + "url89": "", + "url89_sanitized": "", + "url8_sanitized": "", + "url9": "", + "url90": "", + "url90_sanitized": "", + "url91": "", + "url91_sanitized": "", + "url92": "", + "url92_sanitized": "", + "url93": "", + "url93_sanitized": "", + "url94": "", + "url94_sanitized": "", + "url95": "", + "url95_sanitized": "", + "url96": "", + "url96_sanitized": "", + "url97": "", + "url97_sanitized": "", + "url98": "", + "url98_sanitized": "", + "url99": "", + "url99_sanitized": "", + "url9_sanitized": "", + "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "urls": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] }", + "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02]", + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", + "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", + "width_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"width\", 0 ) ) }", + "ytdl_sub_entry_date_eval": "{ %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", + "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) ) }", + "ytdl_sub_input_url": "", + "ytdl_sub_input_url_count": "{ %int(0) }", + "ytdl_sub_input_url_count_sanitized": "0", + "ytdl_sub_input_url_index": "{ %int(-1) }", + "ytdl_sub_input_url_index_sanitized": "-1", + "ytdl_sub_input_url_sanitized": "" +} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_overrides.json b/tests/resources/expected_json/music_video/inspect_overrides.json new file mode 100644 index 00000000..a0b3c3a1 --- /dev/null +++ b/tests/resources/expected_json/music_video/inspect_overrides.json @@ -0,0 +1,136 @@ +{ + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "category_url_array": "{ [ ] }", + "category_url_map": "{ [ ] }", + "enable_bilateral_scraping": "{ %bool(True) }", + "enable_resolution_assert": "{ %bool(True) }", + "enable_throttle_protection": "{ %bool(True) }", + "file_title": "{ title_sanitized_plex }", + "file_uid": "{ uid_sanitized_plex }", + "include_sibling_metadata": "{ %bool(False) }", + "modified_webpage_url": "{ webpage_url }", + "music_video_album": "{ %get_url_field( \"category\", music_video_album_default ) }", + "music_video_album_default": "Music Videos", + "music_video_artist": "Rick Astley", + "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", + "music_video_directory": "tv_show_directory_path", + "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", title ) ) }", + "music_video_file_name_suffix": "", + "music_video_genre": "Pop", + "music_video_genre_default": "ytdl-sub", + "music_video_title": "{ %get_url_field( \"title\", title ) }", + "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_readable": "{ width }x{ height }", + "subscription_array": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\" ] }", + "subscription_indent_1": "Pop", + "subscription_map": "{ { } }", + "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] }" +} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_variables.json b/tests/resources/expected_json/music_video/inspect_variables.json new file mode 100644 index 00000000..318f2685 --- /dev/null +++ b/tests/resources/expected_json/music_video/inspect_variables.json @@ -0,0 +1,480 @@ +{ + "avatar_uncropped_thumbnail_file_name": "", + "avatar_uncropped_thumbnail_file_name_sanitized": "", + "banner_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name_sanitized": "", + "category_url_array": "{ [ ] }", + "category_url_array_sanitized": "[]", + "category_url_map": "{ [ ] }", + "category_url_map_sanitized": "[]", + "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", uploader ) }", + "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", uploader_id ) }", + "channel_id_sanitized": "{ %sanitize( channel_id ) }", + "channel_sanitized": "{ %sanitize( channel ) }", + "chapters": "{ [ ] }", + "chapters_sanitized": "{ %sanitize( chapters ) }", + "comments": "{ [ ] }", + "comments_sanitized": "{ %sanitize( comments ) }", + "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", channel ) }", + "creator_sanitized": "{ %sanitize( creator ) }", + "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "description_sanitized": "{ %sanitize( description ) }", + "download_index": "{ %int( 1 ) }", + "download_index_padded6": "{ %pad_zero( download_index, 6 ) }", + "download_index_padded6_sanitized": "{ %sanitize( download_index_padded6 ) }", + "download_index_sanitized": "{ %sanitize( download_index ) }", + "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", + "duration_sanitized": "{ %sanitize( duration ) }", + "enable_bilateral_scraping": "{ %bool(True) }", + "enable_bilateral_scraping_sanitized": "true", + "enable_resolution_assert": "{ %bool(True) }", + "enable_resolution_assert_sanitized": "true", + "enable_throttle_protection": "{ %bool(True) }", + "enable_throttle_protection_sanitized": "true", + "entry_metadata": "{ { } }", + "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", + "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", + "epoch_date": "{ %datetime_strftime( epoch, \"%Y%m%d\" ) }", + "epoch_date_sanitized": "{ %sanitize( epoch_date ) }", + "epoch_hour": "{ %datetime_strftime( epoch, \"%H\" ) }", + "epoch_hour_sanitized": "{ %sanitize( epoch_hour ) }", + "epoch_sanitized": "{ %sanitize( epoch ) }", + "ext": "{ %map_get( entry_metadata, \"ext\" ) }", + "ext_sanitized": "{ %sanitize( ext ) }", + "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", + "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", + "extractor_key_sanitized": "{ %sanitize( extractor_key ) }", + "extractor_sanitized": "{ %sanitize( extractor ) }", + "file_title": "{ title_sanitized_plex }", + "file_title_sanitized": "{ %sanitize( title_sanitized_plex ) }", + "file_uid": "{ uid_sanitized_plex }", + "file_uid_sanitized": "{ %sanitize( uid_sanitized_plex ) }", + "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "height_sanitized": "{ %sanitize( height ) }", + "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", extractor_key ) }", + "ie_key_sanitized": "{ %sanitize( ie_key ) }", + "include_sibling_metadata": "{ %bool(False) }", + "include_sibling_metadata_sanitized": "false", + "info_json_ext": "info.json", + "info_json_ext_sanitized": "info.json", + "modified_webpage_url": "{ webpage_url }", + "modified_webpage_url_sanitized": "{ %sanitize( webpage_url ) }", + "music_video_album": "{ %get_url_field( \"category\", music_video_album_default ) }", + "music_video_album_default": "Music Videos", + "music_video_album_default_sanitized": "Music Videos", + "music_video_album_sanitized": "{ %sanitize( %get_url_field( \"category\", music_video_album_default ) ) }", + "music_video_artist": "Rick Astley", + "music_video_artist_sanitized": "Rick Astley", + "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", + "music_video_date_sanitized": "{ %sanitize( %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) ) }", + "music_video_directory": "tv_show_directory_path", + "music_video_directory_sanitized": "tv_show_directory_path", + "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", title ) ) }", + "music_video_file_name_sanitized": "{ %sanitize( %concat( \"Rick Astley\", \"/\", %string( %sanitize( %get_url_field( \"title\", title ) ) ), '' ) ) }", + "music_video_file_name_suffix": "", + "music_video_file_name_suffix_sanitized": "", + "music_video_genre": "Pop", + "music_video_genre_default": "ytdl-sub", + "music_video_genre_default_sanitized": "ytdl-sub", + "music_video_genre_sanitized": "Pop", + "music_video_title": "{ %get_url_field( \"title\", title ) }", + "music_video_title_sanitized": "{ %sanitize( %get_url_field( \"title\", title ) ) }", + "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", + "music_video_year_sanitized": "{ %sanitize( %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) ) }", + "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", + "playlist_count_sanitized": "{ %sanitize( playlist_count ) }", + "playlist_description": "{ %map_get_non_empty( playlist_metadata, \"description\", description ) }", + "playlist_description_sanitized": "{ %sanitize( playlist_description ) }", + "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", + "playlist_index_padded": "{ %pad_zero( playlist_index, 2 ) }", + "playlist_index_padded6": "{ %pad_zero( playlist_index, 6 ) }", + "playlist_index_padded6_sanitized": "{ %sanitize( playlist_index_padded6 ) }", + "playlist_index_padded_sanitized": "{ %sanitize( playlist_index_padded ) }", + "playlist_index_reversed": "{ %sub( playlist_count, playlist_index, -1 ) }", + "playlist_index_reversed_padded": "{ %pad_zero( playlist_index_reversed, 2 ) }", + "playlist_index_reversed_padded6": "{ %pad_zero( playlist_index_reversed, 6 ) }", + "playlist_index_reversed_padded6_sanitized": "{ %sanitize( playlist_index_reversed_padded6 ) }", + "playlist_index_reversed_padded_sanitized": "{ %sanitize( playlist_index_reversed_padded ) }", + "playlist_index_reversed_sanitized": "{ %sanitize( playlist_index_reversed ) }", + "playlist_index_sanitized": "{ %sanitize( playlist_index ) }", + "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", + "playlist_max_upload_date_sanitized": "{ %sanitize( playlist_max_upload_date ) }", + "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year\" ) ) }", + "playlist_max_upload_year_sanitized": "{ %sanitize( playlist_max_upload_year ) }", + "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year_truncated\" ) ) }", + "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( playlist_max_upload_year_truncated ) }", + "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", + "playlist_metadata_sanitized": "{ %sanitize( playlist_metadata ) }", + "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", title ) }", + "playlist_title_sanitized": "{ %sanitize( playlist_title ) }", + "playlist_uid": "{ %map_get_non_empty( playlist_metadata, \"playlist_id\", uid ) }", + "playlist_uid_sanitized": "{ %sanitize( playlist_uid ) }", + "playlist_uploader": "{ %map_get_non_empty( playlist_metadata, \"uploader\", uploader ) }", + "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", uploader_id ) }", + "playlist_uploader_id_sanitized": "{ %sanitize( playlist_uploader_id ) }", + "playlist_uploader_sanitized": "{ %sanitize( playlist_uploader ) }", + "playlist_uploader_url": "{ %map_get_non_empty( playlist_metadata, \"uploader_url\", webpage_url ) }", + "playlist_uploader_url_sanitized": "{ %sanitize( playlist_uploader_url ) }", + "playlist_webpage_url": "{ %map_get_non_empty( playlist_metadata, \"webpage_url\", webpage_url ) }", + "playlist_webpage_url_sanitized": "{ %sanitize( playlist_webpage_url ) }", + "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", upload_date ) }", + "release_date_sanitized": "{ %sanitize( release_date ) }", + "release_date_standardized": "{ %string( %map_get( %to_date_metadata( release_date ), \"date_standardized\" ) ) }", + "release_date_standardized_sanitized": "{ %sanitize( release_date_standardized ) }", + "release_day": "{ %int( %map_get( %to_date_metadata( release_date ), \"day\" ) ) }", + "release_day_of_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year\" ) ) }", + "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_padded\" ) ) }", + "release_day_of_year_padded_sanitized": "{ %sanitize( release_day_of_year_padded ) }", + "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed\" ) ) }", + "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed_padded\" ) ) }", + "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( release_day_of_year_reversed_padded ) }", + "release_day_of_year_reversed_sanitized": "{ %sanitize( release_day_of_year_reversed ) }", + "release_day_of_year_sanitized": "{ %sanitize( release_day_of_year ) }", + "release_day_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_padded\" ) ) }", + "release_day_padded_sanitized": "{ %sanitize( release_day_padded ) }", + "release_day_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_reversed\" ) ) }", + "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_reversed_padded\" ) ) }", + "release_day_reversed_padded_sanitized": "{ %sanitize( release_day_reversed_padded ) }", + "release_day_reversed_sanitized": "{ %sanitize( release_day_reversed ) }", + "release_day_sanitized": "{ %sanitize( release_day ) }", + "release_month": "{ %int( %map_get( %to_date_metadata( release_date ), \"month\" ) ) }", + "release_month_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_padded\" ) ) }", + "release_month_padded_sanitized": "{ %sanitize( release_month_padded ) }", + "release_month_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"month_reversed\" ) ) }", + "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_reversed_padded\" ) ) }", + "release_month_reversed_padded_sanitized": "{ %sanitize( release_month_reversed_padded ) }", + "release_month_reversed_sanitized": "{ %sanitize( release_month_reversed ) }", + "release_month_sanitized": "{ %sanitize( release_month ) }", + "release_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"year\" ) ) }", + "release_year_sanitized": "{ %sanitize( release_year ) }", + "release_year_truncated": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated\" ) ) }", + "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated_reversed\" ) ) }", + "release_year_truncated_reversed_sanitized": "{ %sanitize( release_year_truncated_reversed ) }", + "release_year_truncated_sanitized": "{ %sanitize( release_year_truncated ) }", + "requested_subtitles": "{ { } }", + "requested_subtitles_sanitized": "{ %sanitize( requested_subtitles ) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_height_gte_sanitized": "361", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_ignore_titles_sanitized": "[]", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", + "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_assert_print_sanitized": "true", + "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", + "resolution_readable": "{ width }x{ height }", + "resolution_readable_sanitized": "{ %sanitize( %concat( %string( width ), \"x\", %string( height ) ) ) }", + "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", + "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", + "source_count": "{ %map_get_non_empty( playlist_metadata, \"playlist_count\", 1 ) }", + "source_count_sanitized": "{ %sanitize( source_count ) }", + "source_description": "{ %map_get_non_empty( source_metadata, \"description\", playlist_description ) }", + "source_description_sanitized": "{ %sanitize( source_description ) }", + "source_index": "{ %map_get_non_empty( playlist_metadata, \"playlist_index\", 1 ) }", + "source_index_padded": "{ %pad_zero( source_index, 2 ) }", + "source_index_padded_sanitized": "{ %sanitize( source_index_padded ) }", + "source_index_sanitized": "{ %sanitize( source_index ) }", + "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", + "source_metadata_sanitized": "{ %sanitize( source_metadata ) }", + "source_title": "{ %map_get_non_empty( source_metadata, \"title\", playlist_title ) }", + "source_title_sanitized": "{ %sanitize( source_title ) }", + "source_uid": "{ %map_get_non_empty( source_metadata, \"id\", playlist_uid ) }", + "source_uid_sanitized": "{ %sanitize( source_uid ) }", + "source_uploader": "{ %map_get_non_empty( source_metadata, \"uploader\", playlist_uploader ) }", + "source_uploader_id": "{ %map_get_non_empty( source_metadata, \"uploader_id\", playlist_uploader_id ) }", + "source_uploader_id_sanitized": "{ %sanitize( source_uploader_id ) }", + "source_uploader_sanitized": "{ %sanitize( source_uploader ) }", + "source_uploader_url": "{ %map_get_non_empty( source_metadata, \"uploader_url\", source_webpage_url ) }", + "source_uploader_url_sanitized": "{ %sanitize( source_uploader_url ) }", + "source_webpage_url": "{ %map_get_non_empty( source_metadata, \"webpage_url\", playlist_webpage_url ) }", + "source_webpage_url_sanitized": "{ %sanitize( source_webpage_url ) }", + "sponsorblock_chapters": "{ [ ] }", + "sponsorblock_chapters_sanitized": "{ %sanitize( sponsorblock_chapters ) }", + "subscription_array": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\" ] }", + "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\uff02]", + "subscription_has_download_archive": "{ %bool(False) }", + "subscription_has_download_archive_sanitized": "false", + "subscription_indent_1": "Pop", + "subscription_indent_1_sanitized": "Pop", + "subscription_map": "{ { } }", + "subscription_map_sanitized": "{}", + "subscription_name": "Rick Astley", + "subscription_name_sanitized": "Rick Astley", + "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "thumbnail_ext": "jpg", + "thumbnail_ext_sanitized": "jpg", + "title": "{ %map_get_non_empty( entry_metadata, \"title\", uid ) }", + "title_sanitized": "{ %sanitize( title ) }", + "title_sanitized_plex": "{ %sanitize_plex_episode( title ) }", + "title_sanitized_plex_sanitized": "{ %sanitize( title_sanitized_plex ) }", + "uid": "{ %map_get( entry_metadata, \"id\" ) }", + "uid_sanitized": "{ %sanitize( uid ) }", + "uid_sanitized_plex": "{ %sanitize_plex_episode( uid ) }", + "uid_sanitized_plex_sanitized": "{ %sanitize( uid_sanitized_plex ) }", + "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", epoch_date ) }", + "upload_date_index": "{ %int( 1 ) }", + "upload_date_index_padded": "{ %pad_zero( upload_date_index, 2 ) }", + "upload_date_index_padded_sanitized": "{ %sanitize( upload_date_index_padded ) }", + "upload_date_index_reversed": "{ %sub( 100, upload_date_index ) }", + "upload_date_index_reversed_padded": "{ %pad_zero( upload_date_index_reversed, 2 ) }", + "upload_date_index_reversed_padded_sanitized": "{ %sanitize( upload_date_index_reversed_padded ) }", + "upload_date_index_reversed_sanitized": "{ %sanitize( upload_date_index_reversed ) }", + "upload_date_index_sanitized": "{ %sanitize( upload_date_index ) }", + "upload_date_sanitized": "{ %sanitize( upload_date ) }", + "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( upload_date ), \"date_standardized\" ) ) }", + "upload_date_standardized_sanitized": "{ %sanitize( upload_date_standardized ) }", + "upload_day": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day\" ) ) }", + "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year\" ) ) }", + "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_padded\" ) ) }", + "upload_day_of_year_padded_sanitized": "{ %sanitize( upload_day_of_year_padded ) }", + "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed\" ) ) }", + "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed_padded\" ) ) }", + "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( upload_day_of_year_reversed_padded ) }", + "upload_day_of_year_reversed_sanitized": "{ %sanitize( upload_day_of_year_reversed ) }", + "upload_day_of_year_sanitized": "{ %sanitize( upload_day_of_year ) }", + "upload_day_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_padded\" ) ) }", + "upload_day_padded_sanitized": "{ %sanitize( upload_day_padded ) }", + "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_reversed\" ) ) }", + "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_reversed_padded\" ) ) }", + "upload_day_reversed_padded_sanitized": "{ %sanitize( upload_day_reversed_padded ) }", + "upload_day_reversed_sanitized": "{ %sanitize( upload_day_reversed ) }", + "upload_day_sanitized": "{ %sanitize( upload_day ) }", + "upload_month": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month\" ) ) }", + "upload_month_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_padded\" ) ) }", + "upload_month_padded_sanitized": "{ %sanitize( upload_month_padded ) }", + "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month_reversed\" ) ) }", + "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_reversed_padded\" ) ) }", + "upload_month_reversed_padded_sanitized": "{ %sanitize( upload_month_reversed_padded ) }", + "upload_month_reversed_sanitized": "{ %sanitize( upload_month_reversed ) }", + "upload_month_sanitized": "{ %sanitize( upload_month ) }", + "upload_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year\" ) ) }", + "upload_year_sanitized": "{ %sanitize( upload_year ) }", + "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated\" ) ) }", + "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated_reversed\" ) ) }", + "upload_year_truncated_reversed_sanitized": "{ %sanitize( upload_year_truncated_reversed ) }", + "upload_year_truncated_sanitized": "{ %sanitize( upload_year_truncated ) }", + "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", uploader_id ) }", + "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", uid ) }", + "uploader_id_sanitized": "{ %sanitize( uploader_id ) }", + "uploader_sanitized": "{ %sanitize( uploader ) }", + "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", webpage_url ) }", + "uploader_url_sanitized": "{ %sanitize( uploader_url ) }", + "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url10": "", + "url100": "", + "url100_sanitized": "", + "url10_sanitized": "", + "url11": "", + "url11_sanitized": "", + "url12": "", + "url12_sanitized": "", + "url13": "", + "url13_sanitized": "", + "url14": "", + "url14_sanitized": "", + "url15": "", + "url15_sanitized": "", + "url16": "", + "url16_sanitized": "", + "url17": "", + "url17_sanitized": "", + "url18": "", + "url18_sanitized": "", + "url19": "", + "url19_sanitized": "", + "url2": "", + "url20": "", + "url20_sanitized": "", + "url21": "", + "url21_sanitized": "", + "url22": "", + "url22_sanitized": "", + "url23": "", + "url23_sanitized": "", + "url24": "", + "url24_sanitized": "", + "url25": "", + "url25_sanitized": "", + "url26": "", + "url26_sanitized": "", + "url27": "", + "url27_sanitized": "", + "url28": "", + "url28_sanitized": "", + "url29": "", + "url29_sanitized": "", + "url2_sanitized": "", + "url3": "", + "url30": "", + "url30_sanitized": "", + "url31": "", + "url31_sanitized": "", + "url32": "", + "url32_sanitized": "", + "url33": "", + "url33_sanitized": "", + "url34": "", + "url34_sanitized": "", + "url35": "", + "url35_sanitized": "", + "url36": "", + "url36_sanitized": "", + "url37": "", + "url37_sanitized": "", + "url38": "", + "url38_sanitized": "", + "url39": "", + "url39_sanitized": "", + "url3_sanitized": "", + "url4": "", + "url40": "", + "url40_sanitized": "", + "url41": "", + "url41_sanitized": "", + "url42": "", + "url42_sanitized": "", + "url43": "", + "url43_sanitized": "", + "url44": "", + "url44_sanitized": "", + "url45": "", + "url45_sanitized": "", + "url46": "", + "url46_sanitized": "", + "url47": "", + "url47_sanitized": "", + "url48": "", + "url48_sanitized": "", + "url49": "", + "url49_sanitized": "", + "url4_sanitized": "", + "url5": "", + "url50": "", + "url50_sanitized": "", + "url51": "", + "url51_sanitized": "", + "url52": "", + "url52_sanitized": "", + "url53": "", + "url53_sanitized": "", + "url54": "", + "url54_sanitized": "", + "url55": "", + "url55_sanitized": "", + "url56": "", + "url56_sanitized": "", + "url57": "", + "url57_sanitized": "", + "url58": "", + "url58_sanitized": "", + "url59": "", + "url59_sanitized": "", + "url5_sanitized": "", + "url6": "", + "url60": "", + "url60_sanitized": "", + "url61": "", + "url61_sanitized": "", + "url62": "", + "url62_sanitized": "", + "url63": "", + "url63_sanitized": "", + "url64": "", + "url64_sanitized": "", + "url65": "", + "url65_sanitized": "", + "url66": "", + "url66_sanitized": "", + "url67": "", + "url67_sanitized": "", + "url68": "", + "url68_sanitized": "", + "url69": "", + "url69_sanitized": "", + "url6_sanitized": "", + "url7": "", + "url70": "", + "url70_sanitized": "", + "url71": "", + "url71_sanitized": "", + "url72": "", + "url72_sanitized": "", + "url73": "", + "url73_sanitized": "", + "url74": "", + "url74_sanitized": "", + "url75": "", + "url75_sanitized": "", + "url76": "", + "url76_sanitized": "", + "url77": "", + "url77_sanitized": "", + "url78": "", + "url78_sanitized": "", + "url79": "", + "url79_sanitized": "", + "url7_sanitized": "", + "url8": "", + "url80": "", + "url80_sanitized": "", + "url81": "", + "url81_sanitized": "", + "url82": "", + "url82_sanitized": "", + "url83": "", + "url83_sanitized": "", + "url84": "", + "url84_sanitized": "", + "url85": "", + "url85_sanitized": "", + "url86": "", + "url86_sanitized": "", + "url87": "", + "url87_sanitized": "", + "url88": "", + "url88_sanitized": "", + "url89": "", + "url89_sanitized": "", + "url8_sanitized": "", + "url9": "", + "url90": "", + "url90_sanitized": "", + "url91": "", + "url91_sanitized": "", + "url92": "", + "url92_sanitized": "", + "url93": "", + "url93_sanitized": "", + "url94": "", + "url94_sanitized": "", + "url95": "", + "url95_sanitized": "", + "url96": "", + "url96_sanitized": "", + "url97": "", + "url97_sanitized": "", + "url98": "", + "url98_sanitized": "", + "url99": "", + "url99_sanitized": "", + "url9_sanitized": "", + "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "urls": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] }", + "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02]", + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "webpage_url_sanitized": "{ %sanitize( webpage_url ) }", + "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", + "width_sanitized": "{ %sanitize( width ) }", + "ytdl_sub_entry_date_eval": "{ %string( upload_date_standardized ) }", + "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( ytdl_sub_entry_date_eval ) }", + "ytdl_sub_input_url": "{ %string( '' ) }", + "ytdl_sub_input_url_count": "{ %int( 0 ) }", + "ytdl_sub_input_url_count_sanitized": "{ %sanitize( ytdl_sub_input_url_count ) }", + "ytdl_sub_input_url_index": "{ %int( -1 ) }", + "ytdl_sub_input_url_index_sanitized": "{ %sanitize( ytdl_sub_input_url_index ) }", + "ytdl_sub_input_url_sanitized": "{ %sanitize( ytdl_sub_input_url ) }" +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_full_overrides.json b/tests/resources/expected_json/tv_show/inspect_full_overrides.json new file mode 100644 index 00000000..179d4e11 --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_full_overrides.json @@ -0,0 +1,155 @@ +{ + "assert_not_collection": "{ %bool(True) }", + "avatar_uncropped_thumbnail_file_name": "poster.jpg", + "banner_uncropped_thumbnail_file_name": "fanart.jpg", + "enable_bilateral_scraping": "{ %bool(True) }", + "enable_resolution_assert": "{ %bool(True) }", + "enable_throttle_protection": "{ %bool(True) }", + "episode_content_rating": "TV-14", + "episode_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "episode_file_name": "s{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.e{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) } - { %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/{ %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "episode_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }{ upload_date_index_padded }", + "episode_number_and_padded_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", + "episode_number_padded": "{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) }", + "episode_plot": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "episode_title": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "episode_year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }", + "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "include_sibling_metadata": "{ %bool(False) }", + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "only_recent_date_range": "2months", + "only_recent_max_files": "{ %int(30) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "s01_name": "", + "s01_url": "", + "season_directory_name": "Season { %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_number_padded": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", + "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", + "subscription_indent_1": "Documentaries", + "subscription_indent_2": "TV-14", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) }-thumb.jpg", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_content_rating": "TV-14", + "tv_show_content_rating_default": "TV-14", + "tv_show_date_range_type": "upload_date", + "tv_show_directory": "tv_show_directory_path", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_genre": "Documentaries", + "tv_show_genre_default": "ytdl-sub", + "tv_show_name": "NOVA PBS", + "tv_show_poster_file_name": "poster.jpg", + "url": "https://www.youtube.com/@novapbs", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{ [ \"https://www.youtube.com/@novapbs\" ] }" +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_full_variables.json b/tests/resources/expected_json/tv_show/inspect_full_variables.json new file mode 100644 index 00000000..bfb834bf --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_full_variables.json @@ -0,0 +1,520 @@ +{ + "assert_not_collection": "{ %bool(True) }", + "assert_not_collection_sanitized": "true", + "avatar_uncropped_thumbnail_file_name": "poster.jpg", + "avatar_uncropped_thumbnail_file_name_sanitized": "poster.jpg", + "banner_uncropped_thumbnail_file_name": "fanart.jpg", + "banner_uncropped_thumbnail_file_name_sanitized": "fanart.jpg", + "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "channel_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "channel_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "chapters": "{ [ ] }", + "chapters_sanitized": "[]", + "comments": "{ [ ] }", + "comments_sanitized": "[]", + "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "creator_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "description_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", + "download_index": "{ %int(1) }", + "download_index_padded6": "000001", + "download_index_padded6_sanitized": "000001", + "download_index_sanitized": "1", + "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", + "duration_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"duration\", 0 ) ) }", + "enable_bilateral_scraping": "{ %bool(True) }", + "enable_bilateral_scraping_sanitized": "true", + "enable_resolution_assert": "{ %bool(True) }", + "enable_resolution_assert_sanitized": "true", + "enable_throttle_protection": "{ %bool(True) }", + "enable_throttle_protection_sanitized": "true", + "entry_metadata": "{ { } }", + "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", + "episode_content_rating": "TV-14", + "episode_content_rating_sanitized": "TV-14", + "episode_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "episode_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", + "episode_file_name": "s{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.e{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) } - { %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "episode_file_name_sanitized": "{ %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/{ %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "episode_file_path_sanitized": "{ %sanitize( %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) ) }", + "episode_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }{ upload_date_index_padded }", + "episode_number_and_padded_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", + "episode_number_and_padded__sanitized": "{ %sanitize( [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] ) }", + "episode_number_padded": "{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) }", + "episode_number_padded_sanitized": "{ %sanitize( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ) }", + "episode_number_sanitized": "{ %sanitize( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ) }", + "episode_plot": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "episode_plot_sanitized": "{ %sanitize( %concat( %string( %map_get( entry_metadata, \"webpage_url\" ) ), \"\n\n\", %string( %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) ) }", + "episode_title": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "episode_title_sanitized": "{ %sanitize( %concat( %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ), \" - \", %string( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "episode_year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }", + "episode_year_sanitized": "{ %sanitize( %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) ) }", + "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", + "epoch_date": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) }", + "epoch_date_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", + "epoch_hour": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) }", + "epoch_hour_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) ) }", + "epoch_sanitized": "{ %sanitize( %map_get( entry_metadata, \"epoch\" ) ) }", + "ext": "{ %throw( \"Plugin variable ext has not been created yet\" ) }", + "ext_sanitized": "{ %sanitize( ext ) }", + "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", + "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", + "extractor_key_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor_key\" ) ) }", + "extractor_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor\" ) ) }", + "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "file_title_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "file_uid_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", + "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "height_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) }", + "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) }", + "ie_key_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) ) }", + "include_sibling_metadata": "{ %bool(False) }", + "include_sibling_metadata_sanitized": "false", + "info_json_ext": "info.json", + "info_json_ext_sanitized": "info.json", + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "modified_webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", + "only_recent_date_range": "2months", + "only_recent_date_range_sanitized": "2months", + "only_recent_max_files": "{ %int(30) }", + "only_recent_max_files_sanitized": "30", + "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", + "playlist_count_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) ) }", + "playlist_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", + "playlist_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", + "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", + "playlist_index_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", + "playlist_index_padded6": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) }", + "playlist_index_padded6_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) ) }", + "playlist_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ) }", + "playlist_index_reversed": "{ %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) }", + "playlist_index_reversed_padded": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) }", + "playlist_index_reversed_padded6": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) }", + "playlist_index_reversed_padded6_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) ) }", + "playlist_index_reversed_padded_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) ) }", + "playlist_index_reversed_sanitized": "{ %sanitize( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) ) }", + "playlist_index_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) ) }", + "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", + "playlist_max_upload_date_sanitized": "{ %sanitize( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ) }", + "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", + "playlist_max_upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ) }", + "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) }", + "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) ) }", + "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", + "playlist_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) ) }", + "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "playlist_title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "playlist_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) }", + "playlist_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "playlist_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "playlist_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "playlist_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "playlist_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", + "playlist_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "playlist_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", + "playlist_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", + "release_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ) }", + "release_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) }", + "release_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) ) }", + "release_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) }", + "release_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) }", + "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) }", + "release_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) ) }", + "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) }", + "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) }", + "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", + "release_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) ) }", + "release_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) ) }", + "release_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) }", + "release_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) ) }", + "release_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) }", + "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) }", + "release_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) ) }", + "release_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) ) }", + "release_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) ) }", + "release_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) }", + "release_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) }", + "release_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) ) }", + "release_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) }", + "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) }", + "release_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) ) }", + "release_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) ) }", + "release_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) ) }", + "release_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) }", + "release_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) ) }", + "release_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) }", + "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) }", + "release_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) ) }", + "release_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) ) }", + "requested_subtitles": "{ { } }", + "requested_subtitles_sanitized": "{}", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_height_gte_sanitized": "361", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_ignore_titles_sanitized": "[]", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", + "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_assert_print_sanitized": "true", + "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", + "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "resolution_readable_sanitized": "{ %sanitize( %concat( %string( %map_get_non_empty( entry_metadata, \"width\", 0 ) ), \"x\", %string( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) ) }", + "s01_name": "", + "s01_name_sanitized": "", + "s01_url": "", + "s01_url_sanitized": "", + "season_directory_name": "Season { %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_directory_name_sanitized": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }", + "season_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_number_padded": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_number_padded_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", + "season_number_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", + "season_poster_file_name_sanitized": "{ %sanitize( %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/Season\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".jpg\" ) ) }", + "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", + "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", + "source_count": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) }", + "source_count_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) ) }", + "source_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", + "source_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) ) }", + "source_index": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) }", + "source_index_padded": "{ %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) }", + "source_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) ) }", + "source_index_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) ) }", + "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", + "source_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) ) }", + "source_title": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "source_title_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "source_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "source_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "source_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "source_uploader_id": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "source_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "source_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "source_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", + "source_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) ) }", + "source_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "source_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", + "sponsorblock_chapters": "{ [ ] }", + "sponsorblock_chapters_sanitized": "[]", + "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", + "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs\uff02]", + "subscription_has_download_archive": "{ %bool(False) }", + "subscription_has_download_archive_sanitized": "false", + "subscription_indent_1": "Documentaries", + "subscription_indent_1_sanitized": "Documentaries", + "subscription_indent_2": "TV-14", + "subscription_indent_2_sanitized": "TV-14", + "subscription_name": "NOVA PBS", + "subscription_name_sanitized": "NOVA PBS", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", + "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", + "thumbnail_ext": "jpg", + "thumbnail_ext_sanitized": "jpg", + "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) }-thumb.jpg", + "thumbnail_file_name_sanitized": "{ %sanitize( %concat( %string( %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) ), \"-thumb.jpg\" ) ) }", + "title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "title_sanitized_plex": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "title_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_episode_ordering_sanitized": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", + "tv_show_by_date_ordering_pair_validation__sanitized": "{ %sanitize( [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] ) }", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_by_date_season_ordering_sanitized": "upload-year", + "tv_show_content_rating": "TV-14", + "tv_show_content_rating_default": "TV-14", + "tv_show_content_rating_default_sanitized": "TV-14", + "tv_show_content_rating_sanitized": "TV-14", + "tv_show_date_range_type": "upload_date", + "tv_show_date_range_type_sanitized": "upload_date", + "tv_show_directory": "tv_show_directory_path", + "tv_show_directory_sanitized": "tv_show_directory_path", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_fanart_file_name_sanitized": "fanart.jpg", + "tv_show_genre": "Documentaries", + "tv_show_genre_default": "ytdl-sub", + "tv_show_genre_default_sanitized": "ytdl-sub", + "tv_show_genre_sanitized": "Documentaries", + "tv_show_name": "NOVA PBS", + "tv_show_name_sanitized": "NOVA PBS", + "tv_show_poster_file_name": "poster.jpg", + "tv_show_poster_file_name_sanitized": "poster.jpg", + "uid": "{ %map_get( entry_metadata, \"id\" ) }", + "uid_sanitized": "{ %sanitize( %map_get( entry_metadata, \"id\" ) ) }", + "uid_sanitized_plex": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "uid_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", + "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", + "upload_date_index": "{ %int(1) }", + "upload_date_index_padded": "01", + "upload_date_index_padded_sanitized": "01", + "upload_date_index_reversed": "{ %int(99) }", + "upload_date_index_reversed_padded": "99", + "upload_date_index_reversed_padded_sanitized": "99", + "upload_date_index_reversed_sanitized": "99", + "upload_date_index_sanitized": "1", + "upload_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", + "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "upload_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", + "upload_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) }", + "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) }", + "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) }", + "upload_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) ) }", + "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) }", + "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) }", + "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", + "upload_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) ) }", + "upload_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) ) }", + "upload_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) }", + "upload_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) ) }", + "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) }", + "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) }", + "upload_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) ) }", + "upload_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) ) }", + "upload_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) ) }", + "upload_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }", + "upload_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) }", + "upload_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) ) }", + "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) }", + "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) }", + "upload_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) ) }", + "upload_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) ) }", + "upload_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) ) }", + "upload_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", + "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) }", + "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) }", + "upload_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) ) }", + "upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) ) }", + "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) }", + "uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", + "uploader_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", + "uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", + "url": "https://www.youtube.com/@novapbs", + "url10": "", + "url100": "", + "url100_sanitized": "", + "url10_sanitized": "", + "url11": "", + "url11_sanitized": "", + "url12": "", + "url12_sanitized": "", + "url13": "", + "url13_sanitized": "", + "url14": "", + "url14_sanitized": "", + "url15": "", + "url15_sanitized": "", + "url16": "", + "url16_sanitized": "", + "url17": "", + "url17_sanitized": "", + "url18": "", + "url18_sanitized": "", + "url19": "", + "url19_sanitized": "", + "url2": "", + "url20": "", + "url20_sanitized": "", + "url21": "", + "url21_sanitized": "", + "url22": "", + "url22_sanitized": "", + "url23": "", + "url23_sanitized": "", + "url24": "", + "url24_sanitized": "", + "url25": "", + "url25_sanitized": "", + "url26": "", + "url26_sanitized": "", + "url27": "", + "url27_sanitized": "", + "url28": "", + "url28_sanitized": "", + "url29": "", + "url29_sanitized": "", + "url2_sanitized": "", + "url3": "", + "url30": "", + "url30_sanitized": "", + "url31": "", + "url31_sanitized": "", + "url32": "", + "url32_sanitized": "", + "url33": "", + "url33_sanitized": "", + "url34": "", + "url34_sanitized": "", + "url35": "", + "url35_sanitized": "", + "url36": "", + "url36_sanitized": "", + "url37": "", + "url37_sanitized": "", + "url38": "", + "url38_sanitized": "", + "url39": "", + "url39_sanitized": "", + "url3_sanitized": "", + "url4": "", + "url40": "", + "url40_sanitized": "", + "url41": "", + "url41_sanitized": "", + "url42": "", + "url42_sanitized": "", + "url43": "", + "url43_sanitized": "", + "url44": "", + "url44_sanitized": "", + "url45": "", + "url45_sanitized": "", + "url46": "", + "url46_sanitized": "", + "url47": "", + "url47_sanitized": "", + "url48": "", + "url48_sanitized": "", + "url49": "", + "url49_sanitized": "", + "url4_sanitized": "", + "url5": "", + "url50": "", + "url50_sanitized": "", + "url51": "", + "url51_sanitized": "", + "url52": "", + "url52_sanitized": "", + "url53": "", + "url53_sanitized": "", + "url54": "", + "url54_sanitized": "", + "url55": "", + "url55_sanitized": "", + "url56": "", + "url56_sanitized": "", + "url57": "", + "url57_sanitized": "", + "url58": "", + "url58_sanitized": "", + "url59": "", + "url59_sanitized": "", + "url5_sanitized": "", + "url6": "", + "url60": "", + "url60_sanitized": "", + "url61": "", + "url61_sanitized": "", + "url62": "", + "url62_sanitized": "", + "url63": "", + "url63_sanitized": "", + "url64": "", + "url64_sanitized": "", + "url65": "", + "url65_sanitized": "", + "url66": "", + "url66_sanitized": "", + "url67": "", + "url67_sanitized": "", + "url68": "", + "url68_sanitized": "", + "url69": "", + "url69_sanitized": "", + "url6_sanitized": "", + "url7": "", + "url70": "", + "url70_sanitized": "", + "url71": "", + "url71_sanitized": "", + "url72": "", + "url72_sanitized": "", + "url73": "", + "url73_sanitized": "", + "url74": "", + "url74_sanitized": "", + "url75": "", + "url75_sanitized": "", + "url76": "", + "url76_sanitized": "", + "url77": "", + "url77_sanitized": "", + "url78": "", + "url78_sanitized": "", + "url79": "", + "url79_sanitized": "", + "url7_sanitized": "", + "url8": "", + "url80": "", + "url80_sanitized": "", + "url81": "", + "url81_sanitized": "", + "url82": "", + "url82_sanitized": "", + "url83": "", + "url83_sanitized": "", + "url84": "", + "url84_sanitized": "", + "url85": "", + "url85_sanitized": "", + "url86": "", + "url86_sanitized": "", + "url87": "", + "url87_sanitized": "", + "url88": "", + "url88_sanitized": "", + "url89": "", + "url89_sanitized": "", + "url8_sanitized": "", + "url9": "", + "url90": "", + "url90_sanitized": "", + "url91": "", + "url91_sanitized": "", + "url92": "", + "url92_sanitized": "", + "url93": "", + "url93_sanitized": "", + "url94": "", + "url94_sanitized": "", + "url95": "", + "url95_sanitized": "", + "url96": "", + "url96_sanitized": "", + "url97": "", + "url97_sanitized": "", + "url98": "", + "url98_sanitized": "", + "url99": "", + "url99_sanitized": "", + "url9_sanitized": "", + "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", + "urls": "{ [ \"https://www.youtube.com/@novapbs\" ] }", + "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs\uff02]", + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", + "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", + "width_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"width\", 0 ) ) }", + "ytdl_sub_chapters_from_comments": "{ %throw( \"Plugin variable ytdl_sub_chapters_from_comments has not been created yet\" ) }", + "ytdl_sub_chapters_from_comments_sanitized": "{ %sanitize( ytdl_sub_chapters_from_comments ) }", + "ytdl_sub_entry_date_eval": "{ %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", + "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) ) }", + "ytdl_sub_input_url": "", + "ytdl_sub_input_url_count": "{ %int(0) }", + "ytdl_sub_input_url_count_sanitized": "0", + "ytdl_sub_input_url_index": "{ %int(-1) }", + "ytdl_sub_input_url_index_sanitized": "-1", + "ytdl_sub_input_url_sanitized": "" +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_overrides.json b/tests/resources/expected_json/tv_show/inspect_overrides.json new file mode 100644 index 00000000..faf71281 --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_overrides.json @@ -0,0 +1,155 @@ +{ + "assert_not_collection": "{ %bool(True) }", + "avatar_uncropped_thumbnail_file_name": "poster.jpg", + "banner_uncropped_thumbnail_file_name": "fanart.jpg", + "enable_bilateral_scraping": "{ %bool(True) }", + "enable_resolution_assert": "{ %bool(True) }", + "enable_throttle_protection": "{ %bool(True) }", + "episode_content_rating": "TV-14", + "episode_date_standardized": "{ upload_date_standardized }", + "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) } - { title_sanitized_plex }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/{ %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) }", + "episode_number": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", + "episode_number_and_padded_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "episode_number_padded": "{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) }", + "episode_plot": "{ webpage_url }\n\n{ description }", + "episode_title": "{ upload_date_standardized } - { title }", + "episode_year": "{ %slice( upload_date_standardized, 0, 4 ) }", + "file_title": "{ title_sanitized_plex }", + "file_uid": "{ uid_sanitized_plex }", + "include_sibling_metadata": "{ %bool(False) }", + "modified_webpage_url": "{ webpage_url }", + "only_recent_date_range": "2months", + "only_recent_max_files": "{ %int(30) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_readable": "{ width }x{ height }", + "s01_name": "", + "s01_url": "", + "season_directory_name": "Season { upload_year }", + "season_number": "{ upload_year }", + "season_number_padded": "{ upload_year }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/Season{ upload_year }.jpg", + "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", + "subscription_indent_1": "Documentaries", + "subscription_indent_2": "TV-14", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) }-thumb.jpg", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_content_rating": "TV-14", + "tv_show_content_rating_default": "TV-14", + "tv_show_date_range_type": "upload_date", + "tv_show_directory": "tv_show_directory_path", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_genre": "Documentaries", + "tv_show_genre_default": "ytdl-sub", + "tv_show_name": "NOVA PBS", + "tv_show_poster_file_name": "poster.jpg", + "url": "https://www.youtube.com/@novapbs", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{ [ \"https://www.youtube.com/@novapbs\" ] }" +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_variables.json b/tests/resources/expected_json/tv_show/inspect_variables.json new file mode 100644 index 00000000..77659a4b --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_variables.json @@ -0,0 +1,520 @@ +{ + "assert_not_collection": "{ %bool(True) }", + "assert_not_collection_sanitized": "true", + "avatar_uncropped_thumbnail_file_name": "poster.jpg", + "avatar_uncropped_thumbnail_file_name_sanitized": "poster.jpg", + "banner_uncropped_thumbnail_file_name": "fanart.jpg", + "banner_uncropped_thumbnail_file_name_sanitized": "fanart.jpg", + "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", uploader ) }", + "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", uploader_id ) }", + "channel_id_sanitized": "{ %sanitize( channel_id ) }", + "channel_sanitized": "{ %sanitize( channel ) }", + "chapters": "{ [ ] }", + "chapters_sanitized": "{ %sanitize( chapters ) }", + "comments": "{ [ ] }", + "comments_sanitized": "{ %sanitize( comments ) }", + "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", channel ) }", + "creator_sanitized": "{ %sanitize( creator ) }", + "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "description_sanitized": "{ %sanitize( description ) }", + "download_index": "{ %int( 1 ) }", + "download_index_padded6": "{ %pad_zero( download_index, 6 ) }", + "download_index_padded6_sanitized": "{ %sanitize( download_index_padded6 ) }", + "download_index_sanitized": "{ %sanitize( download_index ) }", + "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", + "duration_sanitized": "{ %sanitize( duration ) }", + "enable_bilateral_scraping": "{ %bool(True) }", + "enable_bilateral_scraping_sanitized": "true", + "enable_resolution_assert": "{ %bool(True) }", + "enable_resolution_assert_sanitized": "true", + "enable_throttle_protection": "{ %bool(True) }", + "enable_throttle_protection_sanitized": "true", + "entry_metadata": "{ { } }", + "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", + "episode_content_rating": "TV-14", + "episode_content_rating_sanitized": "TV-14", + "episode_date_standardized": "{ upload_date_standardized }", + "episode_date_standardized_sanitized": "{ %sanitize( upload_date_standardized ) }", + "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) } - { title_sanitized_plex }", + "episode_file_name_sanitized": "{ %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/{ %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) }", + "episode_file_path_sanitized": "{ %sanitize( %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) ) }", + "episode_number": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", + "episode_number_and_padded_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "episode_number_and_padded__sanitized": "{ %sanitize( [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] ) }", + "episode_number_padded": "{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) }", + "episode_number_padded_sanitized": "{ %sanitize( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ) }", + "episode_number_sanitized": "{ %sanitize( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ) }", + "episode_plot": "{ webpage_url }\n\n{ description }", + "episode_plot_sanitized": "{ %sanitize( %concat( %string( webpage_url ), \"\n\n\", %string( description ) ) ) }", + "episode_title": "{ upload_date_standardized } - { title }", + "episode_title_sanitized": "{ %sanitize( %concat( %string( upload_date_standardized ), \" - \", %string( title ) ) ) }", + "episode_year": "{ %slice( upload_date_standardized, 0, 4 ) }", + "episode_year_sanitized": "{ %sanitize( %slice( upload_date_standardized, 0, 4 ) ) }", + "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", + "epoch_date": "{ %datetime_strftime( epoch, \"%Y%m%d\" ) }", + "epoch_date_sanitized": "{ %sanitize( epoch_date ) }", + "epoch_hour": "{ %datetime_strftime( epoch, \"%H\" ) }", + "epoch_hour_sanitized": "{ %sanitize( epoch_hour ) }", + "epoch_sanitized": "{ %sanitize( epoch ) }", + "ext": "{ %throw( \"Plugin variable ext has not been created yet\" ) }", + "ext_sanitized": "{ %sanitize( ext ) }", + "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", + "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", + "extractor_key_sanitized": "{ %sanitize( extractor_key ) }", + "extractor_sanitized": "{ %sanitize( extractor ) }", + "file_title": "{ title_sanitized_plex }", + "file_title_sanitized": "{ %sanitize( title_sanitized_plex ) }", + "file_uid": "{ uid_sanitized_plex }", + "file_uid_sanitized": "{ %sanitize( uid_sanitized_plex ) }", + "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", + "height_sanitized": "{ %sanitize( height ) }", + "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", extractor_key ) }", + "ie_key_sanitized": "{ %sanitize( ie_key ) }", + "include_sibling_metadata": "{ %bool(False) }", + "include_sibling_metadata_sanitized": "false", + "info_json_ext": "info.json", + "info_json_ext_sanitized": "info.json", + "modified_webpage_url": "{ webpage_url }", + "modified_webpage_url_sanitized": "{ %sanitize( webpage_url ) }", + "only_recent_date_range": "2months", + "only_recent_date_range_sanitized": "2months", + "only_recent_max_files": "{ %int(30) }", + "only_recent_max_files_sanitized": "30", + "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", + "playlist_count_sanitized": "{ %sanitize( playlist_count ) }", + "playlist_description": "{ %map_get_non_empty( playlist_metadata, \"description\", description ) }", + "playlist_description_sanitized": "{ %sanitize( playlist_description ) }", + "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", + "playlist_index_padded": "{ %pad_zero( playlist_index, 2 ) }", + "playlist_index_padded6": "{ %pad_zero( playlist_index, 6 ) }", + "playlist_index_padded6_sanitized": "{ %sanitize( playlist_index_padded6 ) }", + "playlist_index_padded_sanitized": "{ %sanitize( playlist_index_padded ) }", + "playlist_index_reversed": "{ %sub( playlist_count, playlist_index, -1 ) }", + "playlist_index_reversed_padded": "{ %pad_zero( playlist_index_reversed, 2 ) }", + "playlist_index_reversed_padded6": "{ %pad_zero( playlist_index_reversed, 6 ) }", + "playlist_index_reversed_padded6_sanitized": "{ %sanitize( playlist_index_reversed_padded6 ) }", + "playlist_index_reversed_padded_sanitized": "{ %sanitize( playlist_index_reversed_padded ) }", + "playlist_index_reversed_sanitized": "{ %sanitize( playlist_index_reversed ) }", + "playlist_index_sanitized": "{ %sanitize( playlist_index ) }", + "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", + "playlist_max_upload_date_sanitized": "{ %sanitize( playlist_max_upload_date ) }", + "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year\" ) ) }", + "playlist_max_upload_year_sanitized": "{ %sanitize( playlist_max_upload_year ) }", + "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year_truncated\" ) ) }", + "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( playlist_max_upload_year_truncated ) }", + "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", + "playlist_metadata_sanitized": "{ %sanitize( playlist_metadata ) }", + "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", title ) }", + "playlist_title_sanitized": "{ %sanitize( playlist_title ) }", + "playlist_uid": "{ %map_get_non_empty( playlist_metadata, \"playlist_id\", uid ) }", + "playlist_uid_sanitized": "{ %sanitize( playlist_uid ) }", + "playlist_uploader": "{ %map_get_non_empty( playlist_metadata, \"uploader\", uploader ) }", + "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", uploader_id ) }", + "playlist_uploader_id_sanitized": "{ %sanitize( playlist_uploader_id ) }", + "playlist_uploader_sanitized": "{ %sanitize( playlist_uploader ) }", + "playlist_uploader_url": "{ %map_get_non_empty( playlist_metadata, \"uploader_url\", webpage_url ) }", + "playlist_uploader_url_sanitized": "{ %sanitize( playlist_uploader_url ) }", + "playlist_webpage_url": "{ %map_get_non_empty( playlist_metadata, \"webpage_url\", webpage_url ) }", + "playlist_webpage_url_sanitized": "{ %sanitize( playlist_webpage_url ) }", + "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", upload_date ) }", + "release_date_sanitized": "{ %sanitize( release_date ) }", + "release_date_standardized": "{ %string( %map_get( %to_date_metadata( release_date ), \"date_standardized\" ) ) }", + "release_date_standardized_sanitized": "{ %sanitize( release_date_standardized ) }", + "release_day": "{ %int( %map_get( %to_date_metadata( release_date ), \"day\" ) ) }", + "release_day_of_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year\" ) ) }", + "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_padded\" ) ) }", + "release_day_of_year_padded_sanitized": "{ %sanitize( release_day_of_year_padded ) }", + "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed\" ) ) }", + "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed_padded\" ) ) }", + "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( release_day_of_year_reversed_padded ) }", + "release_day_of_year_reversed_sanitized": "{ %sanitize( release_day_of_year_reversed ) }", + "release_day_of_year_sanitized": "{ %sanitize( release_day_of_year ) }", + "release_day_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_padded\" ) ) }", + "release_day_padded_sanitized": "{ %sanitize( release_day_padded ) }", + "release_day_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_reversed\" ) ) }", + "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_reversed_padded\" ) ) }", + "release_day_reversed_padded_sanitized": "{ %sanitize( release_day_reversed_padded ) }", + "release_day_reversed_sanitized": "{ %sanitize( release_day_reversed ) }", + "release_day_sanitized": "{ %sanitize( release_day ) }", + "release_month": "{ %int( %map_get( %to_date_metadata( release_date ), \"month\" ) ) }", + "release_month_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_padded\" ) ) }", + "release_month_padded_sanitized": "{ %sanitize( release_month_padded ) }", + "release_month_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"month_reversed\" ) ) }", + "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_reversed_padded\" ) ) }", + "release_month_reversed_padded_sanitized": "{ %sanitize( release_month_reversed_padded ) }", + "release_month_reversed_sanitized": "{ %sanitize( release_month_reversed ) }", + "release_month_sanitized": "{ %sanitize( release_month ) }", + "release_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"year\" ) ) }", + "release_year_sanitized": "{ %sanitize( release_year ) }", + "release_year_truncated": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated\" ) ) }", + "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated_reversed\" ) ) }", + "release_year_truncated_reversed_sanitized": "{ %sanitize( release_year_truncated_reversed ) }", + "release_year_truncated_sanitized": "{ %sanitize( release_year_truncated ) }", + "requested_subtitles": "{ { } }", + "requested_subtitles_sanitized": "{ %sanitize( requested_subtitles ) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": "{ %int(361) }", + "resolution_assert_height_gte_sanitized": "361", + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_ignore_titles_sanitized": "[]", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", + "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) ) }", + "resolution_assert_print": "{ %bool(True) }", + "resolution_assert_print_sanitized": "true", + "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", + "resolution_readable": "{ width }x{ height }", + "resolution_readable_sanitized": "{ %sanitize( %concat( %string( width ), \"x\", %string( height ) ) ) }", + "s01_name": "", + "s01_name_sanitized": "", + "s01_url": "", + "s01_url_sanitized": "", + "season_directory_name": "Season { upload_year }", + "season_directory_name_sanitized": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }", + "season_number": "{ upload_year }", + "season_number_padded": "{ upload_year }", + "season_number_padded_sanitized": "{ %sanitize( upload_year ) }", + "season_number_sanitized": "{ %sanitize( upload_year ) }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/Season{ upload_year }.jpg", + "season_poster_file_name_sanitized": "{ %sanitize( %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/Season\", %string( upload_year ), \".jpg\" ) ) }", + "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", + "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", + "source_count": "{ %map_get_non_empty( playlist_metadata, \"playlist_count\", 1 ) }", + "source_count_sanitized": "{ %sanitize( source_count ) }", + "source_description": "{ %map_get_non_empty( source_metadata, \"description\", playlist_description ) }", + "source_description_sanitized": "{ %sanitize( source_description ) }", + "source_index": "{ %map_get_non_empty( playlist_metadata, \"playlist_index\", 1 ) }", + "source_index_padded": "{ %pad_zero( source_index, 2 ) }", + "source_index_padded_sanitized": "{ %sanitize( source_index_padded ) }", + "source_index_sanitized": "{ %sanitize( source_index ) }", + "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", + "source_metadata_sanitized": "{ %sanitize( source_metadata ) }", + "source_title": "{ %map_get_non_empty( source_metadata, \"title\", playlist_title ) }", + "source_title_sanitized": "{ %sanitize( source_title ) }", + "source_uid": "{ %map_get_non_empty( source_metadata, \"id\", playlist_uid ) }", + "source_uid_sanitized": "{ %sanitize( source_uid ) }", + "source_uploader": "{ %map_get_non_empty( source_metadata, \"uploader\", playlist_uploader ) }", + "source_uploader_id": "{ %map_get_non_empty( source_metadata, \"uploader_id\", playlist_uploader_id ) }", + "source_uploader_id_sanitized": "{ %sanitize( source_uploader_id ) }", + "source_uploader_sanitized": "{ %sanitize( source_uploader ) }", + "source_uploader_url": "{ %map_get_non_empty( source_metadata, \"uploader_url\", source_webpage_url ) }", + "source_uploader_url_sanitized": "{ %sanitize( source_uploader_url ) }", + "source_webpage_url": "{ %map_get_non_empty( source_metadata, \"webpage_url\", playlist_webpage_url ) }", + "source_webpage_url_sanitized": "{ %sanitize( source_webpage_url ) }", + "sponsorblock_chapters": "{ [ ] }", + "sponsorblock_chapters_sanitized": "{ %sanitize( sponsorblock_chapters ) }", + "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", + "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs\uff02]", + "subscription_has_download_archive": "{ %bool(False) }", + "subscription_has_download_archive_sanitized": "false", + "subscription_indent_1": "Documentaries", + "subscription_indent_1_sanitized": "Documentaries", + "subscription_indent_2": "TV-14", + "subscription_indent_2_sanitized": "TV-14", + "subscription_name": "NOVA PBS", + "subscription_name_sanitized": "NOVA PBS", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", + "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", + "thumbnail_ext": "jpg", + "thumbnail_ext_sanitized": "jpg", + "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) }-thumb.jpg", + "thumbnail_file_name_sanitized": "{ %sanitize( %concat( %string( %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) ), \"-thumb.jpg\" ) ) }", + "title": "{ %map_get_non_empty( entry_metadata, \"title\", uid ) }", + "title_sanitized": "{ %sanitize( title ) }", + "title_sanitized_plex": "{ %sanitize_plex_episode( title ) }", + "title_sanitized_plex_sanitized": "{ %sanitize( title_sanitized_plex ) }", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_episode_ordering_sanitized": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "tv_show_by_date_ordering_pair_validation__sanitized": "{ %sanitize( [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] ) }", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_by_date_season_ordering_sanitized": "upload-year", + "tv_show_content_rating": "TV-14", + "tv_show_content_rating_default": "TV-14", + "tv_show_content_rating_default_sanitized": "TV-14", + "tv_show_content_rating_sanitized": "TV-14", + "tv_show_date_range_type": "upload_date", + "tv_show_date_range_type_sanitized": "upload_date", + "tv_show_directory": "tv_show_directory_path", + "tv_show_directory_sanitized": "tv_show_directory_path", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_fanart_file_name_sanitized": "fanart.jpg", + "tv_show_genre": "Documentaries", + "tv_show_genre_default": "ytdl-sub", + "tv_show_genre_default_sanitized": "ytdl-sub", + "tv_show_genre_sanitized": "Documentaries", + "tv_show_name": "NOVA PBS", + "tv_show_name_sanitized": "NOVA PBS", + "tv_show_poster_file_name": "poster.jpg", + "tv_show_poster_file_name_sanitized": "poster.jpg", + "uid": "{ %map_get( entry_metadata, \"id\" ) }", + "uid_sanitized": "{ %sanitize( uid ) }", + "uid_sanitized_plex": "{ %sanitize_plex_episode( uid ) }", + "uid_sanitized_plex_sanitized": "{ %sanitize( uid_sanitized_plex ) }", + "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", epoch_date ) }", + "upload_date_index": "{ %int( 1 ) }", + "upload_date_index_padded": "{ %pad_zero( upload_date_index, 2 ) }", + "upload_date_index_padded_sanitized": "{ %sanitize( upload_date_index_padded ) }", + "upload_date_index_reversed": "{ %sub( 100, upload_date_index ) }", + "upload_date_index_reversed_padded": "{ %pad_zero( upload_date_index_reversed, 2 ) }", + "upload_date_index_reversed_padded_sanitized": "{ %sanitize( upload_date_index_reversed_padded ) }", + "upload_date_index_reversed_sanitized": "{ %sanitize( upload_date_index_reversed ) }", + "upload_date_index_sanitized": "{ %sanitize( upload_date_index ) }", + "upload_date_sanitized": "{ %sanitize( upload_date ) }", + "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( upload_date ), \"date_standardized\" ) ) }", + "upload_date_standardized_sanitized": "{ %sanitize( upload_date_standardized ) }", + "upload_day": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day\" ) ) }", + "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year\" ) ) }", + "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_padded\" ) ) }", + "upload_day_of_year_padded_sanitized": "{ %sanitize( upload_day_of_year_padded ) }", + "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed\" ) ) }", + "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed_padded\" ) ) }", + "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( upload_day_of_year_reversed_padded ) }", + "upload_day_of_year_reversed_sanitized": "{ %sanitize( upload_day_of_year_reversed ) }", + "upload_day_of_year_sanitized": "{ %sanitize( upload_day_of_year ) }", + "upload_day_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_padded\" ) ) }", + "upload_day_padded_sanitized": "{ %sanitize( upload_day_padded ) }", + "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_reversed\" ) ) }", + "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_reversed_padded\" ) ) }", + "upload_day_reversed_padded_sanitized": "{ %sanitize( upload_day_reversed_padded ) }", + "upload_day_reversed_sanitized": "{ %sanitize( upload_day_reversed ) }", + "upload_day_sanitized": "{ %sanitize( upload_day ) }", + "upload_month": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month\" ) ) }", + "upload_month_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_padded\" ) ) }", + "upload_month_padded_sanitized": "{ %sanitize( upload_month_padded ) }", + "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month_reversed\" ) ) }", + "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_reversed_padded\" ) ) }", + "upload_month_reversed_padded_sanitized": "{ %sanitize( upload_month_reversed_padded ) }", + "upload_month_reversed_sanitized": "{ %sanitize( upload_month_reversed ) }", + "upload_month_sanitized": "{ %sanitize( upload_month ) }", + "upload_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year\" ) ) }", + "upload_year_sanitized": "{ %sanitize( upload_year ) }", + "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated\" ) ) }", + "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated_reversed\" ) ) }", + "upload_year_truncated_reversed_sanitized": "{ %sanitize( upload_year_truncated_reversed ) }", + "upload_year_truncated_sanitized": "{ %sanitize( upload_year_truncated ) }", + "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", uploader_id ) }", + "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", uid ) }", + "uploader_id_sanitized": "{ %sanitize( uploader_id ) }", + "uploader_sanitized": "{ %sanitize( uploader ) }", + "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", webpage_url ) }", + "uploader_url_sanitized": "{ %sanitize( uploader_url ) }", + "url": "https://www.youtube.com/@novapbs", + "url10": "", + "url100": "", + "url100_sanitized": "", + "url10_sanitized": "", + "url11": "", + "url11_sanitized": "", + "url12": "", + "url12_sanitized": "", + "url13": "", + "url13_sanitized": "", + "url14": "", + "url14_sanitized": "", + "url15": "", + "url15_sanitized": "", + "url16": "", + "url16_sanitized": "", + "url17": "", + "url17_sanitized": "", + "url18": "", + "url18_sanitized": "", + "url19": "", + "url19_sanitized": "", + "url2": "", + "url20": "", + "url20_sanitized": "", + "url21": "", + "url21_sanitized": "", + "url22": "", + "url22_sanitized": "", + "url23": "", + "url23_sanitized": "", + "url24": "", + "url24_sanitized": "", + "url25": "", + "url25_sanitized": "", + "url26": "", + "url26_sanitized": "", + "url27": "", + "url27_sanitized": "", + "url28": "", + "url28_sanitized": "", + "url29": "", + "url29_sanitized": "", + "url2_sanitized": "", + "url3": "", + "url30": "", + "url30_sanitized": "", + "url31": "", + "url31_sanitized": "", + "url32": "", + "url32_sanitized": "", + "url33": "", + "url33_sanitized": "", + "url34": "", + "url34_sanitized": "", + "url35": "", + "url35_sanitized": "", + "url36": "", + "url36_sanitized": "", + "url37": "", + "url37_sanitized": "", + "url38": "", + "url38_sanitized": "", + "url39": "", + "url39_sanitized": "", + "url3_sanitized": "", + "url4": "", + "url40": "", + "url40_sanitized": "", + "url41": "", + "url41_sanitized": "", + "url42": "", + "url42_sanitized": "", + "url43": "", + "url43_sanitized": "", + "url44": "", + "url44_sanitized": "", + "url45": "", + "url45_sanitized": "", + "url46": "", + "url46_sanitized": "", + "url47": "", + "url47_sanitized": "", + "url48": "", + "url48_sanitized": "", + "url49": "", + "url49_sanitized": "", + "url4_sanitized": "", + "url5": "", + "url50": "", + "url50_sanitized": "", + "url51": "", + "url51_sanitized": "", + "url52": "", + "url52_sanitized": "", + "url53": "", + "url53_sanitized": "", + "url54": "", + "url54_sanitized": "", + "url55": "", + "url55_sanitized": "", + "url56": "", + "url56_sanitized": "", + "url57": "", + "url57_sanitized": "", + "url58": "", + "url58_sanitized": "", + "url59": "", + "url59_sanitized": "", + "url5_sanitized": "", + "url6": "", + "url60": "", + "url60_sanitized": "", + "url61": "", + "url61_sanitized": "", + "url62": "", + "url62_sanitized": "", + "url63": "", + "url63_sanitized": "", + "url64": "", + "url64_sanitized": "", + "url65": "", + "url65_sanitized": "", + "url66": "", + "url66_sanitized": "", + "url67": "", + "url67_sanitized": "", + "url68": "", + "url68_sanitized": "", + "url69": "", + "url69_sanitized": "", + "url6_sanitized": "", + "url7": "", + "url70": "", + "url70_sanitized": "", + "url71": "", + "url71_sanitized": "", + "url72": "", + "url72_sanitized": "", + "url73": "", + "url73_sanitized": "", + "url74": "", + "url74_sanitized": "", + "url75": "", + "url75_sanitized": "", + "url76": "", + "url76_sanitized": "", + "url77": "", + "url77_sanitized": "", + "url78": "", + "url78_sanitized": "", + "url79": "", + "url79_sanitized": "", + "url7_sanitized": "", + "url8": "", + "url80": "", + "url80_sanitized": "", + "url81": "", + "url81_sanitized": "", + "url82": "", + "url82_sanitized": "", + "url83": "", + "url83_sanitized": "", + "url84": "", + "url84_sanitized": "", + "url85": "", + "url85_sanitized": "", + "url86": "", + "url86_sanitized": "", + "url87": "", + "url87_sanitized": "", + "url88": "", + "url88_sanitized": "", + "url89": "", + "url89_sanitized": "", + "url8_sanitized": "", + "url9": "", + "url90": "", + "url90_sanitized": "", + "url91": "", + "url91_sanitized": "", + "url92": "", + "url92_sanitized": "", + "url93": "", + "url93_sanitized": "", + "url94": "", + "url94_sanitized": "", + "url95": "", + "url95_sanitized": "", + "url96": "", + "url96_sanitized": "", + "url97": "", + "url97_sanitized": "", + "url98": "", + "url98_sanitized": "", + "url99": "", + "url99_sanitized": "", + "url9_sanitized": "", + "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", + "urls": "{ [ \"https://www.youtube.com/@novapbs\" ] }", + "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs\uff02]", + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "webpage_url_sanitized": "{ %sanitize( webpage_url ) }", + "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", + "width_sanitized": "{ %sanitize( width ) }", + "ytdl_sub_chapters_from_comments": "{ %throw( \"Plugin variable ytdl_sub_chapters_from_comments has not been created yet\" ) }", + "ytdl_sub_chapters_from_comments_sanitized": "{ %sanitize( ytdl_sub_chapters_from_comments ) }", + "ytdl_sub_entry_date_eval": "{ %string( upload_date_standardized ) }", + "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( ytdl_sub_entry_date_eval ) }", + "ytdl_sub_input_url": "{ %string( '' ) }", + "ytdl_sub_input_url_count": "{ %int( 0 ) }", + "ytdl_sub_input_url_count_sanitized": "{ %sanitize( ytdl_sub_input_url_count ) }", + "ytdl_sub_input_url_index": "{ %int( -1 ) }", + "ytdl_sub_input_url_index_sanitized": "{ %sanitize( ytdl_sub_input_url_index ) }", + "ytdl_sub_input_url_sanitized": "{ %sanitize( ytdl_sub_input_url ) }" +} \ No newline at end of file diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index c48f4f0e..35942f8d 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -8,9 +8,11 @@ import pytest import yaml from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.plugins.nfo_tags import NfoTagsOptions from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.utils.exceptions import ValidationException +from ytdl_sub.utils.script import ScriptUtils @contextmanager diff --git a/tests/unit/config/test_subscription_partial_resolve.py b/tests/unit/config/test_subscription_partial_resolve.py new file mode 100644 index 00000000..0a901fec --- /dev/null +++ b/tests/unit/config/test_subscription_partial_resolve.py @@ -0,0 +1,118 @@ +from pathlib import Path + +from resources import expected_json + +from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.entries.script.variable_definitions import VARIABLES +from ytdl_sub.subscriptions.subscription import Subscription +from ytdl_sub.utils.script import ScriptUtils + + +def _ensure_partial_resolve( + sub: Subscription, preset_type: str, should_fully_resolve: bool +) -> None: + unresolvable = sub.plugins.get_all_variables( + additional_options=[sub.downloader_options, sub.output_options] + ) + unresolvable.add("entry_metadata") + unresolvable.add("sibling_metadata") + + if not should_fully_resolve: + unresolvable.update(VARIABLES.scripts().keys()) + + script = sub.overrides.script.add( + { + f"{preset_type}_directory": "tv_show_directory_path", + f"{preset_type}_directory_sanitized": "tv_show_directory_path", + } + ) + partial_script = script.resolve_partial(unresolvable=unresolvable) + + # Assert only overrides + out = { + name: ScriptUtils.to_native_script(partial_script._variables[name]) + for name in sub.overrides.keys + if not name.startswith("%") + } + + expected_out_filename = f"{preset_type}/inspect_overrides.json" + if should_fully_resolve: + expected_out_filename = f"{preset_type}/inspect_full_overrides.json" + + assert out == expected_json(out, expected_out_filename) + + # Assert all variables + out = { + name: ScriptUtils.to_native_script(partial_script._variables[name]) + for name in partial_script.variable_names + } + + expected_out_filename = f"{preset_type}/inspect_variables.json" + if should_fully_resolve: + expected_out_filename = f"{preset_type}/inspect_full_variables.json" + + assert out == expected_json(out, expected_out_filename) + + +def test_partial_resolve_tv_show(config_file: ConfigFile, tv_show_subscriptions_path: Path): + _ensure_partial_resolve( + sub=Subscription.from_file_path( + config=config_file, subscription_path=tv_show_subscriptions_path + )[0], + preset_type="tv_show", + should_fully_resolve=True, + ) + + +def test_partial_resolve_tv_show_full(config_file: ConfigFile, tv_show_subscriptions_path: Path): + _ensure_partial_resolve( + sub=Subscription.from_file_path( + config=config_file, subscription_path=tv_show_subscriptions_path + )[0], + preset_type="tv_show", + should_fully_resolve=False, + ) + + +def test_partial_resolve_music(default_config: ConfigFile, music_subscriptions_path: Path): + _ensure_partial_resolve( + sub=Subscription.from_file_path( + config=default_config, subscription_path=music_subscriptions_path + )[0], + preset_type="music", + should_fully_resolve=True, + ) + + +def test_partial_resolve_music_full(default_config: ConfigFile, music_subscriptions_path: Path): + _ensure_partial_resolve( + sub=Subscription.from_file_path( + config=default_config, subscription_path=music_subscriptions_path + )[0], + preset_type="music", + should_fully_resolve=False, + ) + + +def test_partial_resolve_music_video( + default_config: ConfigFile, music_video_subscription_path: Path +): + _ensure_partial_resolve( + sub=Subscription.from_file_path( + config=default_config, subscription_path=music_video_subscription_path + )[0], + preset_type="music_video", + should_fully_resolve=False, + ) + + +def test_partial_resolve_music_video_full( + default_config: ConfigFile, music_video_subscription_path: Path +): + _ensure_partial_resolve( + sub=Subscription.from_file_path( + config=default_config, subscription_path=music_video_subscription_path + )[0], + preset_type="music_video", + should_fully_resolve=True, + ) From 942c983f6070a5a4090a4aa1a57bea1723ceef5f Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 16 Jan 2026 13:42:22 -0800 Subject: [PATCH 28/46] [BACKEND] Sorted video tags (#1417) For reproducibility in tests. --- src/ytdl_sub/plugins/video_tags.py | 2 +- .../plugins/file_convert/custom_ffmpeg_args.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ytdl_sub/plugins/video_tags.py b/src/ytdl_sub/plugins/video_tags.py index 16b7d949..00c5d460 100644 --- a/src/ytdl_sub/plugins/video_tags.py +++ b/src/ytdl_sub/plugins/video_tags.py @@ -34,7 +34,7 @@ class VideoTagsPlugin(Plugin[VideoTagsOptions]): Tags the entry's audio file using values defined in the metadata options """ tags_to_write: Dict[str, str] = {} - for tag_name, tag_formatter in self.plugin_options.dict.items(): + for tag_name, tag_formatter in sorted(self.plugin_options.dict.items()): tag_value = self.overrides.apply_formatter(formatter=tag_formatter, entry=entry) tags_to_write[tag_name] = tag_value diff --git a/tests/resources/expected_downloads_summaries/plugins/file_convert/custom_ffmpeg_args.json b/tests/resources/expected_downloads_summaries/plugins/file_convert/custom_ffmpeg_args.json index 642ea323..13bbe1fa 100644 --- a/tests/resources/expected_downloads_summaries/plugins/file_convert/custom_ffmpeg_args.json +++ b/tests/resources/expected_downloads_summaries/plugins/file_convert/custom_ffmpeg_args.json @@ -2,18 +2,18 @@ ".ytdl-sub-subscription_test-download-archive.json": "19cf39d57914ba9cbd1e57ba6f1e0683", "JMC/Mock Entry 20-1.info.json": "INFO_JSON", "JMC/Mock Entry 20-1.jpg": "e80c508c4818454300133fe1dc1a9cd7", - "JMC/Mock Entry 20-1.mkv": "eb9a8ff61701ab673c2f06147fe29a4e", + "JMC/Mock Entry 20-1.mkv": "cbc8c05ea5cf4deefd735b93af9c0259", "JMC/Mock Entry 20-1.nfo": "fefcf0b3e4f4ff80ad636584d50dadec", "JMC/Mock Entry 20-2.info.json": "INFO_JSON", "JMC/Mock Entry 20-2.jpg": "e80c508c4818454300133fe1dc1a9cd7", - "JMC/Mock Entry 20-2.mkv": "65d047562c61a068e8a92bd8df8801fa", + "JMC/Mock Entry 20-2.mkv": "85cc274346abc0c71ab7702b0d61abde", "JMC/Mock Entry 20-2.nfo": "025c0b631da5ff5470382b38fce78d2d", "JMC/Mock Entry 20-3.info.json": "INFO_JSON", "JMC/Mock Entry 20-3.jpg": "e80c508c4818454300133fe1dc1a9cd7", - "JMC/Mock Entry 20-3.mkv": "98582526fcb3bea10ab7b3df7a5bc65d", + "JMC/Mock Entry 20-3.mkv": "c5a9ce268475c3b8fa9d4d0e3c0c25ae", "JMC/Mock Entry 20-3.nfo": "618b0ff948d9de2e10cf1da8c0dd6615", "JMC/Mock Entry 21-1.info.json": "INFO_JSON", "JMC/Mock Entry 21-1.jpg": "e80c508c4818454300133fe1dc1a9cd7", - "JMC/Mock Entry 21-1.mkv": "3fe368f6d4bd9662a24abca9215b4d26", + "JMC/Mock Entry 21-1.mkv": "ebdd8026e72625c56dd2aa8a2e19e814", "JMC/Mock Entry 21-1.nfo": "e5c715749efc1603a6e2f59244d87aba" } \ No newline at end of file From 97df4dac1d6e7ad0606de6a13cb19adc582e0af9 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 16 Jan 2026 13:51:33 -0800 Subject: [PATCH 29/46] [BACKEND] Preserve dict formatter value (#1416) Do not modify the original definition of dict validators. --- src/ytdl_sub/validators/string_formatter_validators.py | 5 +++-- .../expected_json/music_video/inspect_full_overrides.json | 2 +- .../expected_json/music_video/inspect_full_variables.json | 4 ++-- .../expected_json/music_video/inspect_overrides.json | 2 +- .../expected_json/music_video/inspect_variables.json | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 1581c702..21715c03 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -183,12 +183,13 @@ class DictFormatterValidator(LiteralDictValidator): super().__init__(name, value) for key in self._keys: - self._value[key] = self._validate_key(key=key, validator=self._key_validator) + # Gets stored in __validator_dict + _ = self._validate_key(key=key, validator=self._key_validator) @property def dict(self) -> Dict[str, StringFormatterValidator]: """Returns dict with string formatter values""" - return self._value + return self._validator_dict @property def dict_with_format_strings(self) -> Dict[str, str]: diff --git a/tests/resources/expected_json/music_video/inspect_full_overrides.json b/tests/resources/expected_json/music_video/inspect_full_overrides.json index 0ad15d9a..7325202e 100644 --- a/tests/resources/expected_json/music_video/inspect_full_overrides.json +++ b/tests/resources/expected_json/music_video/inspect_full_overrides.json @@ -10,7 +10,7 @@ "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", "include_sibling_metadata": "{ %bool(False) }", "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "music_video_album": "{ %get_url_field( \"category\", music_video_album_default ) }", + "music_video_album": "Music Videos", "music_video_album_default": "Music Videos", "music_video_artist": "Rick Astley", "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", diff --git a/tests/resources/expected_json/music_video/inspect_full_variables.json b/tests/resources/expected_json/music_video/inspect_full_variables.json index fa8b5c85..34dc4392 100644 --- a/tests/resources/expected_json/music_video/inspect_full_variables.json +++ b/tests/resources/expected_json/music_video/inspect_full_variables.json @@ -59,10 +59,10 @@ "info_json_ext_sanitized": "info.json", "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", "modified_webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", - "music_video_album": "{ %get_url_field( \"category\", music_video_album_default ) }", + "music_video_album": "Music Videos", "music_video_album_default": "Music Videos", "music_video_album_default_sanitized": "Music Videos", - "music_video_album_sanitized": "{ %sanitize( %get_url_field( \"category\", music_video_album_default ) ) }", + "music_video_album_sanitized": "Music Videos", "music_video_artist": "Rick Astley", "music_video_artist_sanitized": "Rick Astley", "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", diff --git a/tests/resources/expected_json/music_video/inspect_overrides.json b/tests/resources/expected_json/music_video/inspect_overrides.json index a0b3c3a1..41e0f19d 100644 --- a/tests/resources/expected_json/music_video/inspect_overrides.json +++ b/tests/resources/expected_json/music_video/inspect_overrides.json @@ -10,7 +10,7 @@ "file_uid": "{ uid_sanitized_plex }", "include_sibling_metadata": "{ %bool(False) }", "modified_webpage_url": "{ webpage_url }", - "music_video_album": "{ %get_url_field( \"category\", music_video_album_default ) }", + "music_video_album": "{ %get_url_field( \"category\", \"Music Videos\" ) }", "music_video_album_default": "Music Videos", "music_video_artist": "Rick Astley", "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", diff --git a/tests/resources/expected_json/music_video/inspect_variables.json b/tests/resources/expected_json/music_video/inspect_variables.json index 318f2685..41606b71 100644 --- a/tests/resources/expected_json/music_video/inspect_variables.json +++ b/tests/resources/expected_json/music_video/inspect_variables.json @@ -59,10 +59,10 @@ "info_json_ext_sanitized": "info.json", "modified_webpage_url": "{ webpage_url }", "modified_webpage_url_sanitized": "{ %sanitize( webpage_url ) }", - "music_video_album": "{ %get_url_field( \"category\", music_video_album_default ) }", + "music_video_album": "{ %get_url_field( \"category\", \"Music Videos\" ) }", "music_video_album_default": "Music Videos", "music_video_album_default_sanitized": "Music Videos", - "music_video_album_sanitized": "{ %sanitize( %get_url_field( \"category\", music_video_album_default ) ) }", + "music_video_album_sanitized": "{ %sanitize( %get_url_field( \"category\", \"Music Videos\" ) ) }", "music_video_artist": "Rick Astley", "music_video_artist_sanitized": "Rick Astley", "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", From c4e112e8d5165f42ead251a441a14fb75dc52a30 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 23 Jan 2026 09:23:34 -0800 Subject: [PATCH 30/46] [BUGFIX] Fix soundcloud not downloading tracks (#1419) The /tracks URL is currently broken in yt-dlp. Now uses the input URL which will download the entire artists' page instead to get non-album tracks. --- src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml b/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml index 32eb964a..966be5f8 100644 --- a/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml +++ b/src/ytdl_sub/prebuilt_presets/music/soundcloud.yaml @@ -8,7 +8,7 @@ presets: urls: # The first URL will be all the artist's tracks. # Treat these as singles - an album with a single track - - url: "{url}/tracks" + - url: "{url}" include_sibling_metadata: False variables: sc_track_album: "{title}" From 264e458c1ca57b824eee8b9d195924ba2a29f35f Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 23 Jan 2026 10:58:14 -0800 Subject: [PATCH 31/46] [BACKEND] Cache cycle checks (#1420) Small speedup to make cycle validation faster. --- src/ytdl_sub/script/script.py | 60 +++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index 2e436e81..5d73613e 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -1,5 +1,6 @@ # pylint: disable=missing-raises-doc import copy +from collections import defaultdict from typing import Dict from typing import List from typing import Optional @@ -38,59 +39,71 @@ class Script: ``{ %custom_function: syntax }`` """ - def _ensure_no_cycle( + def _throw_cycle_error( self, name: str, dep: str, deps: List[str], definitions: Dict[str, SyntaxTree] ): - if dep not in definitions: - return # does not exist, will throw downstream in parser + type_name, pre = ( + ("custom functions", "%") if definitions is self._functions else ("variables", "") + ) + cycle_deps = [name] + deps + [dep] + cycle_deps_str = " -> ".join([f"{pre}{name}" for name in cycle_deps]) - if name in deps + [dep]: - type_name, pre = ( - ("custom functions", "%") if definitions is self._functions else ("variables", "") - ) - cycle_deps = [name] + deps + [dep] - cycle_deps_str = " -> ".join([f"{pre}{name}" for name in cycle_deps]) - - raise CycleDetected(f"Cycle detected within these {type_name}: {cycle_deps_str}") + raise CycleDetected(f"Cycle detected within these {type_name}: {cycle_deps_str}") def _traverse_variable_dependencies( self, variable_name: str, variable_dependency: SyntaxTree, deps: List[str], + ensured: Dict[str, Set[str]], ) -> None: for dep in variable_dependency.variables: - self._ensure_no_cycle( - name=variable_name, dep=dep.name, deps=deps, definitions=self._variables - ) + if variable_name == dep.name: + self._throw_cycle_error( + name=variable_name, dep=dep.name, deps=deps, definitions=self._variables + ) + + if dep.name in ensured[variable_name]: + continue + self._traverse_variable_dependencies( variable_name=variable_name, variable_dependency=self._variables[dep.name], deps=deps + [dep.name], + ensured=ensured, ) + ensured[variable_name].add(dep.name) for custom_func in variable_dependency.custom_function_dependencies( custom_function_definitions=self._functions ): for dep in self._functions[custom_func.name].variables: - self._ensure_no_cycle( - name=variable_name, - dep=dep.name, - deps=deps + [custom_func.definition_name()], - definitions=self._variables, - ) + if variable_name == dep.name: + self._throw_cycle_error( + name=variable_name, + dep=dep.name, + deps=deps + [custom_func.definition_name()], + definitions=self._variables, + ) + if dep.name in ensured[variable_name]: + continue + self._traverse_variable_dependencies( variable_name=variable_name, variable_dependency=self._variables[dep.name], deps=deps + [custom_func.definition_name(), dep.name], + ensured=ensured, ) + ensured[variable_name].add(dep.name) def _ensure_no_variable_cycles(self, variables: Dict[str, SyntaxTree]): + ensured: Dict[str, Set[str]] = defaultdict(set) for variable_name, variable_definition in variables.items(): self._traverse_variable_dependencies( variable_name=variable_name, variable_dependency=variable_definition, deps=[], + ensured=ensured, ) def _traverse_custom_function_dependencies( @@ -100,9 +113,10 @@ class Script: deps: List[str], ) -> None: for dep in custom_function_dependency.custom_functions: - self._ensure_no_cycle( - name=custom_function_name, dep=dep.name, deps=deps, definitions=self._functions - ) + if custom_function_name == dep.name: + self._throw_cycle_error( + name=custom_function_name, dep=dep.name, deps=deps, definitions=self._functions + ) self._traverse_custom_function_dependencies( custom_function_name=custom_function_name, custom_function_dependency=self._functions[dep.name], From 60cdbad8c7cbbba51ebfead3c6c59abbce6f7341 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 23 Jan 2026 16:36:17 -0800 Subject: [PATCH 32/46] [BACKEND] More flexible formatter post_process (#1421) Makes the underlying formatting process better handle native (non-string) types in a more flexible way. --- src/ytdl_sub/config/overrides.py | 53 ++++++------------- src/ytdl_sub/config/plugin/plugin.py | 2 +- src/ytdl_sub/config/preset_options.py | 5 +- src/ytdl_sub/downloaders/url/downloader.py | 14 +++-- src/ytdl_sub/downloaders/url/validators.py | 3 +- src/ytdl_sub/plugins/date_range.py | 2 +- src/ytdl_sub/plugins/embed_thumbnail.py | 2 +- src/ytdl_sub/plugins/filter_exclude.py | 11 ++-- src/ytdl_sub/plugins/filter_include.py | 12 +++-- src/ytdl_sub/plugins/nfo_tags.py | 2 +- src/ytdl_sub/plugins/square_thumbnail.py | 2 +- src/ytdl_sub/plugins/throttle_protection.py | 6 +-- .../validators/string_formatter_validators.py | 41 +++++++------- tests/unit/config/test_subscription.py | 13 ----- .../test_tv_show_collection.py | 6 ++- 15 files changed, 74 insertions(+), 100 deletions(-) diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index 623fc06d..f1011fcf 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -3,6 +3,8 @@ from typing import Dict from typing import Iterable from typing import Optional from typing import Set +from typing import Type +from typing import TypeVar from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES @@ -20,10 +22,11 @@ from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.script import ScriptUtils from ytdl_sub.utils.scriptable import Scriptable -from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.string_formatter_validators import UnstructuredDictFormatterValidator +ExpectedT = TypeVar("ExpectedT") + class Overrides(UnstructuredDictFormatterValidator, Scriptable): """ @@ -207,7 +210,8 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): formatter: StringFormatterValidator, entry: Optional[Entry] = None, function_overrides: Optional[Dict[str, str]] = None, - ) -> str: + expected_type: Type[ExpectedT] = str, + ) -> ExpectedT: """ Parameters ---------- @@ -217,6 +221,8 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): Optional. Entry to add source variables to the formatter function_overrides Optional. Explicit values to override the overrides themselves and source variables + expected_type + The expected type that should return. Defaults to string. Returns ------- @@ -227,42 +233,15 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): StringFormattingException If the formatter that is trying to be resolved cannot """ - return formatter.post_process( - str( - self._apply_to_resolvable( - formatter=formatter, entry=entry, function_overrides=function_overrides - ) - ) - ) - - def apply_overrides_formatter_to_native( - self, - formatter: OverridesStringFormatterValidator, - function_overrides: Optional[Dict[str, str]] = None, - ) -> Any: - """ - Parameters - ---------- - formatter - Overrides formatter to apply - function_overrides - Optional. Explicit values to override the overrides themselves and source variables - - Returns - ------- - The native python form of the resolved variable - """ - return formatter.post_process_native( + out = formatter.post_process( self._apply_to_resolvable( - formatter=formatter, entry=None, function_overrides=function_overrides + formatter=formatter, entry=entry, function_overrides=function_overrides ).native ) - def evaluate_boolean( - self, formatter: StringFormatterValidator, entry: Optional[Entry] = None - ) -> bool: - """ - Apply a formatter, and evaluate it to a boolean - """ - output = self.apply_formatter(formatter=formatter, entry=entry) - return ScriptUtils.bool_formatter_output(output) + if not isinstance(out, expected_type): + raise StringFormattingException( + f"Expected type {expected_type.__name__}, but received '{out.__class__.__name__}'" + ) + + return out diff --git a/src/ytdl_sub/config/plugin/plugin.py b/src/ytdl_sub/config/plugin/plugin.py index 0cd02b53..23232c48 100644 --- a/src/ytdl_sub/config/plugin/plugin.py +++ b/src/ytdl_sub/config/plugin/plugin.py @@ -48,7 +48,7 @@ class Plugin(BasePlugin[OptionsValidatorT], Generic[OptionsValidatorT], ABC): Returns True if enabled, False if disabled. """ if isinstance(self.plugin_options, ToggleableOptionsDictValidator): - return self.overrides.evaluate_boolean(self.plugin_options.enable) + return self.overrides.apply_formatter(self.plugin_options.enable, expected_type=bool) return True def ytdl_options_match_filters(self) -> Tuple[List[str], List[str]]: diff --git a/src/ytdl_sub/config/preset_options.py b/src/ytdl_sub/config/preset_options.py index 93f36453..db07ca02 100644 --- a/src/ytdl_sub/config/preset_options.py +++ b/src/ytdl_sub/config/preset_options.py @@ -62,10 +62,7 @@ class YTDLOptions(UnstructuredOverridesDictFormatterValidator): Materializes the entire ytdl-options dict from OverrideStringFormatters into native python. """ - out = { - key: overrides.apply_overrides_formatter_to_native(val) - for key, val in self.dict.items() - } + out = {key: overrides.apply_formatter(val) for key, val in self.dict.items()} if "cookiefile" in out: if not FileHandler.is_file_existent(out["cookiefile"]): raise ValidationException( diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index acb35895..c94d6910 100644 --- a/src/ytdl_sub/downloaders/url/downloader.py +++ b/src/ytdl_sub/downloaders/url/downloader.py @@ -52,12 +52,12 @@ class UrlDownloaderBasePluginExtension(SourcePluginExtension[MultiUrlValidator]) if 0 <= input_url_idx < len(self.plugin_options.urls.list): validator = self.plugin_options.urls.list[input_url_idx] - if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.url): + if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list): return validator # Match the first validator based on the URL, if one exists for validator in self.plugin_options.urls.list: - if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.url): + if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list): return validator # Return the first validator if none exist @@ -382,7 +382,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): entries_to_iter: List[Optional[Entry]] = entries indices = list(range(len(entries_to_iter))) - if self.overrides.evaluate_boolean(validator.download_reverse): + if self.overrides.apply_formatter(validator.download_reverse, expected_type=bool): indices = reversed(indices) for idx in indices: @@ -461,8 +461,8 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): ytdl_option_overrides=validator.ytdl_options.to_native_dict(self.overrides) ) - include_sibling_metadata = self.overrides.evaluate_boolean( - validator.include_sibling_metadata + include_sibling_metadata = self.overrides.apply_formatter( + validator.include_sibling_metadata, expected_type=bool ) parents, orphan_entries = self._download_url_metadata( @@ -487,11 +487,9 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): # download the bottom-most urls first since they are top-priority for idx, url_validator in reversed(list(enumerate(self.collection.urls.list))): # URLs can be empty. If they are, then skip - if not (urls := self.overrides.apply_overrides_formatter_to_native(url_validator.url)): + if not (urls := self.overrides.apply_formatter(url_validator.url, expected_type=list)): continue - assert isinstance(urls, list) - for url in reversed(urls): assert isinstance(url, str) diff --git a/src/ytdl_sub/downloaders/url/validators.py b/src/ytdl_sub/downloaders/url/validators.py index 551d7169..b2010ebc 100644 --- a/src/ytdl_sub/downloaders/url/validators.py +++ b/src/ytdl_sub/downloaders/url/validators.py @@ -1,5 +1,6 @@ from typing import Any from typing import Dict +from typing import List from typing import Optional from typing import Set @@ -44,7 +45,7 @@ class UrlThumbnailListValidator(ListValidator[UrlThumbnailValidator]): class OverridesOneOrManyUrlValidator(OverridesStringFormatterValidator): - def post_process_native(self, resolved: Any) -> Any: + def post_process(self, resolved: Any) -> List[str]: if isinstance(resolved, str): return [resolved] if isinstance(resolved, list): diff --git a/src/ytdl_sub/plugins/date_range.py b/src/ytdl_sub/plugins/date_range.py index 478cf938..0cba6f89 100644 --- a/src/ytdl_sub/plugins/date_range.py +++ b/src/ytdl_sub/plugins/date_range.py @@ -116,7 +116,7 @@ class DateRangePlugin(Plugin[DateRangeOptions]): date_validator=self.plugin_options.after, overrides=self.overrides ) after_filter = f"{date_type} >= {after_str}" - if self.overrides.evaluate_boolean(self.plugin_options.breaks): + if self.overrides.apply_formatter(self.plugin_options.breaks, expected_type=bool): breaking_match_filters.append(after_filter) else: match_filters.append(after_filter) diff --git a/src/ytdl_sub/plugins/embed_thumbnail.py b/src/ytdl_sub/plugins/embed_thumbnail.py index e7d19b8b..4ce2b144 100644 --- a/src/ytdl_sub/plugins/embed_thumbnail.py +++ b/src/ytdl_sub/plugins/embed_thumbnail.py @@ -33,7 +33,7 @@ class EmbedThumbnailPlugin(Plugin[EmbedThumbnailOptions]): @property def _embed_thumbnail(self) -> bool: - return self.overrides.evaluate_boolean(self.plugin_options) + return self.overrides.apply_formatter(self.plugin_options, expected_type=bool) @classmethod def _embed_video_thumbnail(cls, entry: Entry) -> None: diff --git a/src/ytdl_sub/plugins/filter_exclude.py b/src/ytdl_sub/plugins/filter_exclude.py index 7d256998..d4adcc86 100644 --- a/src/ytdl_sub/plugins/filter_exclude.py +++ b/src/ytdl_sub/plugins/filter_exclude.py @@ -7,13 +7,14 @@ from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.entries.entry import Entry from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.logger import Logger -from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator +from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator +from ytdl_sub.validators.validators import ListValidator from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive logger = Logger.get("filter-exclude") -class FilterExcludeOptions(ListFormatterValidator, OptionsValidator): +class FilterExcludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator): """ Applies a conditional OR on any number of filters comprised of either variables or scripts. If any filter evaluates to True, the entry will be excluded. @@ -29,6 +30,8 @@ class FilterExcludeOptions(ListFormatterValidator, OptionsValidator): { %contains( %lower(description), '#short' ) } """ + _inner_list_type = BooleanFormatterValidator + class FilterExcludePlugin(Plugin[FilterExcludeOptions]): plugin_options_type = FilterExcludeOptions @@ -52,7 +55,9 @@ class FilterExcludePlugin(Plugin[FilterExcludeOptions]): return entry for formatter in self.plugin_options.list: - should_exclude = self.overrides.evaluate_boolean(formatter=formatter, entry=entry) + should_exclude = self.overrides.apply_formatter( + formatter=formatter, entry=entry, expected_type=bool + ) if should_exclude: logger.info( diff --git a/src/ytdl_sub/plugins/filter_include.py b/src/ytdl_sub/plugins/filter_include.py index c2bc5217..5002dfdb 100644 --- a/src/ytdl_sub/plugins/filter_include.py +++ b/src/ytdl_sub/plugins/filter_include.py @@ -7,14 +7,14 @@ from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.entries.entry import Entry from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.logger import Logger -from ytdl_sub.utils.script import ScriptUtils -from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator +from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator +from ytdl_sub.validators.validators import ListValidator from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive logger = Logger.get("filter-include") -class FilterIncludeOptions(ListFormatterValidator, OptionsValidator): +class FilterIncludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator): """ Applies a conditional AND on any number of filters comprised of either variables or scripts. If all filters evaluate to True, the entry will be included. @@ -38,6 +38,8 @@ class FilterIncludeOptions(ListFormatterValidator, OptionsValidator): } """ + _inner_list_type = BooleanFormatterValidator + class FilterIncludePlugin(Plugin[FilterIncludeOptions]): plugin_options_type = FilterIncludeOptions @@ -61,8 +63,8 @@ class FilterIncludePlugin(Plugin[FilterIncludeOptions]): return entry for formatter in self.plugin_options.list: - should_exclude = ScriptUtils.bool_formatter_output( - self.overrides.apply_formatter(formatter=formatter, entry=entry) + should_exclude = self.overrides.apply_formatter( + formatter=formatter, entry=entry, expected_type=bool ) if not should_exclude: logger.info( diff --git a/src/ytdl_sub/plugins/nfo_tags.py b/src/ytdl_sub/plugins/nfo_tags.py index 24c07c72..802dab63 100644 --- a/src/ytdl_sub/plugins/nfo_tags.py +++ b/src/ytdl_sub/plugins/nfo_tags.py @@ -140,7 +140,7 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC): if not nfo_tags: return - if self.overrides.evaluate_boolean(self.plugin_options.kodi_safe): + if self.overrides.apply_formatter(self.plugin_options.kodi_safe, expected_type=bool): nfo_root = to_max_3_byte_utf8_string(nfo_root) nfo_tags = { to_max_3_byte_utf8_string(key): [ diff --git a/src/ytdl_sub/plugins/square_thumbnail.py b/src/ytdl_sub/plugins/square_thumbnail.py index 030f5a09..dc5db0b7 100644 --- a/src/ytdl_sub/plugins/square_thumbnail.py +++ b/src/ytdl_sub/plugins/square_thumbnail.py @@ -31,7 +31,7 @@ class SquareThumbnailPlugin(Plugin[SquareThumbnailOptions]): @property def _square_thumbnail(self) -> bool: - return self.overrides.evaluate_boolean(self.plugin_options) + return self.overrides.apply_formatter(self.plugin_options, expected_type=bool) @classmethod def _convert_to_square_thumbnail(cls, entry: Entry) -> None: diff --git a/src/ytdl_sub/plugins/throttle_protection.py b/src/ytdl_sub/plugins/throttle_protection.py index 7acad676..463d1ace 100644 --- a/src/ytdl_sub/plugins/throttle_protection.py +++ b/src/ytdl_sub/plugins/throttle_protection.py @@ -42,8 +42,8 @@ class _RandomizedRangeValidator(StrictDictValidator, ABC): ) def _randomized_float(self, overrides: Overrides, entry: Optional[Entry] = None) -> float: - actualized_min = float(overrides.apply_formatter(self._min, entry=entry)) - actualized_max = float(overrides.apply_formatter(self._max, entry=entry)) + actualized_min = overrides.apply_formatter(self._min, entry=entry, expected_type=float) + actualized_max = overrides.apply_formatter(self._max, entry=entry, expected_type=float) if actualized_min < 0: raise self._validation_exception( @@ -70,7 +70,7 @@ class _RandomizedRangeValidator(StrictDictValidator, ABC): ------- Max possible value """ - actualized_max = float(overrides.apply_formatter(self._max, entry=entry)) + actualized_max = overrides.apply_formatter(self._max, entry=entry, expected_type=float) if actualized_max < 0: raise self._validation_exception( f"max must be greater than zero, received {actualized_max}" diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 21715c03..3783293d 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -82,35 +82,27 @@ class StringFormatterValidator(StringValidator): """ return self._parsed - def post_process(self, resolved: str) -> str: + def post_process(self, resolved: Any) -> Any: """ Returns ------- - Apply any post processing to the resolved value + Apply any post processing to the resolved value. Defaults to casting it to string. """ - return resolved - - def post_process_native(self, resolved: Any) -> Any: - """ - Returns - ------- - Apply any post processing to the resolved native value. - """ - return resolved + return str(resolved) class FloatFormatterValidator(StringFormatterValidator): _expected_value_type_name = "float" - def post_process(self, resolved: str) -> str: + def post_process(self, resolved: str) -> float: try: - float(resolved) + out = float(resolved) except Exception as exc: raise self._validation_exception( f"Expected a float, but received '{resolved}'" ) from exc - return resolved + return out class StandardizedDateValidator(StringFormatterValidator): @@ -127,6 +119,13 @@ class StandardizedDateValidator(StringFormatterValidator): return resolved +class BooleanFormatterValidator(StringFormatterValidator): + _expected_value_type_name = "boolean" + + def post_process(self, resolved: Any) -> bool: + return ScriptUtils.bool_formatter_output(output=str(resolved)) + + # pylint: disable=line-too-long class OverridesStringFormatterValidator(StringFormatterValidator): """ @@ -146,15 +145,14 @@ class OverridesStringFormatterValidator(StringFormatterValidator): class OverridesIntegerFormatterValidator(OverridesStringFormatterValidator): _expected_value_type_name = "integer" - def post_process(self, resolved: str) -> str: + def post_process(self, resolved: str) -> int: try: - int(resolved) + out = int(resolved) except Exception as exc: raise self._validation_exception( f"Expected an integer, but received '{resolved}'" ) from exc - - return resolved + return out class OverridesFloatFormatterValidator(FloatFormatterValidator, OverridesStringFormatterValidator): @@ -163,9 +161,14 @@ class OverridesFloatFormatterValidator(FloatFormatterValidator, OverridesStringF """ -class OverridesBooleanFormatterValidator(OverridesStringFormatterValidator): +class OverridesBooleanFormatterValidator( + BooleanFormatterValidator, OverridesStringFormatterValidator +): _expected_value_type_name = "boolean" + def post_process(self, resolved: Any) -> bool: + return ScriptUtils.bool_formatter_output(output=str(resolved)) + class ListFormatterValidator(ListValidator[StringFormatterValidator]): _inner_list_type = StringFormatterValidator diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index 35942f8d..930708ed 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -475,19 +475,6 @@ def test_advanced_tv_show_subscriptions( assert overrides.script.get("subscription_name").native == "Gardening with Ciscoe" - assert overrides.apply_overrides_formatter_to_native(overrides.dict["subscription_array"]) == [ - "https://www.youtube.com/@gardeningwithciscoe4430", - "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_", - "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn", - "https://www.youtube.com/watch?v=2vq-vPubS5I", - ] - assert overrides.apply_overrides_formatter_to_native(overrides.dict["urls"]) == [ - "https://www.youtube.com/@gardeningwithciscoe4430", - "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_", - "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn", - "https://www.youtube.com/watch?v=2vq-vPubS5I", - ] - def test_music_subscriptions(default_config: ConfigFile, music_subscriptions_path: Path): subs = Subscription.from_file_path( diff --git a/tests/unit/prebuilt_presets/test_tv_show_collection.py b/tests/unit/prebuilt_presets/test_tv_show_collection.py index 07e30006..0f7e9c5e 100644 --- a/tests/unit/prebuilt_presets/test_tv_show_collection.py +++ b/tests/unit/prebuilt_presets/test_tv_show_collection.py @@ -57,12 +57,13 @@ class TestTvShowCollectionPreset: # is_bilateral if i == 0: - url = sub.overrides.apply_overrides_formatter_to_native( + url = sub.overrides.apply_formatter( url_list[itr].url, function_overrides={ # mock so bilateral url gets enabled "subscription_has_download_archive": "True" }, + expected_type=list, ) assert url == [ f"youtube.com/playlist?url_{season_num}_{i}" @@ -81,12 +82,13 @@ class TestTvShowCollectionPreset: # not bilateral else: for j in range(2): - url = sub.overrides.apply_overrides_formatter_to_native( + url = sub.overrides.apply_formatter( url_list[itr + j].url, function_overrides={ # mock so bilateral url gets enabled "subscription_has_download_archive": "True" }, + expected_type=list, ) # First instance is the first url to get thumbnails From c1431c8d5535485727f2ba8c7d2ef41d0762817c Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sat, 24 Jan 2026 00:22:18 -0800 Subject: [PATCH 33/46] Revert "[BACKEND] More flexible formatter post_process (#1421)" (#1422) This reverts commit 60cdbad8c7cbbba51ebfead3c6c59abbce6f7341. Caused a breaking change to ytdl_options --- src/ytdl_sub/config/overrides.py | 53 +++++++++++++------ src/ytdl_sub/config/plugin/plugin.py | 2 +- src/ytdl_sub/config/preset_options.py | 5 +- src/ytdl_sub/downloaders/url/downloader.py | 14 ++--- src/ytdl_sub/downloaders/url/validators.py | 3 +- src/ytdl_sub/plugins/date_range.py | 2 +- src/ytdl_sub/plugins/embed_thumbnail.py | 2 +- src/ytdl_sub/plugins/filter_exclude.py | 11 ++-- src/ytdl_sub/plugins/filter_include.py | 12 ++--- src/ytdl_sub/plugins/nfo_tags.py | 2 +- src/ytdl_sub/plugins/square_thumbnail.py | 2 +- src/ytdl_sub/plugins/throttle_protection.py | 6 +-- .../validators/string_formatter_validators.py | 41 +++++++------- tests/unit/config/test_subscription.py | 13 +++++ .../test_tv_show_collection.py | 6 +-- 15 files changed, 100 insertions(+), 74 deletions(-) diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index f1011fcf..623fc06d 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -3,8 +3,6 @@ from typing import Dict from typing import Iterable from typing import Optional from typing import Set -from typing import Type -from typing import TypeVar from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES @@ -22,11 +20,10 @@ from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.script import ScriptUtils from ytdl_sub.utils.scriptable import Scriptable +from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.string_formatter_validators import UnstructuredDictFormatterValidator -ExpectedT = TypeVar("ExpectedT") - class Overrides(UnstructuredDictFormatterValidator, Scriptable): """ @@ -210,8 +207,7 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): formatter: StringFormatterValidator, entry: Optional[Entry] = None, function_overrides: Optional[Dict[str, str]] = None, - expected_type: Type[ExpectedT] = str, - ) -> ExpectedT: + ) -> str: """ Parameters ---------- @@ -221,8 +217,6 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): Optional. Entry to add source variables to the formatter function_overrides Optional. Explicit values to override the overrides themselves and source variables - expected_type - The expected type that should return. Defaults to string. Returns ------- @@ -233,15 +227,42 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): StringFormattingException If the formatter that is trying to be resolved cannot """ - out = formatter.post_process( + return formatter.post_process( + str( + self._apply_to_resolvable( + formatter=formatter, entry=entry, function_overrides=function_overrides + ) + ) + ) + + def apply_overrides_formatter_to_native( + self, + formatter: OverridesStringFormatterValidator, + function_overrides: Optional[Dict[str, str]] = None, + ) -> Any: + """ + Parameters + ---------- + formatter + Overrides formatter to apply + function_overrides + Optional. Explicit values to override the overrides themselves and source variables + + Returns + ------- + The native python form of the resolved variable + """ + return formatter.post_process_native( self._apply_to_resolvable( - formatter=formatter, entry=entry, function_overrides=function_overrides + formatter=formatter, entry=None, function_overrides=function_overrides ).native ) - if not isinstance(out, expected_type): - raise StringFormattingException( - f"Expected type {expected_type.__name__}, but received '{out.__class__.__name__}'" - ) - - return out + def evaluate_boolean( + self, formatter: StringFormatterValidator, entry: Optional[Entry] = None + ) -> bool: + """ + Apply a formatter, and evaluate it to a boolean + """ + output = self.apply_formatter(formatter=formatter, entry=entry) + return ScriptUtils.bool_formatter_output(output) diff --git a/src/ytdl_sub/config/plugin/plugin.py b/src/ytdl_sub/config/plugin/plugin.py index 23232c48..0cd02b53 100644 --- a/src/ytdl_sub/config/plugin/plugin.py +++ b/src/ytdl_sub/config/plugin/plugin.py @@ -48,7 +48,7 @@ class Plugin(BasePlugin[OptionsValidatorT], Generic[OptionsValidatorT], ABC): Returns True if enabled, False if disabled. """ if isinstance(self.plugin_options, ToggleableOptionsDictValidator): - return self.overrides.apply_formatter(self.plugin_options.enable, expected_type=bool) + return self.overrides.evaluate_boolean(self.plugin_options.enable) return True def ytdl_options_match_filters(self) -> Tuple[List[str], List[str]]: diff --git a/src/ytdl_sub/config/preset_options.py b/src/ytdl_sub/config/preset_options.py index db07ca02..93f36453 100644 --- a/src/ytdl_sub/config/preset_options.py +++ b/src/ytdl_sub/config/preset_options.py @@ -62,7 +62,10 @@ class YTDLOptions(UnstructuredOverridesDictFormatterValidator): Materializes the entire ytdl-options dict from OverrideStringFormatters into native python. """ - out = {key: overrides.apply_formatter(val) for key, val in self.dict.items()} + out = { + key: overrides.apply_overrides_formatter_to_native(val) + for key, val in self.dict.items() + } if "cookiefile" in out: if not FileHandler.is_file_existent(out["cookiefile"]): raise ValidationException( diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index c94d6910..acb35895 100644 --- a/src/ytdl_sub/downloaders/url/downloader.py +++ b/src/ytdl_sub/downloaders/url/downloader.py @@ -52,12 +52,12 @@ class UrlDownloaderBasePluginExtension(SourcePluginExtension[MultiUrlValidator]) if 0 <= input_url_idx < len(self.plugin_options.urls.list): validator = self.plugin_options.urls.list[input_url_idx] - if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list): + if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.url): return validator # Match the first validator based on the URL, if one exists for validator in self.plugin_options.urls.list: - if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list): + if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.url): return validator # Return the first validator if none exist @@ -382,7 +382,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): entries_to_iter: List[Optional[Entry]] = entries indices = list(range(len(entries_to_iter))) - if self.overrides.apply_formatter(validator.download_reverse, expected_type=bool): + if self.overrides.evaluate_boolean(validator.download_reverse): indices = reversed(indices) for idx in indices: @@ -461,8 +461,8 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): ytdl_option_overrides=validator.ytdl_options.to_native_dict(self.overrides) ) - include_sibling_metadata = self.overrides.apply_formatter( - validator.include_sibling_metadata, expected_type=bool + include_sibling_metadata = self.overrides.evaluate_boolean( + validator.include_sibling_metadata ) parents, orphan_entries = self._download_url_metadata( @@ -487,9 +487,11 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): # download the bottom-most urls first since they are top-priority for idx, url_validator in reversed(list(enumerate(self.collection.urls.list))): # URLs can be empty. If they are, then skip - if not (urls := self.overrides.apply_formatter(url_validator.url, expected_type=list)): + if not (urls := self.overrides.apply_overrides_formatter_to_native(url_validator.url)): continue + assert isinstance(urls, list) + for url in reversed(urls): assert isinstance(url, str) diff --git a/src/ytdl_sub/downloaders/url/validators.py b/src/ytdl_sub/downloaders/url/validators.py index b2010ebc..551d7169 100644 --- a/src/ytdl_sub/downloaders/url/validators.py +++ b/src/ytdl_sub/downloaders/url/validators.py @@ -1,6 +1,5 @@ from typing import Any from typing import Dict -from typing import List from typing import Optional from typing import Set @@ -45,7 +44,7 @@ class UrlThumbnailListValidator(ListValidator[UrlThumbnailValidator]): class OverridesOneOrManyUrlValidator(OverridesStringFormatterValidator): - def post_process(self, resolved: Any) -> List[str]: + def post_process_native(self, resolved: Any) -> Any: if isinstance(resolved, str): return [resolved] if isinstance(resolved, list): diff --git a/src/ytdl_sub/plugins/date_range.py b/src/ytdl_sub/plugins/date_range.py index 0cba6f89..478cf938 100644 --- a/src/ytdl_sub/plugins/date_range.py +++ b/src/ytdl_sub/plugins/date_range.py @@ -116,7 +116,7 @@ class DateRangePlugin(Plugin[DateRangeOptions]): date_validator=self.plugin_options.after, overrides=self.overrides ) after_filter = f"{date_type} >= {after_str}" - if self.overrides.apply_formatter(self.plugin_options.breaks, expected_type=bool): + if self.overrides.evaluate_boolean(self.plugin_options.breaks): breaking_match_filters.append(after_filter) else: match_filters.append(after_filter) diff --git a/src/ytdl_sub/plugins/embed_thumbnail.py b/src/ytdl_sub/plugins/embed_thumbnail.py index 4ce2b144..e7d19b8b 100644 --- a/src/ytdl_sub/plugins/embed_thumbnail.py +++ b/src/ytdl_sub/plugins/embed_thumbnail.py @@ -33,7 +33,7 @@ class EmbedThumbnailPlugin(Plugin[EmbedThumbnailOptions]): @property def _embed_thumbnail(self) -> bool: - return self.overrides.apply_formatter(self.plugin_options, expected_type=bool) + return self.overrides.evaluate_boolean(self.plugin_options) @classmethod def _embed_video_thumbnail(cls, entry: Entry) -> None: diff --git a/src/ytdl_sub/plugins/filter_exclude.py b/src/ytdl_sub/plugins/filter_exclude.py index d4adcc86..7d256998 100644 --- a/src/ytdl_sub/plugins/filter_exclude.py +++ b/src/ytdl_sub/plugins/filter_exclude.py @@ -7,14 +7,13 @@ from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.entries.entry import Entry from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.logger import Logger -from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator -from ytdl_sub.validators.validators import ListValidator +from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive logger = Logger.get("filter-exclude") -class FilterExcludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator): +class FilterExcludeOptions(ListFormatterValidator, OptionsValidator): """ Applies a conditional OR on any number of filters comprised of either variables or scripts. If any filter evaluates to True, the entry will be excluded. @@ -30,8 +29,6 @@ class FilterExcludeOptions(ListValidator[BooleanFormatterValidator], OptionsVali { %contains( %lower(description), '#short' ) } """ - _inner_list_type = BooleanFormatterValidator - class FilterExcludePlugin(Plugin[FilterExcludeOptions]): plugin_options_type = FilterExcludeOptions @@ -55,9 +52,7 @@ class FilterExcludePlugin(Plugin[FilterExcludeOptions]): return entry for formatter in self.plugin_options.list: - should_exclude = self.overrides.apply_formatter( - formatter=formatter, entry=entry, expected_type=bool - ) + should_exclude = self.overrides.evaluate_boolean(formatter=formatter, entry=entry) if should_exclude: logger.info( diff --git a/src/ytdl_sub/plugins/filter_include.py b/src/ytdl_sub/plugins/filter_include.py index 5002dfdb..c2bc5217 100644 --- a/src/ytdl_sub/plugins/filter_include.py +++ b/src/ytdl_sub/plugins/filter_include.py @@ -7,14 +7,14 @@ from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.entries.entry import Entry from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.logger import Logger -from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator -from ytdl_sub.validators.validators import ListValidator +from ytdl_sub.utils.script import ScriptUtils +from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive logger = Logger.get("filter-include") -class FilterIncludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator): +class FilterIncludeOptions(ListFormatterValidator, OptionsValidator): """ Applies a conditional AND on any number of filters comprised of either variables or scripts. If all filters evaluate to True, the entry will be included. @@ -38,8 +38,6 @@ class FilterIncludeOptions(ListValidator[BooleanFormatterValidator], OptionsVali } """ - _inner_list_type = BooleanFormatterValidator - class FilterIncludePlugin(Plugin[FilterIncludeOptions]): plugin_options_type = FilterIncludeOptions @@ -63,8 +61,8 @@ class FilterIncludePlugin(Plugin[FilterIncludeOptions]): return entry for formatter in self.plugin_options.list: - should_exclude = self.overrides.apply_formatter( - formatter=formatter, entry=entry, expected_type=bool + should_exclude = ScriptUtils.bool_formatter_output( + self.overrides.apply_formatter(formatter=formatter, entry=entry) ) if not should_exclude: logger.info( diff --git a/src/ytdl_sub/plugins/nfo_tags.py b/src/ytdl_sub/plugins/nfo_tags.py index 802dab63..24c07c72 100644 --- a/src/ytdl_sub/plugins/nfo_tags.py +++ b/src/ytdl_sub/plugins/nfo_tags.py @@ -140,7 +140,7 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC): if not nfo_tags: return - if self.overrides.apply_formatter(self.plugin_options.kodi_safe, expected_type=bool): + if self.overrides.evaluate_boolean(self.plugin_options.kodi_safe): nfo_root = to_max_3_byte_utf8_string(nfo_root) nfo_tags = { to_max_3_byte_utf8_string(key): [ diff --git a/src/ytdl_sub/plugins/square_thumbnail.py b/src/ytdl_sub/plugins/square_thumbnail.py index dc5db0b7..030f5a09 100644 --- a/src/ytdl_sub/plugins/square_thumbnail.py +++ b/src/ytdl_sub/plugins/square_thumbnail.py @@ -31,7 +31,7 @@ class SquareThumbnailPlugin(Plugin[SquareThumbnailOptions]): @property def _square_thumbnail(self) -> bool: - return self.overrides.apply_formatter(self.plugin_options, expected_type=bool) + return self.overrides.evaluate_boolean(self.plugin_options) @classmethod def _convert_to_square_thumbnail(cls, entry: Entry) -> None: diff --git a/src/ytdl_sub/plugins/throttle_protection.py b/src/ytdl_sub/plugins/throttle_protection.py index 463d1ace..7acad676 100644 --- a/src/ytdl_sub/plugins/throttle_protection.py +++ b/src/ytdl_sub/plugins/throttle_protection.py @@ -42,8 +42,8 @@ class _RandomizedRangeValidator(StrictDictValidator, ABC): ) def _randomized_float(self, overrides: Overrides, entry: Optional[Entry] = None) -> float: - actualized_min = overrides.apply_formatter(self._min, entry=entry, expected_type=float) - actualized_max = overrides.apply_formatter(self._max, entry=entry, expected_type=float) + actualized_min = float(overrides.apply_formatter(self._min, entry=entry)) + actualized_max = float(overrides.apply_formatter(self._max, entry=entry)) if actualized_min < 0: raise self._validation_exception( @@ -70,7 +70,7 @@ class _RandomizedRangeValidator(StrictDictValidator, ABC): ------- Max possible value """ - actualized_max = overrides.apply_formatter(self._max, entry=entry, expected_type=float) + actualized_max = float(overrides.apply_formatter(self._max, entry=entry)) if actualized_max < 0: raise self._validation_exception( f"max must be greater than zero, received {actualized_max}" diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 3783293d..21715c03 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -82,27 +82,35 @@ class StringFormatterValidator(StringValidator): """ return self._parsed - def post_process(self, resolved: Any) -> Any: + def post_process(self, resolved: str) -> str: """ Returns ------- - Apply any post processing to the resolved value. Defaults to casting it to string. + Apply any post processing to the resolved value """ - return str(resolved) + return resolved + + def post_process_native(self, resolved: Any) -> Any: + """ + Returns + ------- + Apply any post processing to the resolved native value. + """ + return resolved class FloatFormatterValidator(StringFormatterValidator): _expected_value_type_name = "float" - def post_process(self, resolved: str) -> float: + def post_process(self, resolved: str) -> str: try: - out = float(resolved) + float(resolved) except Exception as exc: raise self._validation_exception( f"Expected a float, but received '{resolved}'" ) from exc - return out + return resolved class StandardizedDateValidator(StringFormatterValidator): @@ -119,13 +127,6 @@ class StandardizedDateValidator(StringFormatterValidator): return resolved -class BooleanFormatterValidator(StringFormatterValidator): - _expected_value_type_name = "boolean" - - def post_process(self, resolved: Any) -> bool: - return ScriptUtils.bool_formatter_output(output=str(resolved)) - - # pylint: disable=line-too-long class OverridesStringFormatterValidator(StringFormatterValidator): """ @@ -145,14 +146,15 @@ class OverridesStringFormatterValidator(StringFormatterValidator): class OverridesIntegerFormatterValidator(OverridesStringFormatterValidator): _expected_value_type_name = "integer" - def post_process(self, resolved: str) -> int: + def post_process(self, resolved: str) -> str: try: - out = int(resolved) + int(resolved) except Exception as exc: raise self._validation_exception( f"Expected an integer, but received '{resolved}'" ) from exc - return out + + return resolved class OverridesFloatFormatterValidator(FloatFormatterValidator, OverridesStringFormatterValidator): @@ -161,14 +163,9 @@ class OverridesFloatFormatterValidator(FloatFormatterValidator, OverridesStringF """ -class OverridesBooleanFormatterValidator( - BooleanFormatterValidator, OverridesStringFormatterValidator -): +class OverridesBooleanFormatterValidator(OverridesStringFormatterValidator): _expected_value_type_name = "boolean" - def post_process(self, resolved: Any) -> bool: - return ScriptUtils.bool_formatter_output(output=str(resolved)) - class ListFormatterValidator(ListValidator[StringFormatterValidator]): _inner_list_type = StringFormatterValidator diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index 930708ed..35942f8d 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -475,6 +475,19 @@ def test_advanced_tv_show_subscriptions( assert overrides.script.get("subscription_name").native == "Gardening with Ciscoe" + assert overrides.apply_overrides_formatter_to_native(overrides.dict["subscription_array"]) == [ + "https://www.youtube.com/@gardeningwithciscoe4430", + "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_", + "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn", + "https://www.youtube.com/watch?v=2vq-vPubS5I", + ] + assert overrides.apply_overrides_formatter_to_native(overrides.dict["urls"]) == [ + "https://www.youtube.com/@gardeningwithciscoe4430", + "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_", + "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn", + "https://www.youtube.com/watch?v=2vq-vPubS5I", + ] + def test_music_subscriptions(default_config: ConfigFile, music_subscriptions_path: Path): subs = Subscription.from_file_path( diff --git a/tests/unit/prebuilt_presets/test_tv_show_collection.py b/tests/unit/prebuilt_presets/test_tv_show_collection.py index 0f7e9c5e..07e30006 100644 --- a/tests/unit/prebuilt_presets/test_tv_show_collection.py +++ b/tests/unit/prebuilt_presets/test_tv_show_collection.py @@ -57,13 +57,12 @@ class TestTvShowCollectionPreset: # is_bilateral if i == 0: - url = sub.overrides.apply_formatter( + url = sub.overrides.apply_overrides_formatter_to_native( url_list[itr].url, function_overrides={ # mock so bilateral url gets enabled "subscription_has_download_archive": "True" }, - expected_type=list, ) assert url == [ f"youtube.com/playlist?url_{season_num}_{i}" @@ -82,13 +81,12 @@ class TestTvShowCollectionPreset: # not bilateral else: for j in range(2): - url = sub.overrides.apply_formatter( + url = sub.overrides.apply_overrides_formatter_to_native( url_list[itr + j].url, function_overrides={ # mock so bilateral url gets enabled "subscription_has_download_archive": "True" }, - expected_type=list, ) # First instance is the first url to get thumbnails From 97ecae79db950c80f0feb1fd3c70c9c4fbd31072 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sun, 25 Jan 2026 09:55:26 -0800 Subject: [PATCH 34/46] [DEV] More tests around passed-in ytdl-options (#1423) --- tests/conftest.py | 3 +- .../plugins/test_throttle_protection.py | 4 +- tests/unit/plugins/test_ytdl_options.py | 70 ++++++++++++++++++- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 449163c8..42d82178 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -117,7 +117,8 @@ def assert_logs( yield for call_args in patched_debug.call_args_list: - occurrences += int(expected_message in call_args.args[0]) + full_print = call_args.args[0] % call_args.args[1:] + occurrences += int(expected_message in full_print) if expected_occurrences is not None: assert ( diff --git a/tests/integration/plugins/test_throttle_protection.py b/tests/integration/plugins/test_throttle_protection.py index 6154f70a..bd56f652 100644 --- a/tests/integration/plugins/test_throttle_protection.py +++ b/tests/integration/plugins/test_throttle_protection.py @@ -71,7 +71,7 @@ class TestThrottleProtectionPlugin: ), assert_logs( logger=throttle_protection_logger, - expected_message="Sleeping between subscriptions for %0.2f seconds", + expected_message="Sleeping between subscriptions for 0.02 seconds", log_level="info", expected_occurrences=1, ), @@ -139,7 +139,7 @@ class TestThrottleProtectionPlugin: ), assert_logs( logger=throttle_protection_logger, - expected_message="Reached subscription max downloads of %d", + expected_message="Reached subscription max downloads of 0 for throttle protection", log_level="info", expected_occurrences=1, ), diff --git a/tests/unit/plugins/test_ytdl_options.py b/tests/unit/plugins/test_ytdl_options.py index bfeb0ef1..e72d960e 100644 --- a/tests/unit/plugins/test_ytdl_options.py +++ b/tests/unit/plugins/test_ytdl_options.py @@ -3,11 +3,15 @@ from typing import Any from typing import Dict import pytest -from conftest import get_match_filters +import yt_dlp +from conftest import assert_logs from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.downloaders.ytdlp import YTDLP from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.utils.exceptions import ValidationException +from ytdl_sub.utils.ffmpeg import FFMPEG +from ytdl_sub.utils.file_path import FilePathTruncater @pytest.fixture @@ -19,6 +23,42 @@ def preset_dict(output_directory) -> Dict[str, Any]: class TestYtdlOptions: + + def test_ytdl_options_are_strings( + self, + default_config: ConfigFile, + preset_dict: Dict[str, Any], + working_directory, + ): + expected_ytdl_options = { + "ignoreerrors": True, + "outtmpl": FilePathTruncater.to_native_filepath( + f"{working_directory}/test_ytdl_options/%(id)S.%(ext)s" + ), + "writethumbnail": False, + "ffmpeg_location": FFMPEG.ffmpeg_path(), + "match_filter": yt_dlp.utils.match_filter_func( + ["!is_live & !is_upcoming & !post_live"], [] + ), + "skip_download": True, + "writeinfojson": True, + "extract_flat": "discard", + } + + with ( + assert_logs( + logger=YTDLP.logger, + expected_message=f"ytdl_options: {str(expected_ytdl_options)}", + log_level="debug", + expected_occurrences=1, + ), + ): + _ = Subscription.from_dict( + config=default_config, + preset_name="test_ytdl_options", + preset_dict=preset_dict, + ).download(dry_run=True) + def test_cookiefile_does_not_exist( self, default_config: ConfigFile, @@ -36,3 +76,31 @@ class TestYtdlOptions: preset_name="test_ytdl_options", preset_dict=preset_dict, ).download(dry_run=False) + + def test_ytdl_option_types_preserved( + self, + default_config: ConfigFile, + output_directory: str, + ): + preset_dict = { + "download": "https://your.name.here", + "output_options": {"output_directory": output_directory, "file_name": "will_error.mp4"}, + "ytdl_options": { + "break_on_existing": True, + "js_runtimes": {"deno": {"path": "/usr/local/bin/deno"}}, + }, + } + + sub = Subscription.from_dict( + config=default_config, + preset_name="test_ytdl_options", + preset_dict=preset_dict, + ) + + expected = { + "break_on_existing": True, + "js_runtimes": {"deno": {"path": "/usr/local/bin/deno"}}, + } + out = sub.ytdl_options.to_native_dict(sub.overrides) + + assert out == expected From a097c6a476799ab9f4c59c9e1b61556b674e30f2 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sun, 25 Jan 2026 22:35:51 -0800 Subject: [PATCH 35/46] [BACKEND][v2] More flexible formatter post_process (#1424) Makes the underlying formatting process better handle native (non-string) types in a more flexible way. 2nd attempt since the last one got reverted due to breaking changes. --- src/ytdl_sub/config/overrides.py | 53 +++++--------- src/ytdl_sub/config/plugin/plugin.py | 2 +- src/ytdl_sub/config/preset_options.py | 2 +- src/ytdl_sub/downloaders/url/downloader.py | 14 ++-- src/ytdl_sub/downloaders/url/validators.py | 3 +- src/ytdl_sub/plugins/date_range.py | 2 +- src/ytdl_sub/plugins/embed_thumbnail.py | 2 +- src/ytdl_sub/plugins/filter_exclude.py | 11 ++- src/ytdl_sub/plugins/filter_include.py | 12 ++-- src/ytdl_sub/plugins/nfo_tags.py | 2 +- src/ytdl_sub/plugins/square_thumbnail.py | 2 +- src/ytdl_sub/plugins/throttle_protection.py | 6 +- .../validators/string_formatter_validators.py | 70 ++++++++++--------- tests/unit/config/test_subscription.py | 13 ---- tests/unit/plugins/test_ytdl_options.py | 9 ++- .../test_tv_show_collection.py | 6 +- 16 files changed, 97 insertions(+), 112 deletions(-) diff --git a/src/ytdl_sub/config/overrides.py b/src/ytdl_sub/config/overrides.py index 623fc06d..f1011fcf 100644 --- a/src/ytdl_sub/config/overrides.py +++ b/src/ytdl_sub/config/overrides.py @@ -3,6 +3,8 @@ from typing import Dict from typing import Iterable from typing import Optional from typing import Set +from typing import Type +from typing import TypeVar from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES @@ -20,10 +22,11 @@ from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.script import ScriptUtils from ytdl_sub.utils.scriptable import Scriptable -from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.string_formatter_validators import UnstructuredDictFormatterValidator +ExpectedT = TypeVar("ExpectedT") + class Overrides(UnstructuredDictFormatterValidator, Scriptable): """ @@ -207,7 +210,8 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): formatter: StringFormatterValidator, entry: Optional[Entry] = None, function_overrides: Optional[Dict[str, str]] = None, - ) -> str: + expected_type: Type[ExpectedT] = str, + ) -> ExpectedT: """ Parameters ---------- @@ -217,6 +221,8 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): Optional. Entry to add source variables to the formatter function_overrides Optional. Explicit values to override the overrides themselves and source variables + expected_type + The expected type that should return. Defaults to string. Returns ------- @@ -227,42 +233,15 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable): StringFormattingException If the formatter that is trying to be resolved cannot """ - return formatter.post_process( - str( - self._apply_to_resolvable( - formatter=formatter, entry=entry, function_overrides=function_overrides - ) - ) - ) - - def apply_overrides_formatter_to_native( - self, - formatter: OverridesStringFormatterValidator, - function_overrides: Optional[Dict[str, str]] = None, - ) -> Any: - """ - Parameters - ---------- - formatter - Overrides formatter to apply - function_overrides - Optional. Explicit values to override the overrides themselves and source variables - - Returns - ------- - The native python form of the resolved variable - """ - return formatter.post_process_native( + out = formatter.post_process( self._apply_to_resolvable( - formatter=formatter, entry=None, function_overrides=function_overrides + formatter=formatter, entry=entry, function_overrides=function_overrides ).native ) - def evaluate_boolean( - self, formatter: StringFormatterValidator, entry: Optional[Entry] = None - ) -> bool: - """ - Apply a formatter, and evaluate it to a boolean - """ - output = self.apply_formatter(formatter=formatter, entry=entry) - return ScriptUtils.bool_formatter_output(output) + if not isinstance(out, expected_type): + raise StringFormattingException( + f"Expected type {expected_type.__name__}, but received '{out.__class__.__name__}'" + ) + + return out diff --git a/src/ytdl_sub/config/plugin/plugin.py b/src/ytdl_sub/config/plugin/plugin.py index 0cd02b53..23232c48 100644 --- a/src/ytdl_sub/config/plugin/plugin.py +++ b/src/ytdl_sub/config/plugin/plugin.py @@ -48,7 +48,7 @@ class Plugin(BasePlugin[OptionsValidatorT], Generic[OptionsValidatorT], ABC): Returns True if enabled, False if disabled. """ if isinstance(self.plugin_options, ToggleableOptionsDictValidator): - return self.overrides.evaluate_boolean(self.plugin_options.enable) + return self.overrides.apply_formatter(self.plugin_options.enable, expected_type=bool) return True def ytdl_options_match_filters(self) -> Tuple[List[str], List[str]]: diff --git a/src/ytdl_sub/config/preset_options.py b/src/ytdl_sub/config/preset_options.py index 93f36453..27ad8a92 100644 --- a/src/ytdl_sub/config/preset_options.py +++ b/src/ytdl_sub/config/preset_options.py @@ -63,7 +63,7 @@ class YTDLOptions(UnstructuredOverridesDictFormatterValidator): native python. """ out = { - key: overrides.apply_overrides_formatter_to_native(val) + key: overrides.apply_formatter(val, expected_type=object) for key, val in self.dict.items() } if "cookiefile" in out: diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index acb35895..c94d6910 100644 --- a/src/ytdl_sub/downloaders/url/downloader.py +++ b/src/ytdl_sub/downloaders/url/downloader.py @@ -52,12 +52,12 @@ class UrlDownloaderBasePluginExtension(SourcePluginExtension[MultiUrlValidator]) if 0 <= input_url_idx < len(self.plugin_options.urls.list): validator = self.plugin_options.urls.list[input_url_idx] - if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.url): + if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list): return validator # Match the first validator based on the URL, if one exists for validator in self.plugin_options.urls.list: - if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.url): + if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list): return validator # Return the first validator if none exist @@ -382,7 +382,7 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): entries_to_iter: List[Optional[Entry]] = entries indices = list(range(len(entries_to_iter))) - if self.overrides.evaluate_boolean(validator.download_reverse): + if self.overrides.apply_formatter(validator.download_reverse, expected_type=bool): indices = reversed(indices) for idx in indices: @@ -461,8 +461,8 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): ytdl_option_overrides=validator.ytdl_options.to_native_dict(self.overrides) ) - include_sibling_metadata = self.overrides.evaluate_boolean( - validator.include_sibling_metadata + include_sibling_metadata = self.overrides.apply_formatter( + validator.include_sibling_metadata, expected_type=bool ) parents, orphan_entries = self._download_url_metadata( @@ -487,11 +487,9 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): # download the bottom-most urls first since they are top-priority for idx, url_validator in reversed(list(enumerate(self.collection.urls.list))): # URLs can be empty. If they are, then skip - if not (urls := self.overrides.apply_overrides_formatter_to_native(url_validator.url)): + if not (urls := self.overrides.apply_formatter(url_validator.url, expected_type=list)): continue - assert isinstance(urls, list) - for url in reversed(urls): assert isinstance(url, str) diff --git a/src/ytdl_sub/downloaders/url/validators.py b/src/ytdl_sub/downloaders/url/validators.py index 551d7169..b2010ebc 100644 --- a/src/ytdl_sub/downloaders/url/validators.py +++ b/src/ytdl_sub/downloaders/url/validators.py @@ -1,5 +1,6 @@ from typing import Any from typing import Dict +from typing import List from typing import Optional from typing import Set @@ -44,7 +45,7 @@ class UrlThumbnailListValidator(ListValidator[UrlThumbnailValidator]): class OverridesOneOrManyUrlValidator(OverridesStringFormatterValidator): - def post_process_native(self, resolved: Any) -> Any: + def post_process(self, resolved: Any) -> List[str]: if isinstance(resolved, str): return [resolved] if isinstance(resolved, list): diff --git a/src/ytdl_sub/plugins/date_range.py b/src/ytdl_sub/plugins/date_range.py index 478cf938..0cba6f89 100644 --- a/src/ytdl_sub/plugins/date_range.py +++ b/src/ytdl_sub/plugins/date_range.py @@ -116,7 +116,7 @@ class DateRangePlugin(Plugin[DateRangeOptions]): date_validator=self.plugin_options.after, overrides=self.overrides ) after_filter = f"{date_type} >= {after_str}" - if self.overrides.evaluate_boolean(self.plugin_options.breaks): + if self.overrides.apply_formatter(self.plugin_options.breaks, expected_type=bool): breaking_match_filters.append(after_filter) else: match_filters.append(after_filter) diff --git a/src/ytdl_sub/plugins/embed_thumbnail.py b/src/ytdl_sub/plugins/embed_thumbnail.py index e7d19b8b..4ce2b144 100644 --- a/src/ytdl_sub/plugins/embed_thumbnail.py +++ b/src/ytdl_sub/plugins/embed_thumbnail.py @@ -33,7 +33,7 @@ class EmbedThumbnailPlugin(Plugin[EmbedThumbnailOptions]): @property def _embed_thumbnail(self) -> bool: - return self.overrides.evaluate_boolean(self.plugin_options) + return self.overrides.apply_formatter(self.plugin_options, expected_type=bool) @classmethod def _embed_video_thumbnail(cls, entry: Entry) -> None: diff --git a/src/ytdl_sub/plugins/filter_exclude.py b/src/ytdl_sub/plugins/filter_exclude.py index 7d256998..d4adcc86 100644 --- a/src/ytdl_sub/plugins/filter_exclude.py +++ b/src/ytdl_sub/plugins/filter_exclude.py @@ -7,13 +7,14 @@ from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.entries.entry import Entry from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.logger import Logger -from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator +from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator +from ytdl_sub.validators.validators import ListValidator from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive logger = Logger.get("filter-exclude") -class FilterExcludeOptions(ListFormatterValidator, OptionsValidator): +class FilterExcludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator): """ Applies a conditional OR on any number of filters comprised of either variables or scripts. If any filter evaluates to True, the entry will be excluded. @@ -29,6 +30,8 @@ class FilterExcludeOptions(ListFormatterValidator, OptionsValidator): { %contains( %lower(description), '#short' ) } """ + _inner_list_type = BooleanFormatterValidator + class FilterExcludePlugin(Plugin[FilterExcludeOptions]): plugin_options_type = FilterExcludeOptions @@ -52,7 +55,9 @@ class FilterExcludePlugin(Plugin[FilterExcludeOptions]): return entry for formatter in self.plugin_options.list: - should_exclude = self.overrides.evaluate_boolean(formatter=formatter, entry=entry) + should_exclude = self.overrides.apply_formatter( + formatter=formatter, entry=entry, expected_type=bool + ) if should_exclude: logger.info( diff --git a/src/ytdl_sub/plugins/filter_include.py b/src/ytdl_sub/plugins/filter_include.py index c2bc5217..5002dfdb 100644 --- a/src/ytdl_sub/plugins/filter_include.py +++ b/src/ytdl_sub/plugins/filter_include.py @@ -7,14 +7,14 @@ from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.entries.entry import Entry from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.logger import Logger -from ytdl_sub.utils.script import ScriptUtils -from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator +from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator +from ytdl_sub.validators.validators import ListValidator from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive logger = Logger.get("filter-include") -class FilterIncludeOptions(ListFormatterValidator, OptionsValidator): +class FilterIncludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator): """ Applies a conditional AND on any number of filters comprised of either variables or scripts. If all filters evaluate to True, the entry will be included. @@ -38,6 +38,8 @@ class FilterIncludeOptions(ListFormatterValidator, OptionsValidator): } """ + _inner_list_type = BooleanFormatterValidator + class FilterIncludePlugin(Plugin[FilterIncludeOptions]): plugin_options_type = FilterIncludeOptions @@ -61,8 +63,8 @@ class FilterIncludePlugin(Plugin[FilterIncludeOptions]): return entry for formatter in self.plugin_options.list: - should_exclude = ScriptUtils.bool_formatter_output( - self.overrides.apply_formatter(formatter=formatter, entry=entry) + should_exclude = self.overrides.apply_formatter( + formatter=formatter, entry=entry, expected_type=bool ) if not should_exclude: logger.info( diff --git a/src/ytdl_sub/plugins/nfo_tags.py b/src/ytdl_sub/plugins/nfo_tags.py index 24c07c72..802dab63 100644 --- a/src/ytdl_sub/plugins/nfo_tags.py +++ b/src/ytdl_sub/plugins/nfo_tags.py @@ -140,7 +140,7 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC): if not nfo_tags: return - if self.overrides.evaluate_boolean(self.plugin_options.kodi_safe): + if self.overrides.apply_formatter(self.plugin_options.kodi_safe, expected_type=bool): nfo_root = to_max_3_byte_utf8_string(nfo_root) nfo_tags = { to_max_3_byte_utf8_string(key): [ diff --git a/src/ytdl_sub/plugins/square_thumbnail.py b/src/ytdl_sub/plugins/square_thumbnail.py index 030f5a09..dc5db0b7 100644 --- a/src/ytdl_sub/plugins/square_thumbnail.py +++ b/src/ytdl_sub/plugins/square_thumbnail.py @@ -31,7 +31,7 @@ class SquareThumbnailPlugin(Plugin[SquareThumbnailOptions]): @property def _square_thumbnail(self) -> bool: - return self.overrides.evaluate_boolean(self.plugin_options) + return self.overrides.apply_formatter(self.plugin_options, expected_type=bool) @classmethod def _convert_to_square_thumbnail(cls, entry: Entry) -> None: diff --git a/src/ytdl_sub/plugins/throttle_protection.py b/src/ytdl_sub/plugins/throttle_protection.py index 7acad676..463d1ace 100644 --- a/src/ytdl_sub/plugins/throttle_protection.py +++ b/src/ytdl_sub/plugins/throttle_protection.py @@ -42,8 +42,8 @@ class _RandomizedRangeValidator(StrictDictValidator, ABC): ) def _randomized_float(self, overrides: Overrides, entry: Optional[Entry] = None) -> float: - actualized_min = float(overrides.apply_formatter(self._min, entry=entry)) - actualized_max = float(overrides.apply_formatter(self._max, entry=entry)) + actualized_min = overrides.apply_formatter(self._min, entry=entry, expected_type=float) + actualized_max = overrides.apply_formatter(self._max, entry=entry, expected_type=float) if actualized_min < 0: raise self._validation_exception( @@ -70,7 +70,7 @@ class _RandomizedRangeValidator(StrictDictValidator, ABC): ------- Max possible value """ - actualized_max = float(overrides.apply_formatter(self._max, entry=entry)) + actualized_max = overrides.apply_formatter(self._max, entry=entry, expected_type=float) if actualized_max < 0: raise self._validation_exception( f"max must be greater than zero, received {actualized_max}" diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 21715c03..66a9815a 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -82,35 +82,27 @@ class StringFormatterValidator(StringValidator): """ return self._parsed - def post_process(self, resolved: str) -> str: + def post_process(self, resolved: Any) -> Any: """ Returns ------- - Apply any post processing to the resolved value + Apply any post processing to the resolved value. Defaults to casting it to string. """ - return resolved - - def post_process_native(self, resolved: Any) -> Any: - """ - Returns - ------- - Apply any post processing to the resolved native value. - """ - return resolved + return str(resolved) class FloatFormatterValidator(StringFormatterValidator): _expected_value_type_name = "float" - def post_process(self, resolved: str) -> str: + def post_process(self, resolved: str) -> float: try: - float(resolved) + out = float(resolved) except Exception as exc: raise self._validation_exception( f"Expected a float, but received '{resolved}'" ) from exc - return resolved + return out class StandardizedDateValidator(StringFormatterValidator): @@ -127,6 +119,13 @@ class StandardizedDateValidator(StringFormatterValidator): return resolved +class BooleanFormatterValidator(StringFormatterValidator): + _expected_value_type_name = "boolean" + + def post_process(self, resolved: Any) -> bool: + return ScriptUtils.bool_formatter_output(output=str(resolved)) + + # pylint: disable=line-too-long class OverridesStringFormatterValidator(StringFormatterValidator): """ @@ -146,15 +145,14 @@ class OverridesStringFormatterValidator(StringFormatterValidator): class OverridesIntegerFormatterValidator(OverridesStringFormatterValidator): _expected_value_type_name = "integer" - def post_process(self, resolved: str) -> str: + def post_process(self, resolved: str) -> int: try: - int(resolved) + out = int(resolved) except Exception as exc: raise self._validation_exception( f"Expected an integer, but received '{resolved}'" ) from exc - - return resolved + return out class OverridesFloatFormatterValidator(FloatFormatterValidator, OverridesStringFormatterValidator): @@ -163,9 +161,14 @@ class OverridesFloatFormatterValidator(FloatFormatterValidator, OverridesStringF """ -class OverridesBooleanFormatterValidator(OverridesStringFormatterValidator): +class OverridesBooleanFormatterValidator( + BooleanFormatterValidator, OverridesStringFormatterValidator +): _expected_value_type_name = "boolean" + def post_process(self, resolved: Any) -> bool: + return ScriptUtils.bool_formatter_output(output=str(resolved)) + class ListFormatterValidator(ListValidator[StringFormatterValidator]): _inner_list_type = StringFormatterValidator @@ -211,7 +214,22 @@ class OverridesDictFormatterValidator(DictFormatterValidator): _key_validator = OverridesStringFormatterValidator +class AnyFormatterValidator(StringFormatterValidator): + """ + Applies no post-processing. + """ + + def post_process(self, resolved: Any) -> Any: + return resolved + + +class AnyOverridesFormatterValidator(AnyFormatterValidator, OverridesStringFormatterValidator): + pass + + class UnstructuredDictFormatterValidator(DictFormatterValidator): + _key_validator = AnyFormatterValidator + def __init__(self, name, value): # Convert the unstructured-ness into a script if isinstance(value, dict): @@ -220,19 +238,7 @@ class UnstructuredDictFormatterValidator(DictFormatterValidator): class UnstructuredOverridesDictFormatterValidator(UnstructuredDictFormatterValidator): - _key_validator = OverridesStringFormatterValidator - - -def to_variable_dependency_format_string(script: Script, parsed_format_string: SyntaxTree) -> str: - """ - Create a dummy format string that contains all variable deps as a string. - """ - dummy_format_string = "" - for var in parsed_format_string.variables: - dummy_format_string += f"{{ {var.name} }}" - for variable_dependency in script._variables[var.name].variables: - dummy_format_string += f"{{ {variable_dependency.name} }}" - return dummy_format_string + _key_validator = AnyOverridesFormatterValidator def _validate_formatter( diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index 35942f8d..930708ed 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -475,19 +475,6 @@ def test_advanced_tv_show_subscriptions( assert overrides.script.get("subscription_name").native == "Gardening with Ciscoe" - assert overrides.apply_overrides_formatter_to_native(overrides.dict["subscription_array"]) == [ - "https://www.youtube.com/@gardeningwithciscoe4430", - "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_", - "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn", - "https://www.youtube.com/watch?v=2vq-vPubS5I", - ] - assert overrides.apply_overrides_formatter_to_native(overrides.dict["urls"]) == [ - "https://www.youtube.com/@gardeningwithciscoe4430", - "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_", - "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn", - "https://www.youtube.com/watch?v=2vq-vPubS5I", - ] - def test_music_subscriptions(default_config: ConfigFile, music_subscriptions_path: Path): subs = Subscription.from_file_path( diff --git a/tests/unit/plugins/test_ytdl_options.py b/tests/unit/plugins/test_ytdl_options.py index e72d960e..205e1750 100644 --- a/tests/unit/plugins/test_ytdl_options.py +++ b/tests/unit/plugins/test_ytdl_options.py @@ -87,8 +87,11 @@ class TestYtdlOptions: "output_options": {"output_directory": output_directory, "file_name": "will_error.mp4"}, "ytdl_options": { "break_on_existing": True, - "js_runtimes": {"deno": {"path": "/usr/local/bin/deno"}}, + "js_runtimes": {"deno": {"path": "/usr/local/bin/{dnope}"}}, + "string_path": "verify overrides: {test_string}", + "list_test": ["hmmm"], }, + "overrides": {"test_string": "hi", "dnope": "deno"}, } sub = Subscription.from_dict( @@ -97,10 +100,12 @@ class TestYtdlOptions: preset_dict=preset_dict, ) + out = sub.ytdl_options.to_native_dict(sub.overrides) expected = { "break_on_existing": True, "js_runtimes": {"deno": {"path": "/usr/local/bin/deno"}}, + "string_path": "verify overrides: hi", + "list_test": ["hmmm"], } - out = sub.ytdl_options.to_native_dict(sub.overrides) assert out == expected diff --git a/tests/unit/prebuilt_presets/test_tv_show_collection.py b/tests/unit/prebuilt_presets/test_tv_show_collection.py index 07e30006..0f7e9c5e 100644 --- a/tests/unit/prebuilt_presets/test_tv_show_collection.py +++ b/tests/unit/prebuilt_presets/test_tv_show_collection.py @@ -57,12 +57,13 @@ class TestTvShowCollectionPreset: # is_bilateral if i == 0: - url = sub.overrides.apply_overrides_formatter_to_native( + url = sub.overrides.apply_formatter( url_list[itr].url, function_overrides={ # mock so bilateral url gets enabled "subscription_has_download_archive": "True" }, + expected_type=list, ) assert url == [ f"youtube.com/playlist?url_{season_num}_{i}" @@ -81,12 +82,13 @@ class TestTvShowCollectionPreset: # not bilateral else: for j in range(2): - url = sub.overrides.apply_overrides_formatter_to_native( + url = sub.overrides.apply_formatter( url_list[itr + j].url, function_overrides={ # mock so bilateral url gets enabled "subscription_has_download_archive": "True" }, + expected_type=list, ) # First instance is the first url to get thumbnails From d37945db7e04d36fcdeef3149fb91975bc89a37f Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Mon, 26 Jan 2026 17:30:44 -0800 Subject: [PATCH 36/46] [HOTFIX] Fix StringFormattingException regarding keep_max_files (#1427) Closes this issue from last release: https://github.com/jmbannon/ytdl-sub/issues/1426 --- src/ytdl_sub/subscriptions/subscription_download.py | 4 ++-- src/ytdl_sub/subscriptions/subscription_ytdl_options.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ytdl_sub/subscriptions/subscription_download.py b/src/ytdl_sub/subscriptions/subscription_download.py index c9a8b6f7..082260cc 100644 --- a/src/ytdl_sub/subscriptions/subscription_download.py +++ b/src/ytdl_sub/subscriptions/subscription_download.py @@ -155,8 +155,8 @@ class SubscriptionDownload(BaseSubscription, ABC): keep_max_files: Optional[int] = None if self.output_options.keep_max_files: # validated it can be cast to int within the validator - keep_max_files = int( - self.overrides.apply_formatter(self.output_options.keep_max_files) + keep_max_files = self.overrides.apply_formatter( + self.output_options.keep_max_files, expected_type=int ) if date_range_to_keep or self.output_options.keep_max_files is not None: diff --git a/src/ytdl_sub/subscriptions/subscription_ytdl_options.py b/src/ytdl_sub/subscriptions/subscription_ytdl_options.py index b5772197..c689c88b 100644 --- a/src/ytdl_sub/subscriptions/subscription_ytdl_options.py +++ b/src/ytdl_sub/subscriptions/subscription_ytdl_options.py @@ -94,8 +94,8 @@ class SubscriptionYTDLOptions: self._enhanced_download_archive.working_ytdl_file_path ) if self._preset.output_options.keep_max_files: - keep_max_files = int( - self._overrides.apply_formatter(self._preset.output_options.keep_max_files) + keep_max_files = self._overrides.apply_formatter( + self._preset.output_options.keep_max_files, expected_type=int ) if keep_max_files > 0: # yt-dlp has a weird bug with max_downloads=1, set to 2 for safe measure From d6eda27371bd5278cb82cd81b822320e80795fb0 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Tue, 27 Jan 2026 10:15:32 -0800 Subject: [PATCH 37/46] [BACKEND] Less string casting behind the scenes (#1425) Simplifies internal scripts by not casting things to String when unnecessary. --- src/ytdl_sub/utils/script.py | 5 ++-- .../music/inspect_full_overrides.json | 4 ++-- .../music/inspect_full_variables.json | 14 +++++------ .../music/inspect_overrides.json | 4 ++-- .../music/inspect_variables.json | 14 +++++------ .../music_video/inspect_full_variables.json | 4 ++-- .../music_video/inspect_variables.json | 4 ++-- .../tv_show/inspect_full_overrides.json | 8 +++---- .../tv_show/inspect_full_variables.json | 24 +++++++++---------- .../tv_show/inspect_overrides.json | 6 ++--- .../tv_show/inspect_variables.json | 22 ++++++++--------- tests/unit/utils/test_script_utils.py | 6 ++--- .../test_string_formatter_validator.py | 8 +++---- 13 files changed, 61 insertions(+), 62 deletions(-) diff --git a/src/ytdl_sub/utils/script.py b/src/ytdl_sub/utils/script.py index 90cb3aa3..7252d7c4 100644 --- a/src/ytdl_sub/utils/script.py +++ b/src/ytdl_sub/utils/script.py @@ -87,9 +87,8 @@ class ScriptUtils: ast = parse(text=value).ast if len(ast) == 1: return ast[0] - return BuiltInFunction( - name="concat", args=[BuiltInFunction(name="string", args=[arg]) for arg in ast] - ) + + return BuiltInFunction(name="concat", args=ast) if isinstance(value, bool): return Boolean(value) if isinstance(value, int): diff --git a/tests/resources/expected_json/music/inspect_full_overrides.json b/tests/resources/expected_json/music/inspect_full_overrides.json index ac17630f..b4fcae9c 100644 --- a/tests/resources/expected_json/music/inspect_full_overrides.json +++ b/tests/resources/expected_json/music/inspect_full_overrides.json @@ -1,5 +1,5 @@ { - "album_cover_path": "Lester Young/{ %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }/folder.jpg", + "album_cover_path": "Lester Young/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/folder.{ thumbnail_ext }", "album_dir": "[{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }] { %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", "artist_dir": "Lester Young", "avatar_uncropped_thumbnail_file_name": "", @@ -24,7 +24,7 @@ "track_artist": "Lester Young", "track_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", "track_file_name": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) } - { %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }.{ ext }", - "track_full_path": "Lester Young/{ %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }/{ %concat( %string( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ), \" - \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), \".\", %string( ext ) ) }", + "track_full_path": "{ artist_dir }/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/{ %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) }", "track_genre": "Jazz", "track_genre_default": "Unset", "track_number": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", diff --git a/tests/resources/expected_json/music/inspect_full_variables.json b/tests/resources/expected_json/music/inspect_full_variables.json index ca5513bf..25c2274a 100644 --- a/tests/resources/expected_json/music/inspect_full_variables.json +++ b/tests/resources/expected_json/music/inspect_full_variables.json @@ -1,8 +1,8 @@ { - "album_cover_path": "Lester Young/{ %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }/folder.jpg", - "album_cover_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %string( %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ), \"/folder.\", \"jpg\" ) ) }", + "album_cover_path": "Lester Young/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/folder.{ thumbnail_ext }", + "album_cover_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ), \"/folder.\", thumbnail_ext ) ) }", "album_dir": "[{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }] { %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "album_dir_sanitized": "{ %sanitize( %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) }", + "album_dir_sanitized": "{ %sanitize( %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", "artist_dir": "Lester Young", "artist_dir_sanitized": "Lester Young", "avatar_uncropped_thumbnail_file_name": "", @@ -140,7 +140,7 @@ "resolution_assert_print_sanitized": "true", "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "resolution_readable_sanitized": "{ %sanitize( %concat( %string( %map_get_non_empty( entry_metadata, \"width\", 0 ) ), \"x\", %string( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) ) }", + "resolution_readable_sanitized": "{ %sanitize( %concat( %map_get_non_empty( entry_metadata, \"width\", 0 ), \"x\", %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) }", "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", "source_count": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) }", @@ -194,9 +194,9 @@ "track_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", "track_date_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", "track_file_name": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) } - { %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }.{ ext }", - "track_file_name_sanitized": "{ %sanitize( %concat( %string( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ), \" - \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), \".\", %string( ext ) ) ) }", - "track_full_path": "Lester Young/{ %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }/{ %concat( %string( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ), \" - \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), \".\", %string( ext ) ) }", - "track_full_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %string( %concat( \"[\", %string( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ), \"] \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ), \"/\", %string( %concat( %string( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ), \" - \", %string( %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), \".\", %string( ext ) ) ) ) ) }", + "track_file_name_sanitized": "{ %sanitize( %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) ) }", + "track_full_path": "{ artist_dir }/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/{ %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) }", + "track_full_path_sanitized": "{ %sanitize( %concat( artist_dir, \"/\", %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ), \"/\", %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) ) ) }", "track_genre": "Jazz", "track_genre_default": "Unset", "track_genre_default_sanitized": "Unset", diff --git a/tests/resources/expected_json/music/inspect_overrides.json b/tests/resources/expected_json/music/inspect_overrides.json index 19490f00..69ef99c4 100644 --- a/tests/resources/expected_json/music/inspect_overrides.json +++ b/tests/resources/expected_json/music/inspect_overrides.json @@ -1,5 +1,5 @@ { - "album_cover_path": "Lester Young/{ %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) }/folder.{ thumbnail_ext }", + "album_cover_path": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/folder.{ thumbnail_ext }", "album_dir": "[{ playlist_max_upload_year }] { %sanitize( playlist_title ) }", "artist_dir": "Lester Young", "avatar_uncropped_thumbnail_file_name": "", @@ -24,7 +24,7 @@ "track_artist": "Lester Young", "track_date": "{ upload_date_standardized }", "track_file_name": "{ playlist_index_padded } - { %sanitize( title ) }.{ ext }", - "track_full_path": "Lester Young/{ %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) }/{ %concat( %string( playlist_index_padded ), \" - \", %string( %sanitize( title ) ), \".\", %string( ext ) ) }", + "track_full_path": "{ artist_dir }/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/{ %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) }", "track_genre": "Jazz", "track_genre_default": "Unset", "track_number": "{ playlist_index }", diff --git a/tests/resources/expected_json/music/inspect_variables.json b/tests/resources/expected_json/music/inspect_variables.json index 131629d8..1fee2525 100644 --- a/tests/resources/expected_json/music/inspect_variables.json +++ b/tests/resources/expected_json/music/inspect_variables.json @@ -1,8 +1,8 @@ { - "album_cover_path": "Lester Young/{ %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) }/folder.{ thumbnail_ext }", - "album_cover_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %string( %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) ), \"/folder.\", %string( thumbnail_ext ) ) ) }", + "album_cover_path": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/folder.{ thumbnail_ext }", + "album_cover_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ), \"/folder.\", thumbnail_ext ) ) }", "album_dir": "[{ playlist_max_upload_year }] { %sanitize( playlist_title ) }", - "album_dir_sanitized": "{ %sanitize( %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) ) }", + "album_dir_sanitized": "{ %sanitize( %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) ) }", "artist_dir": "Lester Young", "artist_dir_sanitized": "Lester Young", "avatar_uncropped_thumbnail_file_name": "", @@ -140,7 +140,7 @@ "resolution_assert_print_sanitized": "true", "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", "resolution_readable": "{ width }x{ height }", - "resolution_readable_sanitized": "{ %sanitize( %concat( %string( width ), \"x\", %string( height ) ) ) }", + "resolution_readable_sanitized": "{ %sanitize( %concat( width, \"x\", height ) ) }", "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", "source_count": "{ %map_get_non_empty( playlist_metadata, \"playlist_count\", 1 ) }", @@ -194,9 +194,9 @@ "track_date": "{ upload_date_standardized }", "track_date_sanitized": "{ %sanitize( upload_date_standardized ) }", "track_file_name": "{ playlist_index_padded } - { %sanitize( title ) }.{ ext }", - "track_file_name_sanitized": "{ %sanitize( %concat( %string( playlist_index_padded ), \" - \", %string( %sanitize( title ) ), \".\", %string( ext ) ) ) }", - "track_full_path": "Lester Young/{ %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) }/{ %concat( %string( playlist_index_padded ), \" - \", %string( %sanitize( title ) ), \".\", %string( ext ) ) }", - "track_full_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %string( %concat( \"[\", %string( playlist_max_upload_year ), \"] \", %string( %sanitize( playlist_title ) ) ) ), \"/\", %string( %concat( %string( playlist_index_padded ), \" - \", %string( %sanitize( title ) ), \".\", %string( ext ) ) ) ) ) }", + "track_file_name_sanitized": "{ %sanitize( %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) ) }", + "track_full_path": "{ artist_dir }/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/{ %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) }", + "track_full_path_sanitized": "{ %sanitize( %concat( artist_dir, \"/\", %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ), \"/\", %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) ) ) }", "track_genre": "Jazz", "track_genre_default": "Unset", "track_genre_default_sanitized": "Unset", diff --git a/tests/resources/expected_json/music_video/inspect_full_variables.json b/tests/resources/expected_json/music_video/inspect_full_variables.json index 34dc4392..d1231e9d 100644 --- a/tests/resources/expected_json/music_video/inspect_full_variables.json +++ b/tests/resources/expected_json/music_video/inspect_full_variables.json @@ -70,7 +70,7 @@ "music_video_directory": "tv_show_directory_path", "music_video_directory_sanitized": "tv_show_directory_path", "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "music_video_file_name_sanitized": "{ %sanitize( %concat( \"Rick Astley\", \"/\", %string( %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ), '' ) ) }", + "music_video_file_name_sanitized": "{ %sanitize( %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), '' ) ) }", "music_video_file_name_suffix": "", "music_video_file_name_suffix_sanitized": "", "music_video_genre": "Pop", @@ -164,7 +164,7 @@ "resolution_assert_print_sanitized": "true", "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "resolution_readable_sanitized": "{ %sanitize( %concat( %string( %map_get_non_empty( entry_metadata, \"width\", 0 ) ), \"x\", %string( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) ) }", + "resolution_readable_sanitized": "{ %sanitize( %concat( %map_get_non_empty( entry_metadata, \"width\", 0 ), \"x\", %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) }", "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", "source_count": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) }", diff --git a/tests/resources/expected_json/music_video/inspect_variables.json b/tests/resources/expected_json/music_video/inspect_variables.json index 41606b71..7e4bc383 100644 --- a/tests/resources/expected_json/music_video/inspect_variables.json +++ b/tests/resources/expected_json/music_video/inspect_variables.json @@ -70,7 +70,7 @@ "music_video_directory": "tv_show_directory_path", "music_video_directory_sanitized": "tv_show_directory_path", "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", title ) ) }", - "music_video_file_name_sanitized": "{ %sanitize( %concat( \"Rick Astley\", \"/\", %string( %sanitize( %get_url_field( \"title\", title ) ) ), '' ) ) }", + "music_video_file_name_sanitized": "{ %sanitize( %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", title ) ), '' ) ) }", "music_video_file_name_suffix": "", "music_video_file_name_suffix_sanitized": "", "music_video_genre": "Pop", @@ -164,7 +164,7 @@ "resolution_assert_print_sanitized": "true", "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", "resolution_readable": "{ width }x{ height }", - "resolution_readable_sanitized": "{ %sanitize( %concat( %string( width ), \"x\", %string( height ) ) ) }", + "resolution_readable_sanitized": "{ %sanitize( %concat( width, \"x\", height ) ) }", "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", "source_count": "{ %map_get_non_empty( playlist_metadata, \"playlist_count\", 1 ) }", diff --git a/tests/resources/expected_json/tv_show/inspect_full_overrides.json b/tests/resources/expected_json/tv_show/inspect_full_overrides.json index 179d4e11..4d17acd4 100644 --- a/tests/resources/expected_json/tv_show/inspect_full_overrides.json +++ b/tests/resources/expected_json/tv_show/inspect_full_overrides.json @@ -8,12 +8,12 @@ "episode_content_rating": "TV-14", "episode_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", "episode_file_name": "s{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.e{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) } - { %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/{ %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/{ %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", "episode_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }{ upload_date_index_padded }", "episode_number_and_padded_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", "episode_number_padded": "{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) }", "episode_plot": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "episode_title": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "episode_title": "{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", "episode_year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }", "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", @@ -32,13 +32,13 @@ "season_directory_name": "Season { %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", "season_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", "season_number_padded": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", "subscription_indent_1": "Documentaries", "subscription_indent_2": "TV-14", "subscription_value": "https://www.youtube.com/@novapbs", "subscription_value_1": "https://www.youtube.com/@novapbs", - "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) }-thumb.jpg", + "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }-thumb.jpg", "tv_show_by_date_episode_ordering": "upload-month-day", "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", "tv_show_by_date_season_ordering": "upload-year", diff --git a/tests/resources/expected_json/tv_show/inspect_full_variables.json b/tests/resources/expected_json/tv_show/inspect_full_variables.json index bfb834bf..4e22fa86 100644 --- a/tests/resources/expected_json/tv_show/inspect_full_variables.json +++ b/tests/resources/expected_json/tv_show/inspect_full_variables.json @@ -36,9 +36,9 @@ "episode_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", "episode_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", "episode_file_name": "s{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.e{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) } - { %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "episode_file_name_sanitized": "{ %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", - "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/{ %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", - "episode_file_path_sanitized": "{ %sanitize( %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) ) }", + "episode_file_name_sanitized": "{ %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/{ %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "episode_file_path_sanitized": "{ %sanitize( %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) }", "episode_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }{ upload_date_index_padded }", "episode_number_and_padded_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", "episode_number_and_padded__sanitized": "{ %sanitize( [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] ) }", @@ -46,9 +46,9 @@ "episode_number_padded_sanitized": "{ %sanitize( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ) }", "episode_number_sanitized": "{ %sanitize( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ) }", "episode_plot": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "episode_plot_sanitized": "{ %sanitize( %concat( %string( %map_get( entry_metadata, \"webpage_url\" ) ), \"\n\n\", %string( %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) ) }", - "episode_title": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", - "episode_title_sanitized": "{ %sanitize( %concat( %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ), \" - \", %string( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "episode_plot_sanitized": "{ %sanitize( %concat( %map_get( entry_metadata, \"webpage_url\" ), \"\n\n\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", + "episode_title": "{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "episode_title_sanitized": "{ %sanitize( %concat( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), \" - \", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", "episode_year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }", "episode_year_sanitized": "{ %sanitize( %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) ) }", "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", @@ -164,19 +164,19 @@ "resolution_assert_print_sanitized": "true", "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "resolution_readable_sanitized": "{ %sanitize( %concat( %string( %map_get_non_empty( entry_metadata, \"width\", 0 ) ), \"x\", %string( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) ) }", + "resolution_readable_sanitized": "{ %sanitize( %concat( %map_get_non_empty( entry_metadata, \"width\", 0 ), \"x\", %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) }", "s01_name": "", "s01_name_sanitized": "", "s01_url": "", "s01_url_sanitized": "", "season_directory_name": "Season { %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "season_directory_name_sanitized": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }", + "season_directory_name_sanitized": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }", "season_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", "season_number_padded": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", "season_number_padded_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", "season_number_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", - "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", - "season_poster_file_name_sanitized": "{ %sanitize( %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/Season\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".jpg\" ) ) }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", + "season_poster_file_name_sanitized": "{ %sanitize( %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/Season\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".jpg\" ) ) }", "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", "source_count": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) }", @@ -219,8 +219,8 @@ "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", "thumbnail_ext": "jpg", "thumbnail_ext_sanitized": "jpg", - "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) }-thumb.jpg", - "thumbnail_file_name_sanitized": "{ %sanitize( %concat( %string( %concat( %string( %sanitize( %concat( \"Season \", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ), \".e\", %string( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ), \" - \", %string( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) ) ), \"-thumb.jpg\" ) ) }", + "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }-thumb.jpg", + "thumbnail_file_name_sanitized": "{ %sanitize( %concat( %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ), \"-thumb.jpg\" ) ) }", "title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", "title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", "title_sanitized_plex": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", diff --git a/tests/resources/expected_json/tv_show/inspect_overrides.json b/tests/resources/expected_json/tv_show/inspect_overrides.json index faf71281..93916b4d 100644 --- a/tests/resources/expected_json/tv_show/inspect_overrides.json +++ b/tests/resources/expected_json/tv_show/inspect_overrides.json @@ -8,7 +8,7 @@ "episode_content_rating": "TV-14", "episode_date_standardized": "{ upload_date_standardized }", "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) } - { title_sanitized_plex }", - "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/{ %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/{ %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) }", "episode_number": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", "episode_number_and_padded_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", "episode_number_padded": "{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) }", @@ -32,13 +32,13 @@ "season_directory_name": "Season { upload_year }", "season_number": "{ upload_year }", "season_number_padded": "{ upload_year }", - "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/Season{ upload_year }.jpg", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/Season{ upload_year }.jpg", "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", "subscription_indent_1": "Documentaries", "subscription_indent_2": "TV-14", "subscription_value": "https://www.youtube.com/@novapbs", "subscription_value_1": "https://www.youtube.com/@novapbs", - "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) }-thumb.jpg", + "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) }-thumb.jpg", "tv_show_by_date_episode_ordering": "upload-month-day", "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", "tv_show_by_date_season_ordering": "upload-year", diff --git a/tests/resources/expected_json/tv_show/inspect_variables.json b/tests/resources/expected_json/tv_show/inspect_variables.json index 77659a4b..f3159ff9 100644 --- a/tests/resources/expected_json/tv_show/inspect_variables.json +++ b/tests/resources/expected_json/tv_show/inspect_variables.json @@ -36,9 +36,9 @@ "episode_date_standardized": "{ upload_date_standardized }", "episode_date_standardized_sanitized": "{ %sanitize( upload_date_standardized ) }", "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) } - { title_sanitized_plex }", - "episode_file_name_sanitized": "{ %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) }", - "episode_file_path": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/{ %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) }", - "episode_file_path_sanitized": "{ %sanitize( %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) ) }", + "episode_file_name_sanitized": "{ %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/{ %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) }", + "episode_file_path_sanitized": "{ %sanitize( %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) ) }", "episode_number": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", "episode_number_and_padded_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", "episode_number_and_padded__sanitized": "{ %sanitize( [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] ) }", @@ -46,9 +46,9 @@ "episode_number_padded_sanitized": "{ %sanitize( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ) }", "episode_number_sanitized": "{ %sanitize( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ) }", "episode_plot": "{ webpage_url }\n\n{ description }", - "episode_plot_sanitized": "{ %sanitize( %concat( %string( webpage_url ), \"\n\n\", %string( description ) ) ) }", + "episode_plot_sanitized": "{ %sanitize( %concat( webpage_url, \"\n\n\", description ) ) }", "episode_title": "{ upload_date_standardized } - { title }", - "episode_title_sanitized": "{ %sanitize( %concat( %string( upload_date_standardized ), \" - \", %string( title ) ) ) }", + "episode_title_sanitized": "{ %sanitize( %concat( upload_date_standardized, \" - \", title ) ) }", "episode_year": "{ %slice( upload_date_standardized, 0, 4 ) }", "episode_year_sanitized": "{ %sanitize( %slice( upload_date_standardized, 0, 4 ) ) }", "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", @@ -164,19 +164,19 @@ "resolution_assert_print_sanitized": "true", "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", "resolution_readable": "{ width }x{ height }", - "resolution_readable_sanitized": "{ %sanitize( %concat( %string( width ), \"x\", %string( height ) ) ) }", + "resolution_readable_sanitized": "{ %sanitize( %concat( width, \"x\", height ) ) }", "s01_name": "", "s01_name_sanitized": "", "s01_url": "", "s01_url_sanitized": "", "season_directory_name": "Season { upload_year }", - "season_directory_name_sanitized": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }", + "season_directory_name_sanitized": "{ %sanitize( %concat( \"Season \", upload_year ) ) }", "season_number": "{ upload_year }", "season_number_padded": "{ upload_year }", "season_number_padded_sanitized": "{ %sanitize( upload_year ) }", "season_number_sanitized": "{ %sanitize( upload_year ) }", - "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %string( upload_year ) ) ) }/Season{ upload_year }.jpg", - "season_poster_file_name_sanitized": "{ %sanitize( %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/Season\", %string( upload_year ), \".jpg\" ) ) }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/Season{ upload_year }.jpg", + "season_poster_file_name_sanitized": "{ %sanitize( %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/Season\", upload_year, \".jpg\" ) ) }", "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", "source_count": "{ %map_get_non_empty( playlist_metadata, \"playlist_count\", 1 ) }", @@ -219,8 +219,8 @@ "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", "thumbnail_ext": "jpg", "thumbnail_ext_sanitized": "jpg", - "thumbnail_file_name": "{ %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) }-thumb.jpg", - "thumbnail_file_name_sanitized": "{ %sanitize( %concat( %string( %concat( %string( %sanitize( %concat( \"Season \", %string( upload_year ) ) ) ), \"/\", %string( %sanitize( %concat( \"s\", %string( upload_year ), \".e\", %string( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ), \" - \", %string( title_sanitized_plex ) ) ) ) ) ), \"-thumb.jpg\" ) ) }", + "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) }-thumb.jpg", + "thumbnail_file_name_sanitized": "{ %sanitize( %concat( %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ), \"-thumb.jpg\" ) ) }", "title": "{ %map_get_non_empty( entry_metadata, \"title\", uid ) }", "title_sanitized": "{ %sanitize( title ) }", "title_sanitized_plex": "{ %sanitize_plex_episode( title ) }", diff --git a/tests/unit/utils/test_script_utils.py b/tests/unit/utils/test_script_utils.py index 608066b5..792020ba 100644 --- a/tests/unit/utils/test_script_utils.py +++ b/tests/unit/utils/test_script_utils.py @@ -81,9 +81,9 @@ class TestScriptUtils: String(value="static_a"): BuiltInFunction( name="concat", args=[ - BuiltInFunction(name="string", args=[String(value="string with ")]), - BuiltInFunction(name="string", args=[Variable(name="var_c")]), - BuiltInFunction(name="string", args=[String(value=" in it")]), + String(value="string with "), + Variable(name="var_c"), + String(value=" in it"), ], ), String(value="quotes"): String(value="has '' and \"\""), diff --git a/tests/unit/validators/test_string_formatter_validator.py b/tests/unit/validators/test_string_formatter_validator.py index 9bb4d1e0..888dc58d 100644 --- a/tests/unit/validators/test_string_formatter_validator.py +++ b/tests/unit/validators/test_string_formatter_validator.py @@ -115,12 +115,12 @@ class TestUnstructuredDictFormatterValidator(object): assert len(validator.dict) == 8 assert all(isinstance(val, expected_formatter_class) for val in validator.dict.values()) assert validator.dict_with_format_strings == { - "key1": '{ %concat( %string( "string with " ), %string( variable ) ) }', + "key1": '{ %concat( "string with ", variable ) }', "key2": "no variables", "key3": "{ %int(3) }", "key4": "{ %float(4.132) }", "key5": "{ %bool(True) }", - "key6": '{ { %concat( %string( variable ), %string( "_key" ) ): "value", "static_key": %concat( %string( variable ), %string( "_value" ) ) } }', - "key7": '{ [ "list_1", %concat( %string( "list_" ), %string( variable_2 ) ) ] }', - "key8": '{ %concat( %string( "string " ), %string( variable1 ), %string( " with multiple " ), %string( variable2 ) ) }', + "key6": '{ { %concat( variable, "_key" ): "value", "static_key": %concat( variable, "_value" ) } }', + "key7": '{ [ "list_1", %concat( "list_", variable_2 ) ] }', + "key8": '{ %concat( "string ", variable1, " with multiple ", variable2 ) }', } From 6b7805c6e18f8590c3ae0ff31bf9f4c453a023c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 21:24:07 -0800 Subject: [PATCH 38/46] Bump yt-dlp[default] from 2025.12.8 to 2026.1.29 (#1428) Bumps [yt-dlp[default]](https://github.com/yt-dlp/yt-dlp) from 2025.12.8 to 2026.1.29. - [Release notes](https://github.com/yt-dlp/yt-dlp/releases) - [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md) - [Commits](https://github.com/yt-dlp/yt-dlp/compare/2025.12.08...2026.01.29) --- updated-dependencies: - dependency-name: yt-dlp[default] dependency-version: 2026.1.29 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 94e18a34..772e91cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ - "yt-dlp[default]==2025.12.8", + "yt-dlp[default]==2026.1.29", "colorama~=0.4", "mergedeep~=1.3", "mediafile~=0.12", From c163f9766a7e8976734905867e14a6886336b31e Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Mon, 2 Feb 2026 09:50:52 -0800 Subject: [PATCH 39/46] [BACKEND] Configurable resolution level when validating variables (#1415) Expands variable validation to also include support for partial resolution. In short, this will partially execute script code until it hits unresolved runtime variables, and store that as the new script representation. This functionality will allow for the upcoming `inspect` command, which will show users their script code in action without having to dry-run. --- src/ytdl_sub/config/preset.py | 11 +- .../config/validators/variable_validation.py | 126 ++++- .../entries/script/variable_definitions.py | 10 + src/ytdl_sub/script/script.py | 150 +++-- src/ytdl_sub/script/types/function.py | 7 - src/ytdl_sub/script/types/syntax_tree.py | 4 + .../script/types/variable_dependency.py | 5 +- .../subscriptions/base_subscription.py | 19 +- .../validators/string_formatter_validators.py | 52 +- .../plugins/test_output_options.py | 22 +- .../music/inspect_full_overrides.json | 137 ----- .../music/inspect_full_variables.json | 482 ---------------- .../music/inspect_overrides.json | 137 ----- .../expected_json/music/inspect_sub_fill.json | 242 ++++++++ .../music/inspect_sub_internal.json | 242 ++++++++ .../music/inspect_sub_original.json | 227 ++++++++ .../music/inspect_sub_resolve.json | 242 ++++++++ .../music/inspect_variables.json | 482 ---------------- .../music_video/inspect_full_overrides.json | 136 ----- .../music_video/inspect_full_variables.json | 480 ---------------- .../music_video/inspect_overrides.json | 136 ----- .../music_video/inspect_sub_fill.json | 314 +++++++++++ .../music_video/inspect_sub_internal.json | 314 +++++++++++ .../music_video/inspect_sub_original.json | 235 ++++++++ .../music_video/inspect_sub_resolve.json | 314 +++++++++++ .../music_video/inspect_variables.json | 480 ---------------- .../tv_show/inspect_full_overrides.json | 155 ------ .../tv_show/inspect_full_variables.json | 520 ------------------ .../tv_show/inspect_overrides.json | 155 ------ .../tv_show/inspect_sub_fill.json | 250 +++++++++ .../tv_show/inspect_sub_internal.json | 250 +++++++++ .../tv_show/inspect_sub_original.json | 255 +++++++++ .../tv_show/inspect_sub_resolve.json | 250 +++++++++ .../tv_show/inspect_variables.json | 520 ------------------ tests/unit/config/test_subscription.py | 67 +-- .../test_subscription_partial_resolve.py | 118 ---- .../config/test_subscription_resolution.py | 92 ++++ .../prebuilt_presets/test_tv_show_by_date.py | 109 ++-- .../functions/test_conditional_functions.py | 29 + tests/unit/script/types/test_array.py | 28 + .../unit/script/types/test_custom_function.py | 29 + .../unit/script/types/test_lambda_function.py | 17 + 42 files changed, 3697 insertions(+), 4153 deletions(-) delete mode 100644 tests/resources/expected_json/music/inspect_full_overrides.json delete mode 100644 tests/resources/expected_json/music/inspect_full_variables.json delete mode 100644 tests/resources/expected_json/music/inspect_overrides.json create mode 100644 tests/resources/expected_json/music/inspect_sub_fill.json create mode 100644 tests/resources/expected_json/music/inspect_sub_internal.json create mode 100644 tests/resources/expected_json/music/inspect_sub_original.json create mode 100644 tests/resources/expected_json/music/inspect_sub_resolve.json delete mode 100644 tests/resources/expected_json/music/inspect_variables.json delete mode 100644 tests/resources/expected_json/music_video/inspect_full_overrides.json delete mode 100644 tests/resources/expected_json/music_video/inspect_full_variables.json delete mode 100644 tests/resources/expected_json/music_video/inspect_overrides.json create mode 100644 tests/resources/expected_json/music_video/inspect_sub_fill.json create mode 100644 tests/resources/expected_json/music_video/inspect_sub_internal.json create mode 100644 tests/resources/expected_json/music_video/inspect_sub_original.json create mode 100644 tests/resources/expected_json/music_video/inspect_sub_resolve.json delete mode 100644 tests/resources/expected_json/music_video/inspect_variables.json delete mode 100644 tests/resources/expected_json/tv_show/inspect_full_overrides.json delete mode 100644 tests/resources/expected_json/tv_show/inspect_full_variables.json delete mode 100644 tests/resources/expected_json/tv_show/inspect_overrides.json create mode 100644 tests/resources/expected_json/tv_show/inspect_sub_fill.json create mode 100644 tests/resources/expected_json/tv_show/inspect_sub_internal.json create mode 100644 tests/resources/expected_json/tv_show/inspect_sub_original.json create mode 100644 tests/resources/expected_json/tv_show/inspect_sub_resolve.json delete mode 100644 tests/resources/expected_json/tv_show/inspect_variables.json delete mode 100644 tests/unit/config/test_subscription_partial_resolve.py create mode 100644 tests/unit/config/test_subscription_resolution.py diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index d8380839..b89e1d53 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -255,11 +255,18 @@ class Preset(_PresetShell): """ return cls(config=config, name=preset_name, value=preset_dict) - @property - def yaml(self) -> str: + def yaml(self, subscription_only: bool) -> str: """ + Parameters + ---------- + subscription_only: + Only include the subscription contents, not the surrounding boiler-plate. + Returns ------- Preset in YAML format """ + if subscription_only: + return dump_yaml(self._value) + return dump_yaml({"presets": {self._name: self._value}}) diff --git a/src/ytdl_sub/config/validators/variable_validation.py b/src/ytdl_sub/config/validators/variable_validation.py index 82069d7c..941d228e 100644 --- a/src/ytdl_sub/config/validators/variable_validation.py +++ b/src/ytdl_sub/config/validators/variable_validation.py @@ -1,4 +1,6 @@ from typing import Dict +from typing import List +from typing import Set from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.plugin.plugin_mapping import PluginMapping @@ -7,80 +9,166 @@ 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.script.variable_definitions import UNRESOLVED_VARIABLES +from ytdl_sub.entries.script.variable_definitions import VARIABLES +from ytdl_sub.script.script import Script +from ytdl_sub.script.utils.name_validation import is_function +from ytdl_sub.utils.script import ScriptUtils from ytdl_sub.validators.string_formatter_validators import validate_formatters +class ResolutionLevel: + ORIGINAL = 0 + FILL = 1 + RESOLVE = 2 + INTERNAL = 3 + + @classmethod + def name_of(cls, resolution_level: int) -> str: + """ + Name of the resolution level. + """ + if resolution_level == cls.ORIGINAL: + return "original" + if resolution_level == cls.FILL: + return "fill" + if resolution_level == cls.RESOLVE: + return "resolve" + if resolution_level == cls.INTERNAL: + return "internal" + raise ValueError("Invalid resolution level") + + @classmethod + def all(cls) -> List[int]: + """ + All possible resolution levels. + """ + return [cls.ORIGINAL, cls.FILL, cls.RESOLVE, cls.INTERNAL] + + class VariableValidation: + + def _get_resolve_partial_filter(self) -> Set[str]: + # Exclude sanitized variables from partial validation. This lessens the work + # and prevents double-evaluation, which can lead to bad behavior like double-prints. + return { + name + for name in self.script.variable_names + if name not in self.unresolved_variables and not name.endswith("_sanitized") + } + + def _apply_resolution_level(self) -> None: + if self._resolution_level == ResolutionLevel.FILL: + self.unresolved_variables |= VARIABLES.variable_names(include_sanitized=True) + # Only partial resolve definitions that are already resolved + self.unresolved_variables |= { + name + for name in self.overrides.keys + if not is_function(name) and not self.script.definition_of(name).maybe_resolvable + } + elif self._resolution_level == ResolutionLevel.RESOLVE: + # Partial resolve everything, but not including internal variables + self.unresolved_variables |= VARIABLES.variable_names(include_sanitized=True) + elif self._resolution_level == ResolutionLevel.INTERNAL: + # Partial resolve everything including internal variables + pass + else: + raise ValueError("Invalid resolution level for validation") + + self.script = self.script.resolve_partial( + unresolvable=self.unresolved_variables, + output_filter=self._get_resolve_partial_filter(), + ) + def __init__( self, overrides: Overrides, downloader_options: MultiUrlValidator, output_options: OutputOptions, plugins: PresetPlugins, + resolution_level: int = ResolutionLevel.RESOLVE, ): self.overrides = overrides self.downloader_options = downloader_options self.output_options = output_options self.plugins = plugins - self.script = self.overrides.script - self.unresolved_variables = self.plugins.get_all_variables( + self.script: Script = self.overrides.script + self.unresolved_variables = ( + self.plugins.get_all_variables( + additional_options=[self.output_options, self.downloader_options] + ) + | UNRESOLVED_VARIABLES + ) + self.unresolved_runtime_variables = self.plugins.get_all_variables( additional_options=[self.output_options, self.downloader_options] ) + self._resolution_level = resolution_level - def _add_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None: + self._apply_resolution_level() + + def _add_runtime_variables(self, plugin_op: PluginOperation, options: OptionsValidator) -> None: """ Add dummy variables for script validation """ added_variables = options.added_variables( - unresolved_variables=self.unresolved_variables, + unresolved_variables=self.unresolved_runtime_variables, ).get(plugin_op, set()) modified_variables = options.modified_variables().get(plugin_op, set()) - self.unresolved_variables -= added_variables | modified_variables + self.unresolved_runtime_variables -= added_variables | modified_variables - def ensure_proper_usage(self) -> Dict: + def ensure_proper_usage(self, partial_resolve_formatters: bool = False) -> Dict: """ Validate variables resolve as plugins are executed, and return a mock script which contains actualized added variables from the plugins """ - resolved_subscription: Dict = {} - self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options) + self._add_runtime_variables(PluginOperation.DOWNLOADER, options=self.downloader_options) # Always add output options first - self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options) + self._add_runtime_variables( + PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options + ) # Metadata variables to be added for plugin_options in PluginMapping.order_options_by( self.plugins.zipped(), PluginOperation.MODIFY_ENTRY_METADATA ): - self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=plugin_options) + self._add_runtime_variables( + PluginOperation.MODIFY_ENTRY_METADATA, options=plugin_options + ) for plugin_options in PluginMapping.order_options_by( self.plugins.zipped(), PluginOperation.MODIFY_ENTRY ): - self._add_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options) + self._add_runtime_variables(PluginOperation.MODIFY_ENTRY, options=plugin_options) # Validate that any formatter in the plugin options can resolve resolved_subscription |= validate_formatters( script=self.script, unresolved_variables=self.unresolved_variables, + unresolved_runtime_variables=self.unresolved_runtime_variables, validator=plugin_options, + partial_resolve_formatters=partial_resolve_formatters, ) resolved_subscription |= validate_formatters( script=self.script, unresolved_variables=self.unresolved_variables, + unresolved_runtime_variables=self.unresolved_runtime_variables, validator=self.output_options, + partial_resolve_formatters=partial_resolve_formatters, ) # TODO: make this a function raw_download_output = validate_formatters( script=self.script, unresolved_variables=self.unresolved_variables, + unresolved_runtime_variables=self.unresolved_runtime_variables, validator=self.downloader_options.urls, + partial_resolve_formatters=partial_resolve_formatters, ) resolved_subscription["download"] = [] for url_output in raw_download_output["download"]: @@ -90,5 +178,19 @@ class VariableValidation: if url_output["url"]: resolved_subscription["download"].append(url_output) - assert not self.unresolved_variables + # TODO: make function + resolved_subscription["overrides"] = {} + for name in self.overrides.keys: + value = self.script.definition_of(name) + if name in self.script.function_names: + # Keep custom functions as-is + resolved_subscription["overrides"][name] = self.overrides.dict_with_format_strings[ + name + ] + elif resolved := value.maybe_resolvable: + resolved_subscription["overrides"][name] = resolved.native + else: + resolved_subscription["overrides"][name] = ScriptUtils.to_native_script(value) + + assert not self.unresolved_runtime_variables return resolved_subscription diff --git a/src/ytdl_sub/entries/script/variable_definitions.py b/src/ytdl_sub/entries/script/variable_definitions.py index 67505473..97fb7759 100644 --- a/src/ytdl_sub/entries/script/variable_definitions.py +++ b/src/ytdl_sub/entries/script/variable_definitions.py @@ -1135,6 +1135,16 @@ class VariableDefinitions( ] } + @cache + def variable_names(self, include_sanitized: bool): + """ + Returns all variable names, and can include sanitized. + """ + var_names: Set[str] = self.scripts().keys() + if include_sanitized: + var_names |= {f"{name}_sanitized" for name in var_names} + return var_names + @cache def injected_variables(self) -> Set[MetadataVariable]: """ diff --git a/src/ytdl_sub/script/script.py b/src/ytdl_sub/script/script.py index 5d73613e..41c3f1be 100644 --- a/src/ytdl_sub/script/script.py +++ b/src/ytdl_sub/script/script.py @@ -1,5 +1,4 @@ # pylint: disable=missing-raises-doc -import copy from collections import defaultdict from typing import Dict from typing import List @@ -693,6 +692,18 @@ class Script: raise RuntimeException(f"Tried to get unresolved variable {variable_name}") + def definition_of(self, name: str) -> SyntaxTree: + """ + Returns + ------- + The definition of the variable or function. + """ + if name.startswith("%") and name[1:] in self._functions: + return self._functions[name[1:]] + if name in self._variables: + return self._variables[name] + raise RuntimeException(f"Tried to get non-existent definition with name {name}") + @property def variable_names(self) -> Set[str]: """ @@ -713,10 +724,57 @@ class Script: """ return set(to_function_definition_name(name) for name in self._functions.keys()) - def resolve_partial( + def _resolve_partial_loop( + self, + output_filter: Optional[Set[str]], + resolved: Dict[Variable, Resolvable], + unresolved: Dict[Variable, Argument], + unresolvable: Optional[Set[str]], + ): + to_partially_resolve: Set[Variable] = ( + {Variable(name) for name in output_filter} if output_filter else set(unresolved.keys()) + ) + + partially_resolved = True + while partially_resolved: + + partially_resolved = False + + for variable in list(to_partially_resolve): + definition = unresolved[variable] + maybe_resolved = definition + + if isinstance(definition, Variable) and definition.name not in unresolvable: + if definition in resolved: + maybe_resolved = resolved[definition] + elif definition in unresolved: + maybe_resolved = unresolved[definition] + else: + raise UNREACHABLE + elif isinstance(definition, VariableDependency): + maybe_resolved = definition.partial_resolve( + resolved_variables=resolved, + unresolved_variables=unresolved, + custom_functions=self._functions, + ) + + if isinstance(maybe_resolved, Resolvable): + resolved[variable] = maybe_resolved + del unresolved[variable] + to_partially_resolve.remove(variable) + partially_resolved = True + else: + unresolved[variable] = maybe_resolved + + # If the definition changed, then the script changed + # which means we can iterate again + partially_resolved |= definition != maybe_resolved + + def _resolve_partial( self, unresolvable: Optional[Set[str]] = None, - ) -> "Script": + output_filter: Optional[Set[str]] = None, + ) -> Dict[str, SyntaxTree]: """ Returns ------- @@ -731,40 +789,54 @@ class Script: if name not in unresolvable } - partially_resolved = True - while partially_resolved: - - partially_resolved = False - - for variable in list(unresolved.keys()): - definition = unresolved[variable] - - maybe_resolved = definition - if isinstance(definition, Variable) and definition.name not in unresolvable: - maybe_resolved = resolved.get(definition, unresolved[definition]) - elif isinstance(definition, VariableDependency): - maybe_resolved = definition.partial_resolve( - resolved_variables=resolved, - unresolved_variables=unresolved, - custom_functions=self._functions, - ) - - if isinstance(maybe_resolved, Resolvable): - resolved[variable] = maybe_resolved - del unresolved[variable] - partially_resolved = True - else: - unresolved[variable] = maybe_resolved - - # If the definition changed, then the script changed - # which means we can iterate again - partially_resolved |= definition != maybe_resolved - - return copy.deepcopy(self).add_parsed( - {var_name: self._variables[var_name] for var_name in unresolvable} - | { - var.name: ResolvedSyntaxTree(ast=[definition]) - for var, definition in resolved.items() - } - | {var.name: SyntaxTree(ast=[definition]) for var, definition in unresolved.items()} + self._resolve_partial_loop( + output_filter=output_filter, + resolved=resolved, + unresolved=unresolved, + unresolvable=unresolvable, ) + + if output_filter: + out: Dict[str, SyntaxTree] = {} + for name in output_filter: + variable_name = Variable(name) + if variable_name in resolved: + out[name] = ResolvedSyntaxTree(ast=[resolved[variable_name]]) + else: + out[name] = SyntaxTree(ast=[unresolved[variable_name]]) + + return out + + return { + var.name: ResolvedSyntaxTree(ast=[definition]) for var, definition in resolved.items() + } | {var.name: SyntaxTree(ast=[definition]) for var, definition in unresolved.items()} + + def resolve_partial( + self, + unresolvable: Optional[Set[str]] = None, + output_filter: Optional[Set[str]] = None, + ) -> "Script": + """ + Updates the internal script to resolve as much as possible. + """ + out = self._resolve_partial(unresolvable=unresolvable, output_filter=output_filter) + for var_name, definition in out.items(): + self._variables[var_name] = definition + + return self + + def resolve_partial_once( + self, variable_definitions: Dict[str, SyntaxTree], unresolvable: Optional[Set[str]] = None + ) -> Dict[str, SyntaxTree]: + """ + Partially resolves the input variable definitions as much as possible. + """ + try: + self.add_parsed(variable_definitions) + return self._resolve_partial( + unresolvable=unresolvable, + output_filter=set(list(variable_definitions.keys())), + ) + finally: + for name in variable_definitions.keys(): + self._variables.pop(name, None) diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index d34c0a47..bf64108b 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -371,13 +371,6 @@ class BuiltInFunction(Function, BuiltInFunctionType): If the conditional partially resolvable enough to warrant evaluation, perform it here. """ - if self.is_subset_of( - variables=resolved_variables, custom_function_definitions=custom_functions - ): - return self.resolve( - resolved_variables=resolved_variables, - custom_functions=custom_functions, - ) if self.name == "if": maybe_resolvable_arg, is_resolvable = VariableDependency.try_partial_resolve( diff --git a/src/ytdl_sub/script/types/syntax_tree.py b/src/ytdl_sub/script/types/syntax_tree.py index 2a970300..e914f396 100644 --- a/src/ytdl_sub/script/types/syntax_tree.py +++ b/src/ytdl_sub/script/types/syntax_tree.py @@ -55,6 +55,10 @@ class SyntaxTree(VariableDependency): custom_functions=custom_functions, ) + # If no arguments, must be empty string + if len(maybe_resolvable_values) == 0: + return String(value="") + # Mimic the above resolve behavior if len(maybe_resolvable_values) > 1: return BuiltInFunction(name="concat", args=maybe_resolvable_values) diff --git a/src/ytdl_sub/script/types/variable_dependency.py b/src/ytdl_sub/script/types/variable_dependency.py index 5cfee863..a185a0af 100644 --- a/src/ytdl_sub/script/types/variable_dependency.py +++ b/src/ytdl_sub/script/types/variable_dependency.py @@ -279,8 +279,11 @@ class VariableDependency(ABC): if not isinstance(maybe_resolvable_args[-1], Resolvable): is_resolvable = False elif isinstance(arg, Variable): - if arg not in resolved_variables: + if arg in resolved_variables: + maybe_resolvable_args[-1] = resolved_variables[arg] + else: is_resolvable = False + # Could be un unresolvable if arg in unresolved_variables: maybe_resolvable_args[-1] = unresolved_variables[arg] diff --git a/src/ytdl_sub/subscriptions/base_subscription.py b/src/ytdl_sub/subscriptions/base_subscription.py index 656e98ed..553eb74d 100644 --- a/src/ytdl_sub/subscriptions/base_subscription.py +++ b/src/ytdl_sub/subscriptions/base_subscription.py @@ -8,6 +8,7 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins 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.config.validators.variable_validation import ResolutionLevel from ytdl_sub.config.validators.variable_validation import VariableValidation from ytdl_sub.downloaders.url.validators import MultiUrlValidator from ytdl_sub.entries.variables.override_variables import SubscriptionVariables @@ -79,7 +80,7 @@ class BaseSubscription(ABC): ) # Validate after adding the subscription name - self._validated_dict = VariableValidation( + _ = VariableValidation( overrides=self.overrides, downloader_options=self.downloader_options, output_options=self.output_options, @@ -254,12 +255,22 @@ class BaseSubscription(ABC): ------- Subscription in yaml format """ - return self._preset_options.yaml + return self._preset_options.yaml(subscription_only=False) - def resolved_yaml(self) -> str: + def resolved_yaml(self, resolution_level: int = ResolutionLevel.RESOLVE) -> str: """ Returns ------- Human-readable, condensed YAML definition of the subscription. """ - return dump_yaml(self._validated_dict) + if resolution_level == ResolutionLevel.ORIGINAL: + return self._preset_options.yaml(subscription_only=True) + + out = VariableValidation( + overrides=self.overrides, + downloader_options=self.downloader_options, + output_options=self.output_options, + plugins=self.plugins, + resolution_level=resolution_level, + ).ensure_proper_usage(partial_resolve_formatters=True) + return dump_yaml(out) diff --git a/src/ytdl_sub/validators/string_formatter_validators.py b/src/ytdl_sub/validators/string_formatter_validators.py index 66a9815a..b9fdd3ea 100644 --- a/src/ytdl_sub/validators/string_formatter_validators.py +++ b/src/ytdl_sub/validators/string_formatter_validators.py @@ -5,14 +5,12 @@ from typing import Set from typing import Union from typing import final -from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.script.parser import parse from ytdl_sub.script.script import Script from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.utils.exceptions import RuntimeException from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved from ytdl_sub.script.utils.exceptions import UserException -from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException from ytdl_sub.utils.script import ScriptUtils from ytdl_sub.validators.validators import DictValidator @@ -244,15 +242,15 @@ class UnstructuredOverridesDictFormatterValidator(UnstructuredDictFormatterValid def _validate_formatter( mock_script: Script, unresolved_variables: Set[str], + unresolved_runtime_variables: Set[str], formatter_validator: Union[StringFormatterValidator, OverridesStringFormatterValidator], -) -> str: + partial_resolve_entry_formatters: bool, +) -> Any: parsed = formatter_validator.parsed if resolved := parsed.maybe_resolvable: - return resolved.native + return formatter_validator.post_process(resolved.native) is_static_formatter = isinstance(formatter_validator, OverridesStringFormatterValidator) - if is_static_formatter: - unresolved_variables = unresolved_variables.union({VARIABLES.entry_metadata.variable_name}) variable_names = {var.name for var in parsed.variables} custom_function_names = {f"%{func.name}" for func in parsed.custom_functions} @@ -272,20 +270,32 @@ def _validate_formatter( "contains the following custom functions that do not exist: " f"{', '.join(sorted(custom_function_names - mock_script.function_names))}" ) - if unresolved := variable_names.intersection(unresolved_variables): + if unresolved := variable_names.intersection(unresolved_runtime_variables): raise StringFormattingVariableNotFoundException( "contains the following variables that are unresolved when executing this " f"formatter: {', '.join(sorted(unresolved))}" ) + + if partial_resolve_entry_formatters and not is_static_formatter: + parsed = mock_script.resolve_partial_once( + variable_definitions={"tmp_var": formatter_validator.parsed}, + unresolvable=unresolved_variables, + )["tmp_var"] + try: if is_static_formatter: - return mock_script.resolve_once_parsed( - {"tmp_var": formatter_validator.parsed}, - unresolvable=unresolved_variables, - update=True, - )["tmp_var"].native + return formatter_validator.post_process( + mock_script.resolve_once_parsed( + {"tmp_var": formatter_validator.parsed}, + unresolvable=unresolved_variables, + update=True, + )["tmp_var"].native + ) - return formatter_validator.format_string + if maybe_resolved := parsed.maybe_resolvable: + return formatter_validator.post_process(maybe_resolved) + + return ScriptUtils.to_native_script(parsed) except RuntimeException as exc: if isinstance(exc, ScriptVariableNotResolved) and is_static_formatter: raise StringFormattingVariableNotFoundException( @@ -293,18 +303,14 @@ def _validate_formatter( "entry variables" ) from exc raise StringFormattingVariableNotFoundException(exc) from exc - except UserThrownRuntimeError as exc: - # Errors are expected for non-static formatters due to missing entry - # data. Raise otherwise. - if not is_static_formatter: - return formatter_validator.format_string - raise exc def validate_formatters( script: Script, unresolved_variables: Set[str], + unresolved_runtime_variables: Set[str], validator: Validator, + partial_resolve_formatters: bool, ) -> Dict: """ Ensure all OverridesStringFormatterValidator's only contain variables from the overrides @@ -320,7 +326,9 @@ def validate_formatters( resolved_dict[validator.leaf_name] |= validate_formatters( script=script, unresolved_variables=unresolved_variables, + unresolved_runtime_variables=unresolved_runtime_variables, validator=validator_value, + partial_resolve_formatters=partial_resolve_formatters, ) elif isinstance(validator, ListValidator): resolved_dict[validator.leaf_name] = [] @@ -328,7 +336,9 @@ def validate_formatters( list_output = validate_formatters( script=script, unresolved_variables=unresolved_variables, + unresolved_runtime_variables=unresolved_runtime_variables, validator=list_value, + partial_resolve_formatters=partial_resolve_formatters, ) assert len(list_output) == 1 resolved_dict[validator.leaf_name].append(list(list_output.values())[0]) @@ -336,7 +346,9 @@ def validate_formatters( resolved_dict[validator.leaf_name] = _validate_formatter( mock_script=script, unresolved_variables=unresolved_variables, + unresolved_runtime_variables=unresolved_runtime_variables, formatter_validator=validator, + partial_resolve_entry_formatters=partial_resolve_formatters, ) elif isinstance(validator, (DictFormatterValidator, OverridesDictFormatterValidator)): resolved_dict[validator.leaf_name] = {} @@ -344,7 +356,9 @@ def validate_formatters( resolved_dict[validator.leaf_name] |= _validate_formatter( mock_script=script, unresolved_variables=unresolved_variables, + unresolved_runtime_variables=unresolved_runtime_variables, formatter_validator=validator_value, + partial_resolve_entry_formatters=partial_resolve_formatters, ) else: resolved_dict[validator.leaf_name] = validator._value diff --git a/tests/integration/plugins/test_output_options.py b/tests/integration/plugins/test_output_options.py index 458416b4..b65692b5 100644 --- a/tests/integration/plugins/test_output_options.py +++ b/tests/integration/plugins/test_output_options.py @@ -236,24 +236,14 @@ class TestOutputOptions: ): output_options_subscription_dict["output_options"]["keep_files_date_eval"] = "nope" - subscription = Subscription.from_dict( - config=config, - preset_name=subscription_name, - preset_dict=output_options_subscription_dict, - ) - expected_error_msg = ( "Validation error in subscription_test.output_options.keep_files_date_eval: " "Expected a standardized date in the form of YYYY-MM-DD, but received 'nope'" ) - with ( - mock_download_collection_entries( - is_youtube_channel=False, - num_urls=1, - is_extracted_audio=False, - is_dry_run=True, - ), - pytest.raises(ValidationException, match=re.escape(expected_error_msg)), - ): - subscription.download(dry_run=True) + with pytest.raises(ValidationException, match=re.escape(expected_error_msg)): + _ = Subscription.from_dict( + config=config, + preset_name=subscription_name, + preset_dict=output_options_subscription_dict, + ) diff --git a/tests/resources/expected_json/music/inspect_full_overrides.json b/tests/resources/expected_json/music/inspect_full_overrides.json deleted file mode 100644 index b4fcae9c..00000000 --- a/tests/resources/expected_json/music/inspect_full_overrides.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "album_cover_path": "Lester Young/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/folder.{ thumbnail_ext }", - "album_dir": "[{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }] { %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "artist_dir": "Lester Young", - "avatar_uncropped_thumbnail_file_name": "", - "banner_uncropped_thumbnail_file_name": "", - "enable_resolution_assert": "{ %bool(True) }", - "enable_throttle_protection": "{ %bool(True) }", - "include_sibling_metadata": "{ %bool(True) }", - "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "music_directory": "tv_show_directory_path", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "subscription_array": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", - "subscription_indent_1": "Jazz", - "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "track_album": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "track_album_artist": "Lester Young", - "track_artist": "Lester Young", - "track_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", - "track_file_name": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) } - { %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }.{ ext }", - "track_full_path": "{ artist_dir }/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/{ %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) }", - "track_genre": "Jazz", - "track_genre_default": "Unset", - "track_number": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", - "track_number_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", - "track_original_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", - "track_title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", - "track_total": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", - "track_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", - "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "url10": "", - "url100": "", - "url11": "", - "url12": "", - "url13": "", - "url14": "", - "url15": "", - "url16": "", - "url17": "", - "url18": "", - "url19": "", - "url2": "", - "url20": "", - "url21": "", - "url22": "", - "url23": "", - "url24": "", - "url25": "", - "url26": "", - "url27": "", - "url28": "", - "url29": "", - "url3": "", - "url30": "", - "url31": "", - "url32": "", - "url33": "", - "url34": "", - "url35": "", - "url36": "", - "url37": "", - "url38": "", - "url39": "", - "url4": "", - "url40": "", - "url41": "", - "url42": "", - "url43": "", - "url44": "", - "url45": "", - "url46": "", - "url47": "", - "url48": "", - "url49": "", - "url5": "", - "url50": "", - "url51": "", - "url52": "", - "url53": "", - "url54": "", - "url55": "", - "url56": "", - "url57": "", - "url58": "", - "url59": "", - "url6": "", - "url60": "", - "url61": "", - "url62": "", - "url63": "", - "url64": "", - "url65": "", - "url66": "", - "url67": "", - "url68": "", - "url69": "", - "url7": "", - "url70": "", - "url71": "", - "url72": "", - "url73": "", - "url74": "", - "url75": "", - "url76": "", - "url77": "", - "url78": "", - "url79": "", - "url8": "", - "url80": "", - "url81": "", - "url82": "", - "url83": "", - "url84": "", - "url85": "", - "url86": "", - "url87": "", - "url88": "", - "url89": "", - "url9": "", - "url90": "", - "url91": "", - "url92": "", - "url93": "", - "url94": "", - "url95": "", - "url96": "", - "url97": "", - "url98": "", - "url99": "", - "urls": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }" -} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_full_variables.json b/tests/resources/expected_json/music/inspect_full_variables.json deleted file mode 100644 index 25c2274a..00000000 --- a/tests/resources/expected_json/music/inspect_full_variables.json +++ /dev/null @@ -1,482 +0,0 @@ -{ - "album_cover_path": "Lester Young/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/folder.{ thumbnail_ext }", - "album_cover_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ), \"/folder.\", thumbnail_ext ) ) }", - "album_dir": "[{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }] { %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "album_dir_sanitized": "{ %sanitize( %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", - "artist_dir": "Lester Young", - "artist_dir_sanitized": "Lester Young", - "avatar_uncropped_thumbnail_file_name": "", - "avatar_uncropped_thumbnail_file_name_sanitized": "", - "banner_uncropped_thumbnail_file_name": "", - "banner_uncropped_thumbnail_file_name_sanitized": "", - "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "channel_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "channel_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "chapters": "{ [ ] }", - "chapters_sanitized": "[]", - "comments": "{ [ ] }", - "comments_sanitized": "[]", - "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "creator_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", - "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "description_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", - "download_index": "{ %int(1) }", - "download_index_padded6": "000001", - "download_index_padded6_sanitized": "000001", - "download_index_sanitized": "1", - "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", - "duration_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"duration\", 0 ) ) }", - "enable_resolution_assert": "{ %bool(True) }", - "enable_resolution_assert_sanitized": "true", - "enable_throttle_protection": "{ %bool(True) }", - "enable_throttle_protection_sanitized": "true", - "entry_metadata": "{ { } }", - "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", - "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", - "epoch_date": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) }", - "epoch_date_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", - "epoch_hour": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) }", - "epoch_hour_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) ) }", - "epoch_sanitized": "{ %sanitize( %map_get( entry_metadata, \"epoch\" ) ) }", - "ext": "{ %throw( \"Plugin variable ext has not been created yet\" ) }", - "ext_sanitized": "{ %sanitize( ext ) }", - "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", - "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", - "extractor_key_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor_key\" ) ) }", - "extractor_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor\" ) ) }", - "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "height_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) }", - "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) }", - "ie_key_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) ) }", - "include_sibling_metadata": "{ %bool(True) }", - "include_sibling_metadata_sanitized": "true", - "info_json_ext": "info.json", - "info_json_ext_sanitized": "info.json", - "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "modified_webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", - "music_directory": "tv_show_directory_path", - "music_directory_sanitized": "tv_show_directory_path", - "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", - "playlist_count_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) ) }", - "playlist_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", - "playlist_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", - "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", - "playlist_index_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", - "playlist_index_padded6": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) }", - "playlist_index_padded6_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) ) }", - "playlist_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ) }", - "playlist_index_reversed": "{ %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) }", - "playlist_index_reversed_padded": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) }", - "playlist_index_reversed_padded6": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) }", - "playlist_index_reversed_padded6_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) ) }", - "playlist_index_reversed_padded_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) ) }", - "playlist_index_reversed_sanitized": "{ %sanitize( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) ) }", - "playlist_index_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) ) }", - "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", - "playlist_max_upload_date_sanitized": "{ %sanitize( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ) }", - "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", - "playlist_max_upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ) }", - "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) }", - "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) ) }", - "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", - "playlist_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) ) }", - "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "playlist_title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "playlist_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) }", - "playlist_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "playlist_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "playlist_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "playlist_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "playlist_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", - "playlist_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "playlist_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", - "playlist_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", - "release_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ) }", - "release_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) }", - "release_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) ) }", - "release_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) }", - "release_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) }", - "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) }", - "release_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) ) }", - "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) }", - "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) }", - "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", - "release_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) ) }", - "release_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) ) }", - "release_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) }", - "release_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) ) }", - "release_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) }", - "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) }", - "release_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) ) }", - "release_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) ) }", - "release_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) ) }", - "release_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) }", - "release_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) }", - "release_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) ) }", - "release_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) }", - "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) }", - "release_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) ) }", - "release_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) ) }", - "release_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) ) }", - "release_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) }", - "release_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) ) }", - "release_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) }", - "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) }", - "release_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) ) }", - "release_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) ) }", - "requested_subtitles": "{ { } }", - "requested_subtitles_sanitized": "{}", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_height_gte_sanitized": "361", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_ignore_titles_sanitized": "[]", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", - "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_assert_print_sanitized": "true", - "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", - "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "resolution_readable_sanitized": "{ %sanitize( %concat( %map_get_non_empty( entry_metadata, \"width\", 0 ), \"x\", %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) }", - "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", - "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", - "source_count": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) }", - "source_count_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) ) }", - "source_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", - "source_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) ) }", - "source_index": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) }", - "source_index_padded": "{ %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) }", - "source_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) ) }", - "source_index_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) ) }", - "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", - "source_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) ) }", - "source_title": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "source_title_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "source_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "source_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "source_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "source_uploader_id": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "source_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "source_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", - "source_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", - "source_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) ) }", - "source_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "source_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", - "sponsorblock_chapters": "{ [ ] }", - "sponsorblock_chapters_sanitized": "[]", - "subscription_array": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", - "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists\uff02]", - "subscription_has_download_archive": "{ %bool(False) }", - "subscription_has_download_archive_sanitized": "false", - "subscription_indent_1": "Jazz", - "subscription_indent_1_sanitized": "Jazz", - "subscription_name": "Lester Young", - "subscription_name_sanitized": "Lester Young", - "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", - "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", - "thumbnail_ext": "jpg", - "thumbnail_ext_sanitized": "jpg", - "title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", - "title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "title_sanitized_plex": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "title_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "track_album": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "track_album_artist": "Lester Young", - "track_album_artist_sanitized": "Lester Young", - "track_album_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "track_artist": "Lester Young", - "track_artist_sanitized": "Lester Young", - "track_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", - "track_date_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", - "track_file_name": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) } - { %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }.{ ext }", - "track_file_name_sanitized": "{ %sanitize( %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) ) }", - "track_full_path": "{ artist_dir }/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/{ %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) }", - "track_full_path_sanitized": "{ %sanitize( %concat( artist_dir, \"/\", %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ), \"/\", %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) ) ) }", - "track_genre": "Jazz", - "track_genre_default": "Unset", - "track_genre_default_sanitized": "Unset", - "track_genre_sanitized": "Jazz", - "track_number": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", - "track_number_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", - "track_number_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ) }", - "track_number_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) ) }", - "track_original_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", - "track_original_date_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", - "track_title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", - "track_title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "track_total": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", - "track_total_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) ) }", - "track_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", - "track_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ) }", - "uid": "{ %map_get( entry_metadata, \"id\" ) }", - "uid_sanitized": "{ %sanitize( %map_get( entry_metadata, \"id\" ) ) }", - "uid_sanitized_plex": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", - "uid_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", - "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", - "upload_date_index": "{ %int(1) }", - "upload_date_index_padded": "01", - "upload_date_index_padded_sanitized": "01", - "upload_date_index_reversed": "{ %int(99) }", - "upload_date_index_reversed_padded": "99", - "upload_date_index_reversed_padded_sanitized": "99", - "upload_date_index_reversed_sanitized": "99", - "upload_date_index_sanitized": "1", - "upload_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", - "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", - "upload_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", - "upload_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) }", - "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) }", - "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) }", - "upload_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) ) }", - "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) }", - "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) }", - "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", - "upload_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) ) }", - "upload_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) ) }", - "upload_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) }", - "upload_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) ) }", - "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) }", - "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) }", - "upload_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) ) }", - "upload_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) ) }", - "upload_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) ) }", - "upload_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }", - "upload_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) }", - "upload_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) ) }", - "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) }", - "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) }", - "upload_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) ) }", - "upload_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) ) }", - "upload_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) ) }", - "upload_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", - "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) }", - "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) }", - "upload_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) ) }", - "upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) ) }", - "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) }", - "uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "uploader_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", - "uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "url10": "", - "url100": "", - "url100_sanitized": "", - "url10_sanitized": "", - "url11": "", - "url11_sanitized": "", - "url12": "", - "url12_sanitized": "", - "url13": "", - "url13_sanitized": "", - "url14": "", - "url14_sanitized": "", - "url15": "", - "url15_sanitized": "", - "url16": "", - "url16_sanitized": "", - "url17": "", - "url17_sanitized": "", - "url18": "", - "url18_sanitized": "", - "url19": "", - "url19_sanitized": "", - "url2": "", - "url20": "", - "url20_sanitized": "", - "url21": "", - "url21_sanitized": "", - "url22": "", - "url22_sanitized": "", - "url23": "", - "url23_sanitized": "", - "url24": "", - "url24_sanitized": "", - "url25": "", - "url25_sanitized": "", - "url26": "", - "url26_sanitized": "", - "url27": "", - "url27_sanitized": "", - "url28": "", - "url28_sanitized": "", - "url29": "", - "url29_sanitized": "", - "url2_sanitized": "", - "url3": "", - "url30": "", - "url30_sanitized": "", - "url31": "", - "url31_sanitized": "", - "url32": "", - "url32_sanitized": "", - "url33": "", - "url33_sanitized": "", - "url34": "", - "url34_sanitized": "", - "url35": "", - "url35_sanitized": "", - "url36": "", - "url36_sanitized": "", - "url37": "", - "url37_sanitized": "", - "url38": "", - "url38_sanitized": "", - "url39": "", - "url39_sanitized": "", - "url3_sanitized": "", - "url4": "", - "url40": "", - "url40_sanitized": "", - "url41": "", - "url41_sanitized": "", - "url42": "", - "url42_sanitized": "", - "url43": "", - "url43_sanitized": "", - "url44": "", - "url44_sanitized": "", - "url45": "", - "url45_sanitized": "", - "url46": "", - "url46_sanitized": "", - "url47": "", - "url47_sanitized": "", - "url48": "", - "url48_sanitized": "", - "url49": "", - "url49_sanitized": "", - "url4_sanitized": "", - "url5": "", - "url50": "", - "url50_sanitized": "", - "url51": "", - "url51_sanitized": "", - "url52": "", - "url52_sanitized": "", - "url53": "", - "url53_sanitized": "", - "url54": "", - "url54_sanitized": "", - "url55": "", - "url55_sanitized": "", - "url56": "", - "url56_sanitized": "", - "url57": "", - "url57_sanitized": "", - "url58": "", - "url58_sanitized": "", - "url59": "", - "url59_sanitized": "", - "url5_sanitized": "", - "url6": "", - "url60": "", - "url60_sanitized": "", - "url61": "", - "url61_sanitized": "", - "url62": "", - "url62_sanitized": "", - "url63": "", - "url63_sanitized": "", - "url64": "", - "url64_sanitized": "", - "url65": "", - "url65_sanitized": "", - "url66": "", - "url66_sanitized": "", - "url67": "", - "url67_sanitized": "", - "url68": "", - "url68_sanitized": "", - "url69": "", - "url69_sanitized": "", - "url6_sanitized": "", - "url7": "", - "url70": "", - "url70_sanitized": "", - "url71": "", - "url71_sanitized": "", - "url72": "", - "url72_sanitized": "", - "url73": "", - "url73_sanitized": "", - "url74": "", - "url74_sanitized": "", - "url75": "", - "url75_sanitized": "", - "url76": "", - "url76_sanitized": "", - "url77": "", - "url77_sanitized": "", - "url78": "", - "url78_sanitized": "", - "url79": "", - "url79_sanitized": "", - "url7_sanitized": "", - "url8": "", - "url80": "", - "url80_sanitized": "", - "url81": "", - "url81_sanitized": "", - "url82": "", - "url82_sanitized": "", - "url83": "", - "url83_sanitized": "", - "url84": "", - "url84_sanitized": "", - "url85": "", - "url85_sanitized": "", - "url86": "", - "url86_sanitized": "", - "url87": "", - "url87_sanitized": "", - "url88": "", - "url88_sanitized": "", - "url89": "", - "url89_sanitized": "", - "url8_sanitized": "", - "url9": "", - "url90": "", - "url90_sanitized": "", - "url91": "", - "url91_sanitized": "", - "url92": "", - "url92_sanitized": "", - "url93": "", - "url93_sanitized": "", - "url94": "", - "url94_sanitized": "", - "url95": "", - "url95_sanitized": "", - "url96": "", - "url96_sanitized": "", - "url97": "", - "url97_sanitized": "", - "url98": "", - "url98_sanitized": "", - "url99": "", - "url99_sanitized": "", - "url9_sanitized": "", - "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", - "urls": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", - "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists\uff02]", - "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", - "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", - "width_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"width\", 0 ) ) }", - "ytdl_sub_entry_date_eval": "{ %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", - "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) ) }", - "ytdl_sub_input_url": "", - "ytdl_sub_input_url_count": "{ %int(0) }", - "ytdl_sub_input_url_count_sanitized": "0", - "ytdl_sub_input_url_index": "{ %int(-1) }", - "ytdl_sub_input_url_index_sanitized": "-1", - "ytdl_sub_input_url_sanitized": "" -} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_overrides.json b/tests/resources/expected_json/music/inspect_overrides.json deleted file mode 100644 index 69ef99c4..00000000 --- a/tests/resources/expected_json/music/inspect_overrides.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "album_cover_path": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/folder.{ thumbnail_ext }", - "album_dir": "[{ playlist_max_upload_year }] { %sanitize( playlist_title ) }", - "artist_dir": "Lester Young", - "avatar_uncropped_thumbnail_file_name": "", - "banner_uncropped_thumbnail_file_name": "", - "enable_resolution_assert": "{ %bool(True) }", - "enable_throttle_protection": "{ %bool(True) }", - "include_sibling_metadata": "{ %bool(True) }", - "modified_webpage_url": "{ webpage_url }", - "music_directory": "tv_show_directory_path", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_readable": "{ width }x{ height }", - "subscription_array": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", - "subscription_indent_1": "Jazz", - "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "track_album": "{ playlist_title }", - "track_album_artist": "Lester Young", - "track_artist": "Lester Young", - "track_date": "{ upload_date_standardized }", - "track_file_name": "{ playlist_index_padded } - { %sanitize( title ) }.{ ext }", - "track_full_path": "{ artist_dir }/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/{ %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) }", - "track_genre": "Jazz", - "track_genre_default": "Unset", - "track_number": "{ playlist_index }", - "track_number_padded": "{ playlist_index_padded }", - "track_original_date": "{ upload_date_standardized }", - "track_title": "{ title }", - "track_total": "{ playlist_count }", - "track_year": "{ playlist_max_upload_year }", - "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "url10": "", - "url100": "", - "url11": "", - "url12": "", - "url13": "", - "url14": "", - "url15": "", - "url16": "", - "url17": "", - "url18": "", - "url19": "", - "url2": "", - "url20": "", - "url21": "", - "url22": "", - "url23": "", - "url24": "", - "url25": "", - "url26": "", - "url27": "", - "url28": "", - "url29": "", - "url3": "", - "url30": "", - "url31": "", - "url32": "", - "url33": "", - "url34": "", - "url35": "", - "url36": "", - "url37": "", - "url38": "", - "url39": "", - "url4": "", - "url40": "", - "url41": "", - "url42": "", - "url43": "", - "url44": "", - "url45": "", - "url46": "", - "url47": "", - "url48": "", - "url49": "", - "url5": "", - "url50": "", - "url51": "", - "url52": "", - "url53": "", - "url54": "", - "url55": "", - "url56": "", - "url57": "", - "url58": "", - "url59": "", - "url6": "", - "url60": "", - "url61": "", - "url62": "", - "url63": "", - "url64": "", - "url65": "", - "url66": "", - "url67": "", - "url68": "", - "url69": "", - "url7": "", - "url70": "", - "url71": "", - "url72": "", - "url73": "", - "url74": "", - "url75": "", - "url76": "", - "url77": "", - "url78": "", - "url79": "", - "url8": "", - "url80": "", - "url81": "", - "url82": "", - "url83": "", - "url84": "", - "url85": "", - "url86": "", - "url87": "", - "url88": "", - "url89": "", - "url9": "", - "url90": "", - "url91": "", - "url92": "", - "url93": "", - "url94": "", - "url95": "", - "url96": "", - "url97": "", - "url98": "", - "url99": "", - "urls": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }" -} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_sub_fill.json b/tests/resources/expected_json/music/inspect_sub_fill.json new file mode 100644 index 00000000..b66bfe9b --- /dev/null +++ b/tests/resources/expected_json/music/inspect_sub_fill.json @@ -0,0 +1,242 @@ +{ + "audio_extract": { + "codec": "best", + "enable": true + }, + "download": [ + { + "download_reverse": true, + "include_sibling_metadata": true, + "playlist_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "url": [ + "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists" + ], + "variables": {}, + "webpage_url": "{ modified_webpage_url }", + "ytdl_options": {} + } + ], + "format": "ba[ext=webm]/ba", + "music_tags": { + "album": [ + "{ track_album }" + ], + "albumartist": [ + "Lester Young" + ], + "albumartists": [ + "Lester Young" + ], + "artist": [ + "Lester Young" + ], + "artists": [ + "Lester Young" + ], + "date": [ + "{ track_date }" + ], + "genres": [ + "Jazz" + ], + "original_date": [ + "{ track_original_date }" + ], + "title": [ + "{ track_title }" + ], + "track": [ + "{ track_number }" + ], + "tracktotal": [ + "{ track_total }" + ], + "year": [ + "{ track_year }" + ] + }, + "output_options": { + "download_archive_name": ".ytdl-sub-Lester Young-download-archive.json", + "file_name": "{ track_full_path }", + "keep_files_date_eval": "{ upload_date_standardized }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpzald2h7x", + "preserve_mtime": false, + "thumbnail_name": "{ album_cover_path }" + }, + "overrides": { + "album_cover_path": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/folder.{ thumbnail_ext }", + "album_dir": "[{ playlist_max_upload_year }] { %sanitize( playlist_title ) }", + "artist_dir": "Lester Young", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "include_sibling_metadata": true, + "modified_webpage_url": "{ webpage_url }", + "music_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpzald2h7x", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "subscription_array": [ + "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists" + ], + "subscription_indent_1": "Jazz", + "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "track_album": "{ playlist_title }", + "track_album_artist": "Lester Young", + "track_artist": "Lester Young", + "track_date": "{ upload_date_standardized }", + "track_file_name": "{ playlist_index_padded } - { %sanitize( title ) }.{ ext }", + "track_full_path": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/{ %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) }", + "track_genre": "Jazz", + "track_genre_default": "Unset", + "track_number": "{ playlist_index }", + "track_number_padded": "{ playlist_index_padded }", + "track_original_date": "{ upload_date_standardized }", + "track_title": "{ title }", + "track_total": "{ playlist_count }", + "track_year": "{ playlist_max_upload_year }", + "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_sub_internal.json b/tests/resources/expected_json/music/inspect_sub_internal.json new file mode 100644 index 00000000..2409b16d --- /dev/null +++ b/tests/resources/expected_json/music/inspect_sub_internal.json @@ -0,0 +1,242 @@ +{ + "audio_extract": { + "codec": "best", + "enable": true + }, + "download": [ + { + "download_reverse": true, + "include_sibling_metadata": true, + "playlist_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "url": [ + "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists" + ], + "variables": {}, + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "ytdl_options": {} + } + ], + "format": "ba[ext=webm]/ba", + "music_tags": { + "album": [ + "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }" + ], + "albumartist": [ + "Lester Young" + ], + "albumartists": [ + "Lester Young" + ], + "artist": [ + "Lester Young" + ], + "artists": [ + "Lester Young" + ], + "date": [ + "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }" + ], + "genres": [ + "Jazz" + ], + "original_date": [ + "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }" + ], + "title": [ + "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }" + ], + "track": [ + "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }" + ], + "tracktotal": [ + "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }" + ], + "year": [ + "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }" + ] + }, + "output_options": { + "download_archive_name": ".ytdl-sub-Lester Young-download-archive.json", + "file_name": "Lester Young/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/{ %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) }", + "keep_files_date_eval": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpdu1vad67", + "preserve_mtime": false, + "thumbnail_name": "Lester Young/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/folder.jpg" + }, + "overrides": { + "album_cover_path": "Lester Young/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/folder.jpg", + "album_dir": "[{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }] { %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "artist_dir": "Lester Young", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "include_sibling_metadata": true, + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "music_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpdu1vad67", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "subscription_array": [ + "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists" + ], + "subscription_indent_1": "Jazz", + "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "track_album": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "track_album_artist": "Lester Young", + "track_artist": "Lester Young", + "track_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "track_file_name": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) } - { %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }.{ ext }", + "track_full_path": "Lester Young/{ %concat( \"[\", %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ), \"] \", %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }/{ %concat( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ), \" - \", %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ), \".\", ext ) }", + "track_genre": "Jazz", + "track_genre_default": "Unset", + "track_number": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", + "track_number_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", + "track_original_date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "track_title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "track_total": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", + "track_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", + "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_sub_original.json b/tests/resources/expected_json/music/inspect_sub_original.json new file mode 100644 index 00000000..988e58a9 --- /dev/null +++ b/tests/resources/expected_json/music/inspect_sub_original.json @@ -0,0 +1,227 @@ +{ + "audio_extract": { + "codec": "best" + }, + "download": [ + { + "include_sibling_metadata": "{include_sibling_metadata}", + "playlist_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped" + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped" + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped" + } + ], + "url": "{ %array_at(urls, 0) }", + "webpage_url": "{modified_webpage_url}" + }, + { + "include_sibling_metadata": "{include_sibling_metadata}", + "url": "{ %array_slice(urls, 1) }", + "webpage_url": "{modified_webpage_url}" + } + ], + "format": "ba[ext=webm]/ba", + "music_tags": { + "album": "{track_album}", + "albumartist": "{track_album_artist}", + "albumartists": [ + "{track_album_artist}" + ], + "artist": "{track_artist}", + "artists": [ + "{track_artist}" + ], + "date": "{track_date}", + "genres": [ + "{track_genre}" + ], + "original_date": "{track_original_date}", + "title": "{track_title}", + "track": "{track_number}", + "tracktotal": "{track_total}", + "year": "{track_year}" + }, + "output_options": { + "file_name": "{track_full_path}", + "maintain_download_archive": true, + "output_directory": "{music_directory}", + "thumbnail_name": "{album_cover_path}" + }, + "overrides": { + "album_cover_path": "{artist_dir}/{album_dir}/folder.{thumbnail_ext}", + "album_dir": "[{track_year}] {track_album_sanitized}", + "artist_dir": "{track_artist_sanitized}", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "include_sibling_metadata": true, + "modified_webpage_url": "{webpage_url}", + "music_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpk6coazyn", + "resolution_assert": "{\n %if(\n %and(\n enable_resolution_assert,\n %ne( height, 0 ),\n %not(resolution_assert_is_ignored)\n ),\n %assert(\n %gte( height, resolution_assert_height_gte ),\n %concat(\n \"Entry \",\n title,\n \" downloaded at a low resolution (\",\n resolution_readable,\n \"), you've probably been throttled. \",\n \"Stopping further downloads, wait a few hours and try again. \",\n \"Disable using the override variable `enable_resolution_assert: False`.\"\n )\n ),\n \"false is no-op\"\n )\n}", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [] }", + "resolution_assert_is_ignored": "{\n %print_if_true(\n %concat(title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\"),\n %contains_any(title, resolution_assert_ignore_titles)\n )\n}", + "resolution_assert_print": "{\n %print(\n %if(\n enable_resolution_assert,\n \"Resolution assert is enabled, will fail on low-quality video downloads and presume throttle. Disable using the override variable `enable_resolution_assert: False`\",\n \"Resolution assert is disabled. Use at your own risk!\"\n ),\n enable_resolution_assert,\n -1\n )\n}", + "resolution_readable": "{width}x{height}", + "subscription_array": "{%from_json('''[\"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\"]''')}", + "subscription_indent_1": "Jazz", + "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "track_album": "{playlist_title}", + "track_album_artist": "{track_artist}", + "track_artist": "{subscription_name}", + "track_date": "{upload_date_standardized}", + "track_file_name": "{track_number_padded} - {track_title_sanitized}.{ext}", + "track_full_path": "{artist_dir}/{album_dir}/{track_file_name}", + "track_genre": "{subscription_indent_1}", + "track_genre_default": "Unset", + "track_number": "{playlist_index}", + "track_number_padded": "{playlist_index_padded}", + "track_original_date": "{track_date}", + "track_title": "{title}", + "track_total": "{playlist_count}", + "track_year": "{playlist_max_upload_year}", + "url": "{subscription_value}", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{subscription_array}" + }, + "preset": [ + "_music_base", + "_multi_url", + "_albums_from_playlists", + "_throttle_protection", + "YouTube Releases", + "__preset__" + ], + "throttle_protection": { + "enable": "{\n %print(\n %if(\n enable_throttle_protection,\n \"Throttle protection is enabled. Disable using the override variable `enable_throttle_protection: False`\",\n \"Throttle protection is disabled. Use at your own risk!\"\n ),\n enable_throttle_protection\n )\n}", + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "ytdl_options": { + "break_on_existing": true + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_sub_resolve.json b/tests/resources/expected_json/music/inspect_sub_resolve.json new file mode 100644 index 00000000..347a61ac --- /dev/null +++ b/tests/resources/expected_json/music/inspect_sub_resolve.json @@ -0,0 +1,242 @@ +{ + "audio_extract": { + "codec": "best", + "enable": true + }, + "download": [ + { + "download_reverse": true, + "include_sibling_metadata": true, + "playlist_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "url": [ + "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists" + ], + "variables": {}, + "webpage_url": "{ webpage_url }", + "ytdl_options": {} + } + ], + "format": "ba[ext=webm]/ba", + "music_tags": { + "album": [ + "{ playlist_title }" + ], + "albumartist": [ + "Lester Young" + ], + "albumartists": [ + "Lester Young" + ], + "artist": [ + "Lester Young" + ], + "artists": [ + "Lester Young" + ], + "date": [ + "{ upload_date_standardized }" + ], + "genres": [ + "Jazz" + ], + "original_date": [ + "{ upload_date_standardized }" + ], + "title": [ + "{ title }" + ], + "track": [ + "{ playlist_index }" + ], + "tracktotal": [ + "{ playlist_count }" + ], + "year": [ + "{ playlist_max_upload_year }" + ] + }, + "output_options": { + "download_archive_name": ".ytdl-sub-Lester Young-download-archive.json", + "file_name": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/{ %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) }", + "keep_files_date_eval": "{ upload_date_standardized }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpd5oeacb3", + "preserve_mtime": false, + "thumbnail_name": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/folder.{ thumbnail_ext }" + }, + "overrides": { + "album_cover_path": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/folder.{ thumbnail_ext }", + "album_dir": "[{ playlist_max_upload_year }] { %sanitize( playlist_title ) }", + "artist_dir": "Lester Young", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "include_sibling_metadata": true, + "modified_webpage_url": "{ webpage_url }", + "music_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpd5oeacb3", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "subscription_array": [ + "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists" + ], + "subscription_indent_1": "Jazz", + "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "track_album": "{ playlist_title }", + "track_album_artist": "Lester Young", + "track_artist": "Lester Young", + "track_date": "{ upload_date_standardized }", + "track_file_name": "{ playlist_index_padded } - { %sanitize( title ) }.{ ext }", + "track_full_path": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/{ %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) }", + "track_genre": "Jazz", + "track_genre_default": "Unset", + "track_number": "{ playlist_index }", + "track_number_padded": "{ playlist_index_padded }", + "track_original_date": "{ upload_date_standardized }", + "track_title": "{ title }", + "track_total": "{ playlist_count }", + "track_year": "{ playlist_max_upload_year }", + "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/music/inspect_variables.json b/tests/resources/expected_json/music/inspect_variables.json deleted file mode 100644 index 1fee2525..00000000 --- a/tests/resources/expected_json/music/inspect_variables.json +++ /dev/null @@ -1,482 +0,0 @@ -{ - "album_cover_path": "Lester Young/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/folder.{ thumbnail_ext }", - "album_cover_path_sanitized": "{ %sanitize( %concat( \"Lester Young\", \"/\", %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ), \"/folder.\", thumbnail_ext ) ) }", - "album_dir": "[{ playlist_max_upload_year }] { %sanitize( playlist_title ) }", - "album_dir_sanitized": "{ %sanitize( %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) ) }", - "artist_dir": "Lester Young", - "artist_dir_sanitized": "Lester Young", - "avatar_uncropped_thumbnail_file_name": "", - "avatar_uncropped_thumbnail_file_name_sanitized": "", - "banner_uncropped_thumbnail_file_name": "", - "banner_uncropped_thumbnail_file_name_sanitized": "", - "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", uploader ) }", - "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", uploader_id ) }", - "channel_id_sanitized": "{ %sanitize( channel_id ) }", - "channel_sanitized": "{ %sanitize( channel ) }", - "chapters": "{ [ ] }", - "chapters_sanitized": "{ %sanitize( chapters ) }", - "comments": "{ [ ] }", - "comments_sanitized": "{ %sanitize( comments ) }", - "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", channel ) }", - "creator_sanitized": "{ %sanitize( creator ) }", - "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "description_sanitized": "{ %sanitize( description ) }", - "download_index": "{ %int( 1 ) }", - "download_index_padded6": "{ %pad_zero( download_index, 6 ) }", - "download_index_padded6_sanitized": "{ %sanitize( download_index_padded6 ) }", - "download_index_sanitized": "{ %sanitize( download_index ) }", - "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", - "duration_sanitized": "{ %sanitize( duration ) }", - "enable_resolution_assert": "{ %bool(True) }", - "enable_resolution_assert_sanitized": "true", - "enable_throttle_protection": "{ %bool(True) }", - "enable_throttle_protection_sanitized": "true", - "entry_metadata": "{ { } }", - "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", - "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", - "epoch_date": "{ %datetime_strftime( epoch, \"%Y%m%d\" ) }", - "epoch_date_sanitized": "{ %sanitize( epoch_date ) }", - "epoch_hour": "{ %datetime_strftime( epoch, \"%H\" ) }", - "epoch_hour_sanitized": "{ %sanitize( epoch_hour ) }", - "epoch_sanitized": "{ %sanitize( epoch ) }", - "ext": "{ %throw( \"Plugin variable ext has not been created yet\" ) }", - "ext_sanitized": "{ %sanitize( ext ) }", - "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", - "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", - "extractor_key_sanitized": "{ %sanitize( extractor_key ) }", - "extractor_sanitized": "{ %sanitize( extractor ) }", - "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "height_sanitized": "{ %sanitize( height ) }", - "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", extractor_key ) }", - "ie_key_sanitized": "{ %sanitize( ie_key ) }", - "include_sibling_metadata": "{ %bool(True) }", - "include_sibling_metadata_sanitized": "true", - "info_json_ext": "info.json", - "info_json_ext_sanitized": "info.json", - "modified_webpage_url": "{ webpage_url }", - "modified_webpage_url_sanitized": "{ %sanitize( webpage_url ) }", - "music_directory": "tv_show_directory_path", - "music_directory_sanitized": "tv_show_directory_path", - "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", - "playlist_count_sanitized": "{ %sanitize( playlist_count ) }", - "playlist_description": "{ %map_get_non_empty( playlist_metadata, \"description\", description ) }", - "playlist_description_sanitized": "{ %sanitize( playlist_description ) }", - "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", - "playlist_index_padded": "{ %pad_zero( playlist_index, 2 ) }", - "playlist_index_padded6": "{ %pad_zero( playlist_index, 6 ) }", - "playlist_index_padded6_sanitized": "{ %sanitize( playlist_index_padded6 ) }", - "playlist_index_padded_sanitized": "{ %sanitize( playlist_index_padded ) }", - "playlist_index_reversed": "{ %sub( playlist_count, playlist_index, -1 ) }", - "playlist_index_reversed_padded": "{ %pad_zero( playlist_index_reversed, 2 ) }", - "playlist_index_reversed_padded6": "{ %pad_zero( playlist_index_reversed, 6 ) }", - "playlist_index_reversed_padded6_sanitized": "{ %sanitize( playlist_index_reversed_padded6 ) }", - "playlist_index_reversed_padded_sanitized": "{ %sanitize( playlist_index_reversed_padded ) }", - "playlist_index_reversed_sanitized": "{ %sanitize( playlist_index_reversed ) }", - "playlist_index_sanitized": "{ %sanitize( playlist_index ) }", - "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", - "playlist_max_upload_date_sanitized": "{ %sanitize( playlist_max_upload_date ) }", - "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year\" ) ) }", - "playlist_max_upload_year_sanitized": "{ %sanitize( playlist_max_upload_year ) }", - "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year_truncated\" ) ) }", - "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( playlist_max_upload_year_truncated ) }", - "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", - "playlist_metadata_sanitized": "{ %sanitize( playlist_metadata ) }", - "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", title ) }", - "playlist_title_sanitized": "{ %sanitize( playlist_title ) }", - "playlist_uid": "{ %map_get_non_empty( playlist_metadata, \"playlist_id\", uid ) }", - "playlist_uid_sanitized": "{ %sanitize( playlist_uid ) }", - "playlist_uploader": "{ %map_get_non_empty( playlist_metadata, \"uploader\", uploader ) }", - "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", uploader_id ) }", - "playlist_uploader_id_sanitized": "{ %sanitize( playlist_uploader_id ) }", - "playlist_uploader_sanitized": "{ %sanitize( playlist_uploader ) }", - "playlist_uploader_url": "{ %map_get_non_empty( playlist_metadata, \"uploader_url\", webpage_url ) }", - "playlist_uploader_url_sanitized": "{ %sanitize( playlist_uploader_url ) }", - "playlist_webpage_url": "{ %map_get_non_empty( playlist_metadata, \"webpage_url\", webpage_url ) }", - "playlist_webpage_url_sanitized": "{ %sanitize( playlist_webpage_url ) }", - "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", upload_date ) }", - "release_date_sanitized": "{ %sanitize( release_date ) }", - "release_date_standardized": "{ %string( %map_get( %to_date_metadata( release_date ), \"date_standardized\" ) ) }", - "release_date_standardized_sanitized": "{ %sanitize( release_date_standardized ) }", - "release_day": "{ %int( %map_get( %to_date_metadata( release_date ), \"day\" ) ) }", - "release_day_of_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year\" ) ) }", - "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_padded\" ) ) }", - "release_day_of_year_padded_sanitized": "{ %sanitize( release_day_of_year_padded ) }", - "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed\" ) ) }", - "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed_padded\" ) ) }", - "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( release_day_of_year_reversed_padded ) }", - "release_day_of_year_reversed_sanitized": "{ %sanitize( release_day_of_year_reversed ) }", - "release_day_of_year_sanitized": "{ %sanitize( release_day_of_year ) }", - "release_day_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_padded\" ) ) }", - "release_day_padded_sanitized": "{ %sanitize( release_day_padded ) }", - "release_day_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_reversed\" ) ) }", - "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_reversed_padded\" ) ) }", - "release_day_reversed_padded_sanitized": "{ %sanitize( release_day_reversed_padded ) }", - "release_day_reversed_sanitized": "{ %sanitize( release_day_reversed ) }", - "release_day_sanitized": "{ %sanitize( release_day ) }", - "release_month": "{ %int( %map_get( %to_date_metadata( release_date ), \"month\" ) ) }", - "release_month_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_padded\" ) ) }", - "release_month_padded_sanitized": "{ %sanitize( release_month_padded ) }", - "release_month_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"month_reversed\" ) ) }", - "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_reversed_padded\" ) ) }", - "release_month_reversed_padded_sanitized": "{ %sanitize( release_month_reversed_padded ) }", - "release_month_reversed_sanitized": "{ %sanitize( release_month_reversed ) }", - "release_month_sanitized": "{ %sanitize( release_month ) }", - "release_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"year\" ) ) }", - "release_year_sanitized": "{ %sanitize( release_year ) }", - "release_year_truncated": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated\" ) ) }", - "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated_reversed\" ) ) }", - "release_year_truncated_reversed_sanitized": "{ %sanitize( release_year_truncated_reversed ) }", - "release_year_truncated_sanitized": "{ %sanitize( release_year_truncated ) }", - "requested_subtitles": "{ { } }", - "requested_subtitles_sanitized": "{ %sanitize( requested_subtitles ) }", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_height_gte_sanitized": "361", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_ignore_titles_sanitized": "[]", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", - "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_assert_print_sanitized": "true", - "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", - "resolution_readable": "{ width }x{ height }", - "resolution_readable_sanitized": "{ %sanitize( %concat( width, \"x\", height ) ) }", - "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", - "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", - "source_count": "{ %map_get_non_empty( playlist_metadata, \"playlist_count\", 1 ) }", - "source_count_sanitized": "{ %sanitize( source_count ) }", - "source_description": "{ %map_get_non_empty( source_metadata, \"description\", playlist_description ) }", - "source_description_sanitized": "{ %sanitize( source_description ) }", - "source_index": "{ %map_get_non_empty( playlist_metadata, \"playlist_index\", 1 ) }", - "source_index_padded": "{ %pad_zero( source_index, 2 ) }", - "source_index_padded_sanitized": "{ %sanitize( source_index_padded ) }", - "source_index_sanitized": "{ %sanitize( source_index ) }", - "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", - "source_metadata_sanitized": "{ %sanitize( source_metadata ) }", - "source_title": "{ %map_get_non_empty( source_metadata, \"title\", playlist_title ) }", - "source_title_sanitized": "{ %sanitize( source_title ) }", - "source_uid": "{ %map_get_non_empty( source_metadata, \"id\", playlist_uid ) }", - "source_uid_sanitized": "{ %sanitize( source_uid ) }", - "source_uploader": "{ %map_get_non_empty( source_metadata, \"uploader\", playlist_uploader ) }", - "source_uploader_id": "{ %map_get_non_empty( source_metadata, \"uploader_id\", playlist_uploader_id ) }", - "source_uploader_id_sanitized": "{ %sanitize( source_uploader_id ) }", - "source_uploader_sanitized": "{ %sanitize( source_uploader ) }", - "source_uploader_url": "{ %map_get_non_empty( source_metadata, \"uploader_url\", source_webpage_url ) }", - "source_uploader_url_sanitized": "{ %sanitize( source_uploader_url ) }", - "source_webpage_url": "{ %map_get_non_empty( source_metadata, \"webpage_url\", playlist_webpage_url ) }", - "source_webpage_url_sanitized": "{ %sanitize( source_webpage_url ) }", - "sponsorblock_chapters": "{ [ ] }", - "sponsorblock_chapters_sanitized": "{ %sanitize( sponsorblock_chapters ) }", - "subscription_array": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", - "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists\uff02]", - "subscription_has_download_archive": "{ %bool(False) }", - "subscription_has_download_archive_sanitized": "false", - "subscription_indent_1": "Jazz", - "subscription_indent_1_sanitized": "Jazz", - "subscription_name": "Lester Young", - "subscription_name_sanitized": "Lester Young", - "subscription_value": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "subscription_value_1": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", - "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", - "thumbnail_ext": "jpg", - "thumbnail_ext_sanitized": "jpg", - "title": "{ %map_get_non_empty( entry_metadata, \"title\", uid ) }", - "title_sanitized": "{ %sanitize( title ) }", - "title_sanitized_plex": "{ %sanitize_plex_episode( title ) }", - "title_sanitized_plex_sanitized": "{ %sanitize( title_sanitized_plex ) }", - "track_album": "{ playlist_title }", - "track_album_artist": "Lester Young", - "track_album_artist_sanitized": "Lester Young", - "track_album_sanitized": "{ %sanitize( playlist_title ) }", - "track_artist": "Lester Young", - "track_artist_sanitized": "Lester Young", - "track_date": "{ upload_date_standardized }", - "track_date_sanitized": "{ %sanitize( upload_date_standardized ) }", - "track_file_name": "{ playlist_index_padded } - { %sanitize( title ) }.{ ext }", - "track_file_name_sanitized": "{ %sanitize( %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) ) }", - "track_full_path": "{ artist_dir }/{ %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ) }/{ %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) }", - "track_full_path_sanitized": "{ %sanitize( %concat( artist_dir, \"/\", %concat( \"[\", playlist_max_upload_year, \"] \", %sanitize( playlist_title ) ), \"/\", %concat( playlist_index_padded, \" - \", %sanitize( title ), \".\", ext ) ) ) }", - "track_genre": "Jazz", - "track_genre_default": "Unset", - "track_genre_default_sanitized": "Unset", - "track_genre_sanitized": "Jazz", - "track_number": "{ playlist_index }", - "track_number_padded": "{ playlist_index_padded }", - "track_number_padded_sanitized": "{ %sanitize( playlist_index_padded ) }", - "track_number_sanitized": "{ %sanitize( playlist_index ) }", - "track_original_date": "{ upload_date_standardized }", - "track_original_date_sanitized": "{ %sanitize( upload_date_standardized ) }", - "track_title": "{ title }", - "track_title_sanitized": "{ %sanitize( title ) }", - "track_total": "{ playlist_count }", - "track_total_sanitized": "{ %sanitize( playlist_count ) }", - "track_year": "{ playlist_max_upload_year }", - "track_year_sanitized": "{ %sanitize( playlist_max_upload_year ) }", - "uid": "{ %map_get( entry_metadata, \"id\" ) }", - "uid_sanitized": "{ %sanitize( uid ) }", - "uid_sanitized_plex": "{ %sanitize_plex_episode( uid ) }", - "uid_sanitized_plex_sanitized": "{ %sanitize( uid_sanitized_plex ) }", - "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", epoch_date ) }", - "upload_date_index": "{ %int( 1 ) }", - "upload_date_index_padded": "{ %pad_zero( upload_date_index, 2 ) }", - "upload_date_index_padded_sanitized": "{ %sanitize( upload_date_index_padded ) }", - "upload_date_index_reversed": "{ %sub( 100, upload_date_index ) }", - "upload_date_index_reversed_padded": "{ %pad_zero( upload_date_index_reversed, 2 ) }", - "upload_date_index_reversed_padded_sanitized": "{ %sanitize( upload_date_index_reversed_padded ) }", - "upload_date_index_reversed_sanitized": "{ %sanitize( upload_date_index_reversed ) }", - "upload_date_index_sanitized": "{ %sanitize( upload_date_index ) }", - "upload_date_sanitized": "{ %sanitize( upload_date ) }", - "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( upload_date ), \"date_standardized\" ) ) }", - "upload_date_standardized_sanitized": "{ %sanitize( upload_date_standardized ) }", - "upload_day": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day\" ) ) }", - "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year\" ) ) }", - "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_padded\" ) ) }", - "upload_day_of_year_padded_sanitized": "{ %sanitize( upload_day_of_year_padded ) }", - "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed\" ) ) }", - "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed_padded\" ) ) }", - "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( upload_day_of_year_reversed_padded ) }", - "upload_day_of_year_reversed_sanitized": "{ %sanitize( upload_day_of_year_reversed ) }", - "upload_day_of_year_sanitized": "{ %sanitize( upload_day_of_year ) }", - "upload_day_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_padded\" ) ) }", - "upload_day_padded_sanitized": "{ %sanitize( upload_day_padded ) }", - "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_reversed\" ) ) }", - "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_reversed_padded\" ) ) }", - "upload_day_reversed_padded_sanitized": "{ %sanitize( upload_day_reversed_padded ) }", - "upload_day_reversed_sanitized": "{ %sanitize( upload_day_reversed ) }", - "upload_day_sanitized": "{ %sanitize( upload_day ) }", - "upload_month": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month\" ) ) }", - "upload_month_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_padded\" ) ) }", - "upload_month_padded_sanitized": "{ %sanitize( upload_month_padded ) }", - "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month_reversed\" ) ) }", - "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_reversed_padded\" ) ) }", - "upload_month_reversed_padded_sanitized": "{ %sanitize( upload_month_reversed_padded ) }", - "upload_month_reversed_sanitized": "{ %sanitize( upload_month_reversed ) }", - "upload_month_sanitized": "{ %sanitize( upload_month ) }", - "upload_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year\" ) ) }", - "upload_year_sanitized": "{ %sanitize( upload_year ) }", - "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated\" ) ) }", - "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated_reversed\" ) ) }", - "upload_year_truncated_reversed_sanitized": "{ %sanitize( upload_year_truncated_reversed ) }", - "upload_year_truncated_sanitized": "{ %sanitize( upload_year_truncated ) }", - "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", uploader_id ) }", - "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", uid ) }", - "uploader_id_sanitized": "{ %sanitize( uploader_id ) }", - "uploader_sanitized": "{ %sanitize( uploader ) }", - "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", webpage_url ) }", - "uploader_url_sanitized": "{ %sanitize( uploader_url ) }", - "url": "https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists", - "url10": "", - "url100": "", - "url100_sanitized": "", - "url10_sanitized": "", - "url11": "", - "url11_sanitized": "", - "url12": "", - "url12_sanitized": "", - "url13": "", - "url13_sanitized": "", - "url14": "", - "url14_sanitized": "", - "url15": "", - "url15_sanitized": "", - "url16": "", - "url16_sanitized": "", - "url17": "", - "url17_sanitized": "", - "url18": "", - "url18_sanitized": "", - "url19": "", - "url19_sanitized": "", - "url2": "", - "url20": "", - "url20_sanitized": "", - "url21": "", - "url21_sanitized": "", - "url22": "", - "url22_sanitized": "", - "url23": "", - "url23_sanitized": "", - "url24": "", - "url24_sanitized": "", - "url25": "", - "url25_sanitized": "", - "url26": "", - "url26_sanitized": "", - "url27": "", - "url27_sanitized": "", - "url28": "", - "url28_sanitized": "", - "url29": "", - "url29_sanitized": "", - "url2_sanitized": "", - "url3": "", - "url30": "", - "url30_sanitized": "", - "url31": "", - "url31_sanitized": "", - "url32": "", - "url32_sanitized": "", - "url33": "", - "url33_sanitized": "", - "url34": "", - "url34_sanitized": "", - "url35": "", - "url35_sanitized": "", - "url36": "", - "url36_sanitized": "", - "url37": "", - "url37_sanitized": "", - "url38": "", - "url38_sanitized": "", - "url39": "", - "url39_sanitized": "", - "url3_sanitized": "", - "url4": "", - "url40": "", - "url40_sanitized": "", - "url41": "", - "url41_sanitized": "", - "url42": "", - "url42_sanitized": "", - "url43": "", - "url43_sanitized": "", - "url44": "", - "url44_sanitized": "", - "url45": "", - "url45_sanitized": "", - "url46": "", - "url46_sanitized": "", - "url47": "", - "url47_sanitized": "", - "url48": "", - "url48_sanitized": "", - "url49": "", - "url49_sanitized": "", - "url4_sanitized": "", - "url5": "", - "url50": "", - "url50_sanitized": "", - "url51": "", - "url51_sanitized": "", - "url52": "", - "url52_sanitized": "", - "url53": "", - "url53_sanitized": "", - "url54": "", - "url54_sanitized": "", - "url55": "", - "url55_sanitized": "", - "url56": "", - "url56_sanitized": "", - "url57": "", - "url57_sanitized": "", - "url58": "", - "url58_sanitized": "", - "url59": "", - "url59_sanitized": "", - "url5_sanitized": "", - "url6": "", - "url60": "", - "url60_sanitized": "", - "url61": "", - "url61_sanitized": "", - "url62": "", - "url62_sanitized": "", - "url63": "", - "url63_sanitized": "", - "url64": "", - "url64_sanitized": "", - "url65": "", - "url65_sanitized": "", - "url66": "", - "url66_sanitized": "", - "url67": "", - "url67_sanitized": "", - "url68": "", - "url68_sanitized": "", - "url69": "", - "url69_sanitized": "", - "url6_sanitized": "", - "url7": "", - "url70": "", - "url70_sanitized": "", - "url71": "", - "url71_sanitized": "", - "url72": "", - "url72_sanitized": "", - "url73": "", - "url73_sanitized": "", - "url74": "", - "url74_sanitized": "", - "url75": "", - "url75_sanitized": "", - "url76": "", - "url76_sanitized": "", - "url77": "", - "url77_sanitized": "", - "url78": "", - "url78_sanitized": "", - "url79": "", - "url79_sanitized": "", - "url7_sanitized": "", - "url8": "", - "url80": "", - "url80_sanitized": "", - "url81": "", - "url81_sanitized": "", - "url82": "", - "url82_sanitized": "", - "url83": "", - "url83_sanitized": "", - "url84": "", - "url84_sanitized": "", - "url85": "", - "url85_sanitized": "", - "url86": "", - "url86_sanitized": "", - "url87": "", - "url87_sanitized": "", - "url88": "", - "url88_sanitized": "", - "url89": "", - "url89_sanitized": "", - "url8_sanitized": "", - "url9": "", - "url90": "", - "url90_sanitized": "", - "url91": "", - "url91_sanitized": "", - "url92": "", - "url92_sanitized": "", - "url93": "", - "url93_sanitized": "", - "url94": "", - "url94_sanitized": "", - "url95": "", - "url95_sanitized": "", - "url96": "", - "url96_sanitized": "", - "url97": "", - "url97_sanitized": "", - "url98": "", - "url98_sanitized": "", - "url99": "", - "url99_sanitized": "", - "url9_sanitized": "", - "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists", - "urls": "{ [ \"https://www.youtube.com/channel/UCsItMF6_fP754ihIsSRLk5A/playlists\" ] }", - "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8channel\u29f8UCsItMF6_fP754ihIsSRLk5A\u29f8playlists\uff02]", - "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "webpage_url_sanitized": "{ %sanitize( webpage_url ) }", - "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", - "width_sanitized": "{ %sanitize( width ) }", - "ytdl_sub_entry_date_eval": "{ %string( upload_date_standardized ) }", - "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( ytdl_sub_entry_date_eval ) }", - "ytdl_sub_input_url": "{ %string( '' ) }", - "ytdl_sub_input_url_count": "{ %int( 0 ) }", - "ytdl_sub_input_url_count_sanitized": "{ %sanitize( ytdl_sub_input_url_count ) }", - "ytdl_sub_input_url_index": "{ %int( -1 ) }", - "ytdl_sub_input_url_index_sanitized": "{ %sanitize( ytdl_sub_input_url_index ) }", - "ytdl_sub_input_url_sanitized": "{ %sanitize( ytdl_sub_input_url ) }" -} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_full_overrides.json b/tests/resources/expected_json/music_video/inspect_full_overrides.json deleted file mode 100644 index 7325202e..00000000 --- a/tests/resources/expected_json/music_video/inspect_full_overrides.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "avatar_uncropped_thumbnail_file_name": "", - "banner_uncropped_thumbnail_file_name": "", - "category_url_array": "{ [ ] }", - "category_url_map": "{ [ ] }", - "enable_bilateral_scraping": "{ %bool(True) }", - "enable_resolution_assert": "{ %bool(True) }", - "enable_throttle_protection": "{ %bool(True) }", - "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", - "include_sibling_metadata": "{ %bool(False) }", - "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "music_video_album": "Music Videos", - "music_video_album_default": "Music Videos", - "music_video_artist": "Rick Astley", - "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", - "music_video_directory": "tv_show_directory_path", - "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "music_video_file_name_suffix": "", - "music_video_genre": "Pop", - "music_video_genre_default": "ytdl-sub", - "music_video_title": "{ %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "subscription_array": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\" ] }", - "subscription_indent_1": "Pop", - "subscription_map": "{ { } }", - "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "url10": "", - "url100": "", - "url11": "", - "url12": "", - "url13": "", - "url14": "", - "url15": "", - "url16": "", - "url17": "", - "url18": "", - "url19": "", - "url2": "", - "url20": "", - "url21": "", - "url22": "", - "url23": "", - "url24": "", - "url25": "", - "url26": "", - "url27": "", - "url28": "", - "url29": "", - "url3": "", - "url30": "", - "url31": "", - "url32": "", - "url33": "", - "url34": "", - "url35": "", - "url36": "", - "url37": "", - "url38": "", - "url39": "", - "url4": "", - "url40": "", - "url41": "", - "url42": "", - "url43": "", - "url44": "", - "url45": "", - "url46": "", - "url47": "", - "url48": "", - "url49": "", - "url5": "", - "url50": "", - "url51": "", - "url52": "", - "url53": "", - "url54": "", - "url55": "", - "url56": "", - "url57": "", - "url58": "", - "url59": "", - "url6": "", - "url60": "", - "url61": "", - "url62": "", - "url63": "", - "url64": "", - "url65": "", - "url66": "", - "url67": "", - "url68": "", - "url69": "", - "url7": "", - "url70": "", - "url71": "", - "url72": "", - "url73": "", - "url74": "", - "url75": "", - "url76": "", - "url77": "", - "url78": "", - "url79": "", - "url8": "", - "url80": "", - "url81": "", - "url82": "", - "url83": "", - "url84": "", - "url85": "", - "url86": "", - "url87": "", - "url88": "", - "url89": "", - "url9": "", - "url90": "", - "url91": "", - "url92": "", - "url93": "", - "url94": "", - "url95": "", - "url96": "", - "url97": "", - "url98": "", - "url99": "", - "urls": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] }" -} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_full_variables.json b/tests/resources/expected_json/music_video/inspect_full_variables.json deleted file mode 100644 index d1231e9d..00000000 --- a/tests/resources/expected_json/music_video/inspect_full_variables.json +++ /dev/null @@ -1,480 +0,0 @@ -{ - "avatar_uncropped_thumbnail_file_name": "", - "avatar_uncropped_thumbnail_file_name_sanitized": "", - "banner_uncropped_thumbnail_file_name": "", - "banner_uncropped_thumbnail_file_name_sanitized": "", - "category_url_array": "{ [ ] }", - "category_url_array_sanitized": "[]", - "category_url_map": "{ [ ] }", - "category_url_map_sanitized": "[]", - "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "channel_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "channel_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "chapters": "{ [ ] }", - "chapters_sanitized": "[]", - "comments": "{ [ ] }", - "comments_sanitized": "[]", - "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "creator_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", - "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "description_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", - "download_index": "{ %int(1) }", - "download_index_padded6": "000001", - "download_index_padded6_sanitized": "000001", - "download_index_sanitized": "1", - "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", - "duration_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"duration\", 0 ) ) }", - "enable_bilateral_scraping": "{ %bool(True) }", - "enable_bilateral_scraping_sanitized": "true", - "enable_resolution_assert": "{ %bool(True) }", - "enable_resolution_assert_sanitized": "true", - "enable_throttle_protection": "{ %bool(True) }", - "enable_throttle_protection_sanitized": "true", - "entry_metadata": "{ { } }", - "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", - "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", - "epoch_date": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) }", - "epoch_date_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", - "epoch_hour": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) }", - "epoch_hour_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) ) }", - "epoch_sanitized": "{ %sanitize( %map_get( entry_metadata, \"epoch\" ) ) }", - "ext": "{ %map_get( entry_metadata, \"ext\" ) }", - "ext_sanitized": "{ %sanitize( %map_get( entry_metadata, \"ext\" ) ) }", - "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", - "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", - "extractor_key_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor_key\" ) ) }", - "extractor_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor\" ) ) }", - "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "file_title_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", - "file_uid_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", - "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "height_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) }", - "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) }", - "ie_key_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) ) }", - "include_sibling_metadata": "{ %bool(False) }", - "include_sibling_metadata_sanitized": "false", - "info_json_ext": "info.json", - "info_json_ext_sanitized": "info.json", - "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "modified_webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", - "music_video_album": "Music Videos", - "music_video_album_default": "Music Videos", - "music_video_album_default_sanitized": "Music Videos", - "music_video_album_sanitized": "Music Videos", - "music_video_artist": "Rick Astley", - "music_video_artist_sanitized": "Rick Astley", - "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", - "music_video_date_sanitized": "{ %sanitize( %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) ) }", - "music_video_directory": "tv_show_directory_path", - "music_video_directory_sanitized": "tv_show_directory_path", - "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "music_video_file_name_sanitized": "{ %sanitize( %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), '' ) ) }", - "music_video_file_name_suffix": "", - "music_video_file_name_suffix_sanitized": "", - "music_video_genre": "Pop", - "music_video_genre_default": "ytdl-sub", - "music_video_genre_default_sanitized": "ytdl-sub", - "music_video_genre_sanitized": "Pop", - "music_video_title": "{ %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "music_video_title_sanitized": "{ %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", - "music_video_year_sanitized": "{ %sanitize( %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) ) }", - "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", - "playlist_count_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) ) }", - "playlist_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", - "playlist_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", - "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", - "playlist_index_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", - "playlist_index_padded6": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) }", - "playlist_index_padded6_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) ) }", - "playlist_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ) }", - "playlist_index_reversed": "{ %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) }", - "playlist_index_reversed_padded": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) }", - "playlist_index_reversed_padded6": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) }", - "playlist_index_reversed_padded6_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) ) }", - "playlist_index_reversed_padded_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) ) }", - "playlist_index_reversed_sanitized": "{ %sanitize( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) ) }", - "playlist_index_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) ) }", - "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", - "playlist_max_upload_date_sanitized": "{ %sanitize( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ) }", - "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", - "playlist_max_upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ) }", - "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) }", - "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) ) }", - "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", - "playlist_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) ) }", - "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "playlist_title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "playlist_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) }", - "playlist_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "playlist_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "playlist_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "playlist_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "playlist_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", - "playlist_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "playlist_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", - "playlist_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", - "release_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ) }", - "release_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) }", - "release_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) ) }", - "release_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) }", - "release_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) }", - "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) }", - "release_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) ) }", - "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) }", - "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) }", - "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", - "release_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) ) }", - "release_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) ) }", - "release_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) }", - "release_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) ) }", - "release_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) }", - "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) }", - "release_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) ) }", - "release_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) ) }", - "release_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) ) }", - "release_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) }", - "release_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) }", - "release_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) ) }", - "release_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) }", - "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) }", - "release_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) ) }", - "release_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) ) }", - "release_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) ) }", - "release_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) }", - "release_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) ) }", - "release_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) }", - "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) }", - "release_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) ) }", - "release_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) ) }", - "requested_subtitles": "{ { } }", - "requested_subtitles_sanitized": "{}", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_height_gte_sanitized": "361", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_ignore_titles_sanitized": "[]", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", - "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_assert_print_sanitized": "true", - "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", - "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "resolution_readable_sanitized": "{ %sanitize( %concat( %map_get_non_empty( entry_metadata, \"width\", 0 ), \"x\", %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) }", - "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", - "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", - "source_count": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) }", - "source_count_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) ) }", - "source_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", - "source_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) ) }", - "source_index": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) }", - "source_index_padded": "{ %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) }", - "source_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) ) }", - "source_index_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) ) }", - "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", - "source_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) ) }", - "source_title": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "source_title_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "source_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "source_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "source_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "source_uploader_id": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "source_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "source_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", - "source_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", - "source_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) ) }", - "source_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "source_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", - "sponsorblock_chapters": "{ [ ] }", - "sponsorblock_chapters_sanitized": "[]", - "subscription_array": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\" ] }", - "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\uff02]", - "subscription_has_download_archive": "{ %bool(False) }", - "subscription_has_download_archive_sanitized": "false", - "subscription_indent_1": "Pop", - "subscription_indent_1_sanitized": "Pop", - "subscription_map": "{ { } }", - "subscription_map_sanitized": "{}", - "subscription_name": "Rick Astley", - "subscription_name_sanitized": "Rick Astley", - "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "thumbnail_ext": "jpg", - "thumbnail_ext_sanitized": "jpg", - "title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", - "title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "title_sanitized_plex": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "title_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "uid": "{ %map_get( entry_metadata, \"id\" ) }", - "uid_sanitized": "{ %sanitize( %map_get( entry_metadata, \"id\" ) ) }", - "uid_sanitized_plex": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", - "uid_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", - "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", - "upload_date_index": "{ %int(1) }", - "upload_date_index_padded": "01", - "upload_date_index_padded_sanitized": "01", - "upload_date_index_reversed": "{ %int(99) }", - "upload_date_index_reversed_padded": "99", - "upload_date_index_reversed_padded_sanitized": "99", - "upload_date_index_reversed_sanitized": "99", - "upload_date_index_sanitized": "1", - "upload_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", - "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", - "upload_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", - "upload_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) }", - "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) }", - "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) }", - "upload_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) ) }", - "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) }", - "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) }", - "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", - "upload_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) ) }", - "upload_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) ) }", - "upload_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) }", - "upload_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) ) }", - "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) }", - "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) }", - "upload_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) ) }", - "upload_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) ) }", - "upload_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) ) }", - "upload_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }", - "upload_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) }", - "upload_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) ) }", - "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) }", - "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) }", - "upload_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) ) }", - "upload_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) ) }", - "upload_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) ) }", - "upload_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", - "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) }", - "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) }", - "upload_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) ) }", - "upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) ) }", - "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) }", - "uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "uploader_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", - "uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "url10": "", - "url100": "", - "url100_sanitized": "", - "url10_sanitized": "", - "url11": "", - "url11_sanitized": "", - "url12": "", - "url12_sanitized": "", - "url13": "", - "url13_sanitized": "", - "url14": "", - "url14_sanitized": "", - "url15": "", - "url15_sanitized": "", - "url16": "", - "url16_sanitized": "", - "url17": "", - "url17_sanitized": "", - "url18": "", - "url18_sanitized": "", - "url19": "", - "url19_sanitized": "", - "url2": "", - "url20": "", - "url20_sanitized": "", - "url21": "", - "url21_sanitized": "", - "url22": "", - "url22_sanitized": "", - "url23": "", - "url23_sanitized": "", - "url24": "", - "url24_sanitized": "", - "url25": "", - "url25_sanitized": "", - "url26": "", - "url26_sanitized": "", - "url27": "", - "url27_sanitized": "", - "url28": "", - "url28_sanitized": "", - "url29": "", - "url29_sanitized": "", - "url2_sanitized": "", - "url3": "", - "url30": "", - "url30_sanitized": "", - "url31": "", - "url31_sanitized": "", - "url32": "", - "url32_sanitized": "", - "url33": "", - "url33_sanitized": "", - "url34": "", - "url34_sanitized": "", - "url35": "", - "url35_sanitized": "", - "url36": "", - "url36_sanitized": "", - "url37": "", - "url37_sanitized": "", - "url38": "", - "url38_sanitized": "", - "url39": "", - "url39_sanitized": "", - "url3_sanitized": "", - "url4": "", - "url40": "", - "url40_sanitized": "", - "url41": "", - "url41_sanitized": "", - "url42": "", - "url42_sanitized": "", - "url43": "", - "url43_sanitized": "", - "url44": "", - "url44_sanitized": "", - "url45": "", - "url45_sanitized": "", - "url46": "", - "url46_sanitized": "", - "url47": "", - "url47_sanitized": "", - "url48": "", - "url48_sanitized": "", - "url49": "", - "url49_sanitized": "", - "url4_sanitized": "", - "url5": "", - "url50": "", - "url50_sanitized": "", - "url51": "", - "url51_sanitized": "", - "url52": "", - "url52_sanitized": "", - "url53": "", - "url53_sanitized": "", - "url54": "", - "url54_sanitized": "", - "url55": "", - "url55_sanitized": "", - "url56": "", - "url56_sanitized": "", - "url57": "", - "url57_sanitized": "", - "url58": "", - "url58_sanitized": "", - "url59": "", - "url59_sanitized": "", - "url5_sanitized": "", - "url6": "", - "url60": "", - "url60_sanitized": "", - "url61": "", - "url61_sanitized": "", - "url62": "", - "url62_sanitized": "", - "url63": "", - "url63_sanitized": "", - "url64": "", - "url64_sanitized": "", - "url65": "", - "url65_sanitized": "", - "url66": "", - "url66_sanitized": "", - "url67": "", - "url67_sanitized": "", - "url68": "", - "url68_sanitized": "", - "url69": "", - "url69_sanitized": "", - "url6_sanitized": "", - "url7": "", - "url70": "", - "url70_sanitized": "", - "url71": "", - "url71_sanitized": "", - "url72": "", - "url72_sanitized": "", - "url73": "", - "url73_sanitized": "", - "url74": "", - "url74_sanitized": "", - "url75": "", - "url75_sanitized": "", - "url76": "", - "url76_sanitized": "", - "url77": "", - "url77_sanitized": "", - "url78": "", - "url78_sanitized": "", - "url79": "", - "url79_sanitized": "", - "url7_sanitized": "", - "url8": "", - "url80": "", - "url80_sanitized": "", - "url81": "", - "url81_sanitized": "", - "url82": "", - "url82_sanitized": "", - "url83": "", - "url83_sanitized": "", - "url84": "", - "url84_sanitized": "", - "url85": "", - "url85_sanitized": "", - "url86": "", - "url86_sanitized": "", - "url87": "", - "url87_sanitized": "", - "url88": "", - "url88_sanitized": "", - "url89": "", - "url89_sanitized": "", - "url8_sanitized": "", - "url9": "", - "url90": "", - "url90_sanitized": "", - "url91": "", - "url91_sanitized": "", - "url92": "", - "url92_sanitized": "", - "url93": "", - "url93_sanitized": "", - "url94": "", - "url94_sanitized": "", - "url95": "", - "url95_sanitized": "", - "url96": "", - "url96_sanitized": "", - "url97": "", - "url97_sanitized": "", - "url98": "", - "url98_sanitized": "", - "url99": "", - "url99_sanitized": "", - "url9_sanitized": "", - "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "urls": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] }", - "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02]", - "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", - "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", - "width_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"width\", 0 ) ) }", - "ytdl_sub_entry_date_eval": "{ %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", - "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) ) }", - "ytdl_sub_input_url": "", - "ytdl_sub_input_url_count": "{ %int(0) }", - "ytdl_sub_input_url_count_sanitized": "0", - "ytdl_sub_input_url_index": "{ %int(-1) }", - "ytdl_sub_input_url_index_sanitized": "-1", - "ytdl_sub_input_url_sanitized": "" -} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_overrides.json b/tests/resources/expected_json/music_video/inspect_overrides.json deleted file mode 100644 index 41e0f19d..00000000 --- a/tests/resources/expected_json/music_video/inspect_overrides.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "avatar_uncropped_thumbnail_file_name": "", - "banner_uncropped_thumbnail_file_name": "", - "category_url_array": "{ [ ] }", - "category_url_map": "{ [ ] }", - "enable_bilateral_scraping": "{ %bool(True) }", - "enable_resolution_assert": "{ %bool(True) }", - "enable_throttle_protection": "{ %bool(True) }", - "file_title": "{ title_sanitized_plex }", - "file_uid": "{ uid_sanitized_plex }", - "include_sibling_metadata": "{ %bool(False) }", - "modified_webpage_url": "{ webpage_url }", - "music_video_album": "{ %get_url_field( \"category\", \"Music Videos\" ) }", - "music_video_album_default": "Music Videos", - "music_video_artist": "Rick Astley", - "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", - "music_video_directory": "tv_show_directory_path", - "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", title ) ) }", - "music_video_file_name_suffix": "", - "music_video_genre": "Pop", - "music_video_genre_default": "ytdl-sub", - "music_video_title": "{ %get_url_field( \"title\", title ) }", - "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_readable": "{ width }x{ height }", - "subscription_array": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\" ] }", - "subscription_indent_1": "Pop", - "subscription_map": "{ { } }", - "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "url10": "", - "url100": "", - "url11": "", - "url12": "", - "url13": "", - "url14": "", - "url15": "", - "url16": "", - "url17": "", - "url18": "", - "url19": "", - "url2": "", - "url20": "", - "url21": "", - "url22": "", - "url23": "", - "url24": "", - "url25": "", - "url26": "", - "url27": "", - "url28": "", - "url29": "", - "url3": "", - "url30": "", - "url31": "", - "url32": "", - "url33": "", - "url34": "", - "url35": "", - "url36": "", - "url37": "", - "url38": "", - "url39": "", - "url4": "", - "url40": "", - "url41": "", - "url42": "", - "url43": "", - "url44": "", - "url45": "", - "url46": "", - "url47": "", - "url48": "", - "url49": "", - "url5": "", - "url50": "", - "url51": "", - "url52": "", - "url53": "", - "url54": "", - "url55": "", - "url56": "", - "url57": "", - "url58": "", - "url59": "", - "url6": "", - "url60": "", - "url61": "", - "url62": "", - "url63": "", - "url64": "", - "url65": "", - "url66": "", - "url67": "", - "url68": "", - "url69": "", - "url7": "", - "url70": "", - "url71": "", - "url72": "", - "url73": "", - "url74": "", - "url75": "", - "url76": "", - "url77": "", - "url78": "", - "url79": "", - "url8": "", - "url80": "", - "url81": "", - "url82": "", - "url83": "", - "url84": "", - "url85": "", - "url86": "", - "url87": "", - "url88": "", - "url89": "", - "url9": "", - "url90": "", - "url91": "", - "url92": "", - "url93": "", - "url94": "", - "url95": "", - "url96": "", - "url97": "", - "url98": "", - "url99": "", - "urls": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] }" -} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_sub_fill.json b/tests/resources/expected_json/music_video/inspect_sub_fill.json new file mode 100644 index 00000000..5999a01f --- /dev/null +++ b/tests/resources/expected_json/music_video/inspect_sub_fill.json @@ -0,0 +1,314 @@ +{ + "download": [ + { + "download_reverse": true, + "include_sibling_metadata": false, + "playlist_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "url": [ + "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc" + ], + "variables": {}, + "webpage_url": "{ modified_webpage_url }", + "ytdl_options": {} + } + ], + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-Rick Astley-download-archive.json", + "file_name": "{ music_video_file_name }.{ ext }", + "info_json_name": "{ music_video_file_name }.{ info_json_ext }", + "keep_files_date_eval": "{ upload_date_standardized }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpyukbh6ta", + "preserve_mtime": false, + "thumbnail_name": "{ music_video_file_name }.jpg" + }, + "overrides": { + "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, subscription_has_download_archive, %is_bilateral_url( $0 ) ), $0, '' ) }", + "%contains_url_field": "{ %not( %is_null( %get_url_field( $0, '' ) ) ) }", + "%flat_array__category_to_map_format": "{ %array_apply_fixed( %assert_then( %is_array( $1 ), $1, \"If using album categories, each category must map to an array\" ), $0, %flat_array__url_to_map_format ) }", + "%flat_array__url_keyed_map_format": "{ { %map_get( $0, \"url\" ): $0 } }", + "%flat_array__url_to_map_format": "{ %elif( %is_map( $0 ), %map_extend( $0, { \"category\": $1 } ), %is_string( $0 ), { \"url\": $0, \"category\": $1 }, %throw( \"If using album categories, each URL must be either a string or map with metadata fields\" ) ) }", + "%get_url_field": "{ %map_get( %map( %map_get( category_url_map, ytdl_sub_input_url, { } ) ), $0, $1 ) }", + "%get_url_i": "{ %map_get( %map( %array_at( category_url_array, %int( %sub( $0, 1 ) ), { } ) ), \"url\", %array_at( subscription_array, %int( %sub( $0, 1 ) ), '' ) ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "category_url_array": "{ [ ] }", + "category_url_map": "{ [ ] }", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "file_title": "{ title_sanitized_plex }", + "file_uid": "{ uid_sanitized_plex }", + "include_sibling_metadata": false, + "modified_webpage_url": "{ webpage_url }", + "music_video_album": "{ %get_url_field( \"category\", \"Music Videos\" ) }", + "music_video_album_default": "Music Videos", + "music_video_artist": "Rick Astley", + "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", + "music_video_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpyukbh6ta", + "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", title ) ) }", + "music_video_file_name_suffix": "", + "music_video_genre": "Pop", + "music_video_genre_default": "ytdl-sub", + "music_video_title": "{ %get_url_field( \"title\", title ) }", + "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "subscription_array": [ + "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc" + ], + "subscription_indent_1": "Pop", + "subscription_map": {}, + "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "video_tags": { + "album": "{ music_video_album }", + "artist": "Rick Astley", + "genre": "Pop", + "premiered": "{ music_video_date }", + "title": "{ music_video_title }", + "year": "{ music_video_year }" + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_sub_internal.json b/tests/resources/expected_json/music_video/inspect_sub_internal.json new file mode 100644 index 00000000..3cd8a4cd --- /dev/null +++ b/tests/resources/expected_json/music_video/inspect_sub_internal.json @@ -0,0 +1,314 @@ +{ + "download": [ + { + "download_reverse": true, + "include_sibling_metadata": false, + "playlist_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "url": [ + "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc" + ], + "variables": {}, + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "ytdl_options": {} + } + ], + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-Rick Astley-download-archive.json", + "file_name": "{ %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), '' ) }.{ %map_get( entry_metadata, \"ext\" ) }", + "info_json_name": "{ %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), '' ) }.info.json", + "keep_files_date_eval": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmput7fc_rs", + "preserve_mtime": false, + "thumbnail_name": "{ %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ), '' ) }.jpg" + }, + "overrides": { + "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, subscription_has_download_archive, %is_bilateral_url( $0 ) ), $0, '' ) }", + "%contains_url_field": "{ %not( %is_null( %get_url_field( $0, '' ) ) ) }", + "%flat_array__category_to_map_format": "{ %array_apply_fixed( %assert_then( %is_array( $1 ), $1, \"If using album categories, each category must map to an array\" ), $0, %flat_array__url_to_map_format ) }", + "%flat_array__url_keyed_map_format": "{ { %map_get( $0, \"url\" ): $0 } }", + "%flat_array__url_to_map_format": "{ %elif( %is_map( $0 ), %map_extend( $0, { \"category\": $1 } ), %is_string( $0 ), { \"url\": $0, \"category\": $1 }, %throw( \"If using album categories, each URL must be either a string or map with metadata fields\" ) ) }", + "%get_url_field": "{ %map_get( %map( %map_get( category_url_map, ytdl_sub_input_url, { } ) ), $0, $1 ) }", + "%get_url_i": "{ %map_get( %map( %array_at( category_url_array, %int( %sub( $0, 1 ) ), { } ) ), \"url\", %array_at( subscription_array, %int( %sub( $0, 1 ) ), '' ) ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "category_url_array": "{ [ ] }", + "category_url_map": "{ [ ] }", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "include_sibling_metadata": false, + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "music_video_album": "{ %get_url_field( \"category\", \"Music Videos\" ) }", + "music_video_album_default": "Music Videos", + "music_video_artist": "Rick Astley", + "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", + "music_video_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmput7fc_rs", + "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", + "music_video_file_name_suffix": "", + "music_video_genre": "Pop", + "music_video_genre_default": "ytdl-sub", + "music_video_title": "{ %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "subscription_array": [ + "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc" + ], + "subscription_indent_1": "Pop", + "subscription_map": {}, + "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "video_tags": { + "album": "{ %get_url_field( \"category\", \"Music Videos\" ) }", + "artist": "Rick Astley", + "genre": "Pop", + "premiered": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", + "title": "{ %get_url_field( \"title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }" + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_sub_original.json b/tests/resources/expected_json/music_video/inspect_sub_original.json new file mode 100644 index 00000000..cdd52c9f --- /dev/null +++ b/tests/resources/expected_json/music_video/inspect_sub_original.json @@ -0,0 +1,235 @@ +{ + "download": [ + { + "download_reverse": false, + "url": "{ %array_apply(urls, %bilateral_url) }", + "webpage_url": "{modified_webpage_url}", + "ytdl_options": { + "playlist_items": "-1:0:-1" + } + }, + { + "include_sibling_metadata": "{include_sibling_metadata}", + "playlist_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped" + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped" + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped" + } + ], + "url": "{ %array_at(urls, 0) }", + "webpage_url": "{modified_webpage_url}" + }, + { + "include_sibling_metadata": "{include_sibling_metadata}", + "url": "{ %array_slice(urls, 1) }", + "webpage_url": "{modified_webpage_url}" + } + ], + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "file_name": "{music_video_file_name}.{ext}", + "info_json_name": "{music_video_file_name}.{info_json_ext}", + "maintain_download_archive": true, + "output_directory": "{music_video_directory}", + "thumbnail_name": "{music_video_file_name}.jpg" + }, + "overrides": { + "%bilateral_url": "{ \n %if(\n %and(\n enable_bilateral_scraping,\n subscription_has_download_archive,\n %is_bilateral_url($0)\n ),\n $0,\n \"\"\n )\n}", + "%contains_url_field": "{ %not( %is_null( %get_url_field( $0, null ) ) ) }", + "%flat_array__category_to_map_format": "{ \n %array_apply_fixed(\n %assert_then(\n %is_array( $1 ),\n $1,\n \"If using album categories, each category must map to an array\"\n ),\n $0,\n %flat_array__url_to_map_format \n )\n}", + "%flat_array__url_keyed_map_format": "{ { %map_get($0, \"url\"): $0 } }", + "%flat_array__url_to_map_format": "{\n %elif(\n %is_map($0),\n %map_extend( $0, { \"category\": $1 } ),\n %is_string($0),\n { \"url\": $0, \"category\": $1 },\n %throw(\"If using album categories, each URL must be either a string or map with metadata fields\")\n )\n}", + "%get_url_field": "{ %map_get( %map( %map_get( category_url_map, ytdl_sub_input_url, {} ) ), $0, $1 ) }", + "%get_url_i": "{ \n %map_get(\n %map( %array_at( category_url_array, %int( %sub($0, 1) ), {} ) ),\n \"url\",\n %array_at(subscription_array, %int( %sub($0, 1) ), null)\n )\n}", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "category_url_array": "{ %array_flatten( %map_apply( subscription_map, %flat_array__category_to_map_format ) ) }", + "category_url_map": "{\n %array_reduce(\n %array_apply( category_url_array, %flat_array__url_keyed_map_format ),\n %map_extend\n )\n}", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "file_title": "{title_sanitized_plex}", + "file_uid": "{uid_sanitized_plex}", + "include_sibling_metadata": false, + "modified_webpage_url": "{webpage_url}", + "music_video_album": "{ %get_url_field(\"category\", music_video_album_default) }", + "music_video_album_default": "Music Videos", + "music_video_artist": "{subscription_name}", + "music_video_date": "{ \n %elif(\n %contains_url_field(\"date\"),\n %get_url_field(\"date\", upload_date_standardized),\n\n %contains_url_field(\"year\"),\n %concat( %get_url_field(\"date\", upload_year), \"-01-01\"),\n\n upload_date_standardized\n )\n}", + "music_video_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmp3bmucwsg", + "music_video_file_name": "{music_video_artist_sanitized}/{music_video_title_sanitized}{music_video_file_name_suffix}", + "music_video_file_name_suffix": "", + "music_video_genre": "{subscription_indent_1}", + "music_video_genre_default": "ytdl-sub", + "music_video_title": "{ %get_url_field(\"title\", title) }", + "music_video_year": "{\n %int(%elif(\n %contains_url_field(\"date\"),\n %slice( %get_url_field(\"date\", upload_date_standardized), 0, 4 ),\n\n %contains_url_field(\"year\"),\n %get_url_field(\"date\", upload_year),\n\n upload_year\n ))\n}", + "resolution_assert": "{\n %if(\n %and(\n enable_resolution_assert,\n %ne( height, 0 ),\n %not(resolution_assert_is_ignored)\n ),\n %assert(\n %gte( height, resolution_assert_height_gte ),\n %concat(\n \"Entry \",\n title,\n \" downloaded at a low resolution (\",\n resolution_readable,\n \"), you've probably been throttled. \",\n \"Stopping further downloads, wait a few hours and try again. \",\n \"Disable using the override variable `enable_resolution_assert: False`.\"\n )\n ),\n \"false is no-op\"\n )\n}", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [] }", + "resolution_assert_is_ignored": "{\n %print_if_true(\n %concat(title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\"),\n %contains_any(title, resolution_assert_ignore_titles)\n )\n}", + "resolution_assert_print": "{\n %print(\n %if(\n enable_resolution_assert,\n \"Resolution assert is enabled, will fail on low-quality video downloads and presume throttle. Disable using the override variable `enable_resolution_assert: False`\",\n \"Resolution assert is disabled. Use at your own risk!\"\n ),\n enable_resolution_assert,\n -1\n )\n}", + "resolution_readable": "{width}x{height}", + "subscription_array": "{%from_json('''[\"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\"]''')}", + "subscription_indent_1": "Pop", + "subscription_map": "{ {} }", + "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url": "{subscription_value}", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{ %array_apply(%range(100, 1), %get_url_i) }" + }, + "preset": [ + "_base", + "_plex_base", + "_url_bilateral_overrides", + "_multi_url_bilateral_inner", + "_multi_url", + "_multi_url_bilateral", + "_throttle_protection", + "_url_categorized", + "_plex_video_base", + "_music_video_base", + "_music_video_tags", + "Plex Music Videos", + "__preset__" + ], + "throttle_protection": { + "enable": "{\n %print(\n %if(\n enable_throttle_protection,\n \"Throttle protection is enabled. Disable using the override variable `enable_throttle_protection: False`\",\n \"Throttle protection is disabled. Use at your own risk!\"\n ),\n enable_throttle_protection\n )\n}", + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "video_tags": { + "album": "{music_video_album}", + "artist": "{music_video_artist}", + "genre": "{music_video_genre}", + "premiered": "{music_video_date}", + "title": "{music_video_title}", + "year": "{music_video_year}" + }, + "ytdl_options": { + "break_on_existing": true + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_sub_resolve.json b/tests/resources/expected_json/music_video/inspect_sub_resolve.json new file mode 100644 index 00000000..f3594e9c --- /dev/null +++ b/tests/resources/expected_json/music_video/inspect_sub_resolve.json @@ -0,0 +1,314 @@ +{ + "download": [ + { + "download_reverse": true, + "include_sibling_metadata": false, + "playlist_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "", + "uid": "avatar_uncropped" + }, + { + "name": "", + "uid": "banner_uncropped" + } + ], + "url": [ + "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc" + ], + "variables": {}, + "webpage_url": "{ webpage_url }", + "ytdl_options": {} + } + ], + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-Rick Astley-download-archive.json", + "file_name": "{ %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", title ) ), '' ) }.{ ext }", + "info_json_name": "{ %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", title ) ), '' ) }.{ info_json_ext }", + "keep_files_date_eval": "{ upload_date_standardized }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpstvsa5ot", + "preserve_mtime": false, + "thumbnail_name": "{ %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", title ) ), '' ) }.jpg" + }, + "overrides": { + "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, subscription_has_download_archive, %is_bilateral_url( $0 ) ), $0, '' ) }", + "%contains_url_field": "{ %not( %is_null( %get_url_field( $0, '' ) ) ) }", + "%flat_array__category_to_map_format": "{ %array_apply_fixed( %assert_then( %is_array( $1 ), $1, \"If using album categories, each category must map to an array\" ), $0, %flat_array__url_to_map_format ) }", + "%flat_array__url_keyed_map_format": "{ { %map_get( $0, \"url\" ): $0 } }", + "%flat_array__url_to_map_format": "{ %elif( %is_map( $0 ), %map_extend( $0, { \"category\": $1 } ), %is_string( $0 ), { \"url\": $0, \"category\": $1 }, %throw( \"If using album categories, each URL must be either a string or map with metadata fields\" ) ) }", + "%get_url_field": "{ %map_get( %map( %map_get( category_url_map, ytdl_sub_input_url, { } ) ), $0, $1 ) }", + "%get_url_i": "{ %map_get( %map( %array_at( category_url_array, %int( %sub( $0, 1 ) ), { } ) ), \"url\", %array_at( subscription_array, %int( %sub( $0, 1 ) ), '' ) ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "avatar_uncropped_thumbnail_file_name": "", + "banner_uncropped_thumbnail_file_name": "", + "category_url_array": "{ [ ] }", + "category_url_map": "{ [ ] }", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "file_title": "{ title_sanitized_plex }", + "file_uid": "{ uid_sanitized_plex }", + "include_sibling_metadata": false, + "modified_webpage_url": "{ webpage_url }", + "music_video_album": "{ %get_url_field( \"category\", \"Music Videos\" ) }", + "music_video_album_default": "Music Videos", + "music_video_artist": "Rick Astley", + "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", + "music_video_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpstvsa5ot", + "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", title ) ) }", + "music_video_file_name_suffix": "", + "music_video_genre": "Pop", + "music_video_genre_default": "ytdl-sub", + "music_video_title": "{ %get_url_field( \"title\", title ) }", + "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "subscription_array": [ + "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc" + ], + "subscription_indent_1": "Pop", + "subscription_map": {}, + "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "video_tags": { + "album": "{ %get_url_field( \"category\", \"Music Videos\" ) }", + "artist": "Rick Astley", + "genre": "Pop", + "premiered": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", + "title": "{ %get_url_field( \"title\", title ) }", + "year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }" + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/music_video/inspect_variables.json b/tests/resources/expected_json/music_video/inspect_variables.json deleted file mode 100644 index 7e4bc383..00000000 --- a/tests/resources/expected_json/music_video/inspect_variables.json +++ /dev/null @@ -1,480 +0,0 @@ -{ - "avatar_uncropped_thumbnail_file_name": "", - "avatar_uncropped_thumbnail_file_name_sanitized": "", - "banner_uncropped_thumbnail_file_name": "", - "banner_uncropped_thumbnail_file_name_sanitized": "", - "category_url_array": "{ [ ] }", - "category_url_array_sanitized": "[]", - "category_url_map": "{ [ ] }", - "category_url_map_sanitized": "[]", - "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", uploader ) }", - "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", uploader_id ) }", - "channel_id_sanitized": "{ %sanitize( channel_id ) }", - "channel_sanitized": "{ %sanitize( channel ) }", - "chapters": "{ [ ] }", - "chapters_sanitized": "{ %sanitize( chapters ) }", - "comments": "{ [ ] }", - "comments_sanitized": "{ %sanitize( comments ) }", - "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", channel ) }", - "creator_sanitized": "{ %sanitize( creator ) }", - "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "description_sanitized": "{ %sanitize( description ) }", - "download_index": "{ %int( 1 ) }", - "download_index_padded6": "{ %pad_zero( download_index, 6 ) }", - "download_index_padded6_sanitized": "{ %sanitize( download_index_padded6 ) }", - "download_index_sanitized": "{ %sanitize( download_index ) }", - "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", - "duration_sanitized": "{ %sanitize( duration ) }", - "enable_bilateral_scraping": "{ %bool(True) }", - "enable_bilateral_scraping_sanitized": "true", - "enable_resolution_assert": "{ %bool(True) }", - "enable_resolution_assert_sanitized": "true", - "enable_throttle_protection": "{ %bool(True) }", - "enable_throttle_protection_sanitized": "true", - "entry_metadata": "{ { } }", - "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", - "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", - "epoch_date": "{ %datetime_strftime( epoch, \"%Y%m%d\" ) }", - "epoch_date_sanitized": "{ %sanitize( epoch_date ) }", - "epoch_hour": "{ %datetime_strftime( epoch, \"%H\" ) }", - "epoch_hour_sanitized": "{ %sanitize( epoch_hour ) }", - "epoch_sanitized": "{ %sanitize( epoch ) }", - "ext": "{ %map_get( entry_metadata, \"ext\" ) }", - "ext_sanitized": "{ %sanitize( ext ) }", - "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", - "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", - "extractor_key_sanitized": "{ %sanitize( extractor_key ) }", - "extractor_sanitized": "{ %sanitize( extractor ) }", - "file_title": "{ title_sanitized_plex }", - "file_title_sanitized": "{ %sanitize( title_sanitized_plex ) }", - "file_uid": "{ uid_sanitized_plex }", - "file_uid_sanitized": "{ %sanitize( uid_sanitized_plex ) }", - "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "height_sanitized": "{ %sanitize( height ) }", - "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", extractor_key ) }", - "ie_key_sanitized": "{ %sanitize( ie_key ) }", - "include_sibling_metadata": "{ %bool(False) }", - "include_sibling_metadata_sanitized": "false", - "info_json_ext": "info.json", - "info_json_ext_sanitized": "info.json", - "modified_webpage_url": "{ webpage_url }", - "modified_webpage_url_sanitized": "{ %sanitize( webpage_url ) }", - "music_video_album": "{ %get_url_field( \"category\", \"Music Videos\" ) }", - "music_video_album_default": "Music Videos", - "music_video_album_default_sanitized": "Music Videos", - "music_video_album_sanitized": "{ %sanitize( %get_url_field( \"category\", \"Music Videos\" ) ) }", - "music_video_artist": "Rick Astley", - "music_video_artist_sanitized": "Rick Astley", - "music_video_date": "{ %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) }", - "music_video_date_sanitized": "{ %sanitize( %elif( %contains_url_field( \"date\" ), %get_url_field( \"date\", upload_date_standardized ), %contains_url_field( \"year\" ), %concat( %get_url_field( \"date\", upload_year ), \"-01-01\" ), upload_date_standardized ) ) }", - "music_video_directory": "tv_show_directory_path", - "music_video_directory_sanitized": "tv_show_directory_path", - "music_video_file_name": "Rick Astley/{ %sanitize( %get_url_field( \"title\", title ) ) }", - "music_video_file_name_sanitized": "{ %sanitize( %concat( \"Rick Astley\", \"/\", %sanitize( %get_url_field( \"title\", title ) ), '' ) ) }", - "music_video_file_name_suffix": "", - "music_video_file_name_suffix_sanitized": "", - "music_video_genre": "Pop", - "music_video_genre_default": "ytdl-sub", - "music_video_genre_default_sanitized": "ytdl-sub", - "music_video_genre_sanitized": "Pop", - "music_video_title": "{ %get_url_field( \"title\", title ) }", - "music_video_title_sanitized": "{ %sanitize( %get_url_field( \"title\", title ) ) }", - "music_video_year": "{ %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) }", - "music_video_year_sanitized": "{ %sanitize( %int( %elif( %contains_url_field( \"date\" ), %slice( %get_url_field( \"date\", upload_date_standardized ), 0, 4 ), %contains_url_field( \"year\" ), %get_url_field( \"date\", upload_year ), upload_year ) ) ) }", - "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", - "playlist_count_sanitized": "{ %sanitize( playlist_count ) }", - "playlist_description": "{ %map_get_non_empty( playlist_metadata, \"description\", description ) }", - "playlist_description_sanitized": "{ %sanitize( playlist_description ) }", - "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", - "playlist_index_padded": "{ %pad_zero( playlist_index, 2 ) }", - "playlist_index_padded6": "{ %pad_zero( playlist_index, 6 ) }", - "playlist_index_padded6_sanitized": "{ %sanitize( playlist_index_padded6 ) }", - "playlist_index_padded_sanitized": "{ %sanitize( playlist_index_padded ) }", - "playlist_index_reversed": "{ %sub( playlist_count, playlist_index, -1 ) }", - "playlist_index_reversed_padded": "{ %pad_zero( playlist_index_reversed, 2 ) }", - "playlist_index_reversed_padded6": "{ %pad_zero( playlist_index_reversed, 6 ) }", - "playlist_index_reversed_padded6_sanitized": "{ %sanitize( playlist_index_reversed_padded6 ) }", - "playlist_index_reversed_padded_sanitized": "{ %sanitize( playlist_index_reversed_padded ) }", - "playlist_index_reversed_sanitized": "{ %sanitize( playlist_index_reversed ) }", - "playlist_index_sanitized": "{ %sanitize( playlist_index ) }", - "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", - "playlist_max_upload_date_sanitized": "{ %sanitize( playlist_max_upload_date ) }", - "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year\" ) ) }", - "playlist_max_upload_year_sanitized": "{ %sanitize( playlist_max_upload_year ) }", - "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year_truncated\" ) ) }", - "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( playlist_max_upload_year_truncated ) }", - "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", - "playlist_metadata_sanitized": "{ %sanitize( playlist_metadata ) }", - "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", title ) }", - "playlist_title_sanitized": "{ %sanitize( playlist_title ) }", - "playlist_uid": "{ %map_get_non_empty( playlist_metadata, \"playlist_id\", uid ) }", - "playlist_uid_sanitized": "{ %sanitize( playlist_uid ) }", - "playlist_uploader": "{ %map_get_non_empty( playlist_metadata, \"uploader\", uploader ) }", - "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", uploader_id ) }", - "playlist_uploader_id_sanitized": "{ %sanitize( playlist_uploader_id ) }", - "playlist_uploader_sanitized": "{ %sanitize( playlist_uploader ) }", - "playlist_uploader_url": "{ %map_get_non_empty( playlist_metadata, \"uploader_url\", webpage_url ) }", - "playlist_uploader_url_sanitized": "{ %sanitize( playlist_uploader_url ) }", - "playlist_webpage_url": "{ %map_get_non_empty( playlist_metadata, \"webpage_url\", webpage_url ) }", - "playlist_webpage_url_sanitized": "{ %sanitize( playlist_webpage_url ) }", - "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", upload_date ) }", - "release_date_sanitized": "{ %sanitize( release_date ) }", - "release_date_standardized": "{ %string( %map_get( %to_date_metadata( release_date ), \"date_standardized\" ) ) }", - "release_date_standardized_sanitized": "{ %sanitize( release_date_standardized ) }", - "release_day": "{ %int( %map_get( %to_date_metadata( release_date ), \"day\" ) ) }", - "release_day_of_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year\" ) ) }", - "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_padded\" ) ) }", - "release_day_of_year_padded_sanitized": "{ %sanitize( release_day_of_year_padded ) }", - "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed\" ) ) }", - "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed_padded\" ) ) }", - "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( release_day_of_year_reversed_padded ) }", - "release_day_of_year_reversed_sanitized": "{ %sanitize( release_day_of_year_reversed ) }", - "release_day_of_year_sanitized": "{ %sanitize( release_day_of_year ) }", - "release_day_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_padded\" ) ) }", - "release_day_padded_sanitized": "{ %sanitize( release_day_padded ) }", - "release_day_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_reversed\" ) ) }", - "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_reversed_padded\" ) ) }", - "release_day_reversed_padded_sanitized": "{ %sanitize( release_day_reversed_padded ) }", - "release_day_reversed_sanitized": "{ %sanitize( release_day_reversed ) }", - "release_day_sanitized": "{ %sanitize( release_day ) }", - "release_month": "{ %int( %map_get( %to_date_metadata( release_date ), \"month\" ) ) }", - "release_month_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_padded\" ) ) }", - "release_month_padded_sanitized": "{ %sanitize( release_month_padded ) }", - "release_month_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"month_reversed\" ) ) }", - "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_reversed_padded\" ) ) }", - "release_month_reversed_padded_sanitized": "{ %sanitize( release_month_reversed_padded ) }", - "release_month_reversed_sanitized": "{ %sanitize( release_month_reversed ) }", - "release_month_sanitized": "{ %sanitize( release_month ) }", - "release_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"year\" ) ) }", - "release_year_sanitized": "{ %sanitize( release_year ) }", - "release_year_truncated": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated\" ) ) }", - "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated_reversed\" ) ) }", - "release_year_truncated_reversed_sanitized": "{ %sanitize( release_year_truncated_reversed ) }", - "release_year_truncated_sanitized": "{ %sanitize( release_year_truncated ) }", - "requested_subtitles": "{ { } }", - "requested_subtitles_sanitized": "{ %sanitize( requested_subtitles ) }", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_height_gte_sanitized": "361", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_ignore_titles_sanitized": "[]", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", - "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_assert_print_sanitized": "true", - "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", - "resolution_readable": "{ width }x{ height }", - "resolution_readable_sanitized": "{ %sanitize( %concat( width, \"x\", height ) ) }", - "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", - "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", - "source_count": "{ %map_get_non_empty( playlist_metadata, \"playlist_count\", 1 ) }", - "source_count_sanitized": "{ %sanitize( source_count ) }", - "source_description": "{ %map_get_non_empty( source_metadata, \"description\", playlist_description ) }", - "source_description_sanitized": "{ %sanitize( source_description ) }", - "source_index": "{ %map_get_non_empty( playlist_metadata, \"playlist_index\", 1 ) }", - "source_index_padded": "{ %pad_zero( source_index, 2 ) }", - "source_index_padded_sanitized": "{ %sanitize( source_index_padded ) }", - "source_index_sanitized": "{ %sanitize( source_index ) }", - "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", - "source_metadata_sanitized": "{ %sanitize( source_metadata ) }", - "source_title": "{ %map_get_non_empty( source_metadata, \"title\", playlist_title ) }", - "source_title_sanitized": "{ %sanitize( source_title ) }", - "source_uid": "{ %map_get_non_empty( source_metadata, \"id\", playlist_uid ) }", - "source_uid_sanitized": "{ %sanitize( source_uid ) }", - "source_uploader": "{ %map_get_non_empty( source_metadata, \"uploader\", playlist_uploader ) }", - "source_uploader_id": "{ %map_get_non_empty( source_metadata, \"uploader_id\", playlist_uploader_id ) }", - "source_uploader_id_sanitized": "{ %sanitize( source_uploader_id ) }", - "source_uploader_sanitized": "{ %sanitize( source_uploader ) }", - "source_uploader_url": "{ %map_get_non_empty( source_metadata, \"uploader_url\", source_webpage_url ) }", - "source_uploader_url_sanitized": "{ %sanitize( source_uploader_url ) }", - "source_webpage_url": "{ %map_get_non_empty( source_metadata, \"webpage_url\", playlist_webpage_url ) }", - "source_webpage_url_sanitized": "{ %sanitize( source_webpage_url ) }", - "sponsorblock_chapters": "{ [ ] }", - "sponsorblock_chapters_sanitized": "{ %sanitize( sponsorblock_chapters ) }", - "subscription_array": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\" ] }", - "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\uff02]", - "subscription_has_download_archive": "{ %bool(False) }", - "subscription_has_download_archive_sanitized": "false", - "subscription_indent_1": "Pop", - "subscription_indent_1_sanitized": "Pop", - "subscription_map": "{ { } }", - "subscription_map_sanitized": "{}", - "subscription_name": "Rick Astley", - "subscription_name_sanitized": "Rick Astley", - "subscription_value": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "subscription_value_1": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "thumbnail_ext": "jpg", - "thumbnail_ext_sanitized": "jpg", - "title": "{ %map_get_non_empty( entry_metadata, \"title\", uid ) }", - "title_sanitized": "{ %sanitize( title ) }", - "title_sanitized_plex": "{ %sanitize_plex_episode( title ) }", - "title_sanitized_plex_sanitized": "{ %sanitize( title_sanitized_plex ) }", - "uid": "{ %map_get( entry_metadata, \"id\" ) }", - "uid_sanitized": "{ %sanitize( uid ) }", - "uid_sanitized_plex": "{ %sanitize_plex_episode( uid ) }", - "uid_sanitized_plex_sanitized": "{ %sanitize( uid_sanitized_plex ) }", - "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", epoch_date ) }", - "upload_date_index": "{ %int( 1 ) }", - "upload_date_index_padded": "{ %pad_zero( upload_date_index, 2 ) }", - "upload_date_index_padded_sanitized": "{ %sanitize( upload_date_index_padded ) }", - "upload_date_index_reversed": "{ %sub( 100, upload_date_index ) }", - "upload_date_index_reversed_padded": "{ %pad_zero( upload_date_index_reversed, 2 ) }", - "upload_date_index_reversed_padded_sanitized": "{ %sanitize( upload_date_index_reversed_padded ) }", - "upload_date_index_reversed_sanitized": "{ %sanitize( upload_date_index_reversed ) }", - "upload_date_index_sanitized": "{ %sanitize( upload_date_index ) }", - "upload_date_sanitized": "{ %sanitize( upload_date ) }", - "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( upload_date ), \"date_standardized\" ) ) }", - "upload_date_standardized_sanitized": "{ %sanitize( upload_date_standardized ) }", - "upload_day": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day\" ) ) }", - "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year\" ) ) }", - "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_padded\" ) ) }", - "upload_day_of_year_padded_sanitized": "{ %sanitize( upload_day_of_year_padded ) }", - "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed\" ) ) }", - "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed_padded\" ) ) }", - "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( upload_day_of_year_reversed_padded ) }", - "upload_day_of_year_reversed_sanitized": "{ %sanitize( upload_day_of_year_reversed ) }", - "upload_day_of_year_sanitized": "{ %sanitize( upload_day_of_year ) }", - "upload_day_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_padded\" ) ) }", - "upload_day_padded_sanitized": "{ %sanitize( upload_day_padded ) }", - "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_reversed\" ) ) }", - "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_reversed_padded\" ) ) }", - "upload_day_reversed_padded_sanitized": "{ %sanitize( upload_day_reversed_padded ) }", - "upload_day_reversed_sanitized": "{ %sanitize( upload_day_reversed ) }", - "upload_day_sanitized": "{ %sanitize( upload_day ) }", - "upload_month": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month\" ) ) }", - "upload_month_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_padded\" ) ) }", - "upload_month_padded_sanitized": "{ %sanitize( upload_month_padded ) }", - "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month_reversed\" ) ) }", - "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_reversed_padded\" ) ) }", - "upload_month_reversed_padded_sanitized": "{ %sanitize( upload_month_reversed_padded ) }", - "upload_month_reversed_sanitized": "{ %sanitize( upload_month_reversed ) }", - "upload_month_sanitized": "{ %sanitize( upload_month ) }", - "upload_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year\" ) ) }", - "upload_year_sanitized": "{ %sanitize( upload_year ) }", - "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated\" ) ) }", - "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated_reversed\" ) ) }", - "upload_year_truncated_reversed_sanitized": "{ %sanitize( upload_year_truncated_reversed ) }", - "upload_year_truncated_sanitized": "{ %sanitize( upload_year_truncated ) }", - "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", uploader_id ) }", - "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", uid ) }", - "uploader_id_sanitized": "{ %sanitize( uploader_id ) }", - "uploader_sanitized": "{ %sanitize( uploader ) }", - "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", webpage_url ) }", - "uploader_url_sanitized": "{ %sanitize( uploader_url ) }", - "url": "https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "url10": "", - "url100": "", - "url100_sanitized": "", - "url10_sanitized": "", - "url11": "", - "url11_sanitized": "", - "url12": "", - "url12_sanitized": "", - "url13": "", - "url13_sanitized": "", - "url14": "", - "url14_sanitized": "", - "url15": "", - "url15_sanitized": "", - "url16": "", - "url16_sanitized": "", - "url17": "", - "url17_sanitized": "", - "url18": "", - "url18_sanitized": "", - "url19": "", - "url19_sanitized": "", - "url2": "", - "url20": "", - "url20_sanitized": "", - "url21": "", - "url21_sanitized": "", - "url22": "", - "url22_sanitized": "", - "url23": "", - "url23_sanitized": "", - "url24": "", - "url24_sanitized": "", - "url25": "", - "url25_sanitized": "", - "url26": "", - "url26_sanitized": "", - "url27": "", - "url27_sanitized": "", - "url28": "", - "url28_sanitized": "", - "url29": "", - "url29_sanitized": "", - "url2_sanitized": "", - "url3": "", - "url30": "", - "url30_sanitized": "", - "url31": "", - "url31_sanitized": "", - "url32": "", - "url32_sanitized": "", - "url33": "", - "url33_sanitized": "", - "url34": "", - "url34_sanitized": "", - "url35": "", - "url35_sanitized": "", - "url36": "", - "url36_sanitized": "", - "url37": "", - "url37_sanitized": "", - "url38": "", - "url38_sanitized": "", - "url39": "", - "url39_sanitized": "", - "url3_sanitized": "", - "url4": "", - "url40": "", - "url40_sanitized": "", - "url41": "", - "url41_sanitized": "", - "url42": "", - "url42_sanitized": "", - "url43": "", - "url43_sanitized": "", - "url44": "", - "url44_sanitized": "", - "url45": "", - "url45_sanitized": "", - "url46": "", - "url46_sanitized": "", - "url47": "", - "url47_sanitized": "", - "url48": "", - "url48_sanitized": "", - "url49": "", - "url49_sanitized": "", - "url4_sanitized": "", - "url5": "", - "url50": "", - "url50_sanitized": "", - "url51": "", - "url51_sanitized": "", - "url52": "", - "url52_sanitized": "", - "url53": "", - "url53_sanitized": "", - "url54": "", - "url54_sanitized": "", - "url55": "", - "url55_sanitized": "", - "url56": "", - "url56_sanitized": "", - "url57": "", - "url57_sanitized": "", - "url58": "", - "url58_sanitized": "", - "url59": "", - "url59_sanitized": "", - "url5_sanitized": "", - "url6": "", - "url60": "", - "url60_sanitized": "", - "url61": "", - "url61_sanitized": "", - "url62": "", - "url62_sanitized": "", - "url63": "", - "url63_sanitized": "", - "url64": "", - "url64_sanitized": "", - "url65": "", - "url65_sanitized": "", - "url66": "", - "url66_sanitized": "", - "url67": "", - "url67_sanitized": "", - "url68": "", - "url68_sanitized": "", - "url69": "", - "url69_sanitized": "", - "url6_sanitized": "", - "url7": "", - "url70": "", - "url70_sanitized": "", - "url71": "", - "url71_sanitized": "", - "url72": "", - "url72_sanitized": "", - "url73": "", - "url73_sanitized": "", - "url74": "", - "url74_sanitized": "", - "url75": "", - "url75_sanitized": "", - "url76": "", - "url76_sanitized": "", - "url77": "", - "url77_sanitized": "", - "url78": "", - "url78_sanitized": "", - "url79": "", - "url79_sanitized": "", - "url7_sanitized": "", - "url8": "", - "url80": "", - "url80_sanitized": "", - "url81": "", - "url81_sanitized": "", - "url82": "", - "url82_sanitized": "", - "url83": "", - "url83_sanitized": "", - "url84": "", - "url84_sanitized": "", - "url85": "", - "url85_sanitized": "", - "url86": "", - "url86_sanitized": "", - "url87": "", - "url87_sanitized": "", - "url88": "", - "url88_sanitized": "", - "url89": "", - "url89_sanitized": "", - "url8_sanitized": "", - "url9": "", - "url90": "", - "url90_sanitized": "", - "url91": "", - "url91_sanitized": "", - "url92": "", - "url92_sanitized": "", - "url93": "", - "url93_sanitized": "", - "url94": "", - "url94_sanitized": "", - "url95": "", - "url95_sanitized": "", - "url96": "", - "url96_sanitized": "", - "url97": "", - "url97_sanitized": "", - "url98": "", - "url98_sanitized": "", - "url99": "", - "url99_sanitized": "", - "url9_sanitized": "", - "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc", - "urls": "{ [ \"https://www.youtube.com/playlist?list=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] }", - "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8playlist\uff1flist=PLlaN88a7y2_plecYoJxvRFTLHVbIVAOoc\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02, \uff02\uff02]", - "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "webpage_url_sanitized": "{ %sanitize( webpage_url ) }", - "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", - "width_sanitized": "{ %sanitize( width ) }", - "ytdl_sub_entry_date_eval": "{ %string( upload_date_standardized ) }", - "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( ytdl_sub_entry_date_eval ) }", - "ytdl_sub_input_url": "{ %string( '' ) }", - "ytdl_sub_input_url_count": "{ %int( 0 ) }", - "ytdl_sub_input_url_count_sanitized": "{ %sanitize( ytdl_sub_input_url_count ) }", - "ytdl_sub_input_url_index": "{ %int( -1 ) }", - "ytdl_sub_input_url_index_sanitized": "{ %sanitize( ytdl_sub_input_url_index ) }", - "ytdl_sub_input_url_sanitized": "{ %sanitize( ytdl_sub_input_url ) }" -} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_full_overrides.json b/tests/resources/expected_json/tv_show/inspect_full_overrides.json deleted file mode 100644 index 4d17acd4..00000000 --- a/tests/resources/expected_json/tv_show/inspect_full_overrides.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "assert_not_collection": "{ %bool(True) }", - "avatar_uncropped_thumbnail_file_name": "poster.jpg", - "banner_uncropped_thumbnail_file_name": "fanart.jpg", - "enable_bilateral_scraping": "{ %bool(True) }", - "enable_resolution_assert": "{ %bool(True) }", - "enable_throttle_protection": "{ %bool(True) }", - "episode_content_rating": "TV-14", - "episode_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", - "episode_file_name": "s{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.e{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) } - { %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "episode_file_path": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/{ %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "episode_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }{ upload_date_index_padded }", - "episode_number_and_padded_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", - "episode_number_padded": "{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) }", - "episode_plot": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "episode_title": "{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", - "episode_year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }", - "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", - "include_sibling_metadata": "{ %bool(False) }", - "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "only_recent_date_range": "2months", - "only_recent_max_files": "{ %int(30) }", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "s01_name": "", - "s01_url": "", - "season_directory_name": "Season { %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "season_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "season_number_padded": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", - "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", - "subscription_indent_1": "Documentaries", - "subscription_indent_2": "TV-14", - "subscription_value": "https://www.youtube.com/@novapbs", - "subscription_value_1": "https://www.youtube.com/@novapbs", - "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }-thumb.jpg", - "tv_show_by_date_episode_ordering": "upload-month-day", - "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", - "tv_show_by_date_season_ordering": "upload-year", - "tv_show_content_rating": "TV-14", - "tv_show_content_rating_default": "TV-14", - "tv_show_date_range_type": "upload_date", - "tv_show_directory": "tv_show_directory_path", - "tv_show_fanart_file_name": "fanart.jpg", - "tv_show_genre": "Documentaries", - "tv_show_genre_default": "ytdl-sub", - "tv_show_name": "NOVA PBS", - "tv_show_poster_file_name": "poster.jpg", - "url": "https://www.youtube.com/@novapbs", - "url10": "", - "url100": "", - "url11": "", - "url12": "", - "url13": "", - "url14": "", - "url15": "", - "url16": "", - "url17": "", - "url18": "", - "url19": "", - "url2": "", - "url20": "", - "url21": "", - "url22": "", - "url23": "", - "url24": "", - "url25": "", - "url26": "", - "url27": "", - "url28": "", - "url29": "", - "url3": "", - "url30": "", - "url31": "", - "url32": "", - "url33": "", - "url34": "", - "url35": "", - "url36": "", - "url37": "", - "url38": "", - "url39": "", - "url4": "", - "url40": "", - "url41": "", - "url42": "", - "url43": "", - "url44": "", - "url45": "", - "url46": "", - "url47": "", - "url48": "", - "url49": "", - "url5": "", - "url50": "", - "url51": "", - "url52": "", - "url53": "", - "url54": "", - "url55": "", - "url56": "", - "url57": "", - "url58": "", - "url59": "", - "url6": "", - "url60": "", - "url61": "", - "url62": "", - "url63": "", - "url64": "", - "url65": "", - "url66": "", - "url67": "", - "url68": "", - "url69": "", - "url7": "", - "url70": "", - "url71": "", - "url72": "", - "url73": "", - "url74": "", - "url75": "", - "url76": "", - "url77": "", - "url78": "", - "url79": "", - "url8": "", - "url80": "", - "url81": "", - "url82": "", - "url83": "", - "url84": "", - "url85": "", - "url86": "", - "url87": "", - "url88": "", - "url89": "", - "url9": "", - "url90": "", - "url91": "", - "url92": "", - "url93": "", - "url94": "", - "url95": "", - "url96": "", - "url97": "", - "url98": "", - "url99": "", - "urls": "{ [ \"https://www.youtube.com/@novapbs\" ] }" -} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_full_variables.json b/tests/resources/expected_json/tv_show/inspect_full_variables.json deleted file mode 100644 index 4e22fa86..00000000 --- a/tests/resources/expected_json/tv_show/inspect_full_variables.json +++ /dev/null @@ -1,520 +0,0 @@ -{ - "assert_not_collection": "{ %bool(True) }", - "assert_not_collection_sanitized": "true", - "avatar_uncropped_thumbnail_file_name": "poster.jpg", - "avatar_uncropped_thumbnail_file_name_sanitized": "poster.jpg", - "banner_uncropped_thumbnail_file_name": "fanart.jpg", - "banner_uncropped_thumbnail_file_name_sanitized": "fanart.jpg", - "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "channel_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "channel_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "chapters": "{ [ ] }", - "chapters_sanitized": "[]", - "comments": "{ [ ] }", - "comments_sanitized": "[]", - "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "creator_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"creator\", %map_get_non_empty( entry_metadata, \"channel\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", - "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "description_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", - "download_index": "{ %int(1) }", - "download_index_padded6": "000001", - "download_index_padded6_sanitized": "000001", - "download_index_sanitized": "1", - "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", - "duration_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"duration\", 0 ) ) }", - "enable_bilateral_scraping": "{ %bool(True) }", - "enable_bilateral_scraping_sanitized": "true", - "enable_resolution_assert": "{ %bool(True) }", - "enable_resolution_assert_sanitized": "true", - "enable_throttle_protection": "{ %bool(True) }", - "enable_throttle_protection_sanitized": "true", - "entry_metadata": "{ { } }", - "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", - "episode_content_rating": "TV-14", - "episode_content_rating_sanitized": "TV-14", - "episode_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", - "episode_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", - "episode_file_name": "s{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.e{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) } - { %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "episode_file_name_sanitized": "{ %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "episode_file_path": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/{ %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "episode_file_path_sanitized": "{ %sanitize( %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) ) }", - "episode_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }{ upload_date_index_padded }", - "episode_number_and_padded_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", - "episode_number_and_padded__sanitized": "{ %sanitize( [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] ) }", - "episode_number_padded": "{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) }", - "episode_number_padded_sanitized": "{ %sanitize( %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ) ) }", - "episode_number_sanitized": "{ %sanitize( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ) }", - "episode_plot": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "episode_plot_sanitized": "{ %sanitize( %concat( %map_get( entry_metadata, \"webpage_url\" ), \"\n\n\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", - "episode_title": "{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", - "episode_title_sanitized": "{ %sanitize( %concat( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), \" - \", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "episode_year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }", - "episode_year_sanitized": "{ %sanitize( %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) ) }", - "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", - "epoch_date": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) }", - "epoch_date_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", - "epoch_hour": "{ %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) }", - "epoch_hour_sanitized": "{ %sanitize( %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%H\" ) ) }", - "epoch_sanitized": "{ %sanitize( %map_get( entry_metadata, \"epoch\" ) ) }", - "ext": "{ %throw( \"Plugin variable ext has not been created yet\" ) }", - "ext_sanitized": "{ %sanitize( ext ) }", - "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", - "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", - "extractor_key_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor_key\" ) ) }", - "extractor_sanitized": "{ %sanitize( %map_get( entry_metadata, \"extractor\" ) ) }", - "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "file_title_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", - "file_uid_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", - "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "height_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"height\", 0 ) ) }", - "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) }", - "ie_key_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"ie_key\", %map_get( entry_metadata, \"extractor_key\" ) ) ) }", - "include_sibling_metadata": "{ %bool(False) }", - "include_sibling_metadata_sanitized": "false", - "info_json_ext": "info.json", - "info_json_ext_sanitized": "info.json", - "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "modified_webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", - "only_recent_date_range": "2months", - "only_recent_date_range_sanitized": "2months", - "only_recent_max_files": "{ %int(30) }", - "only_recent_max_files_sanitized": "30", - "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", - "playlist_count_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) ) }", - "playlist_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) }", - "playlist_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", - "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", - "playlist_index_padded": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) }", - "playlist_index_padded6": "{ %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) }", - "playlist_index_padded6_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 6 ) ) }", - "playlist_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), 2 ) ) }", - "playlist_index_reversed": "{ %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) }", - "playlist_index_reversed_padded": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) }", - "playlist_index_reversed_padded6": "{ %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) }", - "playlist_index_reversed_padded6_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 6 ) ) }", - "playlist_index_reversed_padded_sanitized": "{ %sanitize( %pad_zero( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ), 2 ) ) }", - "playlist_index_reversed_sanitized": "{ %sanitize( %sub( %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ), %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ), -1 ) ) }", - "playlist_index_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) ) }", - "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", - "playlist_max_upload_date_sanitized": "{ %sanitize( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ) }", - "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) }", - "playlist_max_upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year\" ) ) ) }", - "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) }", - "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) ), \"year_truncated\" ) ) ) }", - "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", - "playlist_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) ) }", - "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "playlist_title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "playlist_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) }", - "playlist_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "playlist_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "playlist_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "playlist_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "playlist_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", - "playlist_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "playlist_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", - "playlist_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", - "release_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ) }", - "release_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) }", - "release_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"date_standardized\" ) ) ) }", - "release_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) }", - "release_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) }", - "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) }", - "release_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_padded\" ) ) ) }", - "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) }", - "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) }", - "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", - "release_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year_reversed\" ) ) ) }", - "release_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_of_year\" ) ) ) }", - "release_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) }", - "release_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_padded\" ) ) ) }", - "release_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) }", - "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) }", - "release_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed_padded\" ) ) ) }", - "release_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day_reversed\" ) ) ) }", - "release_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"day\" ) ) ) }", - "release_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) }", - "release_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) }", - "release_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_padded\" ) ) ) }", - "release_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) }", - "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) }", - "release_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed_padded\" ) ) ) }", - "release_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month_reversed\" ) ) ) }", - "release_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"month\" ) ) ) }", - "release_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) }", - "release_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year\" ) ) ) }", - "release_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) }", - "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) }", - "release_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated_reversed\" ) ) ) }", - "release_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"release_date\", %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) ), \"year_truncated\" ) ) ) }", - "requested_subtitles": "{ { } }", - "requested_subtitles_sanitized": "{}", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_height_gte_sanitized": "361", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_ignore_titles_sanitized": "[]", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) }", - "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), resolution_assert_ignore_titles ) ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_assert_print_sanitized": "true", - "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", - "resolution_readable": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }x{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "resolution_readable_sanitized": "{ %sanitize( %concat( %map_get_non_empty( entry_metadata, \"width\", 0 ), \"x\", %map_get_non_empty( entry_metadata, \"height\", 0 ) ) ) }", - "s01_name": "", - "s01_name_sanitized": "", - "s01_url": "", - "s01_url_sanitized": "", - "season_directory_name": "Season { %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "season_directory_name_sanitized": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }", - "season_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "season_number_padded": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "season_number_padded_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", - "season_number_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", - "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", - "season_poster_file_name_sanitized": "{ %sanitize( %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/Season\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".jpg\" ) ) }", - "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", - "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", - "source_count": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) }", - "source_count_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_count\", 1 ) ) }", - "source_description": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) }", - "source_description_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"description\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"description\", %map_get_non_empty( entry_metadata, \"description\", '' ) ) ) ) }", - "source_index": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) }", - "source_index_padded": "{ %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) }", - "source_index_padded_sanitized": "{ %sanitize( %pad_zero( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ), 2 ) ) }", - "source_index_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_index\", 1 ) ) }", - "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", - "source_metadata_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) ) }", - "source_title": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "source_title_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"title\", %map_get_non_empty( entry_metadata, \"playlist_title\", %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "source_uid": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "source_uid_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"id\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"playlist_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "source_uploader": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "source_uploader_id": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "source_uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_id\", %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", - "source_uploader_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"uploader\", %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }", - "source_uploader_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", - "source_uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"uploader_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) ) }", - "source_webpage_url": "{ %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "source_webpage_url_sanitized": "{ %sanitize( %map_get_non_empty( %map_get_non_empty( entry_metadata, \"source_metadata\", { } ), \"webpage_url\", %map_get_non_empty( %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ), \"webpage_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) ) }", - "sponsorblock_chapters": "{ [ ] }", - "sponsorblock_chapters_sanitized": "[]", - "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", - "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs\uff02]", - "subscription_has_download_archive": "{ %bool(False) }", - "subscription_has_download_archive_sanitized": "false", - "subscription_indent_1": "Documentaries", - "subscription_indent_1_sanitized": "Documentaries", - "subscription_indent_2": "TV-14", - "subscription_indent_2_sanitized": "TV-14", - "subscription_name": "NOVA PBS", - "subscription_name_sanitized": "NOVA PBS", - "subscription_value": "https://www.youtube.com/@novapbs", - "subscription_value_1": "https://www.youtube.com/@novapbs", - "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", - "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", - "thumbnail_ext": "jpg", - "thumbnail_ext_sanitized": "jpg", - "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }-thumb.jpg", - "thumbnail_file_name_sanitized": "{ %sanitize( %concat( %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ), \"-thumb.jpg\" ) ) }", - "title": "{ %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", - "title_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "title_sanitized_plex": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", - "title_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "tv_show_by_date_episode_ordering": "upload-month-day", - "tv_show_by_date_episode_ordering_sanitized": "upload-month-day", - "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] }", - "tv_show_by_date_ordering_pair_validation__sanitized": "{ %sanitize( [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), upload_date_index_padded ), 6 ] ) }", - "tv_show_by_date_season_ordering": "upload-year", - "tv_show_by_date_season_ordering_sanitized": "upload-year", - "tv_show_content_rating": "TV-14", - "tv_show_content_rating_default": "TV-14", - "tv_show_content_rating_default_sanitized": "TV-14", - "tv_show_content_rating_sanitized": "TV-14", - "tv_show_date_range_type": "upload_date", - "tv_show_date_range_type_sanitized": "upload_date", - "tv_show_directory": "tv_show_directory_path", - "tv_show_directory_sanitized": "tv_show_directory_path", - "tv_show_fanart_file_name": "fanart.jpg", - "tv_show_fanart_file_name_sanitized": "fanart.jpg", - "tv_show_genre": "Documentaries", - "tv_show_genre_default": "ytdl-sub", - "tv_show_genre_default_sanitized": "ytdl-sub", - "tv_show_genre_sanitized": "Documentaries", - "tv_show_name": "NOVA PBS", - "tv_show_name_sanitized": "NOVA PBS", - "tv_show_poster_file_name": "poster.jpg", - "tv_show_poster_file_name_sanitized": "poster.jpg", - "uid": "{ %map_get( entry_metadata, \"id\" ) }", - "uid_sanitized": "{ %sanitize( %map_get( entry_metadata, \"id\" ) ) }", - "uid_sanitized_plex": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", - "uid_sanitized_plex_sanitized": "{ %sanitize( %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) ) }", - "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) }", - "upload_date_index": "{ %int(1) }", - "upload_date_index_padded": "01", - "upload_date_index_padded_sanitized": "01", - "upload_date_index_reversed": "{ %int(99) }", - "upload_date_index_reversed_padded": "99", - "upload_date_index_reversed_padded_sanitized": "99", - "upload_date_index_reversed_sanitized": "99", - "upload_date_index_sanitized": "1", - "upload_date_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ) }", - "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", - "upload_date_standardized_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", - "upload_day": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) }", - "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) }", - "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) }", - "upload_day_of_year_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_padded\" ) ) ) }", - "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) }", - "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) }", - "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed_padded\" ) ) ) }", - "upload_day_of_year_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year_reversed\" ) ) ) }", - "upload_day_of_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_of_year\" ) ) ) }", - "upload_day_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) }", - "upload_day_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ) ) }", - "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) }", - "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) }", - "upload_day_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed_padded\" ) ) ) }", - "upload_day_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_reversed\" ) ) ) }", - "upload_day_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day\" ) ) ) }", - "upload_month": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }", - "upload_month_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) }", - "upload_month_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_padded\" ) ) ) }", - "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) }", - "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) }", - "upload_month_reversed_padded_sanitized": "{ %sanitize( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed_padded\" ) ) ) }", - "upload_month_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month_reversed\" ) ) ) }", - "upload_month_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) ) }", - "upload_year": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", - "upload_year_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) }", - "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) }", - "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) }", - "upload_year_truncated_reversed_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated_reversed\" ) ) ) }", - "upload_year_truncated_sanitized": "{ %sanitize( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year_truncated\" ) ) ) }", - "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) }", - "uploader_id_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) }", - "uploader_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader\", %map_get_non_empty( entry_metadata, \"uploader_id\", %map_get( entry_metadata, \"id\" ) ) ) ) }", - "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) }", - "uploader_url_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"uploader_url\", %map_get( entry_metadata, \"webpage_url\" ) ) ) }", - "url": "https://www.youtube.com/@novapbs", - "url10": "", - "url100": "", - "url100_sanitized": "", - "url10_sanitized": "", - "url11": "", - "url11_sanitized": "", - "url12": "", - "url12_sanitized": "", - "url13": "", - "url13_sanitized": "", - "url14": "", - "url14_sanitized": "", - "url15": "", - "url15_sanitized": "", - "url16": "", - "url16_sanitized": "", - "url17": "", - "url17_sanitized": "", - "url18": "", - "url18_sanitized": "", - "url19": "", - "url19_sanitized": "", - "url2": "", - "url20": "", - "url20_sanitized": "", - "url21": "", - "url21_sanitized": "", - "url22": "", - "url22_sanitized": "", - "url23": "", - "url23_sanitized": "", - "url24": "", - "url24_sanitized": "", - "url25": "", - "url25_sanitized": "", - "url26": "", - "url26_sanitized": "", - "url27": "", - "url27_sanitized": "", - "url28": "", - "url28_sanitized": "", - "url29": "", - "url29_sanitized": "", - "url2_sanitized": "", - "url3": "", - "url30": "", - "url30_sanitized": "", - "url31": "", - "url31_sanitized": "", - "url32": "", - "url32_sanitized": "", - "url33": "", - "url33_sanitized": "", - "url34": "", - "url34_sanitized": "", - "url35": "", - "url35_sanitized": "", - "url36": "", - "url36_sanitized": "", - "url37": "", - "url37_sanitized": "", - "url38": "", - "url38_sanitized": "", - "url39": "", - "url39_sanitized": "", - "url3_sanitized": "", - "url4": "", - "url40": "", - "url40_sanitized": "", - "url41": "", - "url41_sanitized": "", - "url42": "", - "url42_sanitized": "", - "url43": "", - "url43_sanitized": "", - "url44": "", - "url44_sanitized": "", - "url45": "", - "url45_sanitized": "", - "url46": "", - "url46_sanitized": "", - "url47": "", - "url47_sanitized": "", - "url48": "", - "url48_sanitized": "", - "url49": "", - "url49_sanitized": "", - "url4_sanitized": "", - "url5": "", - "url50": "", - "url50_sanitized": "", - "url51": "", - "url51_sanitized": "", - "url52": "", - "url52_sanitized": "", - "url53": "", - "url53_sanitized": "", - "url54": "", - "url54_sanitized": "", - "url55": "", - "url55_sanitized": "", - "url56": "", - "url56_sanitized": "", - "url57": "", - "url57_sanitized": "", - "url58": "", - "url58_sanitized": "", - "url59": "", - "url59_sanitized": "", - "url5_sanitized": "", - "url6": "", - "url60": "", - "url60_sanitized": "", - "url61": "", - "url61_sanitized": "", - "url62": "", - "url62_sanitized": "", - "url63": "", - "url63_sanitized": "", - "url64": "", - "url64_sanitized": "", - "url65": "", - "url65_sanitized": "", - "url66": "", - "url66_sanitized": "", - "url67": "", - "url67_sanitized": "", - "url68": "", - "url68_sanitized": "", - "url69": "", - "url69_sanitized": "", - "url6_sanitized": "", - "url7": "", - "url70": "", - "url70_sanitized": "", - "url71": "", - "url71_sanitized": "", - "url72": "", - "url72_sanitized": "", - "url73": "", - "url73_sanitized": "", - "url74": "", - "url74_sanitized": "", - "url75": "", - "url75_sanitized": "", - "url76": "", - "url76_sanitized": "", - "url77": "", - "url77_sanitized": "", - "url78": "", - "url78_sanitized": "", - "url79": "", - "url79_sanitized": "", - "url7_sanitized": "", - "url8": "", - "url80": "", - "url80_sanitized": "", - "url81": "", - "url81_sanitized": "", - "url82": "", - "url82_sanitized": "", - "url83": "", - "url83_sanitized": "", - "url84": "", - "url84_sanitized": "", - "url85": "", - "url85_sanitized": "", - "url86": "", - "url86_sanitized": "", - "url87": "", - "url87_sanitized": "", - "url88": "", - "url88_sanitized": "", - "url89": "", - "url89_sanitized": "", - "url8_sanitized": "", - "url9": "", - "url90": "", - "url90_sanitized": "", - "url91": "", - "url91_sanitized": "", - "url92": "", - "url92_sanitized": "", - "url93": "", - "url93_sanitized": "", - "url94": "", - "url94_sanitized": "", - "url95": "", - "url95_sanitized": "", - "url96": "", - "url96_sanitized": "", - "url97": "", - "url97_sanitized": "", - "url98": "", - "url98_sanitized": "", - "url99": "", - "url99_sanitized": "", - "url9_sanitized": "", - "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", - "urls": "{ [ \"https://www.youtube.com/@novapbs\" ] }", - "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs\uff02]", - "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "webpage_url_sanitized": "{ %sanitize( %map_get( entry_metadata, \"webpage_url\" ) ) }", - "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", - "width_sanitized": "{ %sanitize( %map_get_non_empty( entry_metadata, \"width\", 0 ) ) }", - "ytdl_sub_chapters_from_comments": "{ %throw( \"Plugin variable ytdl_sub_chapters_from_comments has not been created yet\" ) }", - "ytdl_sub_chapters_from_comments_sanitized": "{ %sanitize( ytdl_sub_chapters_from_comments ) }", - "ytdl_sub_entry_date_eval": "{ %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) }", - "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( %string( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) ) ) }", - "ytdl_sub_input_url": "", - "ytdl_sub_input_url_count": "{ %int(0) }", - "ytdl_sub_input_url_count_sanitized": "0", - "ytdl_sub_input_url_index": "{ %int(-1) }", - "ytdl_sub_input_url_index_sanitized": "-1", - "ytdl_sub_input_url_sanitized": "" -} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_overrides.json b/tests/resources/expected_json/tv_show/inspect_overrides.json deleted file mode 100644 index 93916b4d..00000000 --- a/tests/resources/expected_json/tv_show/inspect_overrides.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "assert_not_collection": "{ %bool(True) }", - "avatar_uncropped_thumbnail_file_name": "poster.jpg", - "banner_uncropped_thumbnail_file_name": "fanart.jpg", - "enable_bilateral_scraping": "{ %bool(True) }", - "enable_resolution_assert": "{ %bool(True) }", - "enable_throttle_protection": "{ %bool(True) }", - "episode_content_rating": "TV-14", - "episode_date_standardized": "{ upload_date_standardized }", - "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) } - { title_sanitized_plex }", - "episode_file_path": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/{ %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) }", - "episode_number": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", - "episode_number_and_padded_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", - "episode_number_padded": "{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) }", - "episode_plot": "{ webpage_url }\n\n{ description }", - "episode_title": "{ upload_date_standardized } - { title }", - "episode_year": "{ %slice( upload_date_standardized, 0, 4 ) }", - "file_title": "{ title_sanitized_plex }", - "file_uid": "{ uid_sanitized_plex }", - "include_sibling_metadata": "{ %bool(False) }", - "modified_webpage_url": "{ webpage_url }", - "only_recent_date_range": "2months", - "only_recent_max_files": "{ %int(30) }", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_readable": "{ width }x{ height }", - "s01_name": "", - "s01_url": "", - "season_directory_name": "Season { upload_year }", - "season_number": "{ upload_year }", - "season_number_padded": "{ upload_year }", - "season_poster_file_name": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/Season{ upload_year }.jpg", - "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", - "subscription_indent_1": "Documentaries", - "subscription_indent_2": "TV-14", - "subscription_value": "https://www.youtube.com/@novapbs", - "subscription_value_1": "https://www.youtube.com/@novapbs", - "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) }-thumb.jpg", - "tv_show_by_date_episode_ordering": "upload-month-day", - "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", - "tv_show_by_date_season_ordering": "upload-year", - "tv_show_content_rating": "TV-14", - "tv_show_content_rating_default": "TV-14", - "tv_show_date_range_type": "upload_date", - "tv_show_directory": "tv_show_directory_path", - "tv_show_fanart_file_name": "fanart.jpg", - "tv_show_genre": "Documentaries", - "tv_show_genre_default": "ytdl-sub", - "tv_show_name": "NOVA PBS", - "tv_show_poster_file_name": "poster.jpg", - "url": "https://www.youtube.com/@novapbs", - "url10": "", - "url100": "", - "url11": "", - "url12": "", - "url13": "", - "url14": "", - "url15": "", - "url16": "", - "url17": "", - "url18": "", - "url19": "", - "url2": "", - "url20": "", - "url21": "", - "url22": "", - "url23": "", - "url24": "", - "url25": "", - "url26": "", - "url27": "", - "url28": "", - "url29": "", - "url3": "", - "url30": "", - "url31": "", - "url32": "", - "url33": "", - "url34": "", - "url35": "", - "url36": "", - "url37": "", - "url38": "", - "url39": "", - "url4": "", - "url40": "", - "url41": "", - "url42": "", - "url43": "", - "url44": "", - "url45": "", - "url46": "", - "url47": "", - "url48": "", - "url49": "", - "url5": "", - "url50": "", - "url51": "", - "url52": "", - "url53": "", - "url54": "", - "url55": "", - "url56": "", - "url57": "", - "url58": "", - "url59": "", - "url6": "", - "url60": "", - "url61": "", - "url62": "", - "url63": "", - "url64": "", - "url65": "", - "url66": "", - "url67": "", - "url68": "", - "url69": "", - "url7": "", - "url70": "", - "url71": "", - "url72": "", - "url73": "", - "url74": "", - "url75": "", - "url76": "", - "url77": "", - "url78": "", - "url79": "", - "url8": "", - "url80": "", - "url81": "", - "url82": "", - "url83": "", - "url84": "", - "url85": "", - "url86": "", - "url87": "", - "url88": "", - "url89": "", - "url9": "", - "url90": "", - "url91": "", - "url92": "", - "url93": "", - "url94": "", - "url95": "", - "url96": "", - "url97": "", - "url98": "", - "url99": "", - "urls": "{ [ \"https://www.youtube.com/@novapbs\" ] }" -} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_sub_fill.json b/tests/resources/expected_json/tv_show/inspect_sub_fill.json new file mode 100644 index 00000000..6d1a8662 --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_sub_fill.json @@ -0,0 +1,250 @@ +{ + "chapters": { + "allow_chapters_from_comments": false, + "embed_chapters": true, + "enable": true, + "force_key_frames": false + }, + "date_range": { + "breaks": true, + "enable": true, + "type": "upload_date" + }, + "download": [ + { + "download_reverse": true, + "include_sibling_metadata": false, + "playlist_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "url": [ + "https://www.youtube.com/@novapbs" + ], + "variables": {}, + "webpage_url": "{ modified_webpage_url }", + "ytdl_options": {} + } + ], + "file_convert": { + "convert_to": "mp4", + "convert_with": "yt-dlp", + "enable": true + }, + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-NOVA PBS-download-archive.json", + "file_name": "{ episode_file_path }.{ ext }", + "info_json_name": "{ episode_file_path }.{ info_json_ext }", + "keep_files_date_eval": "{ episode_date_standardized }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpc_p6gjjw/NOVA PBS", + "preserve_mtime": false, + "thumbnail_name": "{ thumbnail_file_name }" + }, + "overrides": { + "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, subscription_has_download_archive, %is_bilateral_url( $0 ) ), $0, '' ) }", + "%episode_ordering_": "{ %eq( %lower( tv_show_by_date_episode_ordering ), $0 ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "%ordering_pair_eq": "{ %eq( [ tv_show_by_date_season_ordering, tv_show_by_date_episode_ordering ], [ $0, $1 ] ) }", + "%season_ordering_": "{ %eq( %lower( tv_show_by_date_season_ordering ), $0 ) }", + "assert_not_collection": true, + "avatar_uncropped_thumbnail_file_name": "poster.jpg", + "banner_uncropped_thumbnail_file_name": "fanart.jpg", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "episode_content_rating": "TV-14", + "episode_date_standardized": "{ upload_date_standardized }", + "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) } - { title_sanitized_plex }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/{ %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) }", + "episode_number": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", + "episode_number_and_padded_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "episode_number_padded": "{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) }", + "episode_plot": "{ webpage_url }\n\n{ description }", + "episode_title": "{ upload_date_standardized } - { title }", + "episode_year": "{ %slice( upload_date_standardized, 0, 4 ) }", + "file_title": "{ title_sanitized_plex }", + "file_uid": "{ uid_sanitized_plex }", + "include_sibling_metadata": false, + "modified_webpage_url": "{ webpage_url }", + "only_recent_date_range": "2months", + "only_recent_max_files": 30, + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "s01_name": "", + "s01_url": "", + "season_directory_name": "Season { upload_year }", + "season_number": "{ upload_year }", + "season_number_padded": "{ upload_year }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/Season{ upload_year }.jpg", + "subscription_array": [ + "https://www.youtube.com/@novapbs" + ], + "subscription_indent_1": "Documentaries", + "subscription_indent_2": "TV-14", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) }-thumb.jpg", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_content_rating": "TV-14", + "tv_show_content_rating_default": "TV-14", + "tv_show_date_range_type": "upload_date", + "tv_show_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpc_p6gjjw", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_genre": "Documentaries", + "tv_show_genre_default": "ytdl-sub", + "tv_show_name": "NOVA PBS", + "tv_show_poster_file_name": "poster.jpg", + "url": "https://www.youtube.com/@novapbs", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/@novapbs" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "video_tags": { + "contentRating": "TV-14", + "date": "{ episode_date_standardized }", + "episode_id": "{ episode_number }", + "genre": "Documentaries", + "show": "NOVA PBS", + "synopsis": "{ episode_plot }", + "title": "{ episode_title }", + "year": "{ episode_year }" + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_sub_internal.json b/tests/resources/expected_json/tv_show/inspect_sub_internal.json new file mode 100644 index 00000000..3f1c1eaf --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_sub_internal.json @@ -0,0 +1,250 @@ +{ + "chapters": { + "allow_chapters_from_comments": false, + "embed_chapters": true, + "enable": true, + "force_key_frames": false + }, + "date_range": { + "breaks": true, + "enable": true, + "type": "upload_date" + }, + "download": [ + { + "download_reverse": true, + "include_sibling_metadata": false, + "playlist_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "url": [ + "https://www.youtube.com/@novapbs" + ], + "variables": {}, + "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "ytdl_options": {} + } + ], + "file_convert": { + "convert_to": "mp4", + "convert_with": "yt-dlp", + "enable": true + }, + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-NOVA PBS-download-archive.json", + "file_name": "{ %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), %pad_zero( upload_date_index, 2 ) ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }.{ ext }", + "info_json_name": "{ %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), %pad_zero( upload_date_index, 2 ) ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }.info.json", + "keep_files_date_eval": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpps6shcpt/NOVA PBS", + "preserve_mtime": false, + "thumbnail_name": "{ %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), %pad_zero( upload_date_index, 2 ) ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }-thumb.jpg" + }, + "overrides": { + "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, subscription_has_download_archive, %is_bilateral_url( $0 ) ), $0, '' ) }", + "%episode_ordering_": "{ %eq( %lower( tv_show_by_date_episode_ordering ), $0 ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "%ordering_pair_eq": "{ %eq( [ tv_show_by_date_season_ordering, tv_show_by_date_episode_ordering ], [ $0, $1 ] ) }", + "%season_ordering_": "{ %eq( %lower( tv_show_by_date_season_ordering ), $0 ) }", + "assert_not_collection": true, + "avatar_uncropped_thumbnail_file_name": "poster.jpg", + "banner_uncropped_thumbnail_file_name": "fanart.jpg", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "episode_content_rating": "TV-14", + "episode_date_standardized": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "episode_file_name": "s{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.e{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), %pad_zero( upload_date_index, 2 ) ) ), 6 ) } - { %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/{ %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), %pad_zero( upload_date_index, 2 ) ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) }", + "episode_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }{ %pad_zero( upload_date_index, 2 ) }", + "episode_number_and_padded_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), %pad_zero( upload_date_index, 2 ) ), 6 ] }", + "episode_number_padded": "{ %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), %pad_zero( upload_date_index, 2 ) ) ), 6 ) }", + "episode_plot": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "episode_title": "{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "episode_year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }", + "file_title": "{ %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) }", + "file_uid": "{ %sanitize_plex_episode( %map_get( entry_metadata, \"id\" ) ) }", + "include_sibling_metadata": false, + "modified_webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", + "only_recent_date_range": "2months", + "only_recent_max_files": 30, + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ), [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "s01_name": "", + "s01_url": "", + "season_directory_name": "Season { %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_number": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_number_padded": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ) }/Season{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) }.jpg", + "subscription_array": [ + "https://www.youtube.com/@novapbs" + ], + "subscription_indent_1": "Documentaries", + "subscription_indent_2": "TV-14", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ) ) ), \"/\", %sanitize( %concat( \"s\", %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"year\" ) ), \".e\", %pad_zero( %int( %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), %pad_zero( upload_date_index, 2 ) ) ), 6 ), \" - \", %sanitize_plex_episode( %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) ) ) ) ) }-thumb.jpg", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ), %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) ), %pad_zero( upload_date_index, 2 ) ), 6 ] }", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_content_rating": "TV-14", + "tv_show_content_rating_default": "TV-14", + "tv_show_date_range_type": "upload_date", + "tv_show_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmpps6shcpt", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_genre": "Documentaries", + "tv_show_genre_default": "ytdl-sub", + "tv_show_name": "NOVA PBS", + "tv_show_poster_file_name": "poster.jpg", + "url": "https://www.youtube.com/@novapbs", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/@novapbs" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "video_tags": { + "contentRating": "TV-14", + "date": "{ %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ) }", + "episode_id": "{ %int( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"month\" ) ) }{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"day_padded\" ) }{ %pad_zero( upload_date_index, 2 ) }", + "genre": "Documentaries", + "show": "NOVA PBS", + "synopsis": "{ %map_get( entry_metadata, \"webpage_url\" ) }\n\n{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", + "title": "{ %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) } - { %map_get_non_empty( entry_metadata, \"title\", %map_get( entry_metadata, \"id\" ) ) }", + "year": "{ %slice( %string( %map_get( %to_date_metadata( %map_get_non_empty( entry_metadata, \"upload_date\", %datetime_strftime( %map_get( entry_metadata, \"epoch\" ), \"%Y%m%d\" ) ) ), \"date_standardized\" ) ), 0, 4 ) }" + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_sub_original.json b/tests/resources/expected_json/tv_show/inspect_sub_original.json new file mode 100644 index 00000000..0a7540d3 --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_sub_original.json @@ -0,0 +1,255 @@ +{ + "chapters": { + "embed_chapters": true + }, + "date_range": { + "type": "{tv_show_date_range_type}" + }, + "download": [ + { + "download_reverse": false, + "url": "{ %array_apply(urls, %bilateral_url) }", + "webpage_url": "{modified_webpage_url}", + "ytdl_options": { + "playlist_items": "-1:0:-1" + } + }, + { + "include_sibling_metadata": "{include_sibling_metadata}", + "playlist_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped" + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "{avatar_uncropped_thumbnail_file_name}", + "uid": "avatar_uncropped" + }, + { + "name": "{banner_uncropped_thumbnail_file_name}", + "uid": "banner_uncropped" + } + ], + "url": "{ %array_at(urls, 0) }", + "webpage_url": "{modified_webpage_url}" + }, + { + "include_sibling_metadata": "{include_sibling_metadata}", + "url": "{ %array_slice(urls, 1) }", + "webpage_url": "{modified_webpage_url}" + } + ], + "file_convert": { + "convert_to": "mp4" + }, + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "file_name": "{episode_file_path}.{ext}", + "info_json_name": "{episode_file_path}.{info_json_ext}", + "keep_files_date_eval": "{episode_date_standardized}", + "maintain_download_archive": true, + "output_directory": "{tv_show_directory}/{tv_show_name_sanitized}", + "thumbnail_name": "{thumbnail_file_name}" + }, + "overrides": { + "%bilateral_url": "{ \n %if(\n %and(\n enable_bilateral_scraping,\n subscription_has_download_archive,\n %is_bilateral_url($0)\n ),\n $0,\n \"\"\n )\n}", + "%episode_ordering_": "{ %eq( %lower(tv_show_by_date_episode_ordering), $0 ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "%ordering_pair_eq": "{\n %eq([tv_show_by_date_season_ordering, tv_show_by_date_episode_ordering], [$0, $1])\n}", + "%season_ordering_": "{ %eq( %lower(tv_show_by_date_season_ordering), $0 ) }", + "assert_not_collection": "{\n %assert(\n %and(\n %not( %bool(s01_url) ),\n %not( %bool(s01_name) )\n ),\n \"Provided `s01_url` or `s01_name` variable to TV Show by Date preset when it expects `url`. Perhaps you meant to use the `TV Show Collection` preset?\"\n )\n}", + "avatar_uncropped_thumbnail_file_name": "{tv_show_poster_file_name}", + "banner_uncropped_thumbnail_file_name": "{tv_show_fanart_file_name}", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "episode_content_rating": "{tv_show_content_rating}", + "episode_date_standardized": "{\n %if(\n %contains(tv_show_by_date_season_ordering, \"release\"),\n release_date_standardized,\n upload_date_standardized\n )\n}", + "episode_file_name": "s{season_number_padded}.e{episode_number_padded} - {file_title}", + "episode_file_path": "{season_directory_name_sanitized}/{episode_file_name_sanitized}", + "episode_number": "{ %array_at(episode_number_and_padded_, 0) }", + "episode_number_and_padded_": "{\n %elif(\n %episode_ordering_( \"upload-day\" ), [ %concat(upload_day, upload_date_index_padded), 4 ],\n %episode_ordering_( \"upload-month-day\" ), [ %concat(upload_month, upload_day_padded, upload_date_index_padded), 6],\n %episode_ordering_( \"upload-month-day-reversed\" ), [ %concat(upload_day_of_year_reversed, upload_date_index_reversed_padded), 5],\n %episode_ordering_( \"release-day\" ), [ %concat(release_day, upload_date_index_padded), 4 ],\n %episode_ordering_( \"release-month-day\" ), [ %concat(release_month, release_day_padded, upload_date_index_padded), 6],\n %episode_ordering_( \"release-month-day-reversed\" ), [ %concat(release_day_of_year_reversed, upload_date_index_reversed_padded), 5],\n %episode_ordering_( \"download-index\" ), [ download_index, 6 ],\n %throw(\n 'tv_show_by_date_episode_ordering must be one of the following: \"upload-day\", \"upload-month-day\", \"upload-month-day-reversed\", \"release-day\", \"release-month-day\", \"release-month-day-reversed\", \"download-index\"'\n )\n )\n}", + "episode_number_padded": "{ %pad_zero( %int(episode_number), %int(%array_at(episode_number_and_padded_, 1))) }", + "episode_plot": "{webpage_url}\n\n{description}", + "episode_title": "{episode_date_standardized} - {title}", + "episode_year": "{%slice(episode_date_standardized, 0, 4)}", + "file_title": "{title_sanitized_plex}", + "file_uid": "{uid_sanitized_plex}", + "include_sibling_metadata": false, + "modified_webpage_url": "{webpage_url}", + "only_recent_date_range": "2months", + "only_recent_max_files": 30, + "resolution_assert": "{\n %if(\n %and(\n enable_resolution_assert,\n %ne( height, 0 ),\n %not(resolution_assert_is_ignored)\n ),\n %assert(\n %gte( height, resolution_assert_height_gte ),\n %concat(\n \"Entry \",\n title,\n \" downloaded at a low resolution (\",\n resolution_readable,\n \"), you've probably been throttled. \",\n \"Stopping further downloads, wait a few hours and try again. \",\n \"Disable using the override variable `enable_resolution_assert: False`.\"\n )\n ),\n \"false is no-op\"\n )\n}", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [] }", + "resolution_assert_is_ignored": "{\n %print_if_true(\n %concat(title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\"),\n %contains_any(title, resolution_assert_ignore_titles)\n )\n}", + "resolution_assert_print": "{\n %print(\n %if(\n enable_resolution_assert,\n \"Resolution assert is enabled, will fail on low-quality video downloads and presume throttle. Disable using the override variable `enable_resolution_assert: False`\",\n \"Resolution assert is disabled. Use at your own risk!\"\n ),\n enable_resolution_assert,\n -1\n )\n}", + "resolution_readable": "{width}x{height}", + "s01_name": "", + "s01_url": "", + "season_directory_name": "Season {season_number_padded}", + "season_number": "{\n %elif(\n %season_ordering_( \"upload-year\" ), upload_year,\n %season_ordering_( \"upload-year-month\" ), %concat(upload_year, upload_month_padded),\n %season_ordering_( \"release-year\" ), release_year,\n %season_ordering_( \"release-year-month\" ), %concat(release_year, release_month_padded),\n %throw(\n 'tv_show_by_date_season_ordering must be one of the following: \"upload-year\", \"upload-year-month\", \"release-year\", \"release-year-month\"'\n )\n )\n}", + "season_number_padded": "{season_number}", + "season_poster_file_name": "{season_directory_name_sanitized}/Season{season_number_padded}.jpg", + "subscription_array": "{%from_json('''[\"https://www.youtube.com/@novapbs\"]''')}", + "subscription_indent_1": "Documentaries", + "subscription_indent_2": "{tv_show_content_rating_default}", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "thumbnail_file_name": "{episode_file_path}-thumb.jpg", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{\n %assert_then(\n %or(\n %ordering_pair_eq(\"upload-year\", \"upload-month-day\"),\n %ordering_pair_eq(\"upload-year\", \"upload-month-day-reversed\"),\n %ordering_pair_eq(\"upload-year\", \"download-index\"),\n %ordering_pair_eq(\"upload-year-month\", \"upload-day\"),\n %ordering_pair_eq(\"release-year\", \"release-month-day\"),\n %ordering_pair_eq(\"release-year\", \"release-month-day-reversed\"),\n %ordering_pair_eq(\"release-year\", \"download-index\"),\n %ordering_pair_eq(\"release-year-month\", \"release-day\")\n ),\n episode_number_and_padded_,\n \"Detected incompatibility between tv_show_by_date_season_ordering and tv_show_by_date_episode_ordering. Ensure you are not using both upload and release date, and that the year/month/day are included in the combined season and episode.\"\n )\n}", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_content_rating": "{subscription_indent_2}", + "tv_show_content_rating_default": "TV-14", + "tv_show_date_range_type": "{\n %if(\n %contains(tv_show_by_date_season_ordering, \"release\"),\n \"release_date\",\n \"upload_date\"\n )\n}", + "tv_show_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmp5yx9y73k", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_genre": "{subscription_indent_1}", + "tv_show_genre_default": "ytdl-sub", + "tv_show_name": "{subscription_name}", + "tv_show_poster_file_name": "poster.jpg", + "url": "{subscription_value}", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": "{subscription_array}" + }, + "preset": [ + "_season_by_year", + "plex_tv_show_by_date", + "season_by_year__episode_by_month_day", + "Plex TV Show by Date", + "__preset__" + ], + "throttle_protection": { + "enable": "{\n %print(\n %if(\n enable_throttle_protection,\n \"Throttle protection is enabled. Disable using the override variable `enable_throttle_protection: False`\",\n \"Throttle protection is disabled. Use at your own risk!\"\n ),\n enable_throttle_protection\n )\n}", + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "video_tags": { + "contentRating": "{episode_content_rating}", + "date": "{episode_date_standardized}", + "episode_id": "{episode_number}", + "genre": "{tv_show_genre}", + "show": "{tv_show_name}", + "synopsis": "{episode_plot}", + "title": "{episode_title}", + "year": "{episode_year}" + }, + "ytdl_options": { + "break_on_existing": true + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_sub_resolve.json b/tests/resources/expected_json/tv_show/inspect_sub_resolve.json new file mode 100644 index 00000000..3c2898ea --- /dev/null +++ b/tests/resources/expected_json/tv_show/inspect_sub_resolve.json @@ -0,0 +1,250 @@ +{ + "chapters": { + "allow_chapters_from_comments": false, + "embed_chapters": true, + "enable": true, + "force_key_frames": false + }, + "date_range": { + "breaks": true, + "enable": true, + "type": "upload_date" + }, + "download": [ + { + "download_reverse": true, + "include_sibling_metadata": false, + "playlist_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "source_thumbnails": [ + { + "name": "poster.jpg", + "uid": "avatar_uncropped" + }, + { + "name": "fanart.jpg", + "uid": "banner_uncropped" + } + ], + "url": [ + "https://www.youtube.com/@novapbs" + ], + "variables": {}, + "webpage_url": "{ webpage_url }", + "ytdl_options": {} + } + ], + "file_convert": { + "convert_to": "mp4", + "convert_with": "yt-dlp", + "enable": true + }, + "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", + "output_options": { + "download_archive_name": ".ytdl-sub-NOVA PBS-download-archive.json", + "file_name": "{ %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) }.{ ext }", + "info_json_name": "{ %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) }.{ info_json_ext }", + "keep_files_date_eval": "{ upload_date_standardized }", + "maintain_download_archive": true, + "output_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmp3j0cm_tw/NOVA PBS", + "preserve_mtime": false, + "thumbnail_name": "{ %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) }-thumb.jpg" + }, + "overrides": { + "%bilateral_url": "{ %if( %and( enable_bilateral_scraping, subscription_has_download_archive, %is_bilateral_url( $0 ) ), $0, '' ) }", + "%episode_ordering_": "{ %eq( %lower( tv_show_by_date_episode_ordering ), $0 ) }", + "%is_bilateral_url": "{ %contains( $0, \"youtube.com/playlist\" ) }", + "%ordering_pair_eq": "{ %eq( [ tv_show_by_date_season_ordering, tv_show_by_date_episode_ordering ], [ $0, $1 ] ) }", + "%season_ordering_": "{ %eq( %lower( tv_show_by_date_season_ordering ), $0 ) }", + "assert_not_collection": true, + "avatar_uncropped_thumbnail_file_name": "poster.jpg", + "banner_uncropped_thumbnail_file_name": "fanart.jpg", + "enable_bilateral_scraping": true, + "enable_resolution_assert": true, + "enable_throttle_protection": true, + "episode_content_rating": "TV-14", + "episode_date_standardized": "{ upload_date_standardized }", + "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) } - { title_sanitized_plex }", + "episode_file_path": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/{ %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) }", + "episode_number": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", + "episode_number_and_padded_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "episode_number_padded": "{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) }", + "episode_plot": "{ webpage_url }\n\n{ description }", + "episode_title": "{ upload_date_standardized } - { title }", + "episode_year": "{ %slice( upload_date_standardized, 0, 4 ) }", + "file_title": "{ title_sanitized_plex }", + "file_uid": "{ uid_sanitized_plex }", + "include_sibling_metadata": false, + "modified_webpage_url": "{ webpage_url }", + "only_recent_date_range": "2months", + "only_recent_max_files": 30, + "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", + "resolution_assert_height_gte": 361, + "resolution_assert_ignore_titles": "{ [ ] }", + "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, [ ] ) ) }", + "resolution_assert_print": true, + "resolution_readable": "{ width }x{ height }", + "s01_name": "", + "s01_url": "", + "season_directory_name": "Season { upload_year }", + "season_number": "{ upload_year }", + "season_number_padded": "{ upload_year }", + "season_poster_file_name": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/Season{ upload_year }.jpg", + "subscription_array": [ + "https://www.youtube.com/@novapbs" + ], + "subscription_indent_1": "Documentaries", + "subscription_indent_2": "TV-14", + "subscription_value": "https://www.youtube.com/@novapbs", + "subscription_value_1": "https://www.youtube.com/@novapbs", + "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) }-thumb.jpg", + "tv_show_by_date_episode_ordering": "upload-month-day", + "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", + "tv_show_by_date_season_ordering": "upload-year", + "tv_show_content_rating": "TV-14", + "tv_show_content_rating_default": "TV-14", + "tv_show_date_range_type": "upload_date", + "tv_show_directory": "/var/folders/rw/hl1xmkmj68zdl2kjx3l0dwzc0000gn/T/tmp3j0cm_tw", + "tv_show_fanart_file_name": "fanart.jpg", + "tv_show_genre": "Documentaries", + "tv_show_genre_default": "ytdl-sub", + "tv_show_name": "NOVA PBS", + "tv_show_poster_file_name": "poster.jpg", + "url": "https://www.youtube.com/@novapbs", + "url10": "", + "url100": "", + "url11": "", + "url12": "", + "url13": "", + "url14": "", + "url15": "", + "url16": "", + "url17": "", + "url18": "", + "url19": "", + "url2": "", + "url20": "", + "url21": "", + "url22": "", + "url23": "", + "url24": "", + "url25": "", + "url26": "", + "url27": "", + "url28": "", + "url29": "", + "url3": "", + "url30": "", + "url31": "", + "url32": "", + "url33": "", + "url34": "", + "url35": "", + "url36": "", + "url37": "", + "url38": "", + "url39": "", + "url4": "", + "url40": "", + "url41": "", + "url42": "", + "url43": "", + "url44": "", + "url45": "", + "url46": "", + "url47": "", + "url48": "", + "url49": "", + "url5": "", + "url50": "", + "url51": "", + "url52": "", + "url53": "", + "url54": "", + "url55": "", + "url56": "", + "url57": "", + "url58": "", + "url59": "", + "url6": "", + "url60": "", + "url61": "", + "url62": "", + "url63": "", + "url64": "", + "url65": "", + "url66": "", + "url67": "", + "url68": "", + "url69": "", + "url7": "", + "url70": "", + "url71": "", + "url72": "", + "url73": "", + "url74": "", + "url75": "", + "url76": "", + "url77": "", + "url78": "", + "url79": "", + "url8": "", + "url80": "", + "url81": "", + "url82": "", + "url83": "", + "url84": "", + "url85": "", + "url86": "", + "url87": "", + "url88": "", + "url89": "", + "url9": "", + "url90": "", + "url91": "", + "url92": "", + "url93": "", + "url94": "", + "url95": "", + "url96": "", + "url97": "", + "url98": "", + "url99": "", + "urls": [ + "https://www.youtube.com/@novapbs" + ] + }, + "throttle_protection": { + "enable": true, + "sleep_per_download_s": { + "max": 28.4, + "min": 13.8 + }, + "sleep_per_request_s": { + "max": 0.75, + "min": 0.0 + }, + "sleep_per_subscription_s": { + "max": 26.1, + "min": 16.3 + } + }, + "video_tags": { + "contentRating": "TV-14", + "date": "{ upload_date_standardized }", + "episode_id": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", + "genre": "Documentaries", + "show": "NOVA PBS", + "synopsis": "{ webpage_url }\n\n{ description }", + "title": "{ upload_date_standardized } - { title }", + "year": "{ %slice( upload_date_standardized, 0, 4 ) }" + } +} \ No newline at end of file diff --git a/tests/resources/expected_json/tv_show/inspect_variables.json b/tests/resources/expected_json/tv_show/inspect_variables.json deleted file mode 100644 index f3159ff9..00000000 --- a/tests/resources/expected_json/tv_show/inspect_variables.json +++ /dev/null @@ -1,520 +0,0 @@ -{ - "assert_not_collection": "{ %bool(True) }", - "assert_not_collection_sanitized": "true", - "avatar_uncropped_thumbnail_file_name": "poster.jpg", - "avatar_uncropped_thumbnail_file_name_sanitized": "poster.jpg", - "banner_uncropped_thumbnail_file_name": "fanart.jpg", - "banner_uncropped_thumbnail_file_name_sanitized": "fanart.jpg", - "channel": "{ %map_get_non_empty( entry_metadata, \"channel\", uploader ) }", - "channel_id": "{ %map_get_non_empty( entry_metadata, \"channel_id\", uploader_id ) }", - "channel_id_sanitized": "{ %sanitize( channel_id ) }", - "channel_sanitized": "{ %sanitize( channel ) }", - "chapters": "{ [ ] }", - "chapters_sanitized": "{ %sanitize( chapters ) }", - "comments": "{ [ ] }", - "comments_sanitized": "{ %sanitize( comments ) }", - "creator": "{ %map_get_non_empty( entry_metadata, \"creator\", channel ) }", - "creator_sanitized": "{ %sanitize( creator ) }", - "description": "{ %map_get_non_empty( entry_metadata, \"description\", '' ) }", - "description_sanitized": "{ %sanitize( description ) }", - "download_index": "{ %int( 1 ) }", - "download_index_padded6": "{ %pad_zero( download_index, 6 ) }", - "download_index_padded6_sanitized": "{ %sanitize( download_index_padded6 ) }", - "download_index_sanitized": "{ %sanitize( download_index ) }", - "duration": "{ %map_get_non_empty( entry_metadata, \"duration\", 0 ) }", - "duration_sanitized": "{ %sanitize( duration ) }", - "enable_bilateral_scraping": "{ %bool(True) }", - "enable_bilateral_scraping_sanitized": "true", - "enable_resolution_assert": "{ %bool(True) }", - "enable_resolution_assert_sanitized": "true", - "enable_throttle_protection": "{ %bool(True) }", - "enable_throttle_protection_sanitized": "true", - "entry_metadata": "{ { } }", - "entry_metadata_sanitized": "{ %sanitize( entry_metadata ) }", - "episode_content_rating": "TV-14", - "episode_content_rating_sanitized": "TV-14", - "episode_date_standardized": "{ upload_date_standardized }", - "episode_date_standardized_sanitized": "{ %sanitize( upload_date_standardized ) }", - "episode_file_name": "s{ upload_year }.e{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) } - { title_sanitized_plex }", - "episode_file_name_sanitized": "{ %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) }", - "episode_file_path": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/{ %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) }", - "episode_file_path_sanitized": "{ %sanitize( %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) ) }", - "episode_number": "{ upload_month }{ upload_day_padded }{ upload_date_index_padded }", - "episode_number_and_padded_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", - "episode_number_and_padded__sanitized": "{ %sanitize( [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] ) }", - "episode_number_padded": "{ %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) }", - "episode_number_padded_sanitized": "{ %sanitize( %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ) ) }", - "episode_number_sanitized": "{ %sanitize( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ) }", - "episode_plot": "{ webpage_url }\n\n{ description }", - "episode_plot_sanitized": "{ %sanitize( %concat( webpage_url, \"\n\n\", description ) ) }", - "episode_title": "{ upload_date_standardized } - { title }", - "episode_title_sanitized": "{ %sanitize( %concat( upload_date_standardized, \" - \", title ) ) }", - "episode_year": "{ %slice( upload_date_standardized, 0, 4 ) }", - "episode_year_sanitized": "{ %sanitize( %slice( upload_date_standardized, 0, 4 ) ) }", - "epoch": "{ %map_get( entry_metadata, \"epoch\" ) }", - "epoch_date": "{ %datetime_strftime( epoch, \"%Y%m%d\" ) }", - "epoch_date_sanitized": "{ %sanitize( epoch_date ) }", - "epoch_hour": "{ %datetime_strftime( epoch, \"%H\" ) }", - "epoch_hour_sanitized": "{ %sanitize( epoch_hour ) }", - "epoch_sanitized": "{ %sanitize( epoch ) }", - "ext": "{ %throw( \"Plugin variable ext has not been created yet\" ) }", - "ext_sanitized": "{ %sanitize( ext ) }", - "extractor": "{ %map_get( entry_metadata, \"extractor\" ) }", - "extractor_key": "{ %map_get( entry_metadata, \"extractor_key\" ) }", - "extractor_key_sanitized": "{ %sanitize( extractor_key ) }", - "extractor_sanitized": "{ %sanitize( extractor ) }", - "file_title": "{ title_sanitized_plex }", - "file_title_sanitized": "{ %sanitize( title_sanitized_plex ) }", - "file_uid": "{ uid_sanitized_plex }", - "file_uid_sanitized": "{ %sanitize( uid_sanitized_plex ) }", - "height": "{ %map_get_non_empty( entry_metadata, \"height\", 0 ) }", - "height_sanitized": "{ %sanitize( height ) }", - "ie_key": "{ %map_get_non_empty( entry_metadata, \"ie_key\", extractor_key ) }", - "ie_key_sanitized": "{ %sanitize( ie_key ) }", - "include_sibling_metadata": "{ %bool(False) }", - "include_sibling_metadata_sanitized": "false", - "info_json_ext": "info.json", - "info_json_ext_sanitized": "info.json", - "modified_webpage_url": "{ webpage_url }", - "modified_webpage_url_sanitized": "{ %sanitize( webpage_url ) }", - "only_recent_date_range": "2months", - "only_recent_date_range_sanitized": "2months", - "only_recent_max_files": "{ %int(30) }", - "only_recent_max_files_sanitized": "30", - "playlist_count": "{ %map_get_non_empty( entry_metadata, \"playlist_count\", 1 ) }", - "playlist_count_sanitized": "{ %sanitize( playlist_count ) }", - "playlist_description": "{ %map_get_non_empty( playlist_metadata, \"description\", description ) }", - "playlist_description_sanitized": "{ %sanitize( playlist_description ) }", - "playlist_index": "{ %map_get_non_empty( entry_metadata, \"playlist_index\", 1 ) }", - "playlist_index_padded": "{ %pad_zero( playlist_index, 2 ) }", - "playlist_index_padded6": "{ %pad_zero( playlist_index, 6 ) }", - "playlist_index_padded6_sanitized": "{ %sanitize( playlist_index_padded6 ) }", - "playlist_index_padded_sanitized": "{ %sanitize( playlist_index_padded ) }", - "playlist_index_reversed": "{ %sub( playlist_count, playlist_index, -1 ) }", - "playlist_index_reversed_padded": "{ %pad_zero( playlist_index_reversed, 2 ) }", - "playlist_index_reversed_padded6": "{ %pad_zero( playlist_index_reversed, 6 ) }", - "playlist_index_reversed_padded6_sanitized": "{ %sanitize( playlist_index_reversed_padded6 ) }", - "playlist_index_reversed_padded_sanitized": "{ %sanitize( playlist_index_reversed_padded ) }", - "playlist_index_reversed_sanitized": "{ %sanitize( playlist_index_reversed ) }", - "playlist_index_sanitized": "{ %sanitize( playlist_index ) }", - "playlist_max_upload_date": "{ %array_reduce( %if_passthrough( %extract_field_from_siblings( \"upload_date\" ), [ upload_date ] ), %max ) }", - "playlist_max_upload_date_sanitized": "{ %sanitize( playlist_max_upload_date ) }", - "playlist_max_upload_year": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year\" ) ) }", - "playlist_max_upload_year_sanitized": "{ %sanitize( playlist_max_upload_year ) }", - "playlist_max_upload_year_truncated": "{ %int( %map_get( %to_date_metadata( playlist_max_upload_date ), \"year_truncated\" ) ) }", - "playlist_max_upload_year_truncated_sanitized": "{ %sanitize( playlist_max_upload_year_truncated ) }", - "playlist_metadata": "{ %map_get_non_empty( entry_metadata, \"playlist_metadata\", { } ) }", - "playlist_metadata_sanitized": "{ %sanitize( playlist_metadata ) }", - "playlist_title": "{ %map_get_non_empty( entry_metadata, \"playlist_title\", title ) }", - "playlist_title_sanitized": "{ %sanitize( playlist_title ) }", - "playlist_uid": "{ %map_get_non_empty( playlist_metadata, \"playlist_id\", uid ) }", - "playlist_uid_sanitized": "{ %sanitize( playlist_uid ) }", - "playlist_uploader": "{ %map_get_non_empty( playlist_metadata, \"uploader\", uploader ) }", - "playlist_uploader_id": "{ %map_get_non_empty( entry_metadata, \"playlist_uploader_id\", uploader_id ) }", - "playlist_uploader_id_sanitized": "{ %sanitize( playlist_uploader_id ) }", - "playlist_uploader_sanitized": "{ %sanitize( playlist_uploader ) }", - "playlist_uploader_url": "{ %map_get_non_empty( playlist_metadata, \"uploader_url\", webpage_url ) }", - "playlist_uploader_url_sanitized": "{ %sanitize( playlist_uploader_url ) }", - "playlist_webpage_url": "{ %map_get_non_empty( playlist_metadata, \"webpage_url\", webpage_url ) }", - "playlist_webpage_url_sanitized": "{ %sanitize( playlist_webpage_url ) }", - "release_date": "{ %map_get_non_empty( entry_metadata, \"release_date\", upload_date ) }", - "release_date_sanitized": "{ %sanitize( release_date ) }", - "release_date_standardized": "{ %string( %map_get( %to_date_metadata( release_date ), \"date_standardized\" ) ) }", - "release_date_standardized_sanitized": "{ %sanitize( release_date_standardized ) }", - "release_day": "{ %int( %map_get( %to_date_metadata( release_date ), \"day\" ) ) }", - "release_day_of_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year\" ) ) }", - "release_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_padded\" ) ) }", - "release_day_of_year_padded_sanitized": "{ %sanitize( release_day_of_year_padded ) }", - "release_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed\" ) ) }", - "release_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_of_year_reversed_padded\" ) ) }", - "release_day_of_year_reversed_padded_sanitized": "{ %sanitize( release_day_of_year_reversed_padded ) }", - "release_day_of_year_reversed_sanitized": "{ %sanitize( release_day_of_year_reversed ) }", - "release_day_of_year_sanitized": "{ %sanitize( release_day_of_year ) }", - "release_day_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_padded\" ) ) }", - "release_day_padded_sanitized": "{ %sanitize( release_day_padded ) }", - "release_day_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"day_reversed\" ) ) }", - "release_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"day_reversed_padded\" ) ) }", - "release_day_reversed_padded_sanitized": "{ %sanitize( release_day_reversed_padded ) }", - "release_day_reversed_sanitized": "{ %sanitize( release_day_reversed ) }", - "release_day_sanitized": "{ %sanitize( release_day ) }", - "release_month": "{ %int( %map_get( %to_date_metadata( release_date ), \"month\" ) ) }", - "release_month_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_padded\" ) ) }", - "release_month_padded_sanitized": "{ %sanitize( release_month_padded ) }", - "release_month_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"month_reversed\" ) ) }", - "release_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( release_date ), \"month_reversed_padded\" ) ) }", - "release_month_reversed_padded_sanitized": "{ %sanitize( release_month_reversed_padded ) }", - "release_month_reversed_sanitized": "{ %sanitize( release_month_reversed ) }", - "release_month_sanitized": "{ %sanitize( release_month ) }", - "release_year": "{ %int( %map_get( %to_date_metadata( release_date ), \"year\" ) ) }", - "release_year_sanitized": "{ %sanitize( release_year ) }", - "release_year_truncated": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated\" ) ) }", - "release_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( release_date ), \"year_truncated_reversed\" ) ) }", - "release_year_truncated_reversed_sanitized": "{ %sanitize( release_year_truncated_reversed ) }", - "release_year_truncated_sanitized": "{ %sanitize( release_year_truncated ) }", - "requested_subtitles": "{ { } }", - "requested_subtitles_sanitized": "{ %sanitize( requested_subtitles ) }", - "resolution_assert": "{ %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) }", - "resolution_assert_height_gte": "{ %int(361) }", - "resolution_assert_height_gte_sanitized": "361", - "resolution_assert_ignore_titles": "{ [ ] }", - "resolution_assert_ignore_titles_sanitized": "[]", - "resolution_assert_is_ignored": "{ %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) }", - "resolution_assert_is_ignored_sanitized": "{ %sanitize( %print_if_true( %concat( title, \" has a match in resolution_assert_ignore_titles, skipping resolution assert.\" ), %contains_any( title, resolution_assert_ignore_titles ) ) ) }", - "resolution_assert_print": "{ %bool(True) }", - "resolution_assert_print_sanitized": "true", - "resolution_assert_sanitized": "{ %sanitize( %if( %and( enable_resolution_assert, %ne( height, 0 ), %not( resolution_assert_is_ignored ) ), %assert( %gte( height, resolution_assert_height_gte ), %concat( \"Entry \", title, \" downloaded at a low resolution (\", resolution_readable, \"), you've probably been throttled. \", \"Stopping further downloads, wait a few hours and try again. \", \"Disable using the override variable `enable_resolution_assert: False`.\" ) ), \"false is no-op\" ) ) }", - "resolution_readable": "{ width }x{ height }", - "resolution_readable_sanitized": "{ %sanitize( %concat( width, \"x\", height ) ) }", - "s01_name": "", - "s01_name_sanitized": "", - "s01_url": "", - "s01_url_sanitized": "", - "season_directory_name": "Season { upload_year }", - "season_directory_name_sanitized": "{ %sanitize( %concat( \"Season \", upload_year ) ) }", - "season_number": "{ upload_year }", - "season_number_padded": "{ upload_year }", - "season_number_padded_sanitized": "{ %sanitize( upload_year ) }", - "season_number_sanitized": "{ %sanitize( upload_year ) }", - "season_poster_file_name": "{ %sanitize( %concat( \"Season \", upload_year ) ) }/Season{ upload_year }.jpg", - "season_poster_file_name_sanitized": "{ %sanitize( %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/Season\", upload_year, \".jpg\" ) ) }", - "sibling_metadata": "{ %map_get_non_empty( entry_metadata, \"sibling_metadata\", [ ] ) }", - "sibling_metadata_sanitized": "{ %sanitize( sibling_metadata ) }", - "source_count": "{ %map_get_non_empty( playlist_metadata, \"playlist_count\", 1 ) }", - "source_count_sanitized": "{ %sanitize( source_count ) }", - "source_description": "{ %map_get_non_empty( source_metadata, \"description\", playlist_description ) }", - "source_description_sanitized": "{ %sanitize( source_description ) }", - "source_index": "{ %map_get_non_empty( playlist_metadata, \"playlist_index\", 1 ) }", - "source_index_padded": "{ %pad_zero( source_index, 2 ) }", - "source_index_padded_sanitized": "{ %sanitize( source_index_padded ) }", - "source_index_sanitized": "{ %sanitize( source_index ) }", - "source_metadata": "{ %map_get_non_empty( entry_metadata, \"source_metadata\", { } ) }", - "source_metadata_sanitized": "{ %sanitize( source_metadata ) }", - "source_title": "{ %map_get_non_empty( source_metadata, \"title\", playlist_title ) }", - "source_title_sanitized": "{ %sanitize( source_title ) }", - "source_uid": "{ %map_get_non_empty( source_metadata, \"id\", playlist_uid ) }", - "source_uid_sanitized": "{ %sanitize( source_uid ) }", - "source_uploader": "{ %map_get_non_empty( source_metadata, \"uploader\", playlist_uploader ) }", - "source_uploader_id": "{ %map_get_non_empty( source_metadata, \"uploader_id\", playlist_uploader_id ) }", - "source_uploader_id_sanitized": "{ %sanitize( source_uploader_id ) }", - "source_uploader_sanitized": "{ %sanitize( source_uploader ) }", - "source_uploader_url": "{ %map_get_non_empty( source_metadata, \"uploader_url\", source_webpage_url ) }", - "source_uploader_url_sanitized": "{ %sanitize( source_uploader_url ) }", - "source_webpage_url": "{ %map_get_non_empty( source_metadata, \"webpage_url\", playlist_webpage_url ) }", - "source_webpage_url_sanitized": "{ %sanitize( source_webpage_url ) }", - "sponsorblock_chapters": "{ [ ] }", - "sponsorblock_chapters_sanitized": "{ %sanitize( sponsorblock_chapters ) }", - "subscription_array": "{ [ \"https://www.youtube.com/@novapbs\" ] }", - "subscription_array_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs\uff02]", - "subscription_has_download_archive": "{ %bool(False) }", - "subscription_has_download_archive_sanitized": "false", - "subscription_indent_1": "Documentaries", - "subscription_indent_1_sanitized": "Documentaries", - "subscription_indent_2": "TV-14", - "subscription_indent_2_sanitized": "TV-14", - "subscription_name": "NOVA PBS", - "subscription_name_sanitized": "NOVA PBS", - "subscription_value": "https://www.youtube.com/@novapbs", - "subscription_value_1": "https://www.youtube.com/@novapbs", - "subscription_value_1_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", - "subscription_value_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", - "thumbnail_ext": "jpg", - "thumbnail_ext_sanitized": "jpg", - "thumbnail_file_name": "{ %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ) }-thumb.jpg", - "thumbnail_file_name_sanitized": "{ %sanitize( %concat( %concat( %sanitize( %concat( \"Season \", upload_year ) ), \"/\", %sanitize( %concat( \"s\", upload_year, \".e\", %pad_zero( %int( %concat( upload_month, upload_day_padded, upload_date_index_padded ) ), 6 ), \" - \", title_sanitized_plex ) ) ), \"-thumb.jpg\" ) ) }", - "title": "{ %map_get_non_empty( entry_metadata, \"title\", uid ) }", - "title_sanitized": "{ %sanitize( title ) }", - "title_sanitized_plex": "{ %sanitize_plex_episode( title ) }", - "title_sanitized_plex_sanitized": "{ %sanitize( title_sanitized_plex ) }", - "tv_show_by_date_episode_ordering": "upload-month-day", - "tv_show_by_date_episode_ordering_sanitized": "upload-month-day", - "tv_show_by_date_ordering_pair_validation_": "{ [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] }", - "tv_show_by_date_ordering_pair_validation__sanitized": "{ %sanitize( [ %concat( upload_month, upload_day_padded, upload_date_index_padded ), 6 ] ) }", - "tv_show_by_date_season_ordering": "upload-year", - "tv_show_by_date_season_ordering_sanitized": "upload-year", - "tv_show_content_rating": "TV-14", - "tv_show_content_rating_default": "TV-14", - "tv_show_content_rating_default_sanitized": "TV-14", - "tv_show_content_rating_sanitized": "TV-14", - "tv_show_date_range_type": "upload_date", - "tv_show_date_range_type_sanitized": "upload_date", - "tv_show_directory": "tv_show_directory_path", - "tv_show_directory_sanitized": "tv_show_directory_path", - "tv_show_fanart_file_name": "fanart.jpg", - "tv_show_fanart_file_name_sanitized": "fanart.jpg", - "tv_show_genre": "Documentaries", - "tv_show_genre_default": "ytdl-sub", - "tv_show_genre_default_sanitized": "ytdl-sub", - "tv_show_genre_sanitized": "Documentaries", - "tv_show_name": "NOVA PBS", - "tv_show_name_sanitized": "NOVA PBS", - "tv_show_poster_file_name": "poster.jpg", - "tv_show_poster_file_name_sanitized": "poster.jpg", - "uid": "{ %map_get( entry_metadata, \"id\" ) }", - "uid_sanitized": "{ %sanitize( uid ) }", - "uid_sanitized_plex": "{ %sanitize_plex_episode( uid ) }", - "uid_sanitized_plex_sanitized": "{ %sanitize( uid_sanitized_plex ) }", - "upload_date": "{ %map_get_non_empty( entry_metadata, \"upload_date\", epoch_date ) }", - "upload_date_index": "{ %int( 1 ) }", - "upload_date_index_padded": "{ %pad_zero( upload_date_index, 2 ) }", - "upload_date_index_padded_sanitized": "{ %sanitize( upload_date_index_padded ) }", - "upload_date_index_reversed": "{ %sub( 100, upload_date_index ) }", - "upload_date_index_reversed_padded": "{ %pad_zero( upload_date_index_reversed, 2 ) }", - "upload_date_index_reversed_padded_sanitized": "{ %sanitize( upload_date_index_reversed_padded ) }", - "upload_date_index_reversed_sanitized": "{ %sanitize( upload_date_index_reversed ) }", - "upload_date_index_sanitized": "{ %sanitize( upload_date_index ) }", - "upload_date_sanitized": "{ %sanitize( upload_date ) }", - "upload_date_standardized": "{ %string( %map_get( %to_date_metadata( upload_date ), \"date_standardized\" ) ) }", - "upload_date_standardized_sanitized": "{ %sanitize( upload_date_standardized ) }", - "upload_day": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day\" ) ) }", - "upload_day_of_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year\" ) ) }", - "upload_day_of_year_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_padded\" ) ) }", - "upload_day_of_year_padded_sanitized": "{ %sanitize( upload_day_of_year_padded ) }", - "upload_day_of_year_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed\" ) ) }", - "upload_day_of_year_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_of_year_reversed_padded\" ) ) }", - "upload_day_of_year_reversed_padded_sanitized": "{ %sanitize( upload_day_of_year_reversed_padded ) }", - "upload_day_of_year_reversed_sanitized": "{ %sanitize( upload_day_of_year_reversed ) }", - "upload_day_of_year_sanitized": "{ %sanitize( upload_day_of_year ) }", - "upload_day_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_padded\" ) ) }", - "upload_day_padded_sanitized": "{ %sanitize( upload_day_padded ) }", - "upload_day_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"day_reversed\" ) ) }", - "upload_day_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"day_reversed_padded\" ) ) }", - "upload_day_reversed_padded_sanitized": "{ %sanitize( upload_day_reversed_padded ) }", - "upload_day_reversed_sanitized": "{ %sanitize( upload_day_reversed ) }", - "upload_day_sanitized": "{ %sanitize( upload_day ) }", - "upload_month": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month\" ) ) }", - "upload_month_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_padded\" ) ) }", - "upload_month_padded_sanitized": "{ %sanitize( upload_month_padded ) }", - "upload_month_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"month_reversed\" ) ) }", - "upload_month_reversed_padded": "{ %string( %map_get( %to_date_metadata( upload_date ), \"month_reversed_padded\" ) ) }", - "upload_month_reversed_padded_sanitized": "{ %sanitize( upload_month_reversed_padded ) }", - "upload_month_reversed_sanitized": "{ %sanitize( upload_month_reversed ) }", - "upload_month_sanitized": "{ %sanitize( upload_month ) }", - "upload_year": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year\" ) ) }", - "upload_year_sanitized": "{ %sanitize( upload_year ) }", - "upload_year_truncated": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated\" ) ) }", - "upload_year_truncated_reversed": "{ %int( %map_get( %to_date_metadata( upload_date ), \"year_truncated_reversed\" ) ) }", - "upload_year_truncated_reversed_sanitized": "{ %sanitize( upload_year_truncated_reversed ) }", - "upload_year_truncated_sanitized": "{ %sanitize( upload_year_truncated ) }", - "uploader": "{ %map_get_non_empty( entry_metadata, \"uploader\", uploader_id ) }", - "uploader_id": "{ %map_get_non_empty( entry_metadata, \"uploader_id\", uid ) }", - "uploader_id_sanitized": "{ %sanitize( uploader_id ) }", - "uploader_sanitized": "{ %sanitize( uploader ) }", - "uploader_url": "{ %map_get_non_empty( entry_metadata, \"uploader_url\", webpage_url ) }", - "uploader_url_sanitized": "{ %sanitize( uploader_url ) }", - "url": "https://www.youtube.com/@novapbs", - "url10": "", - "url100": "", - "url100_sanitized": "", - "url10_sanitized": "", - "url11": "", - "url11_sanitized": "", - "url12": "", - "url12_sanitized": "", - "url13": "", - "url13_sanitized": "", - "url14": "", - "url14_sanitized": "", - "url15": "", - "url15_sanitized": "", - "url16": "", - "url16_sanitized": "", - "url17": "", - "url17_sanitized": "", - "url18": "", - "url18_sanitized": "", - "url19": "", - "url19_sanitized": "", - "url2": "", - "url20": "", - "url20_sanitized": "", - "url21": "", - "url21_sanitized": "", - "url22": "", - "url22_sanitized": "", - "url23": "", - "url23_sanitized": "", - "url24": "", - "url24_sanitized": "", - "url25": "", - "url25_sanitized": "", - "url26": "", - "url26_sanitized": "", - "url27": "", - "url27_sanitized": "", - "url28": "", - "url28_sanitized": "", - "url29": "", - "url29_sanitized": "", - "url2_sanitized": "", - "url3": "", - "url30": "", - "url30_sanitized": "", - "url31": "", - "url31_sanitized": "", - "url32": "", - "url32_sanitized": "", - "url33": "", - "url33_sanitized": "", - "url34": "", - "url34_sanitized": "", - "url35": "", - "url35_sanitized": "", - "url36": "", - "url36_sanitized": "", - "url37": "", - "url37_sanitized": "", - "url38": "", - "url38_sanitized": "", - "url39": "", - "url39_sanitized": "", - "url3_sanitized": "", - "url4": "", - "url40": "", - "url40_sanitized": "", - "url41": "", - "url41_sanitized": "", - "url42": "", - "url42_sanitized": "", - "url43": "", - "url43_sanitized": "", - "url44": "", - "url44_sanitized": "", - "url45": "", - "url45_sanitized": "", - "url46": "", - "url46_sanitized": "", - "url47": "", - "url47_sanitized": "", - "url48": "", - "url48_sanitized": "", - "url49": "", - "url49_sanitized": "", - "url4_sanitized": "", - "url5": "", - "url50": "", - "url50_sanitized": "", - "url51": "", - "url51_sanitized": "", - "url52": "", - "url52_sanitized": "", - "url53": "", - "url53_sanitized": "", - "url54": "", - "url54_sanitized": "", - "url55": "", - "url55_sanitized": "", - "url56": "", - "url56_sanitized": "", - "url57": "", - "url57_sanitized": "", - "url58": "", - "url58_sanitized": "", - "url59": "", - "url59_sanitized": "", - "url5_sanitized": "", - "url6": "", - "url60": "", - "url60_sanitized": "", - "url61": "", - "url61_sanitized": "", - "url62": "", - "url62_sanitized": "", - "url63": "", - "url63_sanitized": "", - "url64": "", - "url64_sanitized": "", - "url65": "", - "url65_sanitized": "", - "url66": "", - "url66_sanitized": "", - "url67": "", - "url67_sanitized": "", - "url68": "", - "url68_sanitized": "", - "url69": "", - "url69_sanitized": "", - "url6_sanitized": "", - "url7": "", - "url70": "", - "url70_sanitized": "", - "url71": "", - "url71_sanitized": "", - "url72": "", - "url72_sanitized": "", - "url73": "", - "url73_sanitized": "", - "url74": "", - "url74_sanitized": "", - "url75": "", - "url75_sanitized": "", - "url76": "", - "url76_sanitized": "", - "url77": "", - "url77_sanitized": "", - "url78": "", - "url78_sanitized": "", - "url79": "", - "url79_sanitized": "", - "url7_sanitized": "", - "url8": "", - "url80": "", - "url80_sanitized": "", - "url81": "", - "url81_sanitized": "", - "url82": "", - "url82_sanitized": "", - "url83": "", - "url83_sanitized": "", - "url84": "", - "url84_sanitized": "", - "url85": "", - "url85_sanitized": "", - "url86": "", - "url86_sanitized": "", - "url87": "", - "url87_sanitized": "", - "url88": "", - "url88_sanitized": "", - "url89": "", - "url89_sanitized": "", - "url8_sanitized": "", - "url9": "", - "url90": "", - "url90_sanitized": "", - "url91": "", - "url91_sanitized": "", - "url92": "", - "url92_sanitized": "", - "url93": "", - "url93_sanitized": "", - "url94": "", - "url94_sanitized": "", - "url95": "", - "url95_sanitized": "", - "url96": "", - "url96_sanitized": "", - "url97": "", - "url97_sanitized": "", - "url98": "", - "url98_sanitized": "", - "url99": "", - "url99_sanitized": "", - "url9_sanitized": "", - "url_sanitized": "https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs", - "urls": "{ [ \"https://www.youtube.com/@novapbs\" ] }", - "urls_sanitized": "[\uff02https\uff1a\u29f8\u29f8www.youtube.com\u29f8@novapbs\uff02]", - "webpage_url": "{ %map_get( entry_metadata, \"webpage_url\" ) }", - "webpage_url_sanitized": "{ %sanitize( webpage_url ) }", - "width": "{ %map_get_non_empty( entry_metadata, \"width\", 0 ) }", - "width_sanitized": "{ %sanitize( width ) }", - "ytdl_sub_chapters_from_comments": "{ %throw( \"Plugin variable ytdl_sub_chapters_from_comments has not been created yet\" ) }", - "ytdl_sub_chapters_from_comments_sanitized": "{ %sanitize( ytdl_sub_chapters_from_comments ) }", - "ytdl_sub_entry_date_eval": "{ %string( upload_date_standardized ) }", - "ytdl_sub_entry_date_eval_sanitized": "{ %sanitize( ytdl_sub_entry_date_eval ) }", - "ytdl_sub_input_url": "{ %string( '' ) }", - "ytdl_sub_input_url_count": "{ %int( 0 ) }", - "ytdl_sub_input_url_count_sanitized": "{ %sanitize( ytdl_sub_input_url_count ) }", - "ytdl_sub_input_url_index": "{ %int( -1 ) }", - "ytdl_sub_input_url_index_sanitized": "{ %sanitize( ytdl_sub_input_url_index ) }", - "ytdl_sub_input_url_sanitized": "{ %sanitize( ytdl_sub_input_url ) }" -} \ No newline at end of file diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index 930708ed..719ac9f4 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -8,6 +8,7 @@ import pytest import yaml from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.config.validators.variable_validation import ResolutionLevel from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.plugins.nfo_tags import NfoTagsOptions from ytdl_sub.subscriptions.subscription import Subscription @@ -521,69 +522,3 @@ def test_music_video_subscriptions(default_config: ConfigFile, music_video_subsc assert gnr_urls[0] == "https://www.youtube.com/playlist?list=PLOTK54q5K4INNXaHKtmXYr6J7CajWjqeJ" assert gnr.get("subscription_indent_1").native == "Rock" assert gnr_urls[1] == "https://www.youtube.com/watch?v=OldpIhHPsbs" - - -def test_default_docker_config_and_subscriptions( - docker_default_subscription_path: Path, output_directory: str -): - default_config = ConfigFile.from_file_path("docker/root/defaults/config.yaml") - default_subs = Subscription.from_file_path( - config=default_config, subscription_path=docker_default_subscription_path - ) - assert len(default_subs) == 1 - - resolved_yaml_as_json = yaml.safe_load(default_subs[0].resolved_yaml()) - - # Since this creates random values, ignore it for this test - assert "throttle_protection" in resolved_yaml_as_json - del resolved_yaml_as_json["throttle_protection"] - - assert resolved_yaml_as_json == { - "chapters": { - "allow_chapters_from_comments": False, - "embed_chapters": True, - "enable": "True", - "force_key_frames": False, - }, - "date_range": {"breaks": "True", "enable": "True", "type": "upload_date"}, - "download": [ - { - "download_reverse": "True", - "include_sibling_metadata": False, - "playlist_thumbnails": [ - {"name": "{avatar_uncropped_thumbnail_file_name}", "uid": "avatar_uncropped"}, - {"name": "{banner_uncropped_thumbnail_file_name}", "uid": "banner_uncropped"}, - ], - "source_thumbnails": [ - {"name": "{avatar_uncropped_thumbnail_file_name}", "uid": "avatar_uncropped"}, - {"name": "{banner_uncropped_thumbnail_file_name}", "uid": "banner_uncropped"}, - ], - "url": "https://www.youtube.com/@novapbs", - "variables": {}, - "webpage_url": "{modified_webpage_url}", - "ytdl_options": {}, - } - ], - "file_convert": {"convert_to": "mp4", "convert_with": "yt-dlp", "enable": "True"}, - "format": "(bv*[ext=mp4][vcodec~='^((he|a)vc|h26[45])']+ba[ext=m4a]) / (bv[ext=mp4]*+ba[ext=m4a]/b)", - "output_options": { - "download_archive_name": ".ytdl-sub-NOVA PBS-download-archive.json", - "file_name": "{episode_file_path}.{ext}", - "info_json_name": "{episode_file_path}.{info_json_ext}", - "keep_files_date_eval": "{episode_date_standardized}", - "maintain_download_archive": True, - "output_directory": f"{output_directory}/NOVA PBS", - "preserve_mtime": False, - "thumbnail_name": "{thumbnail_file_name}", - }, - "video_tags": { - "contentRating": "{episode_content_rating}", - "date": "{episode_date_standardized}", - "episode_id": "{episode_number}", - "genre": "{tv_show_genre}", - "show": "{tv_show_name}", - "synopsis": "{episode_plot}", - "title": "{episode_title}", - "year": "{episode_year}", - }, - } diff --git a/tests/unit/config/test_subscription_partial_resolve.py b/tests/unit/config/test_subscription_partial_resolve.py deleted file mode 100644 index 0a901fec..00000000 --- a/tests/unit/config/test_subscription_partial_resolve.py +++ /dev/null @@ -1,118 +0,0 @@ -from pathlib import Path - -from resources import expected_json - -from ytdl_sub.config.config_file import ConfigFile -from ytdl_sub.entries.script.variable_definitions import VARIABLES -from ytdl_sub.subscriptions.subscription import Subscription -from ytdl_sub.utils.script import ScriptUtils - - -def _ensure_partial_resolve( - sub: Subscription, preset_type: str, should_fully_resolve: bool -) -> None: - unresolvable = sub.plugins.get_all_variables( - additional_options=[sub.downloader_options, sub.output_options] - ) - unresolvable.add("entry_metadata") - unresolvable.add("sibling_metadata") - - if not should_fully_resolve: - unresolvable.update(VARIABLES.scripts().keys()) - - script = sub.overrides.script.add( - { - f"{preset_type}_directory": "tv_show_directory_path", - f"{preset_type}_directory_sanitized": "tv_show_directory_path", - } - ) - partial_script = script.resolve_partial(unresolvable=unresolvable) - - # Assert only overrides - out = { - name: ScriptUtils.to_native_script(partial_script._variables[name]) - for name in sub.overrides.keys - if not name.startswith("%") - } - - expected_out_filename = f"{preset_type}/inspect_overrides.json" - if should_fully_resolve: - expected_out_filename = f"{preset_type}/inspect_full_overrides.json" - - assert out == expected_json(out, expected_out_filename) - - # Assert all variables - out = { - name: ScriptUtils.to_native_script(partial_script._variables[name]) - for name in partial_script.variable_names - } - - expected_out_filename = f"{preset_type}/inspect_variables.json" - if should_fully_resolve: - expected_out_filename = f"{preset_type}/inspect_full_variables.json" - - assert out == expected_json(out, expected_out_filename) - - -def test_partial_resolve_tv_show(config_file: ConfigFile, tv_show_subscriptions_path: Path): - _ensure_partial_resolve( - sub=Subscription.from_file_path( - config=config_file, subscription_path=tv_show_subscriptions_path - )[0], - preset_type="tv_show", - should_fully_resolve=True, - ) - - -def test_partial_resolve_tv_show_full(config_file: ConfigFile, tv_show_subscriptions_path: Path): - _ensure_partial_resolve( - sub=Subscription.from_file_path( - config=config_file, subscription_path=tv_show_subscriptions_path - )[0], - preset_type="tv_show", - should_fully_resolve=False, - ) - - -def test_partial_resolve_music(default_config: ConfigFile, music_subscriptions_path: Path): - _ensure_partial_resolve( - sub=Subscription.from_file_path( - config=default_config, subscription_path=music_subscriptions_path - )[0], - preset_type="music", - should_fully_resolve=True, - ) - - -def test_partial_resolve_music_full(default_config: ConfigFile, music_subscriptions_path: Path): - _ensure_partial_resolve( - sub=Subscription.from_file_path( - config=default_config, subscription_path=music_subscriptions_path - )[0], - preset_type="music", - should_fully_resolve=False, - ) - - -def test_partial_resolve_music_video( - default_config: ConfigFile, music_video_subscription_path: Path -): - _ensure_partial_resolve( - sub=Subscription.from_file_path( - config=default_config, subscription_path=music_video_subscription_path - )[0], - preset_type="music_video", - should_fully_resolve=False, - ) - - -def test_partial_resolve_music_video_full( - default_config: ConfigFile, music_video_subscription_path: Path -): - _ensure_partial_resolve( - sub=Subscription.from_file_path( - config=default_config, subscription_path=music_video_subscription_path - )[0], - preset_type="music_video", - should_fully_resolve=True, - ) diff --git a/tests/unit/config/test_subscription_resolution.py b/tests/unit/config/test_subscription_resolution.py new file mode 100644 index 00000000..bfa04d16 --- /dev/null +++ b/tests/unit/config/test_subscription_resolution.py @@ -0,0 +1,92 @@ +from pathlib import Path + +import pytest +import yaml +from resources import expected_json + +from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.config.validators.variable_validation import ResolutionLevel +from ytdl_sub.subscriptions.subscription import Subscription +from ytdl_sub.utils.file_path import FilePathTruncater + + +def _ensure_resolved_yaml( + sub: Subscription, output_directory: str, preset_type: str, resolution_level: int +) -> None: + output_yaml = sub.resolved_yaml(resolution_level=resolution_level) + out = yaml.safe_load(output_yaml) + + expected_out_filename = ( + f"{preset_type}/inspect_sub_{ResolutionLevel.name_of(resolution_level)}.json" + ) + expected_out = expected_json(out, expected_out_filename) + + if resolution_level > ResolutionLevel.ORIGINAL: + output_path = Path(output_directory) + if "tv_show_directory" in expected_out["overrides"]: + output_path = output_path / sub.name + + expected_out["output_options"]["output_directory"] = FilePathTruncater.to_native_filepath( + str(output_path) + ) + + if "tv_show_directory" in expected_out["overrides"]: + expected_out["overrides"]["tv_show_directory"] = output_directory + if "music_directory" in expected_out["overrides"]: + expected_out["overrides"]["music_directory"] = output_directory + if "music_video_directory" in expected_out["overrides"]: + expected_out["overrides"]["music_video_directory"] = output_directory + + assert out == expected_out + + +@pytest.mark.parametrize("resolution_level", ResolutionLevel.all()) +class TestResolution: + + def test_resolution_tv_show( + self, + resolution_level: int, + default_config: ConfigFile, + tv_show_subscriptions_path: Path, + output_directory: str, + ): + _ensure_resolved_yaml( + sub=Subscription.from_file_path( + config=default_config, subscription_path=tv_show_subscriptions_path + )[0], + output_directory=output_directory, + preset_type="tv_show", + resolution_level=resolution_level, + ) + + def test_resolution_music( + self, + resolution_level: int, + default_config: ConfigFile, + music_subscriptions_path: Path, + output_directory: str, + ): + _ensure_resolved_yaml( + sub=Subscription.from_file_path( + config=default_config, subscription_path=music_subscriptions_path + )[0], + output_directory=output_directory, + preset_type="music", + resolution_level=resolution_level, + ) + + def test_resolution_music_video( + self, + resolution_level: int, + default_config: ConfigFile, + music_video_subscription_path: Path, + output_directory: str, + ): + _ensure_resolved_yaml( + sub=Subscription.from_file_path( + config=default_config, subscription_path=music_video_subscription_path + )[0], + output_directory=output_directory, + preset_type="music_video", + resolution_level=resolution_level, + ) diff --git a/tests/unit/prebuilt_presets/test_tv_show_by_date.py b/tests/unit/prebuilt_presets/test_tv_show_by_date.py index 115a2a61..2b21e050 100644 --- a/tests/unit/prebuilt_presets/test_tv_show_by_date.py +++ b/tests/unit/prebuilt_presets/test_tv_show_by_date.py @@ -1,6 +1,7 @@ import re import pytest +import yaml from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError from ytdl_sub.subscriptions.subscription import Subscription @@ -25,57 +26,67 @@ class TestTvShowByDatePreset: }, ) - def test_backward_compatibility_single(self, default_config): - a = Subscription.from_dict( - config=default_config, - preset_name="a", - preset_dict={ - "preset": "Jellyfin TV Show by Date", - "overrides": {"tv_show_directory": "abc", "url": "test_1"}, - }, - ) - - b = Subscription.from_dict( - config=default_config, - preset_name="a", - preset_dict={ - "preset": "Jellyfin TV Show by Date", - "overrides": {"tv_show_directory": "abc", "subscription_value": "test_1"}, - }, - ) - - assert a.resolved_yaml() == b.resolved_yaml() - - def test_backward_compatibility_multi(self, default_config): - a = Subscription.from_dict( - config=default_config, - preset_name="a", - preset_dict={ - "preset": "Jellyfin TV Show by Date", - "overrides": {"tv_show_directory": "abc", "url": "test_1", "url2": "test_2"}, - }, - ) - - b = Subscription.from_dict( - config=default_config, - preset_name="a", - preset_dict={ - "preset": "Jellyfin TV Show by Date", - "overrides": { - "tv_show_directory": "abc", - "subscription_array": ["test_1", "test_2"], + def test_backward_compatibility_single_download(self, default_config): + a = yaml.safe_load( + Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": {"tv_show_directory": "abc", "url": "test_1"}, }, - }, + ).resolved_yaml() ) - c = Subscription.from_dict( - config=default_config, - preset_name="a", - preset_dict={ - "preset": "Jellyfin TV Show by Date", - "overrides": {"tv_show_directory": "abc", "urls": ["test_1", "test_2"]}, - }, + b = yaml.safe_load( + Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": {"tv_show_directory": "abc", "subscription_value": "test_1"}, + }, + ).resolved_yaml() ) - assert a.resolved_yaml() == b.resolved_yaml() - assert a.resolved_yaml() == c.resolved_yaml() + assert a["download"] == b["download"] + + def test_backward_compatibility_multi_download(self, default_config): + a = yaml.safe_load( + Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": {"tv_show_directory": "abc", "url": "test_1", "url2": "test_2"}, + }, + ).resolved_yaml() + ) + + b = yaml.safe_load( + Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": { + "tv_show_directory": "abc", + "subscription_array": ["test_1", "test_2"], + }, + }, + ).resolved_yaml() + ) + + c = yaml.safe_load( + Subscription.from_dict( + config=default_config, + preset_name="a", + preset_dict={ + "preset": "Jellyfin TV Show by Date", + "overrides": {"tv_show_directory": "abc", "urls": ["test_1", "test_2"]}, + }, + ).resolved_yaml() + ) + + assert a["download"] == b["download"] + assert a["download"] == c["download"] diff --git a/tests/unit/script/functions/test_conditional_functions.py b/tests/unit/script/functions/test_conditional_functions.py index eff73214..d2078a80 100644 --- a/tests/unit/script/functions/test_conditional_functions.py +++ b/tests/unit/script/functions/test_conditional_functions.py @@ -3,6 +3,9 @@ import re import pytest from unit.script.conftest import single_variable_output +from ytdl_sub.script.script import Script +from ytdl_sub.script.types.syntax_tree import SyntaxTree +from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.utils.exceptions import FunctionRuntimeException @@ -135,3 +138,29 @@ class TestConditionalFunction: ): output = single_variable_output(function_str) assert output == expected_output + + def test_if_partial_resolve(self): + assert ( + Script( + { + "aa": "a", + "bb": "unresolvable!", + "cc": "{%if( true, aa, bb )}", + } + ) + .resolve_partial(unresolvable={"bb"}) + .get("cc") + .native + == "a" + ) + + def test_if_partial_resolve_unresolved(self): + assert Script( + { + "aa": "a", + "bb": "unresolvable!", + "cc": "{%if( false, aa, bb )}", + } + ).resolve_partial(unresolvable={"bb"}).definition_of("cc") == SyntaxTree( + ast=[Variable("bb")] + ) diff --git a/tests/unit/script/types/test_array.py b/tests/unit/script/types/test_array.py index f816b1bd..312ec325 100644 --- a/tests/unit/script/types/test_array.py +++ b/tests/unit/script/types/test_array.py @@ -10,6 +10,8 @@ from ytdl_sub.script.script_output import ScriptOutput from ytdl_sub.script.types.array import Array from ytdl_sub.script.types.resolvable import Float from ytdl_sub.script.types.resolvable import String +from ytdl_sub.script.types.syntax_tree import SyntaxTree +from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.utils.exceptions import InvalidSyntaxException @@ -117,3 +119,29 @@ class TestArray: ).resolve() == ScriptOutput( {"aa": String("a"), "bb": String("b"), "cc": String('return ["a", "b"]')} ) + + def test_partial_resolve(self): + assert ( + Script( + { + "aa": "a", + "bb": "unresolvable!", + "cc": "{%array_at( [aa, bb], 0 )}", + } + ) + .resolve_partial(unresolvable={"bb"}) + .get("cc") + .native + == "a" + ) + + def test_partial_resolve_unresolved(self): + assert Script( + { + "aa": "a", + "bb": "unresolvable!", + "cc": "{%array_at( [aa, bb], 1 )}", + } + ).resolve_partial(unresolvable={"bb"}).definition_of("cc") == SyntaxTree( + ast=[Variable("bb")] + ) diff --git a/tests/unit/script/types/test_custom_function.py b/tests/unit/script/types/test_custom_function.py index 61096cc3..17919cc0 100644 --- a/tests/unit/script/types/test_custom_function.py +++ b/tests/unit/script/types/test_custom_function.py @@ -5,7 +5,9 @@ import pytest from ytdl_sub.script.parser import CUSTOM_FUNCTION_ARGUMENTS_ONLY_ARGS from ytdl_sub.script.script import Script from ytdl_sub.script.script_output import ScriptOutput +from ytdl_sub.script.types.function import CustomFunction from ytdl_sub.script.types.resolvable import Integer +from ytdl_sub.script.types.syntax_tree import SyntaxTree from ytdl_sub.script.utils.exceptions import CycleDetected from ytdl_sub.script.utils.exceptions import FunctionDoesNotExist from ytdl_sub.script.utils.exceptions import InvalidCustomFunctionArgumentName @@ -251,3 +253,30 @@ class TestCustomFunction: "output": "{%mul(%func1(1), 1)}", } ) + + def test_partial_resolve_custom_functions_any_order_via_init(self): + assert ( + Script( + { + "%custom_cubed": "{%mul(%custom_square($0),$0)}", + "%custom_square": "{%mul($0, $0)}", + "output": "{%custom_cubed(3)}", + } + ) + .resolve_partial() + .get("output") + .native + == 27 + ) + + def test_partial_resolve_unresolved(self): + assert Script( + { + "aa": "nope", + "%custom_cubed": "{%mul(%custom_square($0),$0)}", + "%custom_square": "{%mul($0, aa)}", + "output": "{%custom_cubed(3)}", + } + ).resolve_partial(unresolvable={"aa"}).definition_of("output") == SyntaxTree( + ast=[CustomFunction(name="custom_cubed", args=[Integer(3)])] + ) diff --git a/tests/unit/script/types/test_lambda_function.py b/tests/unit/script/types/test_lambda_function.py index cd054811..0474a1d3 100644 --- a/tests/unit/script/types/test_lambda_function.py +++ b/tests/unit/script/types/test_lambda_function.py @@ -177,3 +177,20 @@ class TestLambdaFunctionIncompatibleNumArguments: match=re.escape("Cycle detected within these variables: two -> %times_two -> two"), ): Script({"%times_two": "{%mul($0, two)}", "two": "{%times_two(2)}"}) + + def test_partial_resolve_nested_lambda_custom_functions_within_custom_functions(self): + assert ( + Script( + { + "%nest4": "{%mul($0, 2)}", + "%nest3": "{%array_at(%array_apply([$0], %nest4), 0)}", + "%nest2": "{%array_at(%array_apply([$0], %nest3), 0)}", + "%nest1": "{%array_at(%array_apply([$0], %nest2), 0)}", + "output": "{%array_at(%array_apply([2], %nest1), 0)}", + } + ) + .resolve_partial() + .get("output") + .native + == 4 + ) From ced547b14a3e1c9f999fac8186d07e140a24e288 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Mon, 2 Feb 2026 10:23:08 -0800 Subject: [PATCH 40/46] [FEATURE] Arg to shuffle subscriptions (#1263) Closes https://github.com/jmbannon/ytdl-sub/issues/1234 Adds `--shuffle` support which shuffles the order of subscriptions when downloading. --- src/ytdl_sub/cli/entrypoint.py | 9 ++++++ src/ytdl_sub/cli/parsers/main.py | 11 +++++++ tests/integration/cli/test_entrypoint.py | 39 ++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/src/ytdl_sub/cli/entrypoint.py b/src/ytdl_sub/cli/entrypoint.py index b825d45e..29f4f482 100644 --- a/src/ytdl_sub/cli/entrypoint.py +++ b/src/ytdl_sub/cli/entrypoint.py @@ -1,5 +1,6 @@ import gc import os +import random import sys from datetime import datetime from pathlib import Path @@ -75,6 +76,7 @@ def _download_subscriptions_from_yaml_files( subscription_override_dict: Dict, update_with_info_json: bool, dry_run: bool, + shuffle: bool, ) -> List[Subscription]: """ Downloads all subscriptions from one or many subscription yaml files. @@ -91,6 +93,8 @@ def _download_subscriptions_from_yaml_files( Whether to actually download or update using existing info json dry_run Whether to dry run or not + shuffle + Whether to shuffle the subscription download order Returns ------- @@ -112,6 +116,10 @@ def _download_subscriptions_from_yaml_files( subscription_override_dict=subscription_override_dict, ) + if shuffle: + logger.info("Shuffling subscriptions") + random.shuffle(subscriptions) + for subscription in subscriptions: with subscription.exception_handling(): logger.info( @@ -253,6 +261,7 @@ def main() -> List[Subscription]: subscription_override_dict=subscription_override_dict, update_with_info_json=args.update_with_info_json, dry_run=args.dry_run, + shuffle=args.shuffle, ) # One-off download diff --git a/src/ytdl_sub/cli/parsers/main.py b/src/ytdl_sub/cli/parsers/main.py index aa26b591..eb9b49ca 100644 --- a/src/ytdl_sub/cli/parsers/main.py +++ b/src/ytdl_sub/cli/parsers/main.py @@ -172,6 +172,10 @@ class SubArguments: short="-o", long="--dl-override", ) + SHUFFLE = CLIArgument( + short="-sh", + long="--shuffle", + ) subscription_parser = subparsers.add_parser("sub") @@ -197,6 +201,13 @@ subscription_parser.add_argument( help="override all subscription config values using `dl` syntax, " "i.e. --dl-override='--ytdl_options.max_downloads 3'", ) +subscription_parser.add_argument( + SubArguments.SHUFFLE.short, + SubArguments.SHUFFLE.long, + action="store_true", + help="shuffle subscription order when downloading", + default=False, +) ################################################################################################### # DOWNLOAD PARSER diff --git a/tests/integration/cli/test_entrypoint.py b/tests/integration/cli/test_entrypoint.py index 084df414..90ea3579 100644 --- a/tests/integration/cli/test_entrypoint.py +++ b/tests/integration/cli/test_entrypoint.py @@ -9,6 +9,7 @@ import pytest from ytdl_sub.cli.entrypoint import _download_subscriptions_from_yaml_files from ytdl_sub.cli.entrypoint import main +from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.utils.exceptions import ExperimentalFeatureNotEnabled @@ -59,6 +60,7 @@ def test_subscription_logs_write_to_file( subscription_override_dict={}, update_with_info_json=False, dry_run=dry_run, + shuffle=False, ) except ValueError: assert not mock_success_output @@ -120,3 +122,40 @@ def test_update_with_info_json_requires_experimental_flag( pytest.raises(ExperimentalFeatureNotEnabled), ): _ = main() + + +def test_subscription_shuffle( + default_config: ConfigFile, + mock_subscription_download_factory: Callable, + music_video_subscription_path: Path, +): + + subscription_paths = [str(music_video_subscription_path)] + + with ( + patch.object( + Subscription, + "download", + new=mock_subscription_download_factory(mock_success_output=True), + ), + ): + out1 = _download_subscriptions_from_yaml_files( + config=default_config, + subscription_paths=subscription_paths, + subscription_matches=[], + subscription_override_dict={}, + update_with_info_json=False, + dry_run=True, + shuffle=True, + ) + out2 = _download_subscriptions_from_yaml_files( + config=default_config, + subscription_paths=subscription_paths, + subscription_matches=[], + subscription_override_dict={}, + update_with_info_json=False, + dry_run=True, + shuffle=True, + ) + + assert [sub.name for sub in out1] != [sub.name for sub in out2] From d526545abbfc0893ad3560522615fda0c83dd83c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:27:33 -0800 Subject: [PATCH 41/46] Bump yt-dlp[default] from 2026.1.29 to 2026.1.31 (#1430) Bumps [yt-dlp[default]](https://github.com/yt-dlp/yt-dlp) from 2026.1.29 to 2026.1.31. - [Release notes](https://github.com/yt-dlp/yt-dlp/releases) - [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md) - [Commits](https://github.com/yt-dlp/yt-dlp/compare/2026.01.29...2026.01.31) --- updated-dependencies: - dependency-name: yt-dlp[default] dependency-version: 2026.1.31 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 772e91cf..5bc3e86b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ - "yt-dlp[default]==2026.1.29", + "yt-dlp[default]==2026.1.31", "colorama~=0.4", "mergedeep~=1.3", "mediafile~=0.12", From e300c3d23a94bd9edeb9e72c56d4e74027bbe50f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:32:42 -0800 Subject: [PATCH 42/46] Bump yt-dlp[default] from 2026.1.31 to 2026.2.4 (#1432) Bumps [yt-dlp[default]](https://github.com/yt-dlp/yt-dlp) from 2026.1.31 to 2026.2.4. - [Release notes](https://github.com/yt-dlp/yt-dlp/releases) - [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md) - [Commits](https://github.com/yt-dlp/yt-dlp/compare/2026.01.31...2026.02.04) --- updated-dependencies: - dependency-name: yt-dlp[default] dependency-version: 2026.2.4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5bc3e86b..0a51cb8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ - "yt-dlp[default]==2026.1.31", + "yt-dlp[default]==2026.2.4", "colorama~=0.4", "mergedeep~=1.3", "mediafile~=0.12", From 3b5f9cba58c0d5a67318e2b94e5a529384fd1d66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:10:39 -0800 Subject: [PATCH 43/46] Bump yt-dlp[default] from 2026.2.4 to 2026.2.21 (#1438) Bumps [yt-dlp[default]](https://github.com/yt-dlp/yt-dlp) from 2026.2.4 to 2026.2.21. - [Release notes](https://github.com/yt-dlp/yt-dlp/releases) - [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md) - [Commits](https://github.com/yt-dlp/yt-dlp/compare/2026.02.04...2026.02.21) --- updated-dependencies: - dependency-name: yt-dlp[default] dependency-version: 2026.2.21 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0a51cb8f..bdc2535e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ - "yt-dlp[default]==2026.2.4", + "yt-dlp[default]==2026.2.21", "colorama~=0.4", "mergedeep~=1.3", "mediafile~=0.12", From 64820daf6c7df10115d5c10a3473402862cb31b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:21:53 -0800 Subject: [PATCH 44/46] Bump yt-dlp[default] from 2026.2.21 to 2026.3.3 (#1440) Bumps [yt-dlp[default]](https://github.com/yt-dlp/yt-dlp) from 2026.2.21 to 2026.3.3. - [Release notes](https://github.com/yt-dlp/yt-dlp/releases) - [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md) - [Commits](https://github.com/yt-dlp/yt-dlp/compare/2026.02.21...2026.03.03) --- updated-dependencies: - dependency-name: yt-dlp[default] dependency-version: 2026.3.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bdc2535e..59afdd59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ - "yt-dlp[default]==2026.2.21", + "yt-dlp[default]==2026.3.3", "colorama~=0.4", "mergedeep~=1.3", "mediafile~=0.12", From ff66ff5be08c69ea88fca95ba761f938a751dcf1 Mon Sep 17 00:00:00 2001 From: Ross Patterson Date: Mon, 9 Mar 2026 09:27:27 -0700 Subject: [PATCH 45/46] fix(docker): Add default crontab download command (#1321) * docs(docker): Recommended image DEFAULT_WORKSPACE * fix(docker): Add default crontab download command Clarify the `/config/ytdl-sub-configs/cron` script with explanatory comments and a default `--dry-run` command. Also change the wrapper script to echo commands for easier debugging. To update an existing script, move the old script aside, restart the container to regenerate it, and edit the new script. Also clarify the Automating page in the Getting Started guide docs. * fix(docker): Unintentional unattended dry runs [PR feedback](https://github.com/jmbannon/ytdl-sub/pull/1321#discussion_r2315215600) prompted me to reconsider having a default command at all. We should assume, unfortunately, that many new users will just skim the docs enough to enable the image's cron integration but not actually incrementally test their configuration. In those cases, they'd end up sending dry-run non-download requests for all their subscriptions every 6 hours for no good reason. There's just no way to provide a default command that isn't also providing a footgun. * docs(docker): More open cron schedule generator From [PR feedback](https://github.com/jmbannon/ytdl-sub/pull/1321#discussion_r2320521024), this seems like less of an ad than the previous and the source for the page is itself open source. * docs(docker): Document image environment footgun From [PR feedback](https://github.com/jmbannon/ytdl-sub/pull/1321#discussion_r2320515419). * docs(automate): Avoid external link 404 responses * docs(automate): False simultaneous run warning * docs(automate): Clarify Docker daemon restarts * docs(automate): Revert run start non-recommended * docs(automate): Remove footgun manual run command Addressing this underlying issue requires more thought and should be a separate PR. * docs(automate): Restore env var footgun in example --- README.md | 2 +- docker/root/custom-cont-init.d/defaults | 2 + docker/root/defaults/cron | 17 ++- docker/root/defaults/subscriptions.yaml | 2 +- .../config_reference/subscription_yaml.rst | 2 +- .../getting_started/automating_downloads.rst | 129 +++++++++++++----- docs/source/guides/install/docker.rst | 7 + examples/advanced/tv_show_config.yaml | 2 +- 8 files changed, 123 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 3ba8ab5f..d9203837 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ __preset__: # Pass any arg directly to yt-dlp's Python API ytdl_options: - cookiefile: "/config/cookie.txt" + cookiefile: "/config/ytdl-sub-configs/cookie.txt" ################################################################### # TV Show Presets. Can replace Plex with Plex/Jellyfin/Emby/Kodi diff --git a/docker/root/custom-cont-init.d/defaults b/docker/root/custom-cont-init.d/defaults index f3bfd62e..af6b8350 100755 --- a/docker/root/custom-cont-init.d/defaults +++ b/docker/root/custom-cont-init.d/defaults @@ -47,6 +47,8 @@ if [ "$CRON_SCHEDULE" != "" ] ; then # create cron script wrapper echo '#!/bin/bash' > "$CRON_WRAPPER_SCRIPT" + # Echo commands for easier user debugging: + echo "set -x" >> "$CRON_WRAPPER_SCRIPT" echo "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> "$CRON_WRAPPER_SCRIPT" echo "cd \"$DEFAULT_WORKSPACE\"" >> "$CRON_WRAPPER_SCRIPT" echo ". \"$CRON_SCRIPT\" >> \"$LOGS_TO_STDOUT\" 2>&1" >> "$CRON_WRAPPER_SCRIPT" diff --git a/docker/root/defaults/cron b/docker/root/defaults/cron index 6fdbea71..0deb3568 100644 --- a/docker/root/defaults/cron +++ b/docker/root/defaults/cron @@ -1,4 +1,15 @@ -echo "Beginning cron job..." - # Place your ytdl-sub command(s) here. -# This script is executed in the same relative path as this file. \ No newline at end of file +# +# This script is executed in the same directory as this file which also contains the +# default `./config.yaml` and `./subscriptions.yaml`, so you don't need to use the +# `--config` CLI option or pass a `SUBPATH` to the `$ ytdl-sub sub` sub-command. +# +# Test your configuration and subscriptions carefully before automating downloads to +# prevent triggering throttles or bans: +# +# https://ytdl-sub.readthedocs.io/en/latest/guides/getting_started/downloading.html +# +# Once you've tested your configuration and you're ready to download entries unattended, +# remove the next line and un-comment the following line: +echo "WARNING: Read /config/ytdl-sub-configs/cron and modify to automate downloads." +# ytdl-sub sub diff --git a/docker/root/defaults/subscriptions.yaml b/docker/root/defaults/subscriptions.yaml index 98535e27..4fd02a39 100644 --- a/docker/root/defaults/subscriptions.yaml +++ b/docker/root/defaults/subscriptions.yaml @@ -20,7 +20,7 @@ __preset__: # Pass any arg directly to yt-dlp's Python API # ytdl_options: - # cookiefile: "/config/cookie.txt" + # cookiefile: "/config/ytdl-sub-configs/cookie.txt" ################################################################### # Subscriptions nested under this will use the diff --git a/docs/source/config_reference/subscription_yaml.rst b/docs/source/config_reference/subscription_yaml.rst index 298456f8..5d1ad0fb 100644 --- a/docs/source/config_reference/subscription_yaml.rst +++ b/docs/source/config_reference/subscription_yaml.rst @@ -31,7 +31,7 @@ supply a cookies file path. # Directly set plugin options: ytdl_options: - cookiefile: "/config/cookie.txt" + cookiefile: "/config/ytdl-sub-configs/cookie.txt" Layout ------ diff --git a/docs/source/guides/getting_started/automating_downloads.rst b/docs/source/guides/getting_started/automating_downloads.rst index 80e86902..094a5f1f 100644 --- a/docs/source/guides/getting_started/automating_downloads.rst +++ b/docs/source/guides/getting_started/automating_downloads.rst @@ -1,51 +1,80 @@ -Automating Downloads -==================== +Automating +========== -:ref:`Guide for Docker and Unraid Containers ` +Automate downloading your subscriptions by running the :ref:`'sub' sub-command +` periodically. There are various tools that can run +commands on a schedule you may use any of them that work with your installation +method. Most users use `cron`_ in `Docker containers `_. -:ref:`Guide for Linux ` - -:ref:`Guide for Windows ` - -.. _cron scheduling syntax: https://crontab.guru/#0_*/6_*_*_* - - -.. _docker-unraid-setup: Docker and Unraid ----------------- -Cron is preconfigured in every ytdl-sub docker container. Enable by adding the following -ENV variables to your docker setup. +:doc:`The 'ytdl-sub' Docker container images <../install/docker>` provide optional cron +support. Enable cron support by setting `a cron schedule`_ in the ``CRON_SCHEDULE`` +environment variable: .. code-block:: yaml + :caption: ./compose.yaml + :emphasize-lines: 4 services: ytdl-sub: environment: - - CRON_SCHEDULE="0 */6 * * *" - - CRON_RUN_ON_START=false + CRON_SCHEDULE: "0 */6 * * *" + # WARNING: See "Getting Started" -> "Automating" docs regarding throttles/bans: + # CRON_RUN_ON_START: false +Then recreate the container to apply the change and start it to generate the default +``/config/ytdl-sub-configs/cron`` script. Read the comments in that script and edit as +appropriate. -- ``CRON_SCHEDULE`` follows the standard `cron scheduling syntax`_. The above value will - run the script once every 6 hours. -- ``CRON_RUN_ON_START`` toggles whether to run your cron script on container start in - addition to the cron schedule. +The container cron wrapper script will write output from the cron job to +``/config/ytdl-sub-configs/.cron.log``. The default image ``ENTRYPOINT`` will ``$ tail +...`` that file so you can monitor the cron job in the container's output and thus also +in the Docker logs. -The cron script will reside in the main directory with the file name ``cron``. Cron -logs should show when viewing the Docker logs. +You may also set the ``CRON_RUN_ON_START`` environment variable to ``true`` to have the +image run your cron script whenever the container starts in addition to the cron +schedule. + +.. warning:: + + Using ``CRON_RUN_ON_START`` may cause your cron script to run too often and may + trigger throttles and bans. When enabled, your cron script will run *whenever* the + container starts including when the host reboots, when ``# dockerd`` restarts such as + when upgrading Docker itself, when a new image is pulled, when something applies + Compose changes, etc.. This may result in running ``ytdl-sub`` right before or after + the next cron scheduled run. .. _linux-setup: -Linux ------ -Must configure crontab manually, like so: +Linux, Mac OS X, BSD, or other UNIX's +------------------------------------- + +For installations on systems already running ``# crond``, you can also use cron to run +``ytdl-sub`` periodically. Write a script to run ``ytdl-sub`` in the cron job. Be sure +the script changes to the same directory as your configuration and uses the full path to +``ytdl-sub``: .. code-block:: shell + :caption: ~/.local/bin/ytdl-sub-cron + :emphasize-lines: 2,3 - crontab -e - 0 */6 * * * /config/run_cron + #!/bin/bash + cd "~/.config/ytdl-sub/" + ~/.local/bin/ytdl-sub --dry-run sub -o '--ytdl_options.max_downloads 3' |& + tee -a "~/.local/state/ytdl-sub/.cron.log" + +Then tell ``# crond`` when to run the script: + +.. code-block:: console + + echo "0 */6 * * * ${HOME}/.local/bin/ytdl-sub-cron" | crontab "-" + +Remove the ``--dry-run`` and ``-o ...`` CLI options from your cron script when you've +tested your configuration and you're ready to download entries unattended. .. _windows-setup: @@ -53,17 +82,51 @@ Must configure crontab manually, like so: Windows ------- -To be tested (please contact code owner or join the discord server if you can test this -out for us) +For most Windows users, the best way to run commands periodically is `the Task +Scheduler`_: -.. code-block:: powershell +.. attention:: - ytdl-sub.exe --config \path\to\config\config.yaml sub \path\to\config\subscriptions.yaml + These instructions are untested. Use at your own risk. If you use them, whether they + work or not, please let us know how it went in `a support post in Discord`_ or `a new + GitHub issue`_. + +#. Open the Task Scheduler app. + +#. Click ``Create Basic Task`` at the top of the right sidebar. + +#. Set all the fields as appropriate until you get to the ``Action``... + +#. For the ``Action``, select ``Start a program``... + +#. Click ``Browse...`` to the installed ``ytdl-sub.exe`` executable... + +#. Add CLI arguments to ``Add arguments (optional):``, for example ``--dry-run sub -o + '--ytdl_options.max_downloads 3'``... + +#. Set ``Start in (optional):`` to the directory containing your configuration. + +#. Finish the rest of the ``Create Basic Task`` wizard. Next Steps ---------- -Once you have a significant quantity of subscriptions or have use cases not served using -:doc:`YAML keys and the special characters <./subscriptions>`, it's time to start -:doc:`defining your own custom presets <./first_config>`. +At this point, ``ytdl-sub`` should run periodically and keep your subscriptions current +in your media library without your intervention. As your :doc:`subscriptions file +<./subscriptions>` grows or you discover new use cases, it becomes worth while to +simplify things by :doc:`defining your own custom presets <./first_config>`. + + + +.. _`cron`: + https://en.wikipedia.org/wiki/Cron +.. _`a cron schedule`: + https://crontab.cronhub.io/ + +.. _`the Task Scheduler`: + https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page +.. _`a support post in Discord`: + https://discord.com/channels/994270357957648404/1084886228266127460 +.. _`a new GitHub issue`: + https://github.com/jmbannon/ytdl-sub/issues/new diff --git a/docs/source/guides/install/docker.rst b/docs/source/guides/install/docker.rst index aa7bfd5f..93df8119 100644 --- a/docs/source/guides/install/docker.rst +++ b/docs/source/guides/install/docker.rst @@ -38,6 +38,13 @@ For example:: $ docker compose run --rm --user="${PUID}:${PGID}" --entrypoint="ytdl-sub" ytdl-sub sub +.. note:: + + In `the recommended GUI image `_, the ``DEFAULT_WORKSPACE`` directory is + ``/config/ytdl-sub-configs/`` which is used throughout the documentation and + examples. In the headless images, that directory is just ``/config/``, so substitute + that path if using a headless image. + Install with Docker Compose --------------------------- diff --git a/examples/advanced/tv_show_config.yaml b/examples/advanced/tv_show_config.yaml index d2019b9a..4b6c5db7 100644 --- a/examples/advanced/tv_show_config.yaml +++ b/examples/advanced/tv_show_config.yaml @@ -69,7 +69,7 @@ presets: # ytdl_options lets you pass any arg into yt-dlp's Python API ytdl_options: # Set the cookie file - # cookiefile: "/config/youtube_cookies.txt" + # cookiefile: "/config/ytdl-sub-configs/youtube_cookies.txt" # For YouTube, get English metadata if multiple languages are present extractor_args: From a758a8870dbede3c7652fb7fceb7992c57e5af1d Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Mon, 9 Mar 2026 10:18:50 -0700 Subject: [PATCH 46/46] [DOCS] Overhaul configuration file getting started guide (#1172) Updates the configuration file's docs to be cleaner. Removes sphinx autodocs entirely in favor of our custom-built one. --- docs/source/conf.py | 20 +- docs/source/config_reference/config_yaml.rst | 193 +++++++++--------- docs/source/config_reference/plugins.rst | 60 ------ .../guides/getting_started/first_config.rst | 32 ++- docs/source/guides/getting_started/index.rst | 2 +- docs/source/usage.rst | 3 +- src/ytdl_sub/config/config_validator.py | 77 +++++-- src/ytdl_sub/config/preset.py | 6 + tests/unit/docgen/test_docgen.py | 4 + tools/docgen/configuration.py | 36 ++++ tools/docgen/plugins.py | 76 ++----- tools/docgen/utils.py | 100 ++++++++- 12 files changed, 349 insertions(+), 260 deletions(-) create mode 100644 tools/docgen/configuration.py diff --git a/docs/source/conf.py b/docs/source/conf.py index f754815a..c226b589 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -7,7 +7,7 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "ytdl-sub" -copyright = "2024, Jesse Bannon" +copyright = "2026, Jesse Bannon" author = "Jesse Bannon" release = "" @@ -15,10 +15,8 @@ release = "" # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - "sphinx.ext.autodoc", "sphinx.ext.autosectionlabel", "sphinx.ext.extlinks", - "sphinx.ext.napoleon", "sphinx_copybutton", "sphinx_design", ] @@ -70,19 +68,3 @@ extlinks = { "lsio-gh": ("https://github.com/linuxserver/%s", "%s image"), "ytdl-sub-gh": ("https://github.com/jmbannon/ytdl-sub/%s", "src %s"), } - -# -- Options for autodoc ---------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration - -# Automatically extract typehints when specified and place them in -# descriptions of the relevant function/method. -autodoc_default_options = { - "autodoc_typehints_format": "short", - "autodoc_class_signature": "separated", - "add_module_names": False, - # "add_class_names": False, -} - -python_use_unqualified_type_names = True -napoleon_numpy_docstring = True -napoleon_use_rtype = False diff --git a/docs/source/config_reference/config_yaml.rst b/docs/source/config_reference/config_yaml.rst index 6955c386..ccfa901b 100644 --- a/docs/source/config_reference/config_yaml.rst +++ b/docs/source/config_reference/config_yaml.rst @@ -1,7 +1,13 @@ -================== +.. + WARNING: This RST file is generated from docstrings in: + The respective function docstrings within ytdl_sub/config/config_validator.py + In order to make a change to this file, edit the respective docstring + and run `make docs`. This will automatically sync the Python RST-based + docstrings into this file. If the docstrings and RST file are out of sync, + it will fail TestDocGen tests in GitHub CI. + Configuration File ================== - ytdl-sub is configured using a ``config.yaml`` file. The ``config.yaml`` is made up of two sections: @@ -11,113 +17,118 @@ The ``config.yaml`` is made up of two sections: configuration: presets: -You can jump to any section and subsection of the config using the navigation section to -the left. Note for Windows users, paths can be represented with ``C:/forward/slashes/like/linux``. -If you wish to represent paths like Windows, you will need to -``C:\\double\\bashslash\\paths`` in order to escape the backslash character. - - -configuration -------------- - -The ``configuration`` section contains app-wide configs applied to all presets and -subscriptions. - -.. autoclass:: ytdl_sub.config.config_validator.ConfigOptions() - :members: - :member-order: bysource - :exclude-members: subscription_value, persist_logs, experimental - -persist_logs -~~~~~~~~~~~~ - -Without this key, ``ytdl-sub`` only prints output to it's ``stdout`` and ``stderr``. If -your configuration includes the ``persist_logs:`` key, then ``ytdl-sub`` also writes log -files to disk. - -.. warning:: - - The log files grow rapidly if ``keep_successful_logs:`` is ``true``, the default, and - may fill up disk space. Set ``keep_successful_logs: false`` or prune the log files - regularly. - -For example: +If you prefer to use a Windows backslash, note that it must have +``C:\\double\\bashslash\\paths`` in order to escape the backslash character. This is due +to it being a YAML escape character. .. code-block:: yaml configuration: + dl_aliases: + mv: "--preset music_video" + u: "--download.url" + + experimental: + enable_update_with_info_json: True + + ffmpeg_path: "/usr/bin/ffmpeg" + ffprobe_path: "/usr/bin/ffprobe" + + file_name_max_bytes: 255 + lock_directory: "/tmp" + persist_logs: - logs_directory: "/path/to/log/directory" + keep_successful_logs: True + logs_directory: "/var/log/ytdl-sub-logs" -.. autoclass:: ytdl_sub.config.config_validator.PersistLogsValidator() - :members: - :member-order: bysource + umask: "022" + working_directory: ".ytdl-sub-working-directory" +dl_aliases +---------- +.. _dl_aliases: -presets -------- - -Each key under ``presets:`` defines a `formula` for how to format downloaded media and -metadata. The key is the name of the preset and the value is a mapping that defines the -preset. - -.. note:: - - The ``presets:`` key at the top of the configuration file contains multiple - user-defined presets, but *each preset* itself may include a ``preset:`` key that - defines *that preset's* base presets. For example: - - .. code-block:: yaml - - presets: - Foo Preset: - preset: - - "Jellyfin TV Show by Date" - - "Only Recent" - -preset -~~~~~~ - -Presets support inheritance by defining one or more parent presets: +Alias definitions to shorten :ref:`dl arguments `. For example, .. code-block:: yaml - presets: - custom_preset: - ... - parent_preset: - ... - child_preset: - preset: - - "parent_preset" + configuration: + dl_aliases: + mv: "--preset music_video" + u: "--download.url" -In the example above, ``child_preset`` inherits all fields defined in ``parent_preset``. -Use parent presets where possible to reduce duplicate yaml definitions. +Simplifies -Presets also support inheritance from multiple presets: +.. code-block:: bash -.. code-block:: yaml + ytdl-sub dl --preset "Jellyfin Music Videos" --download.url "youtube.com/watch?v=a1b2c3" - child_preset: - preset: - - "custom_preset" - - "parent_preset" +to -In this example, ``child_preset`` will inherit all fields from ``custom_preset`` and -``parent_preset`` in that order. The bottom-most preset has the highest priority. More -specifically, presets are merged using `mergedeep`_ via `a TYPESAFE_ADDITIVE merge`_, -which means: +.. code-block:: bash -- if two conflicting keys arent lists or mappings, overwrite the higher priority one -- otherwise, combine then re-evaluate + ytdl-sub dl --mv --u "youtube.com/watch?v=a1b2c3" -If you are only inheriting from one preset, using a single string instead of a list is -valid, for example ``preset: "parent_preset"``, but we recommend always using a list for -consistent readability between presets. +experimental +------------ +Experimental flags reside under the ``experimental`` key. -.. _`mergedeep`: - https://mergedeep.readthedocs.io/en/latest/ -.. _`a TYPESAFE_ADDITIVE merge`: - https://mergedeep.readthedocs.io/en/latest/index.html#merge-strategies +``enable_update_with_info_json`` + +Enables modifying subscription files using info.json files using the argument +``--update-with-info-json``. This feature is still being tested and has the ability to +destroy files. Ensure you have a full backup before usage. You have been warned! + +ffmpeg_path +----------- +Path to ffmpeg executable. Defaults to ``/usr/bin/ffmpeg`` for Linux, +``./ffmpeg.exe`` in the same directory as ytdl-sub for Windows. + +ffprobe_path +------------ +Path to ffprobe executable. Defaults to ``/usr/bin/ffprobe`` for Linux, +``./ffprobe.exe`` in the same directory as ytdl-sub for Windows. + +file_name_max_bytes +------------------- +Max file name size in bytes. Most OS's typically default to 255 bytes. + +lock_directory +-------------- +The directory to temporarily store file locks, which prevents multiple instances +of ``ytdl-sub`` from running. Note that file locks do not work on +network-mounted directories. Ensure that this directory resides on the host +machine. Defaults to ``/tmp``. + +persist_logs +------------ +By default, no logs are persisted. Specifying this key will enable persisted logs. The following +options are available. + +``keep_successful_logs`` + +Defaults to ``True``. When this key is ``False``, only write log files for failed +subscriptions. + +``logs_directory`` + +Required field. Write log files to this directory with names like +``YYYY-mm-dd-HHMMSS.subscription_name.(success|error).log``. + +umask +----- +Umask in octal format to apply to every created file. Defaults to ``022``. + +working_directory +----------------- +The directory to temporarily store downloaded files before moving them into their final +directory. Defaults to ``.ytdl-sub-working-directory``, created in the same directory +that ytdl-sub is invoked from. + +Presets +======= +Custom presets are defined in this section. Refer to the +:ref:`Getting Started Guide` +on how to configure. diff --git a/docs/source/config_reference/plugins.rst b/docs/source/config_reference/plugins.rst index 6b8229e4..75496323 100644 --- a/docs/source/config_reference/plugins.rst +++ b/docs/source/config_reference/plugins.rst @@ -28,7 +28,6 @@ Extracts audio from a video file. The codec to output after extracting the audio. Supported codecs are aac, flac, mp3, m4a, opus, vorbis, wav, and best to grab the best possible format at runtime. - ``enable`` :expected type: Optional[OverridesFormatter] @@ -37,7 +36,6 @@ Extracts audio from a video file. this field can be set using an override variable to easily toggle whether this plugin is enabled or not via Boolean. - ``quality`` :expected type: Float @@ -45,7 +43,6 @@ Extracts audio from a video file. Optional. Specify ffmpeg audio quality. Insert a value between ``0`` (better) and ``9`` (worse) for variable bitrate, or a specific bitrate like ``128`` for 128k. - ---------------------------------------------------------------------------------------------------- chapters @@ -84,14 +81,12 @@ chapters and remove specific ones. Can also remove chapters using regex. Defaults to False. If chapters do not exist in the video/description itself, attempt to scrape comments to find the chapters. - ``embed_chapters`` :expected type: Optional[Boolean] :description: Defaults to True. Embed chapters into the file. - ``enable`` :expected type: Optional[OverridesFormatter] @@ -100,7 +95,6 @@ chapters and remove specific ones. Can also remove chapters using regex. this field can be set using an override variable to easily toggle whether this plugin is enabled or not via Boolean. - ``force_key_frames`` :expected type: Optional[Boolean] @@ -108,7 +102,6 @@ chapters and remove specific ones. Can also remove chapters using regex. Defaults to False. Force keyframes at cuts when removing sections. This is slow due to needing a re-encode, but the resulting video may have fewer artifacts around the cuts. - ``remove_chapters_regex`` :expected type: Optional[List[RegexString] @@ -116,7 +109,6 @@ chapters and remove specific ones. Can also remove chapters using regex. List of regex patterns to match chapter titles against and remove them from the entry. - ``remove_sponsorblock_categories`` :expected type: Optional[List[String]] @@ -125,7 +117,6 @@ chapters and remove specific ones. Can also remove chapters using regex. categories that are specified in ``sponsorblock_categories`` or "all", which removes everything specified in ``sponsorblock_categories``. - ``sponsorblock_categories`` :expected type: Optional[List[String]] @@ -134,7 +125,6 @@ chapters and remove specific ones. Can also remove chapters using regex. "intro", "outro", "selfpromo", "preview", "filler", "interaction", "music_offtopic", "poi_highlight", or "all" to include all categories. - ---------------------------------------------------------------------------------------------------- date_range @@ -169,14 +159,12 @@ intended download files. :description: Only download videos after or on this datetime, inclusive. - ``before`` :expected type: Optional[OverridesFormatter] :description: Only download videos only before this datetime, not inclusive. - ``breaks`` :expected type: Optional[OverridesFormatter] @@ -184,7 +172,6 @@ intended download files. Toggle to enable breaking subsequent metadata downloads if an entry's upload date is out of range. Defaults to True. - ``enable`` :expected type: Optional[OverridesFormatter] @@ -193,7 +180,6 @@ intended download files. this field can be set using an override variable to easily toggle whether this plugin is enabled or not via Boolean. - ``type`` :expected type: Optional[OverridesFormatter] @@ -201,7 +187,6 @@ intended download files. Which type of date to use. Must be either ``upload_date`` or ``release_date``. Defaults to ``upload_date``. - ---------------------------------------------------------------------------------------------------- download @@ -305,7 +290,6 @@ Also supports custom ffmpeg conversions: - Video: avi, flv, mkv, mov, mp4, webm - Audio: aac, flac, mp3, m4a, opus, vorbis, wav - ``convert_with`` :expected type: Optional[String] @@ -314,7 +298,6 @@ Also supports custom ffmpeg conversions: yt-dlp whereas ``ffmpeg`` specifies it will be converted using a custom command specified with ``ffmpeg_post_process_args``. Defaults to ``yt-dlp``. - ``enable`` :expected type: Optional[OverridesFormatter] @@ -323,7 +306,6 @@ Also supports custom ffmpeg conversions: this field can be set using an override variable to easily toggle whether this plugin is enabled or not via Boolean. - ``ffmpeg_post_process_args`` :expected type: Optional[OverridesFormatter] @@ -336,7 +318,6 @@ Also supports custom ffmpeg conversions: The output file will use the extension specified in ``convert_to``. Post-processing args can still be set with ``convert_with`` set to ``yt-dlp``. - ---------------------------------------------------------------------------------------------------- filter_exclude @@ -472,7 +453,6 @@ with a ``.nfo`` extension. You can add any values into the NFO. this field can be set using an override variable to easily toggle whether this plugin is enabled or not via Boolean. - ``kodi_safe`` :expected type: OverridesBooleanFormatterValidator @@ -481,14 +461,12 @@ with a ``.nfo`` extension. You can add any values into the NFO. emojis and some foreign language characters. Setting this to True will replace those characters with '□'. - ``nfo_name`` :expected type: EntryFormatter :description: The NFO file name. - ``nfo_root`` :expected type: EntryFormatter @@ -501,7 +479,6 @@ with a ``.nfo`` extension. You can add any values into the NFO. - ``tags`` :expected type: NfoTags @@ -538,7 +515,6 @@ with a ``.nfo`` extension. You can add any values into the NFO. Comedy Drama - ---------------------------------------------------------------------------------------------------- output_directory_nfo_tags @@ -570,7 +546,6 @@ Usage: this field can be set using an override variable to easily toggle whether this plugin is enabled or not via Boolean. - ``kodi_safe`` :expected type: OverridesBooleanFormatterValidator @@ -579,14 +554,12 @@ Usage: emojis and some foreign language characters. Setting this to True will replace those characters with '□'. - ``nfo_name`` :expected type: EntryFormatter :description: The NFO file name. - ``nfo_root`` :expected type: EntryFormatter @@ -599,7 +572,6 @@ Usage: - ``tags`` :expected type: NfoTags @@ -634,7 +606,6 @@ Usage: Comedy Drama - ---------------------------------------------------------------------------------------------------- output_options @@ -669,7 +640,6 @@ Defines where to output files and thumbnails after all post-processing has compl The file name to store a subscriptions download archive placed relative to the output directory. Defaults to ``.ytdl-sub-{subscription_name}-download-archive.json`` - ``file_name`` :expected type: EntryFormatter @@ -677,7 +647,6 @@ Defines where to output files and thumbnails after all post-processing has compl The file name for the media file. This can include directories such as ``"Season {upload_year}/{title}.{ext}"``, and will be placed in the output directory. - ``info_json_name`` :expected type: Optional[EntryFormatter] @@ -686,7 +655,6 @@ Defines where to output files and thumbnails after all post-processing has compl as ``"Season {upload_year}/{title}.{info_json_ext}"``, and will be placed in the output directory. Can be set to empty string or `null` to disable info json writes. - ``keep_files_after`` :expected type: Optional[OverridesFormatter] @@ -698,7 +666,6 @@ Defines where to output files and thumbnails after all post-processing has compl files after ``19000101``, which implies all files. Can be used in conjunction with ``keep_max_files``. - ``keep_files_before`` :expected type: Optional[OverridesFormatter] @@ -710,7 +677,6 @@ Defines where to output files and thumbnails after all post-processing has compl files before ``now``, which implies all files. Can be used in conjunction with ``keep_max_files``. - ``keep_files_date_eval`` :expected type: str @@ -720,7 +686,6 @@ Defines where to output files and thumbnails after all post-processing has compl perform evaluation for keep_files_before/after and keep_max_files. Defaults to the entry's upload_date_standardized variable. - ``keep_max_files`` :expected type: Optional[OverridesFormatter] @@ -730,7 +695,6 @@ Defines where to output files and thumbnails after all post-processing has compl Only keeps N most recently uploaded videos. If set to <= 0, ``keep_max_files`` will not be applied. Can be used in conjunction with ``keep_files_before`` and ``keep_files_after``. - ``maintain_download_archive`` :expected type: Optional[Boolean] @@ -745,7 +709,6 @@ Defines where to output files and thumbnails after all post-processing has compl Defaults to False. - ``migrated_download_archive_name`` :expected type: Optional[OverridesFormatter] @@ -755,14 +718,12 @@ Defines where to output files and thumbnails after all post-processing has compl name first, and fallback to ``download_archive_name``. It will always save to this file and remove the original ``download_archive_name``. - ``output_directory`` :expected type: OverridesFormatter :description: The output directory to store all media files downloaded. - ``preserve_mtime`` :expected type: Optional[Boolean] @@ -771,7 +732,6 @@ Defines where to output files and thumbnails after all post-processing has compl When True, sets the file's mtime to match the video's upload_date from yt-dlp metadata. Defaults to False. - ``thumbnail_name`` :expected type: Optional[EntryFormatter] @@ -780,7 +740,6 @@ Defines where to output files and thumbnails after all post-processing has compl as ``"Season {upload_year}/{title}.{thumbnail_ext}"``, and will be placed in the output directory. Can be set to empty string or `null` to disable thumbnail writes. - ---------------------------------------------------------------------------------------------------- overrides @@ -845,7 +804,6 @@ used with no modifications. If a file has no chapters and is set to "pass", then ``chapter_title`` is set to the entry's title and ``chapter_index``, ``chapter_count`` are both set to 1. - ---------------------------------------------------------------------------------------------------- square_thumbnail @@ -892,7 +850,6 @@ Usage: this field can be set using an override variable to easily toggle whether this plugin is enabled or not via Boolean. - ``kodi_safe`` :expected type: OverridesBooleanFormatterValidator @@ -901,14 +858,12 @@ Usage: emojis and some foreign language characters. Setting this to True will replace those characters with '□'. - ``nfo_name`` :expected type: EntryFormatter :description: The NFO file name. - ``nfo_root`` :expected type: EntryFormatter @@ -921,7 +876,6 @@ Usage: - ``tags`` :expected type: NfoTags @@ -935,7 +889,6 @@ Usage: My custom season name! - ---------------------------------------------------------------------------------------------------- subtitles @@ -963,7 +916,6 @@ It will set the respective language to the correct subtitle file. :description: Defaults to False. Whether to allow auto generated subtitles. - ``embed_subtitles`` :expected type: Optional[Boolean] @@ -971,7 +923,6 @@ It will set the respective language to the correct subtitle file. Defaults to False. Whether to embed the subtitles into the video file. Note that webm files can only embed "vtt" subtitle types. - ``enable`` :expected type: Optional[OverridesFormatter] @@ -980,7 +931,6 @@ It will set the respective language to the correct subtitle file. this field can be set using an override variable to easily toggle whether this plugin is enabled or not via Boolean. - ``languages`` :expected type: Optional[List[String]] @@ -988,7 +938,6 @@ It will set the respective language to the correct subtitle file. Language code(s) to download for subtitles. Supports a single or list of multiple language codes. Defaults to only "en". - ``languages_required`` :expected type: Optional[List[String]] @@ -996,7 +945,6 @@ It will set the respective language to the correct subtitle file. Language code(s) that are required to be present for downloads to continue. If missing, ytdl-sub will throw an error. NOTE: currently this only checks file-based subtitles. - ``subtitles_name`` :expected type: Optional[EntryFormatter] @@ -1006,14 +954,12 @@ It will set the respective language to the correct subtitle file. and will be placed in the output directory. ``lang`` is dynamic since you can download multiple subtitles. It will set the respective language to the correct subtitle file. - ``subtitles_type`` :expected type: Optional[String] :description: Defaults to "srt". One of the subtitle file types "srt", "vtt", "ass", "lrc". - ---------------------------------------------------------------------------------------------------- throttle_protection @@ -1054,14 +1000,12 @@ Range min and max values support static override variables within their definiti this field can be set using an override variable to easily toggle whether this plugin is enabled or not via Boolean. - ``max_downloads_per_subscription`` :expected type: Optional[Range] :description: Number of downloads to perform per subscription. - ``sleep_per_download_s`` :expected type: Optional[Range] @@ -1069,7 +1013,6 @@ Range min and max values support static override variables within their definiti Number in seconds to sleep between each download. Does not include time it takes for ytdl-sub to perform post-processing. - ``sleep_per_request_s`` :expected type: Optional[Range] @@ -1079,14 +1022,12 @@ Range min and max values support static override variables within their definiti download for the entry. Also, yt-dlp only supports a single value at this time for this, so will always use the max value. - ``sleep_per_subscription_s`` :expected type: Optional[Range] :description: Number in seconds to sleep between each subscription. - ``subscription_download_probability`` :expected type: Optional[Float] @@ -1095,7 +1036,6 @@ Range min and max values support static override variables within their definiti recommended to set if you run ytdl-sub in a cron-job, that way you are statistically guaranteed over time to eventually download the subscription. - ---------------------------------------------------------------------------------------------------- video_tags diff --git a/docs/source/guides/getting_started/first_config.rst b/docs/source/guides/getting_started/first_config.rst index 34dabdb2..0f98b84e 100644 --- a/docs/source/guides/getting_started/first_config.rst +++ b/docs/source/guides/getting_started/first_config.rst @@ -3,13 +3,33 @@ Basic Configuration A configuration file serves two purposes: -1. Set advanced functionality that is not specifiable in a subscription file, such as - working directory location. These are set underneath ``configuration``. -2. Create custom presets, which can drastically simplify your subscription file. These - are defined underneath ``presets``. Presets are intended to be applicable and - reusable across multiple subscriptions. +1. Set application-level functionality that is not specifiable in a subscription file. -Below is a common configuration: + .. note:: + + ytdl-sub does not require a configuration file. However, + certain application settings may be desirable for tweak, such as setting + ``working_directory`` to make ytdl-sub perform the initial download + to an SSD drive. + +2. Create custom presets. + + .. note:: + + In the prior Initial Subscription examples, we leveraged the prebuilt preset + ``Jellyfin TV Show by Date``. This preset is entirely built using the same + YAML configuration system offered to users by using a configuration file. + +The following section attempts to demystify and explain how to... + +- Set an application setting +- Know whether or not custom presets are actually needed +- How to create a custom preset +- How to use a custom preset on subscriptions + +------------- + +how this works, and show-case how .. code-block:: yaml :linenos: diff --git a/docs/source/guides/getting_started/index.rst b/docs/source/guides/getting_started/index.rst index 1288380d..5c04e47f 100644 --- a/docs/source/guides/getting_started/index.rst +++ b/docs/source/guides/getting_started/index.rst @@ -134,7 +134,7 @@ use no preset at all and will just run ``yt-dlp`` without any customization or p processing. The subscriptions file has special support for :ref:`overriding the presets of all subscriptions in the file `. The configuration file supports :ref:`a few special options -` that are not about defining presets. See +` that are not about defining presets. See :doc:`the reference documentation <../../config_reference/index>` for technically complete details, but for almost all of the use cases served by ``ytdl-sub``, the above is accurate and representative. diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 0db65064..cb5b827e 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -87,8 +87,7 @@ Using the command: --overrides.tv_show_name "Rick A" \ --overrides.url: "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw" -See how to shorten commands using `download aliases -`_. +See how to shorten commands using :ref:`download aliases `. View Options diff --git a/src/ytdl_sub/config/config_validator.py b/src/ytdl_sub/config/config_validator.py index 073eb23a..0dc2c1e0 100644 --- a/src/ytdl_sub/config/config_validator.py +++ b/src/ytdl_sub/config/config_validator.py @@ -24,6 +24,10 @@ from ytdl_sub.validators.validators import StringValidator class ExperimentalValidator(StrictDictValidator): + """ + Experimental flags reside under the ``experimental`` key. + """ + _optional_keys = {"enable_update_with_info_json"} _allow_extra_keys = True @@ -45,6 +49,11 @@ class ExperimentalValidator(StrictDictValidator): class PersistLogsValidator(StrictDictValidator): + """ + By default, no logs are persisted. Specifying this key will enable persisted logs. The following + options are available. + """ + _required_keys = {"logs_directory"} _optional_keys = {"keep_logs_after", "keep_successful_logs"} @@ -69,8 +78,8 @@ class PersistLogsValidator(StrictDictValidator): @property def logs_directory(self) -> str: """ - Write log files to this directory with names like - ``YYYY-mm-dd-HHMMSS.subscription_name.(success|error).log``. (required) + Required field. Write log files to this directory with names like + ``YYYY-mm-dd-HHMMSS.subscription_name.(success|error).log``. """ return self._logs_directory.value @@ -95,15 +104,54 @@ class PersistLogsValidator(StrictDictValidator): @property def keep_successful_logs(self) -> bool: """ - If the ``persist_logs:`` key is in the configuration, then ``ytdl-sub`` *always* - writes log files for the subscription both for successful downloads and when it - encounters an error while downloading. When this key is ``False``, only write - log files for errors. (default ``True``) + Defaults to ``True``. When this key is ``False``, only write log files for failed + subscriptions. """ return self._keep_successful_logs.value class ConfigOptions(StrictDictValidator): + """ + ytdl-sub is configured using a ``config.yaml`` file. + + The ``config.yaml`` is made up of two sections: + + .. code-block:: yaml + + configuration: + presets: + + + Note for Windows users, paths can be represented with ``C:/forward/slashes/like/linux``. + If you prefer to use a Windows backslash, note that it must have + ``C:\\\\double\\\\bashslash\\\\paths`` in order to escape the backslash character. This is due + to it being a YAML escape character. + + .. code-block:: yaml + + configuration: + dl_aliases: + mv: "--preset music_video" + u: "--download.url" + + experimental: + enable_update_with_info_json: True + + ffmpeg_path: "/usr/bin/ffmpeg" + ffprobe_path: "/usr/bin/ffprobe" + + file_name_max_bytes: 255 + lock_directory: "/tmp" + + persist_logs: + keep_successful_logs: True + logs_directory: "/var/log/ytdl-sub-logs" + + umask: "022" + working_directory: ".ytdl-sub-working-directory" + + """ + _optional_keys = { "working_directory", "umask", @@ -159,7 +207,8 @@ class ConfigOptions(StrictDictValidator): def working_directory(self) -> str: """ The directory to temporarily store downloaded files before moving them into their final - directory. (default ``./.ytdl-sub-working-directory``) + directory. Defaults to ``.ytdl-sub-working-directory``, created in the same directory + that ytdl-sub is invoked from. """ # Expands tildas to actual paths, use native os sep return os.path.expanduser(self._working_directory.value.replace(posixpath.sep, os.sep)) @@ -167,7 +216,7 @@ class ConfigOptions(StrictDictValidator): @property def umask(self) -> Optional[str]: """ - Umask in octal format to apply to every created file. (default ``022``) + Umask in octal format to apply to every created file. Defaults to ``022``. """ return self._umask.value @@ -176,7 +225,7 @@ class ConfigOptions(StrictDictValidator): """ .. _dl_aliases: - Alias definitions to shorten ``ytdl-sub dl`` arguments. For example, + Alias definitions to shorten :ref:`dl arguments `. For example, .. code-block:: yaml @@ -228,23 +277,23 @@ class ConfigOptions(StrictDictValidator): The directory to temporarily store file locks, which prevents multiple instances of ``ytdl-sub`` from running. Note that file locks do not work on network-mounted directories. Ensure that this directory resides on the host - machine. (default ``/tmp``) + machine. Defaults to ``/tmp``. """ return self._lock_directory.value @property def ffmpeg_path(self) -> str: """ - Path to ffmpeg executable. (default ``/usr/bin/ffmpeg`` for Linux, - ``./ffmpeg.exe`` in the same directory as ytdl-sub for Windows) + Path to ffmpeg executable. Defaults to ``/usr/bin/ffmpeg`` for Linux, + ``./ffmpeg.exe`` in the same directory as ytdl-sub for Windows. """ return self._ffmpeg_path.value @property def ffprobe_path(self) -> str: """ - Path to ffprobe executable. (default ``/usr/bin/ffprobe`` for Linux, - ``./ffprobe.exe`` in the same directory as ytdl-sub for Windows) + Path to ffprobe executable. Defaults to ``/usr/bin/ffprobe`` for Linux, + ``./ffprobe.exe`` in the same directory as ytdl-sub for Windows. """ return self._ffprobe_path.value diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index b89e1d53..2c8ad348 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -55,6 +55,12 @@ class _PresetShell(StrictDictValidator): class Preset(_PresetShell): + """ + Custom presets are defined in this section. Refer to the + :ref:`Getting Started Guide` + on how to configure. + """ + @classmethod def preset_partial_validate(cls, config: ConfigValidator, name: str, value: Any) -> None: """ diff --git a/tests/unit/docgen/test_docgen.py b/tests/unit/docgen/test_docgen.py index 5d326839..5cfb8f92 100644 --- a/tests/unit/docgen/test_docgen.py +++ b/tests/unit/docgen/test_docgen.py @@ -1,5 +1,6 @@ from typing import Type +from tools.docgen.configuration import ConfigurationDocGen from tools.docgen.docgen import DocGen from tools.docgen.entry_variables import EntryVariablesDocGen from tools.docgen.plugins import PluginsDocGen @@ -28,3 +29,6 @@ class TestDocGen: def test_plugins_generated(self): _test_doc_gen(PluginsDocGen) + + def test_configuration_generated(self): + _test_doc_gen(ConfigurationDocGen) diff --git a/tools/docgen/configuration.py b/tools/docgen/configuration.py new file mode 100644 index 00000000..958c09a1 --- /dev/null +++ b/tools/docgen/configuration.py @@ -0,0 +1,36 @@ +from pathlib import Path + +from tools.docgen.docgen import DocGen +from tools.docgen.utils import generate_options_validator_docs +from ytdl_sub.config.config_validator import ConfigOptions +from ytdl_sub.config.preset import Preset + + +class ConfigurationDocGen(DocGen): + + LOCATION = Path("docs/source/config_reference/config_yaml.rst") + DOCSTRING_LOCATION = ( + "The respective function docstrings within ytdl_sub/config/config_validator.py" + ) + + @classmethod + def generate(cls): + docs = generate_options_validator_docs( + name="Configuration File", + options=ConfigOptions, + offset=0, + skip_properties=False, + recurse_property_options=True, + property_sections=True, + ) + + docs += generate_options_validator_docs( + name="Presets", + options=Preset, + offset=0, + skip_properties=True, + recurse_property_options=False, + property_sections=False, + ) + + return docs diff --git a/tools/docgen/plugins.py b/tools/docgen/plugins.py index d2be80d3..f0331acc 100644 --- a/tools/docgen/plugins.py +++ b/tools/docgen/plugins.py @@ -1,12 +1,11 @@ -import inspect from pathlib import Path -from typing import Any from typing import Dict +from typing import Set from typing import Type from tools.docgen.docgen import DocGen +from tools.docgen.utils import generate_options_validator_docs from tools.docgen.utils import line_section -from tools.docgen.utils import properties from tools.docgen.utils import section from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.plugin.plugin_mapping import PluginMapping @@ -15,59 +14,17 @@ from ytdl_sub.config.preset_options import YTDLOptions from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.downloaders.url.validators import MultiUrlValidator - -def should_filter_all_properties(plugin_name: str) -> bool: - return plugin_name in ( - "format", - "match_filters", - "music_tags", - "filter_include", - "filter_exclude", - "embed_thumbnail", - "square_thumbnail", - "video_tags", - "download", - ) - - -def should_filter_property(property_name: str) -> bool: - return property_name.startswith("_") or property_name in ( - "value", - "source_variable_capture_dict", - "dict", - "keys", - "dict_with_format_strings", - "dict_with_parsed_format_strings", - "subscription_name", - "list", - "script", - "unresolvable", - "leaf_name", - ) - - -def get_function_docs(function_name: str, obj: Any, level: int) -> str: - docs = f"\n``{function_name}``\n\n" - docs += inspect.cleandoc(getattr(obj, function_name).__doc__) - docs += "\n\n" - return docs - - -def generate_plugin_docs(name: str, options: Type[OptionsValidator], offset: int) -> str: - docs = "" - docs += section(name, level=offset + 0) - - docs += inspect.cleandoc(options.__doc__) - docs += "\n" - - if should_filter_all_properties(name): - return docs - - property_names = [prop for prop in properties(options) if not should_filter_property(prop)] - for property_name in sorted(property_names): - docs += get_function_docs(function_name=property_name, obj=options, level=offset + 1) - - return docs +PLUGIN_NAMES_TO_SKIP_PROPERTIES: Set[str] = { + "format", + "match_filters", + "music_tags", + "filter_include", + "filter_exclude", + "embed_thumbnail", + "square_thumbnail", + "video_tags", + "download", +} class PluginsDocGen(DocGen): @@ -91,6 +48,11 @@ class PluginsDocGen(DocGen): docs = section("Plugins", level=0) for idx, name in enumerate(sorted(options_dict.keys())): docs += line_section(section_idx=idx) - docs += generate_plugin_docs(name, options_dict[name], offset=1) + docs += generate_options_validator_docs( + name=name, + options=options_dict[name], + offset=1, + skip_properties=name in PLUGIN_NAMES_TO_SKIP_PROPERTIES, + ) return docs diff --git a/tools/docgen/utils.py b/tools/docgen/utils.py index 81ddc3fa..5cd6f357 100644 --- a/tools/docgen/utils.py +++ b/tools/docgen/utils.py @@ -6,12 +6,51 @@ from typing import List from typing import Optional from typing import Type +from ytdl_sub.script.utils.type_checking import get_optional_type +from ytdl_sub.script.utils.type_checking import is_optional +from ytdl_sub.validators.validators import Validator + LEVEL_CHARS: Dict[int, str] = {0: "=", 1: "-", 2: "~", 3: "^"} -def section(name: str, level: int, as_code: bool = False) -> str: - if as_code: - name = f"``{name}``" +def _should_filter_property(property_name: str) -> bool: + return property_name.startswith("_") or property_name in ( + "value", + "source_variable_capture_dict", + "dict", + "keys", + "dict_with_format_strings", + "subscription_name", + "list", + "script", + "unresolvable", + "dict_with_parsed_format_strings", + "leaf_name", + ) + + +def _is_validator_property( + options: Type[Validator], property_name: str +) -> Optional[Type[Validator]]: + property_return_type = inspect.getfullargspec(getattr(options, property_name).fget).annotations[ + "return" + ] + if is_optional(property_return_type): + property_return_type = get_optional_type(property_return_type) + + try: + if issubclass(property_return_type, Validator): + return property_return_type + except TypeError: + return None + + return None + + +def section(name: str, level: Optional[int]) -> str: + if level is None: + return f"\n{name}\n\n" + return f"\n{name}\n{len(name) * LEVEL_CHARS[level]}\n" @@ -40,10 +79,20 @@ def camel_case_to_human(string: str) -> str: return output_str +def line() -> str: + return "\n" + ("-" * 100) + "\n" + + +def line_section(section_idx: int) -> str: + if section_idx > 0: + return line() + return "" + + def get_function_docs( function_name: str, obj: Any, - level: int, + level: Optional[int], display_function_name: Optional[str] = None, pre_docstring: Optional[str] = None, ) -> str: @@ -56,11 +105,42 @@ def get_function_docs( return docs -def line() -> str: - return "\n" + ("-" * 100) + "\n" +def generate_options_validator_docs( + name: str, + options: Type[Validator], + offset: int, + skip_properties: bool, + recurse_property_options: bool = False, + property_sections: bool = False, +) -> str: + docs = "" + docs += section(name, level=offset + 0) + docs += inspect.cleandoc(options.__doc__) + docs += "\n" -def line_section(section_idx: int) -> str: - if section_idx > 0: - return line() - return "" + if skip_properties: + return docs + + property_names = [prop for prop in properties(options) if not _should_filter_property(prop)] + for property_name in sorted(property_names): + maybe_validator_property = ( + _is_validator_property(options, property_name) if recurse_property_options else None + ) + if maybe_validator_property: + docs += generate_options_validator_docs( + name=property_name, + options=maybe_validator_property, + offset=offset + 1, + skip_properties=False, + recurse_property_options=False, + ) + else: + docs += get_function_docs( + function_name=property_name, + obj=options, + level=(offset + 1) if property_sections else None, + display_function_name=f"``{property_name}``" if not property_sections else None, + ) + + return docs