Merge branch 'master' into emby

This commit is contained in:
Jesse Bannon 2025-06-01 08:46:15 -07:00
commit 9f54aeda69
199 changed files with 5270 additions and 710 deletions

View file

@ -19,7 +19,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Run unit tests with coverage - name: Run unit tests with coverage
run: | run: |
@ -42,7 +42,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Run integration tests with coverage - name: Run integration tests with coverage
run: | run: |
@ -66,7 +66,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Run prebuilt preset integration tests with coverage - name: Run prebuilt preset integration tests with coverage
run: | run: |
@ -89,7 +89,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Run e2e tests with coverage - name: Run e2e tests with coverage
run: | run: |

View file

@ -9,7 +9,7 @@ on:
- master - master
jobs: jobs:
test-lint: test-lint:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@ -19,7 +19,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Run linters - name: Run linters
run: | run: |
@ -27,7 +27,7 @@ jobs:
make check_lint make check_lint
test-unit: test-unit:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@ -37,7 +37,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Run unit tests with coverage - name: Run unit tests with coverage
run: | run: |
@ -53,7 +53,7 @@ jobs:
key: ${{github.sha}}-coverage-unit key: ${{github.sha}}-coverage-unit
test-integration: test-integration:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@ -63,7 +63,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Run integration tests with coverage - name: Run integration tests with coverage
run: | run: |
@ -79,7 +79,7 @@ jobs:
key: ${{github.sha}}-coverage-integration key: ${{github.sha}}-coverage-integration
test-integration-prebuilt-presets: test-integration-prebuilt-presets:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@ -89,7 +89,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Run prebuilt preset integration tests with coverage - name: Run prebuilt preset integration tests with coverage
run: | run: |
@ -105,7 +105,7 @@ jobs:
key: ${{github.sha}}-coverage-integration-prebuilt-presets key: ${{github.sha}}-coverage-integration-prebuilt-presets
test-e2e: test-e2e:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@ -115,7 +115,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Run e2e tests with coverage - name: Run e2e tests with coverage
run: | run: |
@ -125,7 +125,7 @@ jobs:
coverage run -m pytest tests/e2e && coverage xml -o /opt/coverage/e2e/coverage.xml coverage run -m pytest tests/e2e && coverage xml -o /opt/coverage/e2e/coverage.xml
codecov-upload: codecov-upload:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
test-unit, test-unit,
test-integration, test-integration,

View file

@ -63,10 +63,10 @@ jobs:
echo 'init_contents=__pypi_version__ = "${{ env.PYPI_VERSION }}";__local_version__ = "${{ env.LOCAL_VERSION }}"' >> "$GITHUB_OUTPUT" echo 'init_contents=__pypi_version__ = "${{ env.PYPI_VERSION }}";__local_version__ = "${{ env.LOCAL_VERSION }}"' >> "$GITHUB_OUTPUT"
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [ "3.10" ] python-version: [ "3.12" ]
permissions: permissions:
contents: read contents: read
@ -92,7 +92,7 @@ jobs:
# Build ARM64 container, only on master branch to save time testing # Build ARM64 container, only on master branch to save time testing
package-arm64: package-arm64:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
build build
] ]
@ -141,7 +141,7 @@ jobs:
# Build AMD64 container # Build AMD64 container
package-amd64: package-amd64:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
build build
] ]
@ -186,7 +186,7 @@ jobs:
# On master branch, build the docker manifest file from the cached # On master branch, build the docker manifest file from the cached
# docker builds and push to the registry # docker builds and push to the registry
deploy: deploy:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
version, version,
build, build,

View file

@ -63,10 +63,10 @@ jobs:
echo 'init_contents=__pypi_version__ = "${{ env.PYPI_VERSION }}";__local_version__ = "${{ env.LOCAL_VERSION }}"' >> "$GITHUB_OUTPUT" echo 'init_contents=__pypi_version__ = "${{ env.PYPI_VERSION }}";__local_version__ = "${{ env.LOCAL_VERSION }}"' >> "$GITHUB_OUTPUT"
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [ "3.10" ] python-version: [ "3.12" ]
permissions: permissions:
contents: read contents: read
@ -92,7 +92,7 @@ jobs:
# Build ARM64 container, only on master branch to save time testing # Build ARM64 container, only on master branch to save time testing
package-arm64: package-arm64:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
build build
] ]
@ -141,7 +141,7 @@ jobs:
# Build AMD64 container # Build AMD64 container
package-amd64: package-amd64:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
build build
] ]
@ -186,7 +186,7 @@ jobs:
# On master branch, build the docker manifest file from the cached # On master branch, build the docker manifest file from the cached
# docker builds and push to the registry # docker builds and push to the registry
deploy: deploy:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
version, version,
build, build,

View file

@ -63,10 +63,10 @@ jobs:
echo 'init_contents=__pypi_version__ = "${{ env.PYPI_VERSION }}";__local_version__ = "${{ env.LOCAL_VERSION }}"' >> "$GITHUB_OUTPUT" echo 'init_contents=__pypi_version__ = "${{ env.PYPI_VERSION }}";__local_version__ = "${{ env.LOCAL_VERSION }}"' >> "$GITHUB_OUTPUT"
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [ "3.10" ] python-version: [ "3.12" ]
permissions: permissions:
contents: read contents: read
@ -92,7 +92,7 @@ jobs:
# Build ARM64 container, only on master branch to save time testing # Build ARM64 container, only on master branch to save time testing
package-arm64: package-arm64:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
build build
] ]
@ -136,7 +136,7 @@ jobs:
# Build AMD64 container # Build AMD64 container
package-amd64: package-amd64:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
build build
] ]
@ -180,7 +180,7 @@ jobs:
# On master branch, build the docker manifest file from the cached # On master branch, build the docker manifest file from the cached
# docker builds and push to the registry # docker builds and push to the registry
deploy: deploy:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
needs: [ needs: [
version, version,
build, build,

View file

@ -61,9 +61,16 @@ jobs:
strategy: strategy:
matrix: matrix:
arch: [ "aarch64", "x86_64" ] arch: [ "aarch64", "x86_64" ]
runs-on: ubuntu-latest include:
- arch: "aarch64"
runner: "ubuntu-24.04-arm"
container: "quay.io/pypa/manylinux_2_28_aarch64"
- arch: "x86_64"
runner: "ubuntu-latest"
container: "quay.io/pypa/manylinux_2_28_x86_64"
runs-on: ${{ matrix.runner }}
container: container:
image: quay.io/pypa/manylinux_2_28_x86_64 image: ${{ matrix.container }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Write version to init file - name: Write version to init file
@ -76,17 +83,17 @@ jobs:
dnf install -y epel-release tar wget make gcc openssl-devel bzip2-devel libffi-devel zlib-devel dnf install -y epel-release tar wget make gcc openssl-devel bzip2-devel libffi-devel zlib-devel
- name: Install Python - name: Install Python
run: | run: |
wget https://www.python.org/ftp/python/3.10.10/Python-3.10.10.tar.xz wget https://www.python.org/ftp/python/3.12.9/Python-3.12.9.tar.xz
tar -xf Python-3.10.10.tar.xz tar -xf Python-3.12.9.tar.xz
cd Python-3.10.10 && ./configure --with-ensurepip=install --prefix=/usr/local --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib" cd Python-3.12.9 && ./configure --with-ensurepip=install --prefix=/usr/local --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib"
make -j 8 make -j 8
make altinstall make altinstall
python3.10 --version python3.12 --version
python3.10 -m ensurepip --upgrade python3.12 -m ensurepip --upgrade
- name: Build Package - name: Build Package
run: | run: |
python3.10 -m pip install -e . python3.12 -m pip install -e .
python3.10 -m pip install pyinstaller python3.12 -m pip install pyinstaller
# Build executable # Build executable
pyinstaller ytdl-sub.spec pyinstaller ytdl-sub.spec
mkdir -p /opt/builds mkdir -p /opt/builds
@ -95,7 +102,7 @@ jobs:
mv dist/ytdl-sub /opt/builds/ytdl-sub_${{ matrix.arch }} mv dist/ytdl-sub /opt/builds/ytdl-sub_${{ matrix.arch }}
- name: Upload build - name: Upload build
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: ytdl-sub_${{ matrix.arch }} name: ytdl-sub_${{ matrix.arch }}
path: /opt/builds/ytdl-sub_${{ matrix.arch }} path: /opt/builds/ytdl-sub_${{ matrix.arch }}
@ -112,7 +119,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.12"
- name: Write version to init file - name: Write version to init file
run: | run: |
echo '${{ needs.version.outputs.init_contents }}'> src/ytdl_sub/__init__.py echo '${{ needs.version.outputs.init_contents }}'> src/ytdl_sub/__init__.py
@ -124,7 +131,7 @@ jobs:
.\dist\ytdl-sub.exe -h .\dist\ytdl-sub.exe -h
- name: Upload build - name: Upload build
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: ytdl-sub_exe name: ytdl-sub_exe
path: .\dist\ytdl-sub.exe path: .\dist\ytdl-sub.exe
@ -145,19 +152,19 @@ jobs:
echo '${{ needs.version.outputs.init_contents }}' > src/ytdl_sub/__init__.py echo '${{ needs.version.outputs.init_contents }}' > src/ytdl_sub/__init__.py
- name: Restore exe build - name: Restore exe build
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: ytdl-sub_exe name: ytdl-sub_exe
path: /opt/builds path: /opt/builds
- name: Restore aarch64 build - name: Restore aarch64 build
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: ytdl-sub_aarch64 name: ytdl-sub_aarch64
path: /opt/builds path: /opt/builds
- name: Restore x86_64 build - name: Restore x86_64 build
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: ytdl-sub_x86_64 name: ytdl-sub_x86_64
path: /opt/builds path: /opt/builds
@ -193,13 +200,13 @@ jobs:
name: pypi-publish name: pypi-publish
needs: needs:
- version - version
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: '3.10' python-version: '3.12'
- name: Write version to init file - name: Write version to init file
run: | run: |
echo '${{ needs.version.outputs.init_contents }}' > src/ytdl_sub/__init__.py echo '${{ needs.version.outputs.init_contents }}' > src/ytdl_sub/__init__.py

View file

@ -14,9 +14,9 @@ else
endif endif
lint: lint:
@-isort . python3 -m isort .
@-black . python3 -m black .
@-pylint src/ python3 -m pylint src
check_lint: check_lint:
isort . --check-only --diff \ isort . --check-only --diff \
&& black . --check \ && black . --check \

View file

@ -3,8 +3,10 @@ FROM ghcr.io/linuxserver/baseimage-alpine:edge
############################################################################### ###############################################################################
# YTDL-SUB INSTALL # YTDL-SUB INSTALL
# Needed for phantomjs # For phantomjs
ENV OPENSSL_CONF="/etc/ssl" ENV OPENSSL_CONF="/etc/ssl"
# For downloading thumbnails
ENV SSL_CERT_DIR="/etc/ssl/certs/"
COPY root/ / COPY root/ /
RUN mkdir -p /config && \ RUN mkdir -p /config && \
@ -55,6 +57,12 @@ RUN mkdir -p /config && \
# CONTAINER CONFIGS # CONTAINER CONFIGS
ENV EDITOR="nano" \ ENV EDITOR="nano" \
HOME="/config" HOME="/config" \
DOCKER_MODS=linuxserver/mods:universal-stdout-logs|linuxserver/mods:universal-cron \
DEFAULT_WORKSPACE=/config \
CRON_SCRIPT="/config/cron" \
CRON_WRAPPER_SCRIPT="/config/.cron_wrapper" \
LOGS_TO_STDOUT=/config/.cron.log \
LSIO_FIRST_PARTY=false
VOLUME /config VOLUME /config

View file

@ -1,7 +1,9 @@
FROM lscr.io/linuxserver/code-server:4.18.0-ls181 FROM lscr.io/linuxserver/code-server:4.98.2
# Needed for phantomjs # For phantomjs
ENV OPENSSL_CONF=/etc/ssl ENV OPENSSL_CONF="/etc/ssl"
# For downloading thumbnails
ENV SSL_CERT_DIR="/etc/ssl/certs/"
############################################################################### ###############################################################################
# YTDL-SUB INSTALL # YTDL-SUB INSTALL
@ -20,7 +22,6 @@ RUN mkdir -p /config && \
g++ \ g++ \
nano \ nano \
make \ make \
python3.10-dev \
python3-pip \ python3-pip \
fontconfig \ fontconfig \
xz-utils \ xz-utils \
@ -59,7 +60,7 @@ RUN mkdir -p /config && \
phantomjs --version ; \ phantomjs --version ; \
fi && \ fi && \
# Install ytdl-sub, ensure it is installed properly # Install ytdl-sub, ensure it is installed properly
pip install --no-cache-dir ytdl_sub-*.whl && \ pip install --no-cache-dir --break-system-packages ytdl_sub-*.whl && \
ytdl-sub -h && \ ytdl-sub -h && \
# Delete unneeded packages after install # Delete unneeded packages after install
rm ytdl_sub-*.whl && \ rm ytdl_sub-*.whl && \
@ -68,7 +69,6 @@ RUN mkdir -p /config && \
make \ make \
xz-utils \ xz-utils \
bzip2 \ bzip2 \
python3.10-dev \
python3-venv && \ python3-venv && \
apt-get autoremove -y && \ apt-get autoremove -y && \
apt-get purge -y --auto-remove && \ apt-get purge -y --auto-remove && \
@ -77,11 +77,13 @@ RUN mkdir -p /config && \
############################################################################### ###############################################################################
# CONTAINER CONFIGS # CONTAINER CONFIGS
ENV EDITOR="nano" \
ENV YTDL_SUB_TYPE="gui" \
EDITOR="nano" \
HOME="/config" \ HOME="/config" \
DOCKER_MODS=linuxserver/mods:universal-cron \ DOCKER_MODS=linuxserver/mods:universal-stdout-logs|linuxserver/mods:universal-cron \
DEFAULT_WORKSPACE=/config/ytdl-sub-configs DEFAULT_WORKSPACE=/config/ytdl-sub-configs \
CRON_SCRIPT="/config/ytdl-sub-configs/cron" \
CRON_WRAPPER_SCRIPT="/config/.cron_wrapper" \
LOGS_TO_STDOUT=/config/.cron.log \
LSIO_FIRST_PARTY=false
VOLUME /config VOLUME /config

View file

@ -1,10 +1,12 @@
FROM ghcr.io/linuxserver/baseimage-ubuntu:jammy FROM ghcr.io/linuxserver/baseimage-ubuntu:noble
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
# Needed for phantomjs # For phantomjs
ENV OPENSSL_CONF=/etc/ssl ENV OPENSSL_CONF="/etc/ssl"
# For downloading thumbnails
ENV SSL_CERT_DIR="/etc/ssl/certs/"
############################################################################### ###############################################################################
# YTDL-SUB INSTALL # YTDL-SUB INSTALL
@ -23,7 +25,6 @@ RUN mkdir -p /config && \
g++ \ g++ \
nano \ nano \
make \ make \
python3.10-dev \
python3-pip \ python3-pip \
fontconfig \ fontconfig \
xz-utils \ xz-utils \
@ -62,7 +63,7 @@ RUN mkdir -p /config && \
phantomjs --version ; \ phantomjs --version ; \
fi && \ fi && \
# Install ytdl-sub, ensure it is installed properly # Install ytdl-sub, ensure it is installed properly
pip install --no-cache-dir ytdl_sub-*.whl && \ pip install --no-cache-dir --break-system-packages ytdl_sub-*.whl && \
ytdl-sub -h && \ ytdl-sub -h && \
# Delete unneeded packages after install # Delete unneeded packages after install
rm ytdl_sub-*.whl && \ rm ytdl_sub-*.whl && \
@ -71,7 +72,6 @@ RUN mkdir -p /config && \
make \ make \
xz-utils \ xz-utils \
bzip2 \ bzip2 \
python3.10-dev \
python3-venv && \ python3-venv && \
apt-get autoremove -y && \ apt-get autoremove -y && \
apt-get purge -y --auto-remove && \ apt-get purge -y --auto-remove && \
@ -81,6 +81,12 @@ RUN mkdir -p /config && \
# CONTAINER CONFIGS # CONTAINER CONFIGS
ENV EDITOR="nano" \ ENV EDITOR="nano" \
HOME="/config" HOME="/config" \
DOCKER_MODS=linuxserver/mods:universal-stdout-logs|linuxserver/mods:universal-cron \
DEFAULT_WORKSPACE=/config \
CRON_SCRIPT="/config/cron" \
CRON_WRAPPER_SCRIPT="/config/.cron_wrapper" \
LOGS_TO_STDOUT=/config/.cron.log \
LSIO_FIRST_PARTY=false
VOLUME /config VOLUME /config

View file

@ -0,0 +1,68 @@
#!/usr/bin/with-contenv bash
echo "Starting ytdl-sub..."
# copy config
[[ ! -e "$DEFAULT_WORKSPACE/config.yaml" ]] && \
mkdir -p "$DEFAULT_WORKSPACE" && \
cp /defaults/config.yaml "$DEFAULT_WORKSPACE/config.yaml"
[[ ! -e "$DEFAULT_WORKSPACE/subscriptions.yaml" ]] && \
mkdir -p "$DEFAULT_WORKSPACE" && \
cp /defaults/subscriptions.yaml "$DEFAULT_WORKSPACE/subscriptions.yaml"
[[ ! -d "$DEFAULT_WORKSPACE/examples" ]] && \
mkdir -p "$DEFAULT_WORKSPACE/examples" && \
cp -r /defaults/examples/* "$DEFAULT_WORKSPACE/examples"
[[ ! -e "/config/.bashrc" ]] && \
echo "alias ls='ls --color=auto'" > /config/.bashrc && \
echo "cd ." >> /config/.bashrc
# permissions
chown -R ${PUID:-abc}:${PGID:-abc} \
/config
# always create empty cron log file on start
echo "" > "$LOGS_TO_STDOUT"
# set up cron
if [ "$CRON_SCHEDULE" != "" ] ; then
[[ ! -e "$CRON_SCRIPT" ]] && \
cp /defaults/cron "$CRON_SCRIPT"
# create cron script wrapper
echo '#!/bin/bash' > "$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\" | tee -a \"$LOGS_TO_STDOUT\"" >> "$CRON_WRAPPER_SCRIPT"
chmod +x "$CRON_WRAPPER_SCRIPT"
chown abc:abc "$CRON_WRAPPER_SCRIPT"
# Set the crontab file to the schedule, cleanly
CRON_SCHEDULE_CLEAN="${CRON_SCHEDULE//\"/}"
CRON_SCHEDULE_CLEAN="${CRON_SCHEDULE_CLEAN//\'/}"
echo "# min hour day month weekday command" > /config/crontabs/abc
echo "$CRON_SCHEDULE_CLEAN $CRON_WRAPPER_SCRIPT" >> /config/crontabs/abc
chmod +x "$CRON_SCRIPT"
chown abc:abc "$CRON_SCRIPT"
crontab -u abc /config/crontabs/abc
CRON_SUCCESS=$?
if [ $CRON_SUCCESS -eq 0 ] ; then
echo "Cron enabled with schedule $CRON_SCHEDULE_CLEAN"
if [ "$CRON_RUN_ON_START" = true ] ; then
echo "Running cron script on start in the background"
# ensure it runs as abc to respect puid/guid with delay for tail to start
su -s "/bin/bash" -c "sleep 5 && . '$CRON_WRAPPER_SCRIPT'" abc > /dev/null 2>&1 &
fi
else
echo "Error in CRON_SCHEDULE definition, disabling cron."
exit 1
fi
else
echo "CRON_SCHEDULE not specified, leaving crontabs as-is. Current configuration in /config/crontabs/abc"
cat /config/crontabs/abc
fi

View file

@ -1,23 +0,0 @@
#!/usr/bin/with-contenv bash
# Exit if not gui
if [ "$YTDL_SUB_TYPE" != "gui" ] ; then
exit 0
fi
echo "Checking ytdl-sub-gui defaults..."
# copy config
[[ ! -e /config/ytdl-sub-configs/config.yaml ]] && \
mkdir -p /config/ytdl-sub-configs && \
cp /defaults/config.yaml /config/ytdl-sub-configs/config.yaml
[[ ! -e /config/ytdl-sub-configs/subscriptions.yaml ]] && \
mkdir -p /config/ytdl-sub-configs && \
cp /defaults/subscriptions.yaml /config/ytdl-sub-configs/subscriptions.yaml
[[ ! -d /config/ytdl-sub-configs/examples ]] && \
mkdir -p /config/ytdl-sub-configs/examples && \
cp /defaults/examples/* /config/ytdl-sub-configs/examples
# permissions
chown -R ${PUID:-abc}:${PGID:-abc} \
/config

View file

@ -1,20 +0,0 @@
#!/usr/bin/with-contenv bash
# Exit if gui
if [ "$YTDL_SUB_TYPE" == "gui" ] ; then
exit 0
fi
echo "Checking ytdl-sub defaults..."
# copy config
[[ ! -e /config/config.yaml ]] && \
cp /defaults/config.yaml /config/config.yaml
[[ ! -e /config/subscriptions.yaml ]] && \
cp /defaults/subscriptions.yaml /config/subscriptions.yaml
[[ ! -d /config/examples ]] && \
cp -R /defaults/examples /config/
# permissions
chown -R ${PUID:-abc}:${PGID:-abc} \
/config

View file

@ -0,0 +1,4 @@
echo "Beginning cron job..."
# Place your ytdl-sub command(s) here.
# This script is executed in the same relative path as this file.

View file

@ -0,0 +1,15 @@
――――――――――――――――――――――――――――――――――――
██╗ ██╗████████╗██████╗ ██╗
╚██╗ ██╔╝╚══██╔══╝██╔══██╗██║
╚████╔╝ ██║ ██║ ██║██║
╚██╔╝ ██║ ██║ ██║██║
██║ ██║ ██████╔╝███████╗
╚═╝ ╚═╝ ╚═════╝ ╚══════╝
███████╗██╗ ██╗██████╗
██╔════╝██║ ██║██╔══██╗
███████╗██║ ██║██████╔╝
╚════██║██║ ██║██╔══██╗
███████║╚██████╔╝██████╔╝
╚══════╝ ╚═════╝ ╚═════╝
――――――――――――――――――――――――――――――――――――

View file

@ -6,8 +6,34 @@ services:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
- TZ=America/Los_Angeles - TZ=America/Los_Angeles
- CRON_SCHEDULE="*/1 * * * *"
- CRON_RUN_ON_START=true
volumes: volumes:
- ./volumes/ytdl-sub-gui:/config - ./volumes/ytdl-sub-gui:/config
ports: ports:
- 8443:8443 - 8443:8443
restart: unless-stopped restart: unless-stopped
ytdl-sub:
image: ytdl-sub:local
container_name: ytdl-sub
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
- CRON_SCHEDULE="*/1 * * * *"
- CRON_RUN_ON_START=true
volumes:
- ./volumes/ytdl-sub:/config
restart: unless-stopped
ytdl-sub-ubuntu:
image: ytdl-sub-ubuntu:local
container_name: ytdl-sub-ubuntu
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
- CRON_SCHEDULE="*/1 * * * *"
- CRON_RUN_ON_START=true
volumes:
- ./volumes/ytdl-sub:/config
restart: unless-stopped

View file

@ -456,7 +456,7 @@ with a ``.nfo`` extension. You can add any values into the NFO.
``kodi_safe`` ``kodi_safe``
:expected type: Optional[Boolean] :expected type: OverridesBooleanFormatterValidator
:description: :description:
Defaults to False. Kodi does not support > 3-byte unicode characters, which include Defaults to False. Kodi does not support > 3-byte unicode characters, which include
emojis and some foreign language characters. Setting this to True will replace those emojis and some foreign language characters. Setting this to True will replace those
@ -554,7 +554,7 @@ Usage:
``kodi_safe`` ``kodi_safe``
:expected type: Optional[Boolean] :expected type: OverridesBooleanFormatterValidator
:description: :description:
Defaults to False. Kodi does not support > 3-byte unicode characters, which include Defaults to False. Kodi does not support > 3-byte unicode characters, which include
emojis and some foreign language characters. Setting this to True will replace those emojis and some foreign language characters. Setting this to True will replace those
@ -640,6 +640,8 @@ Defines where to output files and thumbnails after all post-processing has compl
maintain_download_archive: True maintain_download_archive: True
keep_files_before: now keep_files_before: now
keep_files_after: 19000101 keep_files_after: 19000101
keep_max_files: 1000
keep_files_date_eval: "{upload_date_standardized}"
``download_archive_name`` ``download_archive_name``
@ -690,6 +692,16 @@ Defines where to output files and thumbnails after all post-processing has compl
``keep_max_files``. ``keep_max_files``.
``keep_files_date_eval``
:expected type: str
:description:
Uses this standardized date in the form of YYYY-MM-DD to record in the
download archive for a given entry. Subsequently, uses this value to
perform evaluation for keep_files_before/after and keep_max_files. Defaults
to the entry's upload_date_standardized variable.
``keep_max_files`` ``keep_max_files``
:expected type: Optional[OverridesFormatter] :expected type: Optional[OverridesFormatter]
@ -966,6 +978,9 @@ scripted.
presets: presets:
my_example_preset: my_example_preset:
throttle_protection: throttle_protection:
sleep_per_request_s:
min: 5.5
max: 10.4
sleep_per_download_s: sleep_per_download_s:
min: 2.2 min: 2.2
max: 10.8 max: 10.8
@ -1001,6 +1016,16 @@ scripted.
ytdl-sub to perform post-processing. ytdl-sub to perform post-processing.
``sleep_per_request_s``
:expected type: Optional[Range]
:description:
Number in seconds to sleep between each request during metadata download. Note that
metadata download refers to the initial info.json download, not the actual audio/video
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`` ``sleep_per_subscription_s``
:expected type: Optional[Range] :expected type: Optional[Range]

View file

@ -659,3 +659,9 @@ ytdl_sub_input_url_index
:type: ``Integer`` :type: ``Integer``
:description: :description:
The index of the input URL as defined in the subscription, top-most being the 0th index. The index of the input URL as defined in the subscription, top-most being the 0th index.
ytdl_sub_keep_files_date_eval
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:type: ``String``
:description:
The standardized date variable supplied in ``output_options.keep_files_date_eval``.

View file

@ -523,6 +523,38 @@ sub
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
Print Functions
---------------
print
~~~~~
:spec: ``print(message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer]) -> ReturnableArgument``
:description:
Print the ``message`` and return ``passthrough``.
Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error.
Defaults to info.
print_if_false
~~~~~~~~~~~~~~
:spec: ``print_if_false(message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer]) -> ReturnableArgument``
:description:
Print the ``message`` if ``passthrough`` evaluates to ``false``. Return ``passthrough``.
Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error.
Defaults to info.
print_if_true
~~~~~~~~~~~~~
:spec: ``print_if_true(message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer]) -> ReturnableArgument``
:description:
Print the ``message`` if ``passthrough`` evaluates to ``true``. Return ``passthrough``.
Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error.
Defaults to info.
----------------------------------------------------------------------------------------------------
Regex Functions Regex Functions
--------------- ---------------
@ -634,7 +666,7 @@ capitalize
concat concat
~~~~~~ ~~~~~~
:spec: ``concat(values: String, ...) -> String`` :spec: ``concat(values: AnyArgument, ...) -> String``
:description: :description:
Concatenate multiple Strings into a single String. Concatenate multiple Strings into a single String.
@ -660,6 +692,24 @@ contains_any
:description: :description:
Returns true if any element in ``contains_array`` is in ``string``. False otherwise. Returns true if any element in ``contains_array`` is in ``string``. False otherwise.
join
~~~~
:spec: ``join(separator: String, array: Array) -> String``
:description:
Join all elements in the array together as a string, and insert the
separator between them.
:usage:
.. code-block:: python
{
%join( ", ", ["item1", "item2"] )
}
# "item1, item2"
lower lower
~~~~~ ~~~~~
:spec: ``lower(string: String) -> String`` :spec: ``lower(string: String) -> String``
@ -710,6 +760,23 @@ string
:description: :description:
Cast to String. Cast to String.
strip
~~~~~
:spec: ``strip(string: String) -> String``
:description:
Strip a string of all its whitespace at the beginning and end.
:usage:
.. code-block:: python
{
%trim(" delete the outer! ")
}
# "delete the outer!"
titlecase titlecase
~~~~~~~~~ ~~~~~~~~~
:spec: ``titlecase(string: String) -> String`` :spec: ``titlecase(string: String) -> String``

View file

@ -62,6 +62,29 @@ See the prebuilt preset :doc:`chunk_initial_download </prebuilt_presets/helpers>
See the prebuilt preset :doc:`Filter Keywords </prebuilt_presets/helpers>`. See the prebuilt preset :doc:`Filter Keywords </prebuilt_presets/helpers>`.
...prevent creation of NFO file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Creation of NFO files is done by the NFO tags plugin. It, as any other plugin, can be disabled:
.. code-block:: yaml
nfo_tags:
enabled: False
...prevent download of images
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :ref:`config_reference/prebuilt_presets/tv_show:TV Show` presets by default downloads images corresponding to show and each episode.
This can be prevented by overriding following variables:
.. code-block:: yaml
overrides:
tv_show_fanart_file_name: "" # to stop creation of fanart.jpg in subscription
tv_show_poster_file_name: "" # to stop creation of poster.jpg in subscription
thumbnail_name: "" # to stop creation of episode thumbnails
There is a bug where... There is a bug where...
----------------------- -----------------------

View file

@ -1,127 +1,43 @@
Automating Downloads Automating Downloads
==================== ====================
One of the key capabilities of ``ytdl-sub`` automating new downloads.
To take advantage of this, you must set up scheduling to execute the commands at some interval.
How you set up this scheduling depends on which version of ``ytdl-sub`` you downloaded.
:ref:`Guide for Docker and Unraid Containers <guides/getting_started/automating_downloads:docker and unraid>` :ref:`Guide for Docker and Unraid Containers <guides/getting_started/automating_downloads:docker and unraid>`
:ref:`Guide for Linux <guides/getting_started/automating_downloads:linux>` :ref:`Guide for Linux <guides/getting_started/automating_downloads:linux>`
:ref:`Guide for Windows <guides/getting_started/automating_downloads:windows>` :ref:`Guide for Windows <guides/getting_started/automating_downloads:windows>`
.. _cron tab manpage: https://man7.org/linux/man-pages/man5/crontab.5.html#EXAMPLE_CRON_FILE .. _cron scheduling syntax: https://crontab.guru/#0_*/6_*_*_*
.. _docker-unraid-setup: .. _docker-unraid-setup:
Docker and Unraid Docker and Unraid
----------------- -----------------
.. tab-set:: Cron is preconfigured in every ytdl-sub docker container. Enable by adding the following
ENV variables to your docker setup.
.. tab-item:: GUI Image
The script that will execute automatically is located at ``/config/ytdl-sub-configs/run_cron``.
Access your container at http://localhost:8443/, then in the GUI terminal run these commands:
.. code-block:: shell
echo '#!/bin/bash' > /config/ytdl-sub-configs/run_cron
echo "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> /config/ytdl-sub-configs/run_cron
echo "echo 'Cron started, running ytdl-sub...'" >> /config/ytdl-sub-configs/run_cron
echo "cd /config/ytdl-sub-configs" >> /config/ytdl-sub-configs/run_cron
echo "ytdl-sub --config=config.yaml sub subscriptions.yaml" >> /config/ytdl-sub-configs/run_cron
chmod +x /config/ytdl-sub-configs/run_cron
chown abc:abc /config/ytdl-sub-configs/run_cron
You can test the newly created script by running:
.. code-block:: shell
/config/ytdl-sub-configs/run_cron
To create the cron definition, run the following command:
.. code-block:: shell
echo "# min hour day month weekday command" > /config/crontabs/abc
echo " 0 */6 * * * /config/ytdl-sub-configs/run_cron" >> /config/crontabs/abc
This will run the script every 6 hours. To run every hour, change ``*/6`` to ``*/1``, or to run once a day, change the same value to the hour (in 24hr format) that you want it to run at. See the `cron tab manpage`_ for more options.
.. tab-item:: Headless Image
.. _LinuxServer's Universal Cron mod: https://github.com/linuxserver/docker-mods/tree/universal-cron
The first step is to ensure you have `LinuxServer's Universal Cron mod`_ enabled via the environment variable. For the GUI image, this is already included (no need to add it).
.. code-block:: yaml .. code-block:: yaml
services: services:
ytdl-sub: ytdl-sub:
image: ghcr.io/jmbannon/ytdl-sub:latest
container_name: ytdl-sub
environment: environment:
- PUID=1000 - CRON_SCHEDULE="0 */6 * * *"
- PGID=1000 - CRON_RUN_ON_START=false
- TZ=America/Los_Angeles
- DOCKER_MODS=linuxserver/mods:universal-cron # <-- Make sure you have this!
volumes:
# ensure directories have user permissions
- </path/to/ytdl-sub/config>:/config
- </path/to/ytdl-sub/tv_shows>:/tv_shows
restart: unless-stopped
This line will tell your container to install and enable cron on start.
If you had to add this line, you will need to restart your container. - ``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.
.. code-block:: shell The cron script will reside in the main directory with the file name ``cron``.
Cron logs should show when viewing the Docker logs.
docker compose restart
The script that will execute automatically is located at ``/config/run_cron``.
Access your container from the terminal by running:
.. code-block:: shell
docker exec -itu abc ytdl-sub /bin/bash
then in the terminal run these commands:
.. code-block:: shell
echo '#!/bin/bash' > /config/run_cron
echo "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> /config/run_cron
echo "echo 'Cron started, running ytdl-sub...'" >> /config/run_cron
echo "cd /config" >> /config/run_cron
echo "ytdl-sub --config=config.yaml sub subscriptions.yaml" >> /config/run_cron
chmod +x /config/run_cron
chown abc:abc /config/run_cron
You can test the newly created script by running:
.. code-block::
/config/run_cron
To create the cron definition, run the following command:
.. code-block:: shell
echo "# min hour day month weekday command" > /config/crontabs/abc
echo " 0 */6 * * * /config/run_cron" >> /config/crontabs/abc
This will run the script every 6 hours. To run every hour, change ``*/6`` to ``*/1``, or to run once a day, change the same value to the hour (in 24hr format) that you want it to run at. See the `cron tab manpage`_ for more options.
.. _linux-setup: .. _linux-setup:
Linux Linux
----- -----
Must configure crontab manually, like so:
.. code-block:: shell .. code-block:: shell

View file

@ -79,7 +79,6 @@ Docker Compose is an easy "set it and forget it" install method. Follow the inst
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
- TZ=America/Los_Angeles - TZ=America/Los_Angeles
- DOCKER_MODS=linuxserver/mods:universal-cron
volumes: volumes:
- <path/to/ytdl-sub/config>:/config - <path/to/ytdl-sub/config>:/config
- <path/to/tv_shows>:/tv_shows # optional - <path/to/tv_shows>:/tv_shows # optional

View file

@ -28,23 +28,18 @@ Plex
---------------------------------------------- ----------------------------------------------
Generic Presets
===============
There are two main methods for downloading and formatting videos as a TV show.
TV Show by Date TV Show by Date
--------------- ===============
TV Show by Date will organize something like a YouTube channel or playlist into a tv show, where seasons and episodes are organized using upload date. TV Show by Date will organize something like a YouTube channel or playlist into a tv show, where seasons and episodes are organized using upload date.
Example Example
~~~~~~~ -------
Must define ``tv_show_directory``. Available presets: Must define ``tv_show_directory``. Available presets:
* ``"Kodi TV Show by Date"`` * ``Kodi TV Show by Date``
* ``"Jellyfin TV Show by Date"`` * ``Jellyfin TV Show by Date``
* ``"Plex TV Show by Date"`` * ``Plex TV Show by Date``
.. code-block:: yaml .. code-block:: yaml
@ -72,30 +67,68 @@ Must define ``tv_show_directory``. Available presets:
- "https://www.youtube.com/@rickbeato240" - "https://www.youtube.com/@rickbeato240"
Advanced Usage Advanced Usage
~~~~~~~~~~~~~~ --------------
If you prefer a different organization method, you can instead apply multiple presets to your subscriptions. If you prefer a different season/episode organization method, you can set the following override variables.
You will need a base of one of the below: .. code-block:: yaml
* ``kodi_tv_show_by_date`` __preset__:
* ``jellyfin_tv_show_by_date`` overrides:
* ``plex_tv_show_by_date`` tv_show_directory: "/tv_shows"
tv_show_by_date_season_ordering: "upload-year-month"
tv_show_by_date_episode_ordering: "upload-day"
And then add one of these: Or for a specific preset
* ``season_by_year__episode_by_month_day`` .. code-block:: yaml
* ``season_by_year_month__episode_by_day``
* ``season_by_year__episode_by_month_day_reversed``
* Episode numbers are reversed, meaning more recent episodes appear at the top of a season by having a lower value. "~Kids Toys Play":
* ``season_by_year__episode_by_download_index`` url: "https://www.youtube.com/@KidsToysPlayChannel"
tv_show_by_date_season_ordering: "upload-year-month"
tv_show_by_date_episode_ordering: "upload-day"
* Episodes are numbered by the download order. NOTE that this is fetched using the length of the download archive. Do not use if you intend to remove old videos. The following are supported. Be sure the combined season + episode ordering
include the year, month, day, i.e. upload-year + upload-month-day.
Season Ordering
~~~~~~~~~~~~~~~
``tv_show_by_date_season_ordering`` supports one of the following:
* ``upload-year`` (default)
* ``upload-year-month``
* ``release-year``
* ``release-year-month``
Episode Ordering
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``tv_show_by_date_episode_ordering`` supports one of the following:
* ``upload-month-day`` (default)
* ``upload-month-day-reversed``
* Reversed means more recent episodes appear at the top of a season by having a lower value.
* ``upload-day``
* ``release-day``
* ``release-month-day``
* ``release-month-day-reversed``
* ``download-index``
* Episodes are numbered by the download order. **NOTE**: this is fetched using the length of the download archive. Do not use if you intend to remove old videos.
TV Show by Date presets use the following for defaults:
.. code-block:: yaml
tv_show_by_date_season_ordering: "upload-year"
tv_show_by_date_episode_ordering: "upload-month-day"
TV Show Collection TV Show Collection
------------------ ==================
TV Show Collections set each URL as its own season. If a video belongs to multiple URLs TV Show Collections set each URL as its own season. If a video belongs to multiple URLs
(i.e. a channel and a channel's playlist), the video will only download once and reside in (i.e. a channel and a channel's playlist), the video will only download once and reside in
@ -109,12 +142,12 @@ Two main use cases of a collection are:
represents a separate channel/playlist. represents a separate channel/playlist.
Example Example
~~~~~~~ -------
Must define ``tv_show_directory``. Available presets: Must define ``tv_show_directory``. Available presets:
* ``"Kodi TV Show Collection"`` * ``Kodi TV Show Collection``
* ``"Jellyfin TV Show Collection"`` * ``Jellyfin TV Show Collection``
* ``"Plex TV Show Collection"`` * ``Plex TV Show Collection``
.. code-block:: yaml .. code-block:: yaml
@ -132,21 +165,44 @@ Must define ``tv_show_directory``. Available presets:
s02_url: "https://www.youtube.com/playlist?list=PLE62gWlWZk5NWVAVuf0Lm9jdv_-_KXs0W" s02_url: "https://www.youtube.com/playlist?list=PLE62gWlWZk5NWVAVuf0Lm9jdv_-_KXs0W"
Advanced Usage Advanced Usage
~~~~~~~~~~~~~~ --------------
If you prefer a different organization method, you can instead apply multiple presets to your subscriptions. If you prefer a different episode organization method, you can set the following override variables.
You will need a base of one of the below: .. code-block:: yaml
* ``kodi_tv_show_collection`` __preset__:
* ``jellyfin_tv_show_collection`` overrides:
* ``plex_tv_show_collection`` tv_show_directory: "/tv_shows"
tv_show_collection_episode_ordering: "release-year-month-day"
And then add one of these: Or for a specific preset
* ``season_by_collection__episode_by_year_month_day`` .. code-block:: yaml
* ``season_by_collection__episode_by_year_month_day_reversed``
* ``season_by_collection__episode_by_playlist_index``
* Only use playlist_index episode formatting for playlists that will be fully downloaded once and never again. Otherwise, indices can change. "~Beyond the Guitar":
* ``season_by_collection__episode_by_playlist_index_reversed`` tv_show_collection_episode_ordering: "release-year-month-day"
s01_name: "Videos"
s01_url: "https://www.youtube.com/c/BeyondTheGuitar"
s02_name: "Covers"
s02_url: "https://www.youtube.com/playlist?list=PLE62gWlWZk5NWVAVuf0Lm9jdv_-_KXs0W"
The following are supported.
Episode Ordering
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``tv_show_collection_episode_ordering`` supports one of the following:
* ``upload-year-month-day`` (default)
* ``upload-year-month-day-reversed``
* ``release-year-month-day``
* ``release-year-month-day-reversed``
* ``playlist-index``
* Only use ``playlist-index`` episode formatting for playlists that will be fully downloaded once and never again. Otherwise, indices can change.
* ``playlist-index-reversed``
TV Show Collection presets use upload-year-month-day as the default.

View file

@ -15,7 +15,7 @@ classifiers = [
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
] ]
dependencies = [ dependencies = [
"yt-dlp[default]==2024.12.23", "yt-dlp[default]==2025.5.22",
"colorama~=0.4", "colorama~=0.4",
"mergedeep~=1.3", "mergedeep~=1.3",
"mediafile~=0.12", "mediafile~=0.12",
@ -48,8 +48,8 @@ test = [
] ]
lint = [ lint = [
"black==24.10.0", "black==24.10.0",
"isort==5.13.2", "isort==6.0.1",
"pylint==3.3.3", "pylint==3.3.7",
] ]
docs = [ docs = [
"sphinx>=7,<9", "sphinx>=7,<9",

View file

@ -271,6 +271,6 @@ def main() -> List[Subscription]:
transaction_log_file_path=args.transaction_log, transaction_log_file_path=args.transaction_log,
) )
output_summary(subscriptions) output_summary(subscriptions, suppress_colors=args.suppress_colors)
return subscriptions return subscriptions

View file

@ -8,16 +8,16 @@ from ytdl_sub.utils.logger import Logger
logger = Logger.get() logger = Logger.get()
def _green(value: str) -> str: def _green(value: str, suppress_colors: bool = False) -> str:
return Fore.GREEN + value + Fore.RESET return value if suppress_colors else Fore.GREEN + value + Fore.RESET
def _red(value: str) -> str: def _red(value: str, suppress_colors: bool = False) -> str:
return Fore.RED + value + Fore.RESET return value if suppress_colors else Fore.RED + value + Fore.RESET
def _no_color(value: str) -> str: def _no_color(value: str, suppress_colors: bool = False) -> str:
return Fore.RESET + value + Fore.RESET return value if suppress_colors else Fore.RESET + value + Fore.RESET
def _str_int(value: int) -> str: def _str_int(value: int) -> str:
@ -26,21 +26,23 @@ def _str_int(value: int) -> str:
return str(value) return str(value)
def _color_int(value: int) -> str: def _color_int(value: int, suppress_colors: bool = False) -> str:
str_int = _str_int(value) str_int = _str_int(value)
if value > 0: if value > 0:
return _green(str_int) return _green(str_int, suppress_colors)
if value < 0: if value < 0:
return _red(str_int) return _red(str_int, suppress_colors)
return _no_color(str_int) return _no_color(str_int, suppress_colors)
def output_summary(subscriptions: List[Subscription]) -> None: def output_summary(subscriptions: List[Subscription], suppress_colors: bool) -> None:
""" """
Parameters Parameters
---------- ----------
subscriptions subscriptions
Processed subscriptions Processed subscriptions
suppress_colors
Whether to have color or not
Returns Returns
------- -------
@ -65,21 +67,21 @@ def output_summary(subscriptions: List[Subscription]) -> None:
# Initialize widths to 0 # Initialize widths to 0
width_sub_name: int = max(len(sub.name) for sub in subscriptions) + 4 # aesthetics width_sub_name: int = max(len(sub.name) for sub in subscriptions) + 4 # aesthetics
width_num_entries_added: int = len(_color_int(total_added)) width_num_entries_added: int = len(_color_int(total_added, suppress_colors))
width_num_entries_modified: int = len(_color_int(total_modified)) width_num_entries_modified: int = len(_color_int(total_modified, suppress_colors))
width_num_entries_removed: int = len(_color_int(total_removed)) width_num_entries_removed: int = len(_color_int(total_removed, suppress_colors))
width_num_entries: int = len(str(total_entries)) + 4 # aesthetics width_num_entries: int = len(str(total_entries)) + 4 # aesthetics
# Build the summary # Build the summary
for subscription in subscriptions: for subscription in subscriptions:
num_entries_added = _color_int(subscription.num_entries_added) num_entries_added = _color_int(subscription.num_entries_added, suppress_colors)
num_entries_modified = _color_int(subscription.num_entries_modified) num_entries_modified = _color_int(subscription.num_entries_modified, suppress_colors)
num_entries_removed = _color_int(subscription.num_entries_removed * -1) num_entries_removed = _color_int(subscription.num_entries_removed * -1, suppress_colors)
num_entries = str(subscription.num_entries) num_entries = str(subscription.num_entries)
status = ( status = (
_red(subscription.exception.__class__.__name__) _red(subscription.exception.__class__.__name__, suppress_colors)
if subscription.exception if subscription.exception
else _green("") else _green("", suppress_colors)
) )
summary.append( summary.append(
@ -92,14 +94,16 @@ def output_summary(subscriptions: List[Subscription]) -> None:
) )
total_errors_str = ( total_errors_str = (
_green("Success") if total_errors == 0 else _red(f"Error{'s' if total_errors > 1 else ''}") _green("Success", suppress_colors)
if total_errors == 0
else _red(f"Error{'s' if total_errors > 1 else ''}", suppress_colors)
) )
summary.append( summary.append(
f"{total_subs_str:<{width_sub_name}} " f"{total_subs_str:<{width_sub_name}} "
f"{_color_int(total_added):>{width_num_entries_added}} " f"{_color_int(total_added, suppress_colors):>{width_num_entries_added}} "
f"{_color_int(total_modified):>{width_num_entries_modified}} " f"{_color_int(total_modified, suppress_colors):>{width_num_entries_modified}} "
f"{_color_int(total_removed):>{width_num_entries_removed}} " f"{_color_int(total_removed * -1, suppress_colors):>{width_num_entries_removed}} "
f"{total_entries:>{width_num_entries}} " f"{total_entries:>{width_num_entries}} "
f"{total_errors_str}" f"{total_errors_str}"
) )

View file

@ -44,6 +44,7 @@ class MainArguments:
short="-m", short="-m",
long="--match", long="--match",
) )
SUPPRESS_COLORS = CLIArgument(short="-nc", long="--suppress-colors")
@classmethod @classmethod
def all(cls) -> List[CLIArgument]: def all(cls) -> List[CLIArgument]:
@ -59,6 +60,7 @@ class MainArguments:
cls.TRANSACTION_LOG, cls.TRANSACTION_LOG,
cls.SUPPRESS_TRANSACTION_LOG, cls.SUPPRESS_TRANSACTION_LOG,
cls.MATCH, cls.MATCH,
cls.SUPPRESS_COLORS,
] ]
@classmethod @classmethod
@ -129,6 +131,13 @@ def _add_shared_arguments(arg_parser: argparse.ArgumentParser, suppress_defaults
help="do not output transaction logs to console or file", help="do not output transaction logs to console or file",
default=argparse.SUPPRESS if suppress_defaults else False, default=argparse.SUPPRESS if suppress_defaults else False,
) )
arg_parser.add_argument(
MainArguments.SUPPRESS_COLORS.short,
MainArguments.SUPPRESS_COLORS.long,
action="store_true",
help="do not use colors in ytdl-sub output",
default=argparse.SUPPRESS if suppress_defaults else False,
)
arg_parser.add_argument( arg_parser.add_argument(
MainArguments.MATCH.short, MainArguments.MATCH.short,
MainArguments.MATCH.long, MainArguments.MATCH.long,

View file

@ -221,3 +221,12 @@ class Overrides(UnstructuredDictFormatterValidator, Scriptable):
return self._apply_to_resolvable( return self._apply_to_resolvable(
formatter=formatter, entry=None, function_overrides=None formatter=formatter, entry=None, function_overrides=None
).native ).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)

View file

@ -13,7 +13,6 @@ from ytdl_sub.config.validators.options import OptionsValidatorT
from ytdl_sub.config.validators.options import ToggleableOptionsDictValidator from ytdl_sub.config.validators.options import ToggleableOptionsDictValidator
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.utils.script import ScriptUtils
from ytdl_sub.ytdl_additions.enhanced_download_archive import DownloadArchiver from ytdl_sub.ytdl_additions.enhanced_download_archive import DownloadArchiver
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
@ -49,9 +48,7 @@ class Plugin(BasePlugin[OptionsValidatorT], Generic[OptionsValidatorT], ABC):
Returns True if enabled, False if disabled. Returns True if enabled, False if disabled.
""" """
if isinstance(self.plugin_options, ToggleableOptionsDictValidator): if isinstance(self.plugin_options, ToggleableOptionsDictValidator):
return ScriptUtils.bool_formatter_output( return self.overrides.evaluate_boolean(self.plugin_options.enable)
self.overrides.apply_formatter(self.plugin_options.enable)
)
return True return True
def ytdl_options_match_filters(self) -> Tuple[List[str], List[str]]: def ytdl_options_match_filters(self) -> Tuple[List[str], List[str]]:

View file

@ -1,15 +1,19 @@
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
from typing import Set
from ytdl_sub.config.defaults import DEFAULT_DOWNLOAD_ARCHIVE_NAME from ytdl_sub.config.defaults import DEFAULT_DOWNLOAD_ARCHIVE_NAME
from ytdl_sub.config.overrides import Overrides 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.validators.file_path_validators import OverridesStringFormatterFilePathValidator from ytdl_sub.validators.file_path_validators import OverridesStringFormatterFilePathValidator
from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
from ytdl_sub.validators.string_datetime import StringDatetimeValidator from ytdl_sub.validators.string_datetime import StringDatetimeValidator
from ytdl_sub.validators.string_formatter_validators import OverridesIntegerFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesIntegerFormatterValidator
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
from ytdl_sub.validators.string_formatter_validators import StandardizedDateValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.string_formatter_validators import ( from ytdl_sub.validators.string_formatter_validators import (
UnstructuredOverridesDictFormatterValidator, UnstructuredOverridesDictFormatterValidator,
@ -65,7 +69,7 @@ class YTDLOptions(UnstructuredOverridesDictFormatterValidator):
# pylint: disable=line-too-long # pylint: disable=line-too-long
class OutputOptions(StrictDictValidator): class OutputOptions(OptionsDictValidator):
""" """
Defines where to output files and thumbnails after all post-processing has completed. Defines where to output files and thumbnails after all post-processing has completed.
@ -87,6 +91,8 @@ class OutputOptions(StrictDictValidator):
maintain_download_archive: True maintain_download_archive: True
keep_files_before: now keep_files_before: now
keep_files_after: 19000101 keep_files_after: 19000101
keep_max_files: 1000
keep_files_date_eval: "{upload_date_standardized}"
""" """
_required_keys = {"output_directory", "file_name"} _required_keys = {"output_directory", "file_name"}
@ -99,6 +105,8 @@ class OutputOptions(StrictDictValidator):
"keep_files_before", "keep_files_before",
"keep_files_after", "keep_files_after",
"keep_max_files", "keep_max_files",
"download_archive_standardized_date",
"keep_files_date_eval",
} }
@classmethod @classmethod
@ -156,6 +164,11 @@ class OutputOptions(StrictDictValidator):
self._keep_max_files = self._validate_key_if_present( self._keep_max_files = self._validate_key_if_present(
"keep_max_files", OverridesIntegerFormatterValidator "keep_max_files", OverridesIntegerFormatterValidator
) )
self._keep_files_date_eval = self._validate_key(
"keep_files_date_eval",
StandardizedDateValidator,
default=f"{{{v.upload_date_standardized.variable_name}}}",
)
if ( if (
self._keep_files_before or self._keep_files_after or self._keep_max_files self._keep_files_before or self._keep_files_after or self._keep_max_files
@ -272,6 +285,18 @@ class OutputOptions(StrictDictValidator):
""" """
return self._keep_files_after return self._keep_files_after
@property
def keep_files_date_eval(self) -> StandardizedDateValidator:
"""
:expected type: str
:description:
Uses this standardized date in the form of YYYY-MM-DD to record in the
download archive for a given entry. Subsequently, uses this value to
perform evaluation for keep_files_before/after and keep_max_files. Defaults
to the entry's upload_date_standardized variable.
"""
return self._keep_files_date_eval
@property @property
def keep_max_files(self) -> Optional[OverridesIntegerFormatterValidator]: def keep_max_files(self) -> Optional[OverridesIntegerFormatterValidator]:
""" """
@ -283,3 +308,10 @@ class OutputOptions(StrictDictValidator):
applied. Can be used in conjunction with ``keep_files_before`` and ``keep_files_after``. applied. Can be used in conjunction with ``keep_files_before`` and ``keep_files_after``.
""" """
return self._keep_max_files return self._keep_max_files
def added_variables(self, unresolved_variables: Set[str]) -> Dict[PluginOperation, Set[str]]:
return {
# PluginOperation.MODIFY_ENTRY_METADATA: {
# VARIABLES.ytdl_sub_entry_date_eval.variable_name
# }
}

View file

@ -57,13 +57,14 @@ def _add_dummy_overrides(overrides: Overrides) -> Dict[str, str]:
def _get_added_and_modified_variables( def _get_added_and_modified_variables(
plugins: PresetPlugins, downloader_options: MultiUrlValidator plugins: PresetPlugins, downloader_options: MultiUrlValidator, output_options: OutputOptions
) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]: ) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]:
""" """
Iterates and returns the plugin options, added variables, modified variables Iterates and returns the plugin options, added variables, modified variables
""" """
options: List[OptionsValidator] = plugins.plugin_options options: List[OptionsValidator] = plugins.plugin_options
options.append(downloader_options) options.append(downloader_options)
options.append(output_options)
for plugin_options in options: for plugin_options in options:
added_variables: Set[str] = set() added_variables: Set[str] = set()
@ -117,6 +118,7 @@ class VariableValidation:
) in _get_added_and_modified_variables( ) in _get_added_and_modified_variables(
plugins=self.plugins, plugins=self.plugins,
downloader_options=self.downloader_options, downloader_options=self.downloader_options,
output_options=self.output_options,
): ):
for added_variable in added_variables: for added_variable in added_variables:
@ -183,6 +185,9 @@ class VariableValidation:
self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options) self._add_variables(PluginOperation.DOWNLOADER, options=self.downloader_options)
self._add_subscription_override_variables() self._add_subscription_override_variables()
# Always add output options first
self._add_variables(PluginOperation.MODIFY_ENTRY_METADATA, options=self.output_options)
# Metadata variables to be added # Metadata variables to be added
for plugin_options in PluginMapping.order_options_by( for plugin_options in PluginMapping.order_options_by(
self.plugins.zipped(), PluginOperation.MODIFY_ENTRY_METADATA self.plugins.zipped(), PluginOperation.MODIFY_ENTRY_METADATA

View file

@ -25,7 +25,6 @@ from ytdl_sub.entries.script.variable_definitions import VARIABLES
from ytdl_sub.entries.script.variable_definitions import VariableDefinitions from ytdl_sub.entries.script.variable_definitions import VariableDefinitions
from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.logger import Logger from ytdl_sub.utils.logger import Logger
from ytdl_sub.utils.script import ScriptUtils
from ytdl_sub.utils.thumbnail import ThumbnailTypes from ytdl_sub.utils.thumbnail import ThumbnailTypes
from ytdl_sub.utils.thumbnail import download_and_convert_url_thumbnail from ytdl_sub.utils.thumbnail import download_and_convert_url_thumbnail
from ytdl_sub.utils.thumbnail import try_convert_download_thumbnail from ytdl_sub.utils.thumbnail import try_convert_download_thumbnail
@ -459,11 +458,9 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
metadata_ytdl_options = self.metadata_ytdl_options( metadata_ytdl_options = self.metadata_ytdl_options(
ytdl_option_overrides=validator.ytdl_options.to_native_dict(self.overrides) ytdl_option_overrides=validator.ytdl_options.to_native_dict(self.overrides)
) )
download_reversed = ScriptUtils.bool_formatter_output( download_reversed = self.overrides.evaluate_boolean(validator.download_reverse)
self.overrides.apply_formatter(validator.download_reverse) include_sibling_metadata = self.overrides.evaluate_boolean(
) validator.include_sibling_metadata
include_sibling_metadata = ScriptUtils.bool_formatter_output(
self.overrides.apply_formatter(validator.include_sibling_metadata)
) )
parents, orphan_entries = self._download_url_metadata( parents, orphan_entries = self._download_url_metadata(
@ -534,8 +531,8 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]):
download_logger.info("Entry rejected by download match-filter, skipping ..") download_logger.info("Entry rejected by download match-filter, skipping ..")
return None return None
upload_date_idx = self._enhanced_download_archive.mapping.get_num_entries_with_upload_date( upload_date_idx = self._enhanced_download_archive.mapping.get_num_entries_with_date(
upload_date_standardized=entry.get(v.upload_date_standardized, str) standardized_date=entry.get(v.ytdl_sub_keep_files_date_eval, str)
) )
download_idx = self._enhanced_download_archive.num_entries download_idx = self._enhanced_download_archive.num_entries

View file

@ -823,6 +823,17 @@ class YtdlSubVariableDefinitions(ABC):
variable_name="upload_date_index_reversed_padded", pad=2 variable_name="upload_date_index_reversed_padded", pad=2
) )
@cached_property
def ytdl_sub_keep_files_date_eval(self: "VariableDefinitions") -> StringVariable:
"""
:description:
The standardized date variable supplied in ``output_options.keep_files_date_eval``.
"""
return StringVariable(
variable_name="ytdl_sub_entry_date_eval",
definition=f"{{%string({self.upload_date_standardized.variable_name})}}",
)
class EntryVariableDefinitions(ABC): class EntryVariableDefinitions(ABC):
@cached_property @cached_property
@ -1121,6 +1132,7 @@ class VariableDefinitions(
self.ytdl_sub_input_url, self.ytdl_sub_input_url,
self.ytdl_sub_input_url_index, self.ytdl_sub_input_url_index,
self.ytdl_sub_input_url_count, self.ytdl_sub_input_url_count,
self.ytdl_sub_keep_files_date_eval,
} }
@cache @cache

View file

@ -5,7 +5,6 @@ from typing import Tuple
from ytdl_sub.config.plugin.plugin import Plugin from ytdl_sub.config.plugin.plugin import Plugin
from ytdl_sub.config.validators.options import ToggleableOptionsDictValidator from ytdl_sub.config.validators.options import ToggleableOptionsDictValidator
from ytdl_sub.utils.datetime import to_date_str from ytdl_sub.utils.datetime import to_date_str
from ytdl_sub.utils.script import ScriptUtils
from ytdl_sub.validators.string_datetime import StringDatetimeValidator from ytdl_sub.validators.string_datetime import StringDatetimeValidator
from ytdl_sub.validators.string_formatter_validators import OverridesBooleanFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesBooleanFormatterValidator
@ -95,9 +94,7 @@ class DateRangePlugin(Plugin[DateRangeOptions]):
date_validator=self.plugin_options.after, overrides=self.overrides date_validator=self.plugin_options.after, overrides=self.overrides
) )
after_filter = f"upload_date >= {after_str}" after_filter = f"upload_date >= {after_str}"
if ScriptUtils.bool_formatter_output( if self.overrides.evaluate_boolean(self.plugin_options.breaks):
self.overrides.apply_formatter(self.plugin_options.breaks)
):
breaking_match_filters.append(after_filter) breaking_match_filters.append(after_filter)
else: else:
match_filters.append(after_filter) match_filters.append(after_filter)

View file

@ -7,7 +7,6 @@ from ytdl_sub.config.validators.options import OptionsValidator
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.utils.exceptions import StringFormattingException from ytdl_sub.utils.exceptions import StringFormattingException
from ytdl_sub.utils.logger import Logger 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 ListFormatterValidator
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
@ -53,10 +52,9 @@ class FilterExcludePlugin(Plugin[FilterExcludeOptions]):
return entry return entry
for formatter in self.plugin_options.list: for formatter in self.plugin_options.list:
out = ScriptUtils.bool_formatter_output( should_exclude = self.overrides.evaluate_boolean(formatter=formatter, entry=entry)
self.overrides.apply_formatter(formatter=formatter, entry=entry)
) if should_exclude:
if bool(out):
logger.info( logger.info(
"Filtering '%s' from the filter %s evaluating to True", "Filtering '%s' from the filter %s evaluating to True",
entry.title, entry.title,

View file

@ -61,10 +61,10 @@ class FilterIncludePlugin(Plugin[FilterIncludeOptions]):
return entry return entry
for formatter in self.plugin_options.list: for formatter in self.plugin_options.list:
out = ScriptUtils.bool_formatter_output( should_exclude = ScriptUtils.bool_formatter_output(
self.overrides.apply_formatter(formatter=formatter, entry=entry) self.overrides.apply_formatter(formatter=formatter, entry=entry)
) )
if not bool(out): if not should_exclude:
logger.info( logger.info(
"Filtering '%s' from the filter %s evaluating to False", "Filtering '%s' from the filter %s evaluating to False",
entry.title, entry.title,

View file

@ -5,7 +5,6 @@ from pathlib import Path
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import Optional
from ytdl_sub.config.plugin.plugin import Plugin from ytdl_sub.config.plugin.plugin import Plugin
from ytdl_sub.config.validators.options import ToggleableOptionsDictValidator from ytdl_sub.config.validators.options import ToggleableOptionsDictValidator
@ -19,8 +18,8 @@ from ytdl_sub.utils.xml import to_xml
from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator
from ytdl_sub.validators.nfo_validators import NfoTagsValidator from ytdl_sub.validators.nfo_validators import NfoTagsValidator
from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator
from ytdl_sub.validators.string_formatter_validators import OverridesBooleanFormatterValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.validators import BoolValidator
class SharedNfoTagsOptions(ToggleableOptionsDictValidator): class SharedNfoTagsOptions(ToggleableOptionsDictValidator):
@ -54,8 +53,8 @@ class SharedNfoTagsOptions(ToggleableOptionsDictValidator):
) )
self._tags = self._validate_key_if_present(key="tags", validator=NfoTagsValidator) self._tags = self._validate_key_if_present(key="tags", validator=NfoTagsValidator)
self._kodi_safe = self._validate_key_if_present( self._kodi_safe = self._validate_key_if_present(
key="kodi_safe", validator=BoolValidator, default=False key="kodi_safe", validator=OverridesBooleanFormatterValidator, default="False"
).value )
@property @property
def nfo_name(self) -> StringFormatterFileNameValidator: def nfo_name(self) -> StringFormatterFileNameValidator:
@ -81,9 +80,9 @@ class SharedNfoTagsOptions(ToggleableOptionsDictValidator):
return self._tags return self._tags
@property @property
def kodi_safe(self) -> Optional[bool]: def kodi_safe(self) -> OverridesBooleanFormatterValidator:
""" """
:expected type: Optional[Boolean] :expected type: OverridesBooleanFormatterValidator
:description: :description:
Defaults to False. Kodi does not support > 3-byte unicode characters, which include Defaults to False. Kodi does not support > 3-byte unicode characters, which include
emojis and some foreign language characters. Setting this to True will replace those emojis and some foreign language characters. Setting this to True will replace those
@ -141,7 +140,7 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC):
if not nfo_tags: if not nfo_tags:
return return
if self.plugin_options.kodi_safe: if self.overrides.evaluate_boolean(self.plugin_options.kodi_safe):
nfo_root = to_max_3_byte_utf8_string(nfo_root) nfo_root = to_max_3_byte_utf8_string(nfo_root)
nfo_tags = { nfo_tags = {
to_max_3_byte_utf8_string(key): [ to_max_3_byte_utf8_string(key): [

View file

@ -146,7 +146,12 @@ class SubtitlesPlugin(Plugin[SubtitleOptions]):
plugin_options_type = SubtitleOptions plugin_options_type = SubtitleOptions
def ytdl_options(self) -> Optional[Dict]: def ytdl_options(self) -> Optional[Dict]:
builder = YTDLOptionsBuilder().add({"writesubtitles": True}) builder = YTDLOptionsBuilder().add(
{
"writesubtitles": True,
"sleep_interval_subtitles": 5.367, # for safe measure
}
)
if self.plugin_options.embed_subtitles: if self.plugin_options.embed_subtitles:
builder.add( builder.add(

View file

@ -1,5 +1,6 @@
import random import random
import time import time
from typing import Dict
from typing import Optional from typing import Optional
from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.overrides import Overrides
@ -40,6 +41,22 @@ class RandomizedRangeValidator(StrictDictValidator):
f"max ({self._max}) must be greater than or equal to min ({self._min})" f"max ({self._max}) must be greater than or equal to min ({self._min})"
) )
def min_value(self) -> float:
"""
Returns
-------
Minimum value
"""
return self._min
def max_value(self) -> float:
"""
Returns
-------
Maximum value
"""
return self._max
def randomized_float(self) -> float: def randomized_float(self) -> float:
""" """
Returns Returns
@ -70,6 +87,9 @@ class ThrottleProtectionOptions(ToggleableOptionsDictValidator):
presets: presets:
my_example_preset: my_example_preset:
throttle_protection: throttle_protection:
sleep_per_request_s:
min: 5.5
max: 10.4
sleep_per_download_s: sleep_per_download_s:
min: 2.2 min: 2.2
max: 10.8 max: 10.8
@ -84,6 +104,7 @@ class ThrottleProtectionOptions(ToggleableOptionsDictValidator):
_optional_keys = { _optional_keys = {
"enable", "enable",
"sleep_per_request_s",
"sleep_per_download_s", "sleep_per_download_s",
"sleep_per_subscription_s", "sleep_per_subscription_s",
"max_downloads_per_subscription", "max_downloads_per_subscription",
@ -93,6 +114,9 @@ class ThrottleProtectionOptions(ToggleableOptionsDictValidator):
def __init__(self, name, value): def __init__(self, name, value):
super().__init__(name, value) super().__init__(name, value)
self._sleep_per_request_s = self._validate_key_if_present(
key="sleep_per_request_s", validator=RandomizedRangeValidator
)
self._sleep_per_download_s = self._validate_key_if_present( self._sleep_per_download_s = self._validate_key_if_present(
key="sleep_per_download_s", validator=RandomizedRangeValidator key="sleep_per_download_s", validator=RandomizedRangeValidator
) )
@ -106,6 +130,18 @@ class ThrottleProtectionOptions(ToggleableOptionsDictValidator):
key="subscription_download_probability", validator=ProbabilityValidator key="subscription_download_probability", validator=ProbabilityValidator
) )
@property
def sleep_per_request_s(self) -> Optional[RandomizedRangeValidator]:
"""
:expected type: Optional[Range]
:description:
Number in seconds to sleep between each request during metadata download. Note that
metadata download refers to the initial info.json download, not the actual audio/video
download for the entry. Also, yt-dlp only supports a single value at this time for this,
so will always use the max value.
"""
return self._sleep_per_request_s
@property @property
def sleep_per_download_s(self) -> Optional[RandomizedRangeValidator]: def sleep_per_download_s(self) -> Optional[RandomizedRangeValidator]:
""" """
@ -165,6 +201,11 @@ class ThrottleProtectionPlugin(Plugin[ThrottleProtectionOptions]):
self.plugin_options.max_downloads_per_subscription.randomized_int() self.plugin_options.max_downloads_per_subscription.randomized_int()
) )
def ytdl_options(self) -> Optional[Dict]:
if self.plugin_options.sleep_per_request_s is not None:
return {"sleep_interval_requests": self.plugin_options.sleep_per_request_s.max_value()}
return {}
def initialize_subscription(self) -> bool: def initialize_subscription(self) -> bool:
if self.plugin_options.subscription_download_probability: if self.plugin_options.subscription_download_probability:
proba = self.plugin_options.subscription_download_probability.value proba = self.plugin_options.subscription_download_probability.value

View file

@ -12,28 +12,6 @@ class TvShowByDatePresets(PrebuiltPresets):
} }
class TvShowByDateOldPresets(PrebuiltPresets):
"""
TV Show by Date presets create a TV show from a single URL using upload dates as season/episode
numbers.
"""
preset_names = {
"kodi_tv_show_by_date",
"jellyfin_tv_show_by_date",
"plex_tv_show_by_date",
}
class TvShowByDateEpisodeFormattingPresets(PrebuiltPresets):
preset_names = {
"season_by_year__episode_by_month_day",
"season_by_year__episode_by_month_day_reversed",
"season_by_year_month__episode_by_day",
"season_by_year__episode_by_download_index",
}
class TvShowCollectionPresets(PrebuiltPresets): class TvShowCollectionPresets(PrebuiltPresets):
preset_names = { preset_names = {
"Kodi TV Show Collection", "Kodi TV Show Collection",
@ -48,24 +26,3 @@ class TvShowCollectionOldPresets(PrebuiltPresets):
"jellyfin_tv_show_collection", "jellyfin_tv_show_collection",
"plex_tv_show_collection", "plex_tv_show_collection",
} }
class TvShowCollectionEpisodeFormattingPresets(PrebuiltPresets):
preset_names = {
"season_by_collection__episode_by_year_month_day",
"season_by_collection__episode_by_year_month_day_reversed",
"season_by_collection__episode_by_playlist_index",
"season_by_collection__episode_by_playlist_index_reversed",
}
class TvShowCollectionSeasonPresets(PrebuiltPresets):
"""Now Deprecated"""
preset_names = {
"collection_season_1",
"collection_season_2",
"collection_season_3",
"collection_season_4",
"collection_season_5",
}

View file

@ -9,6 +9,7 @@ presets:
thumbnail_name: "{thumbnail_file_name}" thumbnail_name: "{thumbnail_file_name}"
info_json_name: "{episode_file_path}.{info_json_ext}" info_json_name: "{episode_file_path}.{info_json_ext}"
maintain_download_archive: True maintain_download_archive: True
keep_files_date_eval: "{episode_date_standardized}"
ytdl_options: ytdl_options:
break_on_existing: True break_on_existing: True
@ -36,14 +37,11 @@ presets:
tv_show_genre: "{subscription_indent_1}" tv_show_genre: "{subscription_indent_1}"
tv_show_content_rating: "{subscription_indent_2}" tv_show_content_rating: "{subscription_indent_2}"
season_directory_name: "Season {season_number_padded}" season_directory_name: "Season {season_number_padded}"
episode_title: "{upload_date_standardized} - {title}" episode_title: "{episode_date_standardized} - {title}"
episode_plot: "{webpage_url}\n\n{description}" episode_plot: "{webpage_url}\n\n{description}"
episode_year: "{upload_year}"
episode_content_rating: "{tv_show_content_rating}" episode_content_rating: "{tv_show_content_rating}"
episode_date_standardized: "{upload_date_standardized}" episode_date_standardized: "{upload_date_standardized}"
episode_file_name: "s{season_number_padded}.e{episode_number_padded} - {file_title}" 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_file_path: "{season_directory_name_sanitized}/{episode_file_name_sanitized}"
thumbnail_file_name: "{episode_file_path}-thumb.jpg" thumbnail_file_name: "{episode_file_path}-thumb.jpg"
episode_year: "{%slice(episode_date_standardized, 0, 4)}"

View file

@ -35,22 +35,125 @@ presets:
# TV show from one or more sources. Uses {url}'s avatar and banner as poster and fanart # TV show from one or more sources. Uses {url}'s avatar and banner as poster and fanart
_tv_show_by_date: _tv_show_by_date:
preset: "_multi_url_bilateral" preset:
- "_multi_url_bilateral"
- "_tv_show_by_date_asserts"
overrides: overrides:
avatar_uncropped_thumbnail_file_name: "{tv_show_poster_file_name}" avatar_uncropped_thumbnail_file_name: "{tv_show_poster_file_name}"
banner_uncropped_thumbnail_file_name: "{tv_show_fanart_file_name}" banner_uncropped_thumbnail_file_name: "{tv_show_fanart_file_name}"
_tv_show_by_date_asserts:
overrides:
s01_url: ""
s01_name: ""
assert_not_collection: >-
{
%assert(
%and(
%not( %bool(s01_url) ),
%not( %bool(s01_name) )
),
"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?"
)
}
#################################################################################################### ####################################################################################################
_tv_show_by_date_season_episode_ordering:
overrides:
tv_show_by_date_season_ordering: >-
{ %throw("Must specify tv_show_by_date_season_ordering") }
"%season_ordering_": >-
{ %eq( %lower(tv_show_by_date_season_ordering), $0 ) }
season_number: >-
{
%elif(
%season_ordering_( "upload-year" ), upload_year,
%season_ordering_( "upload-year-month" ), %concat(upload_year, upload_month_padded),
%season_ordering_( "release-year" ), release_year,
%season_ordering_( "release-year-month" ), %concat(release_year, release_month_padded),
%throw(
'tv_show_by_date_season_ordering must be one of the following: "upload-year", "upload-year-month", "release-year", "release-year-month"'
)
)
}
# both year and year-month are fixed width, until the year 10000 :)
season_number_padded: "{season_number}"
##################################### episode overrides
tv_show_by_date_episode_ordering: >-
{ %throw("Must specify tv_show_by_date_episode_ordering") }
"%episode_ordering_": >-
{ %eq( %lower(tv_show_by_date_episode_ordering), $0 ) }
episode_number_and_padded_: >-
{
%elif(
%episode_ordering_( "upload-day" ), [ %concat(upload_day, upload_date_index_padded), 4 ],
%episode_ordering_( "upload-month-day" ), [ %concat(upload_month, upload_day_padded, upload_date_index_padded), 6],
%episode_ordering_( "upload-month-day-reversed" ), [ %concat(upload_day_of_year_reversed, upload_date_index_reversed_padded), 5],
%episode_ordering_( "release-day" ), [ %concat(release_day, upload_date_index_padded), 4 ],
%episode_ordering_( "release-month-day" ), [ %concat(release_month, release_day_padded, upload_date_index_padded), 6],
%episode_ordering_( "release-month-day-reversed" ), [ %concat(release_day_of_year_reversed, upload_date_index_reversed_padded), 5],
%episode_ordering_( "download-index" ), [ download_index, 6 ],
%throw(
'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"'
)
)
}
episode_number: "{ %array_at(episode_number_and_padded_, 0) }"
episode_number_padded: "{ %pad_zero( %int(episode_number), %int(%array_at(episode_number_and_padded_, 1))) }"
episode_date_standardized: >-
{
%if(
%contains(tv_show_by_date_season_ordering, "release"),
release_date_standardized,
upload_date_standardized
)
}
##################################### season/episode pairing validation
"%ordering_pair_eq": >-
{
%eq([tv_show_by_date_season_ordering, tv_show_by_date_episode_ordering], [$0, $1])
}
tv_show_by_date_ordering_pair_validation_: >-
{
%assert_then(
%or(
%ordering_pair_eq("upload-year", "upload-month-day"),
%ordering_pair_eq("upload-year", "upload-month-day-reversed"),
%ordering_pair_eq("upload-year", "download-index"),
%ordering_pair_eq("upload-year-month", "upload-day"),
%ordering_pair_eq("release-year", "release-month-day"),
%ordering_pair_eq("release-year", "release-month-day-reversed"),
%ordering_pair_eq("release-year", "download-index"),
%ordering_pair_eq("release-year-month", "release-day")
),
episode_number_and_padded_,
"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."
)
}
####################################################################################################
# LEGACY PRESETS
_season_by_year: _season_by_year:
preset: "_tv_show_by_date_season_episode_ordering"
overrides: overrides:
season_number: "{upload_year}" tv_show_by_date_season_ordering: "upload-year"
season_number_padded: "{season_number}"
_season_by_year_month: _season_by_year_month:
preset: "_tv_show_by_date_season_episode_ordering"
overrides: overrides:
season_number: "{upload_year}{upload_month_padded}" tv_show_by_date_season_ordering: "upload-year-month"
season_number_padded: "{season_number}"
#################################################################################################### ####################################################################################################
@ -58,26 +161,22 @@ presets:
preset: preset:
- "_season_by_year_month" - "_season_by_year_month"
overrides: overrides:
episode_number: "{upload_day}{upload_date_index_padded}" tv_show_by_date_episode_ordering: "upload-day"
episode_number_padded: "{upload_day_padded}{upload_date_index_padded}"
season_by_year__episode_by_month_day: season_by_year__episode_by_month_day:
preset: preset:
- "_season_by_year" - "_season_by_year"
overrides: overrides:
episode_number: "{upload_month}{upload_day_padded}{upload_date_index_padded}" tv_show_by_date_episode_ordering: "upload-month-day"
episode_number_padded: "{upload_month_padded}{upload_day_padded}{upload_date_index_padded}"
season_by_year__episode_by_month_day_reversed: season_by_year__episode_by_month_day_reversed:
preset: preset:
- "_season_by_year" - "_season_by_year"
overrides: overrides:
episode_number: "{upload_day_of_year_reversed}{upload_date_index_reversed_padded}" tv_show_by_date_episode_ordering: "upload-month-day-reversed"
episode_number_padded: "{upload_day_of_year_reversed_padded}{upload_date_index_reversed_padded}"
season_by_year__episode_by_download_index: season_by_year__episode_by_download_index:
preset: preset:
- "_season_by_year" - "_season_by_year"
overrides: overrides:
episode_number: "{download_index}" tv_show_by_date_episode_ordering: "download-index"
episode_number_padded: "{download_index_padded6}"

View file

@ -39,31 +39,68 @@ presets:
############################### ###############################
season_by_collection__episode_by_year_month_day: _tv_show_collection_episode_ordering:
overrides: overrides:
episode_number: "{upload_year_truncated}{upload_month_padded}{upload_day_padded}{upload_date_index_padded}" tv_show_collection_episode_ordering: >-
episode_number_padded: "{episode_number}" { %throw("Must specify tv_show_collection_episode_ordering") }
"%episode_ordering_": >-
{ %eq( %lower(tv_show_collection_episode_ordering), $0 ) }
episode_number_and_padded_: >-
{
%elif(
%episode_ordering_( "upload-year-month-day" ), [ %concat(upload_year_truncated, upload_month_padded, upload_day_padded, upload_date_index_padded), 8 ],
%episode_ordering_( "upload-year-month-day-reversed" ), [ %concat(upload_year_truncated_reversed, upload_month_reversed_padded, upload_day_reversed_padded, upload_date_index_reversed_padded), 8],
%episode_ordering_( "release-year-month-day" ), [ %concat(release_year_truncated, release_month_padded, release_day_padded, upload_date_index_padded), 8 ],
%episode_ordering_( "release-year-month-day-reversed" ), [ %concat(release_year_truncated_reversed, release_month_reversed_padded, release_day_reversed_padded, upload_date_index_reversed_padded), 8],
%episode_ordering_( "playlist-index" ), [ %concat(playlist_index), 6],
%episode_ordering_( "playlist-index-reversed" ), [ %concat(playlist_index_reversed), 6],
%throw(
'tv_show_collection_episode_ordering must be one of the following: "upload-year-month-day", "upload-year-month-day-reversed", "release-year-month-day", "release-year-month-day-reversed", "playlist-index", "playlist-index-reversed"'
)
)
}
episode_number: "{ %array_at(episode_number_and_padded_, 0) }"
episode_number_padded: "{ %pad_zero( %int(episode_number), %int(%array_at(episode_number_and_padded_, 1))) }"
episode_date_standardized: >-
{
%if(
%contains(tv_show_collection_episode_ordering, "release"),
release_date_standardized,
upload_date_standardized
)
}
### LEGACY PRESETS
season_by_collection__episode_by_year_month_day:
preset: "_tv_show_collection_episode_ordering"
overrides:
tv_show_collection_episode_ordering: "upload-year-month-day"
season_by_collection__episode_by_year_month_day_reversed: season_by_collection__episode_by_year_month_day_reversed:
preset: "_tv_show_collection_episode_ordering"
overrides: overrides:
episode_number: "{upload_year_truncated_reversed}{upload_month_reversed_padded}{upload_day_reversed_padded}{upload_date_index_reversed_padded}" tv_show_collection_episode_ordering: "upload-year-month-day-reversed"
episode_number_padded: "{episode_number}"
season_by_collection__episode_by_playlist_index: season_by_collection__episode_by_playlist_index:
preset: "_tv_show_collection_episode_ordering"
overrides: overrides:
episode_number: "{playlist_index}" tv_show_collection_episode_ordering: "playlist-index"
episode_number_padded: "{playlist_index_padded6}"
season_by_collection__episode_by_playlist_index_reversed: season_by_collection__episode_by_playlist_index_reversed:
preset: "_tv_show_collection_episode_ordering"
overrides: overrides:
episode_number: "{playlist_index_reversed}" tv_show_collection_episode_ordering: "playlist-index-reversed"
episode_number_padded: "{playlist_index_reversed_padded6}"
############## ##############
_tv_show_collection: _tv_show_collection:
preset: preset:
- "_tv_show_collection_bilateral" - "_tv_show_collection_bilateral"
- "_tv_show_collection_asserts"
download: download:
- url: "{collection_season_1_url}" - url: "{collection_season_1_url}"
@ -1017,6 +1054,16 @@ presets:
ytdl_options: ytdl_options:
playlist_items: "-1:0:-1" playlist_items: "-1:0:-1"
_tv_show_collection_asserts:
overrides:
url: ""
assert_not_by_date: >-
{
%assert(
%not( %bool(url) ),
"Provided `url` to TV Show Collection preset when it expects `s01_url`. Perhaps you meant to use the `TV Show by Date` preset?"
)
}
#################################################################################################### ####################################################################################################
# DEPRECATED SEASON PRESETS # DEPRECATED SEASON PRESETS

View file

@ -9,6 +9,7 @@ from ytdl_sub.script.functions.error_functions import ErrorFunctions
from ytdl_sub.script.functions.json_functions import JsonFunctions from ytdl_sub.script.functions.json_functions import JsonFunctions
from ytdl_sub.script.functions.map_functions import MapFunctions from ytdl_sub.script.functions.map_functions import MapFunctions
from ytdl_sub.script.functions.numeric_functions import NumericFunctions from ytdl_sub.script.functions.numeric_functions import NumericFunctions
from ytdl_sub.script.functions.print_functions import PrintFunctions
from ytdl_sub.script.functions.regex_functions import RegexFunctions from ytdl_sub.script.functions.regex_functions import RegexFunctions
from ytdl_sub.script.functions.string_functions import StringFunctions from ytdl_sub.script.functions.string_functions import StringFunctions
from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.resolvable import Resolvable
@ -26,6 +27,7 @@ class Functions(
RegexFunctions, RegexFunctions,
DateFunctions, DateFunctions,
JsonFunctions, JsonFunctions,
PrintFunctions,
): ):
_custom_functions: Dict[str, Callable[..., Resolvable]] = {} _custom_functions: Dict[str, Callable[..., Resolvable]] = {}

View file

@ -0,0 +1,67 @@
from typing import Optional
from ytdl_sub.script.types.resolvable import AnyArgument
from ytdl_sub.script.types.resolvable import Integer
from ytdl_sub.script.types.resolvable import ReturnableArgument
from ytdl_sub.utils.logger import Logger
logger = Logger.get(name="preset")
def _log(message: AnyArgument, level: Optional[Integer]) -> None:
if level is None:
logger.info(str(message))
return
level_value: int = level.native
if level_value < 0:
logger.debug(str(message))
elif level_value == 0:
logger.info(str(message))
elif level_value == 1:
logger.warning(str(message))
else: # > 1
logger.error(str(message))
class PrintFunctions:
@staticmethod
def print(
message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer] = None
) -> ReturnableArgument:
"""
:description:
Print the ``message`` and return ``passthrough``.
Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error.
Defaults to info.
"""
_log(message=message, level=level)
return passthrough
@staticmethod
def print_if_true(
message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer] = None
) -> ReturnableArgument:
"""
:description:
Print the ``message`` if ``passthrough`` evaluates to ``true``. Return ``passthrough``.
Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error.
Defaults to info.
"""
if passthrough.value:
_log(message=message, level=level)
return passthrough
@staticmethod
def print_if_false(
message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer] = None
) -> ReturnableArgument:
"""
:description:
Print the ``message`` if ``passthrough`` evaluates to ``false``. Return ``passthrough``.
Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error.
Defaults to info.
"""
if not passthrough.value:
_log(message=message, level=level)
return passthrough

View file

@ -127,12 +127,12 @@ class StringFunctions:
return Array([String(split_val) for split_val in string.value.split(sep=sep.value)]) return Array([String(split_val) for split_val in string.value.split(sep=sep.value)])
@staticmethod @staticmethod
def concat(*values: String) -> String: def concat(*values: AnyArgument) -> String:
""" """
:description: :description:
Concatenate multiple Strings into a single String. Concatenate multiple Strings into a single String.
""" """
return String("".join(val.value for val in values)) return String("".join(str(val.value) for val in values))
@staticmethod @staticmethod
def pad(string: String, length: Integer, char: String) -> String: def pad(string: String, length: Integer, char: String) -> String:
@ -176,3 +176,41 @@ class StringFunctions:
# World # World
""" """
return String(string.value.encode("utf-8").decode("unicode_escape")) return String(string.value.encode("utf-8").decode("unicode_escape"))
@staticmethod
def join(separator: String, array: Array) -> String:
"""
:description:
Join all elements in the array together as a string, and insert the
separator between them.
:usage:
.. code-block:: python
{
%join( ", ", ["item1", "item2"] )
}
# "item1, item2"
"""
return String(separator.value.join(str(val) for val in array.value))
@staticmethod
def strip(string: String) -> String:
"""
:description:
Strip a string of all its whitespace at the beginning and end.
:usage:
.. code-block:: python
{
%trim(" delete the outer! ")
}
# "delete the outer!"
"""
return String(string.value.strip())

View file

@ -17,6 +17,7 @@ from ytdl_sub.downloaders.source_plugin import SourcePlugin
from ytdl_sub.downloaders.url.downloader import MultiUrlDownloader from ytdl_sub.downloaders.url.downloader import MultiUrlDownloader
from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.entries.script.variable_definitions import VARIABLES
from ytdl_sub.subscriptions.base_subscription import BaseSubscription from ytdl_sub.subscriptions.base_subscription import BaseSubscription
from ytdl_sub.subscriptions.subscription_ytdl_options import SubscriptionYTDLOptions from ytdl_sub.subscriptions.subscription_ytdl_options import SubscriptionYTDLOptions
from ytdl_sub.utils.datetime import to_date_range from ytdl_sub.utils.datetime import to_date_range
@ -215,9 +216,23 @@ class SubscriptionDownload(BaseSubscription, ABC):
FileHandler.delete(entry.get_download_thumbnail_path()) FileHandler.delete(entry.get_download_thumbnail_path())
FileHandler.delete(entry.get_download_info_json_path()) FileHandler.delete(entry.get_download_info_json_path())
@classmethod def _preprocess_entry(self, plugins: List[Plugin], entry: Entry) -> Optional[Entry]:
def _preprocess_entry(cls, plugins: List[Plugin], entry: Entry) -> Optional[Entry]:
maybe_entry: Optional[Entry] = entry maybe_entry: Optional[Entry] = entry
# Inject OutputOption variables here
entry.add(
{
VARIABLES.ytdl_sub_keep_files_date_eval: (
self.output_options.keep_files_date_eval.format_string
)
}
)
# Run it to make sure it's actually a standardized date
_ = self.overrides.apply_formatter(
formatter=self.output_options.keep_files_date_eval, entry=entry
)
for plugin in PluginMapping.order_plugins_by( for plugin in PluginMapping.order_plugins_by(
plugins, PluginOperation.MODIFY_ENTRY_METADATA plugins, PluginOperation.MODIFY_ENTRY_METADATA
): ):

View file

@ -19,6 +19,7 @@ from ytdl_sub.plugins.match_filters import MatchFiltersPlugin
from ytdl_sub.plugins.match_filters import combine_filters from ytdl_sub.plugins.match_filters import combine_filters
from ytdl_sub.plugins.match_filters import default_filters from ytdl_sub.plugins.match_filters import default_filters
from ytdl_sub.plugins.subtitles import SubtitlesPlugin from ytdl_sub.plugins.subtitles import SubtitlesPlugin
from ytdl_sub.plugins.throttle_protection import ThrottleProtectionPlugin
from ytdl_sub.utils.ffmpeg import FFMPEG from ytdl_sub.utils.ffmpeg import FFMPEG
from ytdl_sub.utils.logger import Logger from ytdl_sub.utils.logger import Logger
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
@ -175,6 +176,7 @@ class SubscriptionYTDLOptions:
self._global_options, self._global_options,
self._output_options, self._output_options,
self._plugin_match_filters, self._plugin_match_filters,
self._plugin_ytdl_options(ThrottleProtectionPlugin),
self._plugin_ytdl_options(FormatPlugin), self._plugin_ytdl_options(FormatPlugin),
self._plugin_ytdl_options(AudioExtractPlugin), # will override format self._plugin_ytdl_options(AudioExtractPlugin), # will override format
self._user_ytdl_options, # user ytdl options... self._user_ytdl_options, # user ytdl options...

View file

@ -220,10 +220,17 @@ class Chapters:
# Timestamp captured, store it # Timestamp captured, store it
if match := Timestamp.TIMESTAMP_REGEX.search(line): if match := Timestamp.TIMESTAMP_REGEX.search(line):
timestamp_str = match.group(1) timestamp_str = match.group(1)
timestamps.append(Timestamp.from_str(timestamp_str))
# Remove timestamp and surrounding whitespace from it # Remove timestamp and surrounding whitespace from it
title_str = re.sub(f"\\s*{re.escape(timestamp_str)}\\s*", " ", line).strip() title_str = re.sub(f"\\s*{re.escape(timestamp_str)}\\s*", " ", line).strip()
timestamp = Timestamp.from_str(timestamp_str)
if timestamps and (timestamps[-1].timestamp_sec == timestamp.timestamp_sec):
# duplicate timestamp... combine into one chapter
titles[-1] += " // " + title_str
continue
timestamps.append(timestamp)
titles.append(title_str) titles.append(title_str)
# If more than 3 timestamps were parsed, return it # If more than 3 timestamps were parsed, return it

View file

@ -1,3 +1,4 @@
from datetime import datetime
from typing import Dict from typing import Dict
from typing import Set from typing import Set
from typing import Union from typing import Union
@ -73,6 +74,20 @@ class StringFormatterValidator(StringValidator):
return resolved return resolved
class StandardizedDateValidator(StringFormatterValidator):
_expected_value_type_name = "standardized_date"
def post_process(self, resolved: str) -> str:
try:
datetime.strptime(resolved, "%Y-%m-%d")
except ValueError as exc:
raise self._validation_exception(
f"Expected a standardized date in the form of YYYY-MM-DD, but received '{resolved}'"
) from exc
return resolved
# pylint: disable=line-too-long # pylint: disable=line-too-long
class OverridesStringFormatterValidator(StringFormatterValidator): class OverridesStringFormatterValidator(StringFormatterValidator):
""" """

View file

@ -75,7 +75,7 @@ class DownloadMapping:
DownloadMapping for the entry DownloadMapping for the entry
""" """
return DownloadMapping( return DownloadMapping(
upload_date=entry.get(v.upload_date_standardized, str), upload_date=entry.get(v.ytdl_sub_keep_files_date_eval, str),
extractor=entry.download_archive_extractor, extractor=entry.download_archive_extractor,
file_names=set(), file_names=set(),
) )
@ -248,20 +248,18 @@ class DownloadMappings:
del self._entry_mappings[entry_id] del self._entry_mappings[entry_id]
return self return self
def get_num_entries_with_upload_date(self, upload_date_standardized: str) -> int: def get_num_entries_with_date(self, standardized_date: str) -> int:
""" """
Parameters Parameters
---------- ----------
upload_date_standardized standardized_date
A standardized upload date A standardized upload date
Returns Returns
------- -------
Number of entries in the mapping with this upload date Number of entries in the mapping with this upload date
""" """
return len( return len([_ for _ in self._entry_mappings.values() if _.upload_date == standardized_date])
[_ for _ in self._entry_mappings.values() if _.upload_date == upload_date_standardized]
)
def get_num_entries(self) -> int: def get_num_entries(self) -> int:
""" """

View file

@ -1,3 +1,5 @@
from typing import List
import pytest import pytest
from conftest import assert_logs from conftest import assert_logs
from expected_download import assert_expected_downloads from expected_download import assert_expected_downloads
@ -17,6 +19,7 @@ def subscription_dict(output_directory):
"subscription_indent_1": "Acoustic", "subscription_indent_1": "Acoustic",
"music_directory": output_directory, "music_directory": output_directory,
}, },
"throttle_protection": {"sleep_per_request_s": {"min": 0.1, "max": 0.5}},
} }
@ -34,6 +37,21 @@ class TestSoundcloudDiscography:
output_directory, output_directory,
dry_run, dry_run,
): ):
ignored_mp3s: List[str] = [
"j_b/[2021] Baby Santana's Dorian Groove/01 - Baby Santana's Dorian Groove.mp3",
"j_b/[2021] Purple Clouds/01 - Purple Clouds.mp3",
"j_b/[2022] Acoustic Treats/01 - 20160426 184214.mp3",
"j_b/[2022] Acoustic Treats/02 - 20160502 123150.mp3",
"j_b/[2022] Acoustic Treats/03 - 20160504 143832.mp3",
"j_b/[2022] Acoustic Treats/04 - 20160601 221234.mp3",
"j_b/[2022] Acoustic Treats/05 - 20160601 222440.mp3",
"j_b/[2022] Acoustic Treats/06 - 20170604 190236.mp3",
"j_b/[2022] Acoustic Treats/07 - 20170612 193646.mp3",
"j_b/[2022] Acoustic Treats/08 - 20170628 215206.mp3",
"j_b/[2022] Acoustic Treats/09 - Finding Home.mp3",
"j_b/[2022] Acoustic Treats/10 - Shallow Water WIP.mp3",
"j_b/[2022] Acoustic Treats/11 - Untold History.mp3",
]
discography_subscription = Subscription.from_dict( discography_subscription = Subscription.from_dict(
preset_dict=subscription_dict, preset_dict=subscription_dict,
preset_name="j_b", preset_name="j_b",
@ -49,6 +67,7 @@ class TestSoundcloudDiscography:
output_directory=output_directory, output_directory=output_directory,
dry_run=dry_run, dry_run=dry_run,
expected_download_summary_file_name="soundcloud/test_soundcloud_discography.json", expected_download_summary_file_name="soundcloud/test_soundcloud_discography.json",
ignore_md5_hashes_for=ignored_mp3s,
) )
# Ensure another invocation will hit ExistingVideoReached # Ensure another invocation will hit ExistingVideoReached
@ -65,4 +84,5 @@ class TestSoundcloudDiscography:
output_directory=output_directory, output_directory=output_directory,
dry_run=dry_run, dry_run=dry_run,
expected_download_summary_file_name="soundcloud/test_soundcloud_discography.json", expected_download_summary_file_name="soundcloud/test_soundcloud_discography.json",
ignore_md5_hashes_for=ignored_mp3s,
) )

View file

@ -5,6 +5,7 @@ from pathlib import Path
from typing import Callable from typing import Callable
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import Optional
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
@ -53,6 +54,7 @@ def mock_entry_dict_factory(mock_downloaded_file_path) -> Callable:
is_youtube_channel: bool = False, is_youtube_channel: bool = False,
mock_download_to_working_dir: bool = True, mock_download_to_working_dir: bool = True,
is_extracted_audio: bool = False, is_extracted_audio: bool = False,
release_date: Optional[str] = None,
) -> Dict: ) -> Dict:
entry_dict = { entry_dict = {
v.uid.metadata_key: uid, v.uid.metadata_key: uid,
@ -71,6 +73,10 @@ def mock_entry_dict_factory(mock_downloaded_file_path) -> Callable:
v.description.metadata_key: "The Description", v.description.metadata_key: "The Description",
} }
# TODO: Make this required eventually
if release_date is not None:
entry_dict[v.release_date.metadata_key] = release_date
if is_youtube_channel: if is_youtube_channel:
entry_dict[v.playlist_metadata.metadata_key]["thumbnails"] = [ entry_dict[v.playlist_metadata.metadata_key]["thumbnails"] = [
{ {
@ -153,6 +159,7 @@ def mock_download_collection_entries(
mock_entry_dict_factory( mock_entry_dict_factory(
uid="21-1", uid="21-1",
upload_date="20210808", upload_date="20210808",
release_date="20010808",
playlist_title="Download First", playlist_title="Download First",
playlist_index=1, playlist_index=1,
playlist_count=4, playlist_count=4,
@ -163,6 +170,7 @@ def mock_download_collection_entries(
mock_entry_dict_factory( mock_entry_dict_factory(
uid="20-1", uid="20-1",
upload_date="20200808", upload_date="20200808",
release_date="20000808",
playlist_title="Download First", playlist_title="Download First",
playlist_index=2, playlist_index=2,
playlist_count=4, playlist_count=4,
@ -173,6 +181,7 @@ def mock_download_collection_entries(
mock_entry_dict_factory( mock_entry_dict_factory(
uid="20-2", uid="20-2",
upload_date="20200808", upload_date="20200808",
release_date="20000808",
playlist_title="Download First", playlist_title="Download First",
playlist_index=3, playlist_index=3,
playlist_count=4, playlist_count=4,
@ -183,6 +192,7 @@ def mock_download_collection_entries(
mock_entry_dict_factory( mock_entry_dict_factory(
uid="20-3", uid="20-3",
upload_date="20200807", upload_date="20200807",
release_date="20000807",
playlist_title="Download First", playlist_title="Download First",
playlist_index=4, playlist_index=4,
playlist_count=4, playlist_count=4,
@ -196,6 +206,7 @@ def mock_download_collection_entries(
mock_entry_dict_factory( mock_entry_dict_factory(
uid="20-3", uid="20-3",
upload_date="20200807", upload_date="20200807",
release_date="20000807",
playlist_title="Download Second", playlist_title="Download Second",
playlist_index=1, playlist_index=1,
playlist_count=5, playlist_count=5,
@ -206,6 +217,7 @@ def mock_download_collection_entries(
mock_entry_dict_factory( mock_entry_dict_factory(
uid="20-4", uid="20-4",
upload_date="20200806", upload_date="20200806",
release_date="20000806",
playlist_title="Download Second", playlist_title="Download Second",
playlist_index=2, playlist_index=2,
playlist_count=5, playlist_count=5,
@ -216,6 +228,7 @@ def mock_download_collection_entries(
mock_entry_dict_factory( mock_entry_dict_factory(
uid="20-5", uid="20-5",
upload_date="20200706", upload_date="20200706",
release_date="20000706",
playlist_title="Download Second", playlist_title="Download Second",
playlist_index=3, playlist_index=3,
playlist_count=5, playlist_count=5,
@ -226,6 +239,7 @@ def mock_download_collection_entries(
mock_entry_dict_factory( mock_entry_dict_factory(
uid="20-6", uid="20-6",
upload_date="20200706", upload_date="20200706",
release_date="20000706",
playlist_title="Download Second", playlist_title="Download Second",
playlist_index=4, playlist_index=4,
playlist_count=5, playlist_count=5,
@ -236,6 +250,7 @@ def mock_download_collection_entries(
mock_entry_dict_factory( mock_entry_dict_factory(
uid="20-7", uid="20-7",
upload_date="20200606", upload_date="20200606",
release_date="20000606",
playlist_title="Download Second", playlist_title="Download Second",
playlist_index=5, playlist_index=5,
playlist_count=5, playlist_count=5,

View file

@ -1,3 +1,4 @@
import re
from pathlib import Path from pathlib import Path
from typing import Dict from typing import Dict
from unittest.mock import patch from unittest.mock import patch
@ -11,6 +12,7 @@ from ytdl_sub.config.config_file import ConfigFile
from ytdl_sub.downloaders.ytdlp import YTDLP from ytdl_sub.downloaders.ytdlp import YTDLP
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.subscriptions.subscription import Subscription
from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.thumbnail import try_convert_download_thumbnail from ytdl_sub.utils.thumbnail import try_convert_download_thumbnail
@ -223,3 +225,35 @@ class TestOutputOptions:
dry_run=False, dry_run=False,
expected_download_summary_file_name="plugins/output_options/test_missing_thumb.json", expected_download_summary_file_name="plugins/output_options/test_missing_thumb.json",
) )
def test_invalid_keep_files_date_eval(
self,
config: ConfigFile,
subscription_name: str,
output_options_subscription_dict: Dict,
output_directory: str,
mock_download_collection_entries,
):
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)

View file

@ -1,19 +1,30 @@
import re
from typing import Set
import pytest import pytest
from expected_download import assert_expected_downloads from expected_download import assert_expected_downloads
from expected_transaction_log import assert_transaction_log_matches from expected_transaction_log import assert_transaction_log_matches
from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.config_file import ConfigFile
from ytdl_sub.prebuilt_presets.tv_show import TvShowByDateEpisodeFormattingPresets
from ytdl_sub.prebuilt_presets.tv_show import TvShowByDatePresets from ytdl_sub.prebuilt_presets.tv_show import TvShowByDatePresets
from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError
from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.subscriptions.subscription import Subscription
DEPRECATED_TV_SHOW_PRESET_EQUIVALENTS = { DEFAULT_SEASON_ORDERING = "upload-year"
"Kodi TV Show by Date": "kodi_tv_show_by_date", DEFAULT_EPISODE_ORDERING = "upload-month-day"
"Jellyfin TV Show by Date": "jellyfin_tv_show_by_date",
"Plex TV Show by Date": "plex_tv_show_by_date",
}
DEFAULT_EPISODE_ORDERING_PRESET = "season_by_year__episode_by_month_day" VALID_ORDERING_COMBOS = [
# upload
("upload-year", "upload-month-day"),
("upload-year", "upload-month-day-reversed"),
("upload-year", "download-index"),
("upload-year-month", "upload-day"),
# release
("release-year", "release-month-day"),
("release-year", "release-month-day-reversed"),
("release-year", "download-index"),
("release-year-month", "release-day"),
]
class TestPrebuiltTVShowPresets: class TestPrebuiltTVShowPresets:
@ -24,24 +35,28 @@ class TestPrebuiltTVShowPresets:
subscription_name: str, subscription_name: str,
output_directory: str, output_directory: str,
tv_show_preset: str, tv_show_preset: str,
episode_ordering_preset: str, season_ordering: str,
episode_ordering: str,
is_youtube_channel: bool = True, is_youtube_channel: bool = True,
is_many_urls: bool = False, is_many_urls: bool = False,
): ):
expected_summary_name = "unit/{}/{}/is_yt_{}{}".format( expected_summary_name = "integration/by-date/{}/{}/{}/is_yt_{}{}".format(
DEPRECATED_TV_SHOW_PRESET_EQUIVALENTS[tv_show_preset], tv_show_preset.split(" ")[0],
episode_ordering_preset, season_ordering,
episode_ordering,
int(is_youtube_channel), int(is_youtube_channel),
"_many_urls" if is_many_urls else "", "_many_urls" if is_many_urls else "",
) )
preset_dict = { preset_dict = {
"preset": [tv_show_preset, episode_ordering_preset], "preset": tv_show_preset,
"overrides": { "overrides": {
"url": "https://your.name.here", "url": "https://your.name.here",
"tv_show_name": "Best Prebuilt TV Show by Date", "tv_show_name": "Best Prebuilt TV Show by Date",
"tv_show_directory": output_directory, "tv_show_directory": output_directory,
"tv_show_by_date_season_ordering": season_ordering,
"tv_show_by_date_episode_ordering": episode_ordering,
}, },
} }
if is_many_urls: if is_many_urls:
@ -67,16 +82,19 @@ class TestPrebuiltTVShowPresets:
) )
###################################### Perform reformat ###################################### Perform reformat
reformatted_tv_show_structure_preset = "season_by_year__episode_by_download_index" reformatted_season_ordering = "upload-year"
reformatted_expected_summary_name = "unit/{}/{}/is_yt_{}{}".format( reformatted_episode_ordering = "download-index"
DEPRECATED_TV_SHOW_PRESET_EQUIVALENTS[tv_show_preset],
reformatted_tv_show_structure_preset, reformatted_expected_summary_name = "integration/by-date/{}/{}/{}/is_yt_{}{}".format(
tv_show_preset.split(" ")[0],
reformatted_season_ordering,
reformatted_episode_ordering,
int(is_youtube_channel), int(is_youtube_channel),
"_many_urls" if is_many_urls else "", "_many_urls" if is_many_urls else "",
) )
reformatted_preset_dict = { reformatted_preset_dict = {
"preset": [tv_show_preset, reformatted_tv_show_structure_preset], "preset": tv_show_preset,
"output_options": { "output_options": {
"migrated_download_archive_name": ".ytdl-sub-{tv_show_name_sanitized}-download-archive.json" "migrated_download_archive_name": ".ytdl-sub-{tv_show_name_sanitized}-download-archive.json"
}, },
@ -84,6 +102,8 @@ class TestPrebuiltTVShowPresets:
"url": "https://your.name.here", "url": "https://your.name.here",
"tv_show_name": "Best Prebuilt TV Show by Date", "tv_show_name": "Best Prebuilt TV Show by Date",
"tv_show_directory": output_directory, "tv_show_directory": output_directory,
"tv_show_by_date_season_ordering": reformatted_season_ordering,
"tv_show_by_date_episode_ordering": reformatted_episode_ordering,
}, },
} }
if is_many_urls: if is_many_urls:
@ -98,7 +118,7 @@ class TestPrebuiltTVShowPresets:
output_directory=output_directory, output_directory=output_directory,
transaction_log=reformatted_transaction_log, transaction_log=reformatted_transaction_log,
transaction_log_summary_file_name=( transaction_log_summary_file_name=(
f"{expected_summary_name}_reformatted_to_{reformatted_tv_show_structure_preset}.txt" f"{expected_summary_name}_reformatted_to_{reformatted_season_ordering}.txt"
), ),
) )
assert_expected_downloads( assert_expected_downloads(
@ -123,11 +143,13 @@ class TestPrebuiltTVShowPresets:
subscription_name=subscription_name, subscription_name=subscription_name,
output_directory=output_directory, output_directory=output_directory,
tv_show_preset=tv_show_preset, tv_show_preset=tv_show_preset,
episode_ordering_preset=DEFAULT_EPISODE_ORDERING_PRESET, season_ordering=DEFAULT_SEASON_ORDERING,
episode_ordering=DEFAULT_EPISODE_ORDERING,
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
"episode_ordering_preset", TvShowByDateEpisodeFormattingPresets.preset_names "season_ordering, episode_ordering",
VALID_ORDERING_COMBOS,
) )
def test_episode_ordering_presets( def test_episode_ordering_presets(
self, self,
@ -135,7 +157,8 @@ class TestPrebuiltTVShowPresets:
subscription_name, subscription_name,
output_directory, output_directory,
mock_download_collection_entries, mock_download_collection_entries,
episode_ordering_preset: str, season_ordering: str,
episode_ordering: str,
): ):
with mock_download_collection_entries( with mock_download_collection_entries(
@ -146,5 +169,91 @@ class TestPrebuiltTVShowPresets:
subscription_name=subscription_name, subscription_name=subscription_name,
output_directory=output_directory, output_directory=output_directory,
tv_show_preset="Kodi TV Show by Date", tv_show_preset="Kodi TV Show by Date",
episode_ordering_preset=episode_ordering_preset, season_ordering=season_ordering,
episode_ordering=episode_ordering,
)
def test_invalid_season_ordering(
self, config, subscription_name, output_directory, mock_download_collection_entries
):
expected_message = (
"tv_show_by_date_season_ordering must be one of the following: "
'"upload-year", '
'"upload-year-month", '
'"release-year", '
'"release-year-month"'
)
with (
mock_download_collection_entries(is_youtube_channel=True, num_urls=1),
pytest.raises(UserThrownRuntimeError, match=re.escape(expected_message)),
):
self.run(
config=config,
subscription_name=subscription_name,
output_directory=output_directory,
tv_show_preset="Kodi TV Show by Date",
season_ordering="nope",
episode_ordering=DEFAULT_EPISODE_ORDERING,
)
def test_invalid_episode_ordering(
self, config, subscription_name, output_directory, mock_download_collection_entries
):
expected_message = (
"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"'
)
with (
mock_download_collection_entries(is_youtube_channel=True, num_urls=1),
pytest.raises(UserThrownRuntimeError, match=re.escape(expected_message)),
):
self.run(
config=config,
subscription_name=subscription_name,
output_directory=output_directory,
tv_show_preset="Kodi TV Show by Date",
season_ordering=DEFAULT_SEASON_ORDERING,
episode_ordering="not-a-valid",
)
def test_invalid_season_episode_ordering_combo(
self, config, subscription_name, output_directory, mock_download_collection_entries
):
expected_message = (
"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."
)
possible_seasons: Set[str] = set()
possible_episodes: Set[str] = set()
for season_ordering, episode_ordering in VALID_ORDERING_COMBOS:
possible_seasons.add(season_ordering)
possible_episodes.add(episode_ordering)
for season_ordering in possible_seasons:
for episode_ordering in possible_episodes:
if (season_ordering, episode_ordering) in VALID_ORDERING_COMBOS:
continue
with (
mock_download_collection_entries(is_youtube_channel=True, num_urls=1),
pytest.raises(UserThrownRuntimeError, match=re.escape(expected_message)),
):
self.run(
config=config,
subscription_name=subscription_name,
output_directory=output_directory,
tv_show_preset="Kodi TV Show by Date",
season_ordering=season_ordering,
episode_ordering=episode_ordering,
) )

View file

@ -1,3 +1,4 @@
import re
from typing import Dict from typing import Dict
from typing import List from typing import List
@ -6,17 +7,11 @@ from expected_download import assert_expected_downloads
from expected_transaction_log import assert_transaction_log_matches from expected_transaction_log import assert_transaction_log_matches
from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.config_file import ConfigFile
from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionEpisodeFormattingPresets
from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionPresets from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionPresets
from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError
from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.subscriptions.subscription import Subscription
DEPRECATED_TV_SHOW_PRESET_EQUIVALENTS = { DEFAULT_EPISODE_ORDERING = "upload-year-month-day"
"Kodi TV Show Collection": "kodi_tv_show_collection",
"Jellyfin TV Show Collection": "jellyfin_tv_show_collection",
"Plex TV Show Collection": "plex_tv_show_collection",
}
DEFAULT_EPISODE_ORDERING_PRESET = "season_by_collection__episode_by_year_month_day"
class TestPrebuiltTvShowCollectionPresets: class TestPrebuiltTvShowCollectionPresets:
@ -27,22 +22,19 @@ class TestPrebuiltTvShowCollectionPresets:
subscription_name: str, subscription_name: str,
output_directory: str, output_directory: str,
media_player_preset: str, media_player_preset: str,
tv_show_structure_preset: str, episode_ordering: str,
season_indices: List[int], season_indices: List[int],
is_youtube_channel: bool = True, is_youtube_channel: bool = True,
): ):
expected_summary_name = "unit/{}/{}/s_{}/is_yt_{}".format( expected_summary_name = "integration/collection/{}/{}/s_{}/is_yt_{}".format(
DEPRECATED_TV_SHOW_PRESET_EQUIVALENTS[media_player_preset], media_player_preset.split(" ")[0],
tv_show_structure_preset, episode_ordering,
len(season_indices), len(season_indices),
int(is_youtube_channel), int(is_youtube_channel),
) )
parent_presets: List[str] = [media_player_preset, tv_show_structure_preset]
overrides: Dict[str, str] = {} overrides: Dict[str, str] = {}
for season_index in season_indices: for season_index in season_indices:
parent_presets.append(f"collection_season_{season_index}")
overrides = dict( overrides = dict(
overrides, overrides,
**{ **{
@ -55,12 +47,13 @@ class TestPrebuiltTvShowCollectionPresets:
config=config, config=config,
preset_name=subscription_name, preset_name=subscription_name,
preset_dict={ preset_dict={
"preset": parent_presets, "preset": media_player_preset,
"overrides": dict( "overrides": dict(
overrides, overrides,
**{ **{
"tv_show_name": "Best Prebuilt TV Show Collection", "tv_show_name": "Best Prebuilt TV Show Collection",
"tv_show_directory": output_directory, "tv_show_directory": output_directory,
"tv_show_collection_episode_ordering": episode_ordering,
}, },
), ),
}, },
@ -79,12 +72,10 @@ class TestPrebuiltTvShowCollectionPresets:
) )
###################################### Perform reformat ###################################### Perform reformat
reformatted_tv_show_structure_preset = ( reformatted_tv_show_collection_episode_ordering = "playlist-index-reversed"
"season_by_collection__episode_by_playlist_index_reversed" reformatted_expected_summary_name = "integration/collection/{}/{}/s_{}/is_yt_{}".format(
) media_player_preset.split(" ")[0],
reformatted_expected_summary_name = "unit/{}/{}/s_{}/is_yt_{}".format( reformatted_tv_show_collection_episode_ordering,
DEPRECATED_TV_SHOW_PRESET_EQUIVALENTS[media_player_preset],
reformatted_tv_show_structure_preset,
len(season_indices), len(season_indices),
int(is_youtube_channel), int(is_youtube_channel),
) )
@ -93,7 +84,7 @@ class TestPrebuiltTvShowCollectionPresets:
config=config, config=config,
preset_name=subscription_name, preset_name=subscription_name,
preset_dict={ preset_dict={
"preset": parent_presets + [reformatted_tv_show_structure_preset], "preset": media_player_preset,
"output_options": { "output_options": {
"migrated_download_archive_name": ".ytdl-sub-{tv_show_name_sanitized}-download-archive.json" "migrated_download_archive_name": ".ytdl-sub-{tv_show_name_sanitized}-download-archive.json"
}, },
@ -102,6 +93,7 @@ class TestPrebuiltTvShowCollectionPresets:
**{ **{
"tv_show_name": "Best Prebuilt TV Show Collection", "tv_show_name": "Best Prebuilt TV Show Collection",
"tv_show_directory": output_directory, "tv_show_directory": output_directory,
"tv_show_collection_episode_ordering": reformatted_tv_show_collection_episode_ordering,
}, },
), ),
}, },
@ -112,7 +104,7 @@ class TestPrebuiltTvShowCollectionPresets:
output_directory=output_directory, output_directory=output_directory,
transaction_log=reformatted_transaction_log, transaction_log=reformatted_transaction_log,
transaction_log_summary_file_name=( transaction_log_summary_file_name=(
f"{expected_summary_name}_reformatted_to_{reformatted_tv_show_structure_preset}.txt" f"{expected_summary_name}_reformatted_to_{reformatted_tv_show_collection_episode_ordering}.txt"
), ),
) )
assert_expected_downloads( assert_expected_downloads(
@ -141,12 +133,20 @@ class TestPrebuiltTvShowCollectionPresets:
subscription_name=subscription_name, subscription_name=subscription_name,
output_directory=output_directory, output_directory=output_directory,
media_player_preset=media_player_preset, media_player_preset=media_player_preset,
tv_show_structure_preset=DEFAULT_EPISODE_ORDERING_PRESET, episode_ordering=DEFAULT_EPISODE_ORDERING,
season_indices=season_indices, season_indices=season_indices,
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
"episode_ordering_preset", TvShowCollectionEpisodeFormattingPresets.preset_names "episode_ordering",
[
"upload-year-month-day",
"upload-year-month-day-reversed",
"release-year-month-day",
"release-year-month-day-reversed",
"playlist-index",
"playlist-index-reversed",
],
) )
@pytest.mark.parametrize("season_indices", [[1], [1, 2]]) @pytest.mark.parametrize("season_indices", [[1], [1, 2]])
def test_episode_ordering_presets( def test_episode_ordering_presets(
@ -155,7 +155,7 @@ class TestPrebuiltTvShowCollectionPresets:
subscription_name, subscription_name,
output_directory, output_directory,
mock_download_collection_entries, mock_download_collection_entries,
episode_ordering_preset: str, episode_ordering: str,
season_indices: List[int], season_indices: List[int],
): ):
@ -167,6 +167,32 @@ class TestPrebuiltTvShowCollectionPresets:
subscription_name=subscription_name, subscription_name=subscription_name,
output_directory=output_directory, output_directory=output_directory,
media_player_preset="Kodi TV Show Collection", media_player_preset="Kodi TV Show Collection",
tv_show_structure_preset=episode_ordering_preset, episode_ordering=episode_ordering,
season_indices=season_indices, season_indices=season_indices,
) )
def test_invalid_episode_ordering(
self, config, subscription_name, output_directory, mock_download_collection_entries
):
expected_message = (
"tv_show_collection_episode_ordering must be one of the following: "
'"upload-year-month-day", '
'"upload-year-month-day-reversed", '
'"release-year-month-day", '
'"release-year-month-day-reversed", '
'"playlist-index", '
'"playlist-index-reversed"'
)
with (
mock_download_collection_entries(is_youtube_channel=True, num_urls=1),
pytest.raises(UserThrownRuntimeError, match=re.escape(expected_message)),
):
self.run(
config=config,
subscription_name=subscription_name,
output_directory=output_directory,
media_player_preset="Kodi TV Show Collection",
episode_ordering="does-not-exist",
season_indices=[1],
)

View file

@ -2,18 +2,18 @@
".ytdl-sub-subscription_test-download-archive.json": "76e202bd03ceef93daaffffee2cfa193", ".ytdl-sub-subscription_test-download-archive.json": "76e202bd03ceef93daaffffee2cfa193",
"JMC/Mock Entry 20-1.info.json": "INFO_JSON", "JMC/Mock Entry 20-1.info.json": "INFO_JSON",
"JMC/Mock Entry 20-1.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 20-1.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 20-1.mp4": "dbaeb2a3bfd1de1c7e9615e8bdacb910", "JMC/Mock Entry 20-1.mp4": "d8dc9918d1646c92b4a4bd246291d849",
"JMC/Mock Entry 20-1.nfo": "5d001a105d2380844db181911d5df5c6", "JMC/Mock Entry 20-1.nfo": "5d001a105d2380844db181911d5df5c6",
"JMC/Mock Entry 20-2.info.json": "INFO_JSON", "JMC/Mock Entry 20-2.info.json": "INFO_JSON",
"JMC/Mock Entry 20-2.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 20-2.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 20-2.mp4": "76c2c70572489c684923a51cb1b50687", "JMC/Mock Entry 20-2.mp4": "6f925179a5b6bf7ffcaf2f70ad585296",
"JMC/Mock Entry 20-2.nfo": "dccdded1ca7ebb38d2c9a0e5130bea33", "JMC/Mock Entry 20-2.nfo": "dccdded1ca7ebb38d2c9a0e5130bea33",
"JMC/Mock Entry 20-3.info.json": "INFO_JSON", "JMC/Mock Entry 20-3.info.json": "INFO_JSON",
"JMC/Mock Entry 20-3.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 20-3.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 20-3.mp4": "7bb4f21d59a6fb91836537b27a24e776", "JMC/Mock Entry 20-3.mp4": "b48f1803f14be665677e9ac9da787593",
"JMC/Mock Entry 20-3.nfo": "7b1eab48693161e83c21203b1308b64e", "JMC/Mock Entry 20-3.nfo": "7b1eab48693161e83c21203b1308b64e",
"JMC/Mock Entry 21-1.info.json": "INFO_JSON", "JMC/Mock Entry 21-1.info.json": "INFO_JSON",
"JMC/Mock Entry 21-1.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 21-1.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 21-1.mp4": "7b008531ca5660b51fb8adc83c799084", "JMC/Mock Entry 21-1.mp4": "5fb865f27ee1cac381597d1e182dfa48",
"JMC/Mock Entry 21-1.nfo": "3b4b0e6820f80370d6738ce828f14a89" "JMC/Mock Entry 21-1.nfo": "3b4b0e6820f80370d6738ce828f14a89"
} }

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json": "456d8882fc5e35d74f19b386d1d9a059",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "1d0714fb3ca4d31a0f88492a06a6142d",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "a8f3f8580989f3927606bf6060a64e30",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "265b1104b382fd157a123842e49fcdfd",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "09fa4599fd48836c6c0da0760c306006",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "e30d83126c2c6981c936ed894a6f159b",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry 20-3.mp4": "c712d7fd5dfdb9bb6c2be99f7c796c6f",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry 20-3.nfo": "7c0f2a9d38bf617377e474fcb9bf2bc2",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry 20-2.mp4": "7ec7ac1923053c4b5653bbfb8924fdc6",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry 20-2.nfo": "1f4dec756555ee345cd9d6489b63e070",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry 20-1.mp4": "414c243016f406451c790757949173aa",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry 20-1.nfo": "f6f704ae3ae0c4055590a147eb425609",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry 21-1.mp4": "e2f858efd08ac82749fd01db65d8341e",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry 21-1.nfo": "c6e0d368bfe9d70134e5a2ff2d3dccc6",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "95d3790190cece6e49d2f4d5f68868b1",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0701 - Mock Entry 20-3.mp4": "6dd6860e8075e5e99c85e14729c41bdb",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0701 - Mock Entry 20-3.nfo": "92d978e6f64d97e7e71e7045c9d2fb1f",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0801 - Mock Entry 20-2.mp4": "6b3537db708f88a877d3c5273df02eb0",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0801 - Mock Entry 20-2.nfo": "b1a3768f43d0f7f4dde1207a8670f9b1",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0802 - Mock Entry 20-1.mp4": "7e40cf2da8eedd93b3df9a017c78cd2e",
"Best Prebuilt TV Show by Date/Season 200008/s200008.e0802 - Mock Entry 20-1.nfo": "ea37e0d89713f971a87ddf67f8933be1",
"Best Prebuilt TV Show by Date/Season 200108/s200108.e0801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 200108/s200108.e0801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 200108/s200108.e0801 - Mock Entry 21-1.mp4": "c14c0b1901c456e87e1070539d32917e",
"Best Prebuilt TV Show by Date/Season 200108/s200108.e0801 - Mock Entry 21-1.nfo": "4e18e9e0454bc948cf8daa0151a0b137",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "d2888d629be62badedce415f8296a995",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000001 - Mock Entry 20-3.mp4": "f94c693da1964bd8e916459253ede6d4",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000001 - Mock Entry 20-3.nfo": "57b7ef31d0b9948bd67ba6a7c83dd22b",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000002 - Mock Entry 20-2.mp4": "68a76930c4456ab58f30ad1a3eb109aa",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000002 - Mock Entry 20-2.nfo": "1cc84f0bbe25315268dc453db8941c7f",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000003 - Mock Entry 20-1.mp4": "b24b301ec95f359ba28ca503e678522f",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e000003 - Mock Entry 20-1.nfo": "e77cc16953d13241f83fbd01792c15ba",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e000004 - Mock Entry 21-1.mp4": "cf36da70822036f06e5c834dac291dd3",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e000004 - Mock Entry 21-1.nfo": "cbd1abadd656f6d047775969604e9af4",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "1e8b4072d6d6aace4cac0c94ac0e711e",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14698 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14698 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14698 - Mock Entry 20-1.mp4": "f4846a3b9ee6a16df3348916077d6476",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14698 - Mock Entry 20-1.nfo": "ad715f955b255f356b6263f06941bf05",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14699 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14699 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14699 - Mock Entry 20-2.mp4": "82a1f043b879412d6219e16993b8b188",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14699 - Mock Entry 20-2.nfo": "2fa884c057a44122a245efe3c33921cd",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14799 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14799 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14799 - Mock Entry 20-3.mp4": "6626433c9c3399c6403ba5753e3b31c2",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e14799 - Mock Entry 20-3.nfo": "63f4405964a11f99ff1595cc18266489",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e14699 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e14699 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e14699 - Mock Entry 21-1.mp4": "bac622134b8f65eaeccce6f3b822be64",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e14699 - Mock Entry 21-1.nfo": "6b9659879d7a1b9f5e2dccee70d7fe66",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "f13a43a83e725981df8417ab1d36705a",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080701 - Mock Entry 20-3.mp4": "219480c0ee2075ed86ccba740cca3d70",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080701 - Mock Entry 20-3.nfo": "6085b15cd1a32e8b45b97cee69ca4741",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080801 - Mock Entry 20-2.mp4": "7ed6abcf342149769ea2de23a0fee0a1",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080801 - Mock Entry 20-2.nfo": "599b687960602264db2f7f44dbd2f849",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080802 - Mock Entry 20-1.mp4": "1431320eb3acc337b1c70a895362397a",
"Best Prebuilt TV Show by Date/Season 2000/s2000.e080802 - Mock Entry 20-1.nfo": "6da492b1897671a3309df38e6fa33279",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e080801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e080801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e080801 - Mock Entry 21-1.mp4": "5c7f600b9a40039ea77729deda8d103f",
"Best Prebuilt TV Show by Date/Season 2001/s2001.e080801 - Mock Entry 21-1.nfo": "294fb0959c2cf640312f112c0a7aae27",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "5269b5e9e4fad7b63b6fdb3fef477b1e",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0701 - Mock Entry 20-3.mp4": "b095358d6b335a25f3be3ae44a6d10ac",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0701 - Mock Entry 20-3.nfo": "4ba0e01c1e7295952c3f5e6b9b251d7d",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0801 - Mock Entry 20-2.mp4": "43f73b6f99439e4873bf8d4ba1802452",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0801 - Mock Entry 20-2.nfo": "db9196b729c7b2cd26e3f0fc649b39f0",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0802 - Mock Entry 20-1.mp4": "632af351287dde4d1a096460f6bd93b1",
"Best Prebuilt TV Show by Date/Season 202008/s202008.e0802 - Mock Entry 20-1.nfo": "ba4a387d9462f416ba176c31c1c55316",
"Best Prebuilt TV Show by Date/Season 202108/s202108.e0801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 202108/s202108.e0801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 202108/s202108.e0801 - Mock Entry 21-1.mp4": "bf0104b783e168b150ea165faa898915",
"Best Prebuilt TV Show by Date/Season 202108/s202108.e0801 - Mock Entry 21-1.nfo": "93dd5c4c493422f9a60e61a6b965b39a",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "456d8882fc5e35d74f19b386d1d9a059",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "1d0714fb3ca4d31a0f88492a06a6142d",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "a8f3f8580989f3927606bf6060a64e30",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "265b1104b382fd157a123842e49fcdfd",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "09fa4599fd48836c6c0da0760c306006",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json": "456d8882fc5e35d74f19b386d1d9a059",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "1d0714fb3ca4d31a0f88492a06a6142d",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "a8f3f8580989f3927606bf6060a64e30",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "265b1104b382fd157a123842e49fcdfd",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "09fa4599fd48836c6c0da0760c306006",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "05539b932348da7e949a63eb71f77efc",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14698 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14698 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14698 - Mock Entry 20-1.mp4": "c27d1898d39763a265e4fe4451777688",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14698 - Mock Entry 20-1.nfo": "88fee5353ad7eecd96865b34654bbde6",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14699 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14699 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14699 - Mock Entry 20-2.mp4": "80c087803d5ac9c0efe02361fdaaa5bd",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14699 - Mock Entry 20-2.nfo": "d781b80ef24916acbf3bf181ab32ddea",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14799 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14799 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14799 - Mock Entry 20-3.mp4": "1b5084c06b33f7e0d6daf7cff03adc73",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e14799 - Mock Entry 20-3.nfo": "252b3639b4d1686f8c79476943f89439",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e14699 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e14699 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e14699 - Mock Entry 21-1.mp4": "a83870aaca55947b7a91ac90109163af",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e14699 - Mock Entry 21-1.nfo": "e7374b766d9ec0599ecf209a0f78c0ba",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,22 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "e30d83126c2c6981c936ed894a6f159b",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry 20-3.mp4": "c712d7fd5dfdb9bb6c2be99f7c796c6f",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry 20-3.nfo": "7c0f2a9d38bf617377e474fcb9bf2bc2",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry 20-2.mp4": "7ec7ac1923053c4b5653bbfb8924fdc6",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry 20-2.nfo": "1f4dec756555ee345cd9d6489b63e070",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry 20-1.mp4": "414c243016f406451c790757949173aa",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry 20-1.nfo": "f6f704ae3ae0c4055590a147eb425609",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry 21-1.mp4": "e2f858efd08ac82749fd01db65d8341e",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry 21-1.nfo": "c6e0d368bfe9d70134e5a2ff2d3dccc6",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show by Date/tvshow.nfo": "2439ebc18e46a67064956cb940c992e9"
}

View file

@ -0,0 +1,17 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-Best Prebuilt TV Show by Date-download-archive.json": "0011c85c3339ac1301d80be2f3330e67",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000001 - Mock Entry -.mp4": "1d0714fb3ca4d31a0f88492a06a6142d",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000002 - Mock Entry -.mp4": "a8f3f8580989f3927606bf6060a64e30",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e000003 - Mock Entry -.mp4": "265b1104b382fd157a123842e49fcdfd",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e000004 - Mock Entry -.mp4": "09fa4599fd48836c6c0da0760c306006",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a"
}

View file

@ -0,0 +1,17 @@
{
"Best Prebuilt TV Show by Date/.ytdl-sub-subscription_test-download-archive.json": "6cd67e6b935bc5b2521f6ed0fa2ea2ed",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080701 - Mock Entry -.mp4": "c712d7fd5dfdb9bb6c2be99f7c796c6f",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080801 - Mock Entry -.mp4": "7ec7ac1923053c4b5653bbfb8924fdc6",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2020/s2020.e080802 - Mock Entry -.mp4": "414c243016f406451c790757949173aa",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show by Date/Season 2021/s2021.e080801 - Mock Entry -.mp4": "e2f858efd08ac82749fd01db65d8341e",
"Best Prebuilt TV Show by Date/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show by Date/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a"
}

View file

@ -0,0 +1,23 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json": "809885e6c29bba06215b840516628090",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3.mp4": "e107a93fa177c5f8594ccc3e023db8e8",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3.nfo": "ddf2763f976aaa79199bddbfd3b66501",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2.mp4": "2788e6b165908e6d52d7d771859b085e",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2.nfo": "cda58b9d5b7ee05d5e4d5144ebfe0f94",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1.mp4": "6f0fd7cb3b3c49a8cf7baa6657cb5148",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1.nfo": "4d5775492f2731cb5dc04a84ebcb029d",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1.mp4": "6a983aa1b5dff65c54bd5f0b78003e07",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1.nfo": "2980b224863465a8b8a465c9d381d1f4",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "a3f1061135b7aa0c25e5176a271d32f0"
}

View file

@ -0,0 +1,40 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json": "7331c3a7cc0b43cd547b82af4ec35a3f",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7.mp4": "6390603e1f9642ea573f25179b42f6af",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7.nfo": "42ad110e3f608de3edb94735631d39a7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6.mp4": "179e310bd4de1638d84b6da787ff9fa8",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6.nfo": "ea4dee97b5bb9e49cb72f0942f851feb",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.mp4": "3c412f47cb87e820138fb8909c672216",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.nfo": "7f71bd60012d850ece38fa644eeddc5e",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4.mp4": "8409a144e67c50573bae29bf76431d76",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4.nfo": "c487f013b8d5adc399a793bc803b898f",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3.mp4": "e107a93fa177c5f8594ccc3e023db8e8",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3.nfo": "4eedc1c0e99858530e6228001d024766",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2.mp4": "2788e6b165908e6d52d7d771859b085e",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2.nfo": "cc1541a65508fcbfd9f024042cb6b6ce",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1.mp4": "6f0fd7cb3b3c49a8cf7baa6657cb5148",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1.nfo": "d5fa573faf1d29c367117917417df62c",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1.mp4": "6a983aa1b5dff65c54bd5f0b78003e07",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1.nfo": "6c40cd5d13c8d088e57906e809a591f8",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/season02-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "61943a17325c5188475651ca7c68446d"
}

View file

@ -0,0 +1,23 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "ede1deeba072bae50f7f7039deca0543",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry 20-3.mp4": "a874866ad7096eb3bf31c72ea3315354",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry 20-3.nfo": "043c6de15ab09f32e64414d1f4a8a89c",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry 20-2.mp4": "fe6fcf8bfc13c0913ad1509703b447f1",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry 20-2.nfo": "4fb018858502fe2c7a0d3dcb89145a8d",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry 20-1.mp4": "1d0f1081b402ebea9aeedf75f891c811",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry 20-1.nfo": "6ac2ee3bd87b453d56bd90b42bbf52a0",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry 21-1.mp4": "e5a844cc2c8fe19037afdf0692731414",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry 21-1.nfo": "d54ba1888f0083f8de6f401e399ad379",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "a3f1061135b7aa0c25e5176a271d32f0"
}

View file

@ -0,0 +1,40 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "9b9e26ff3d036f450501bf5adb979aeb",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry 20-7-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry 20-7.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry 20-7.mp4": "fab0f2d0a74558a86b3935c6b962ebe3",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry 20-7.nfo": "9ceac835c0166bf4f7354106379e6d17",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry 20-6-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry 20-6.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry 20-6.mp4": "9c2569c62ff81f067fe3d91324942c11",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry 20-6.nfo": "35f15e5437cae813095fe2ca3bd2ca18",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry 20-5-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry 20-5.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry 20-5.mp4": "921e686edbf2a2e11f8d3eba9ef32592",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry 20-5.nfo": "9aa69e4c86bb282ac57b2024ec2fbd34",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry 20-4-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry 20-4.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry 20-4.mp4": "a9fae3eca046637910a6e79aae4a8354",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry 20-4.nfo": "709533b6b8f501196db0d98dfe2b2b16",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry 20-3.mp4": "a874866ad7096eb3bf31c72ea3315354",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry 20-3.nfo": "05f34cd0e6be4b09ea96134faec892f6",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry 20-2.mp4": "fe6fcf8bfc13c0913ad1509703b447f1",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry 20-2.nfo": "54b4f84daf25626703f7b3a36b0dea49",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry 20-1.mp4": "1d0f1081b402ebea9aeedf75f891c811",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry 20-1.nfo": "3d2644161e57b94cfac5e95732864cc3",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry 21-1.mp4": "e5a844cc2c8fe19037afdf0692731414",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry 21-1.nfo": "fd91adb2089d0fcdbb2e568a10ac880e",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/season02-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "61943a17325c5188475651ca7c68446d"
}

View file

@ -0,0 +1,23 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "809885e6c29bba06215b840516628090",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3.mp4": "e107a93fa177c5f8594ccc3e023db8e8",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3.nfo": "ddf2763f976aaa79199bddbfd3b66501",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2.mp4": "2788e6b165908e6d52d7d771859b085e",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2.nfo": "cda58b9d5b7ee05d5e4d5144ebfe0f94",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1.mp4": "6f0fd7cb3b3c49a8cf7baa6657cb5148",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1.nfo": "4d5775492f2731cb5dc04a84ebcb029d",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1.mp4": "6a983aa1b5dff65c54bd5f0b78003e07",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1.nfo": "2980b224863465a8b8a465c9d381d1f4",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "a3f1061135b7aa0c25e5176a271d32f0"
}

View file

@ -0,0 +1,23 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json": "809885e6c29bba06215b840516628090",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3.mp4": "e107a93fa177c5f8594ccc3e023db8e8",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-3.nfo": "ddf2763f976aaa79199bddbfd3b66501",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2.mp4": "2788e6b165908e6d52d7d771859b085e",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-2.nfo": "cda58b9d5b7ee05d5e4d5144ebfe0f94",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1.mp4": "6f0fd7cb3b3c49a8cf7baa6657cb5148",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-1.nfo": "4d5775492f2731cb5dc04a84ebcb029d",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1.mp4": "6a983aa1b5dff65c54bd5f0b78003e07",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 21-1.nfo": "2980b224863465a8b8a465c9d381d1f4",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "a3f1061135b7aa0c25e5176a271d32f0"
}

View file

@ -0,0 +1,40 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "7331c3a7cc0b43cd547b82af4ec35a3f",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7.mp4": "6390603e1f9642ea573f25179b42f6af",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7.nfo": "42ad110e3f608de3edb94735631d39a7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6.mp4": "179e310bd4de1638d84b6da787ff9fa8",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6.nfo": "ea4dee97b5bb9e49cb72f0942f851feb",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.mp4": "3c412f47cb87e820138fb8909c672216",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.nfo": "7f71bd60012d850ece38fa644eeddc5e",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4.mp4": "8409a144e67c50573bae29bf76431d76",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4.nfo": "c487f013b8d5adc399a793bc803b898f",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3.mp4": "e107a93fa177c5f8594ccc3e023db8e8",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3.nfo": "4eedc1c0e99858530e6228001d024766",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2.mp4": "2788e6b165908e6d52d7d771859b085e",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2.nfo": "cc1541a65508fcbfd9f024042cb6b6ce",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1.mp4": "6f0fd7cb3b3c49a8cf7baa6657cb5148",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1.nfo": "d5fa573faf1d29c367117917417df62c",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1.mp4": "6a983aa1b5dff65c54bd5f0b78003e07",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1.nfo": "6c40cd5d13c8d088e57906e809a591f8",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/season02-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "61943a17325c5188475651ca7c68446d"
}

View file

@ -0,0 +1,40 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json": "7331c3a7cc0b43cd547b82af4ec35a3f",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7.mp4": "6390603e1f9642ea573f25179b42f6af",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 20-7.nfo": "42ad110e3f608de3edb94735631d39a7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6.mp4": "179e310bd4de1638d84b6da787ff9fa8",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-6.nfo": "ea4dee97b5bb9e49cb72f0942f851feb",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.mp4": "3c412f47cb87e820138fb8909c672216",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.nfo": "7f71bd60012d850ece38fa644eeddc5e",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4.mp4": "8409a144e67c50573bae29bf76431d76",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-4.nfo": "c487f013b8d5adc399a793bc803b898f",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3.mp4": "e107a93fa177c5f8594ccc3e023db8e8",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 20-3.nfo": "4eedc1c0e99858530e6228001d024766",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2.mp4": "2788e6b165908e6d52d7d771859b085e",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-2.nfo": "cc1541a65508fcbfd9f024042cb6b6ce",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1.mp4": "6f0fd7cb3b3c49a8cf7baa6657cb5148",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-1.nfo": "d5fa573faf1d29c367117917417df62c",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1.mp4": "6a983aa1b5dff65c54bd5f0b78003e07",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 21-1.nfo": "6c40cd5d13c8d088e57906e809a591f8",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/season02-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "61943a17325c5188475651ca7c68446d"
}

View file

@ -0,0 +1,23 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "4e5ef7394c029e7fe230f82dfe134e4c",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 21-1.mp4": "79cac07b1964b53ff128ef041e3ed10b",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry 21-1.nfo": "2a3d9b51221a541fefc29eebffe47cd4",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-1.mp4": "ebd20e58d50951bfcd2316bcf660ba11",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-1.nfo": "ec57b2c78058e18cdc015813e8a14b9d",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-2.mp4": "331068e0bf01c2baaa131c7760a46c02",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-2.nfo": "cd919caf88b0eed99fd2a595c1afa6b1",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-3.mp4": "c646ebe3838f553abb5e9cb1dd96b85f",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-3.nfo": "7c4b28456fe5d398b7dad966eb194421",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "a3f1061135b7aa0c25e5176a271d32f0"
}

View file

@ -0,0 +1,40 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "c7c2427315924db344d4659a60c59bc9",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-4-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-4.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-4.mp4": "a37dba001208063f0c04b2a681973c1c",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry 20-4.nfo": "7d8f03bdcd8ee66fd1578a86f20365cc",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.mp4": "3c412f47cb87e820138fb8909c672216",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry 20-5.nfo": "7f71bd60012d850ece38fa644eeddc5e",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-6-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-6.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-6.mp4": "0bee8e6eb39dbfa8aab81fb78c74ffa0",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry 20-6.nfo": "39996f43e9761247dce614247f99c808",
"Best Prebuilt TV Show Collection/Season 01/s01.e000005 - Mock Entry 20-7-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000005 - Mock Entry 20-7.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000005 - Mock Entry 20-7.mp4": "db88af54bfd315035ce187c2da47f95a",
"Best Prebuilt TV Show Collection/Season 01/s01.e000005 - Mock Entry 20-7.nfo": "cae377877c966dad492eaa3f224b86ba",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 21-1.mp4": "79cac07b1964b53ff128ef041e3ed10b",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry 21-1.nfo": "974bb83bd43b9579ab1716f66589ccc1",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-1.mp4": "ebd20e58d50951bfcd2316bcf660ba11",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry 20-1.nfo": "b12dcb09ffad7f23f21d350e1f60f128",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-2.mp4": "331068e0bf01c2baaa131c7760a46c02",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry 20-2.nfo": "e6fd0fc3e95b77c595d1a77c2060fd98",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 20-3.mp4": "c646ebe3838f553abb5e9cb1dd96b85f",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry 20-3.nfo": "23386ea27c1e64afcecdb235acb81b79",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/season02-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "61943a17325c5188475651ca7c68446d"
}

View file

@ -0,0 +1,23 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "df87dea55d4f4076ab4c075a3bceaa3d",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052498 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052498 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052498 - Mock Entry 20-1.mp4": "170f6eaaa831b92e80ba798b5ae50b60",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052498 - Mock Entry 20-1.nfo": "93e75d3231077883d37c5baac435e182",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052499 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052499 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052499 - Mock Entry 20-2.mp4": "0e1805471b84d94f605f645d9ea94c9e",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052499 - Mock Entry 20-2.nfo": "6d022da37f3545d9ab8608f11b8059cd",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052599 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052599 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052599 - Mock Entry 20-3.mp4": "2b59eab2673eb7f2a0ec91033737634a",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052599 - Mock Entry 20-3.nfo": "f47403746915586d1bd87436210311c5",
"Best Prebuilt TV Show Collection/Season 01/s01.e99052499 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e99052499 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e99052499 - Mock Entry 21-1.mp4": "450737dd9b8307f498eddb2ac7f4e567",
"Best Prebuilt TV Show Collection/Season 01/s01.e99052499 - Mock Entry 21-1.nfo": "5c095683a8c1ae4ff950d4302dc5f06e",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "a3f1061135b7aa0c25e5176a271d32f0"
}

View file

@ -0,0 +1,40 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "e89ac9f59f9f9ef22e01c9055708b81c",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052699 - Mock Entry 20-4-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052699 - Mock Entry 20-4.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052699 - Mock Entry 20-4.mp4": "1ea0b0274a45288449dddcb1c91a0ac7",
"Best Prebuilt TV Show Collection/Season 01/s01.e100052699 - Mock Entry 20-4.nfo": "f670533b516a47b833cd0ae04bec5877",
"Best Prebuilt TV Show Collection/Season 01/s01.e100062698 - Mock Entry 20-5-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e100062698 - Mock Entry 20-5.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e100062698 - Mock Entry 20-5.mp4": "9b03d1c2e2be9cd75920821accae7774",
"Best Prebuilt TV Show Collection/Season 01/s01.e100062698 - Mock Entry 20-5.nfo": "a223cdfe24fb5d285704351f38db7b46",
"Best Prebuilt TV Show Collection/Season 01/s01.e100062699 - Mock Entry 20-6-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e100062699 - Mock Entry 20-6.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e100062699 - Mock Entry 20-6.mp4": "2240a972bc91ef589b3b0a25f1e41725",
"Best Prebuilt TV Show Collection/Season 01/s01.e100062699 - Mock Entry 20-6.nfo": "bb1cc5d794aa28d43e405179622f2095",
"Best Prebuilt TV Show Collection/Season 01/s01.e100072599 - Mock Entry 20-7-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e100072599 - Mock Entry 20-7.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e100072599 - Mock Entry 20-7.mp4": "5944f9af51010e2783576fdbd2ad5436",
"Best Prebuilt TV Show Collection/Season 01/s01.e100072599 - Mock Entry 20-7.nfo": "dbd5b09d734a10795a11344a5b536b8f",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052498 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052498 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052498 - Mock Entry 20-1.mp4": "170f6eaaa831b92e80ba798b5ae50b60",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052498 - Mock Entry 20-1.nfo": "bb5c4debab0a9f5bbd0a4e303c9bed2d",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052499 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052499 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052499 - Mock Entry 20-2.mp4": "0e1805471b84d94f605f645d9ea94c9e",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052499 - Mock Entry 20-2.nfo": "de6eab4d23acac23213ee25adb8095ac",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052599 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052599 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052599 - Mock Entry 20-3.mp4": "2b59eab2673eb7f2a0ec91033737634a",
"Best Prebuilt TV Show Collection/Season 02/s02.e100052599 - Mock Entry 20-3.nfo": "23e88cb7a059946b12c0702a1f987bc7",
"Best Prebuilt TV Show Collection/Season 02/s02.e99052499 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e99052499 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e99052499 - Mock Entry 21-1.mp4": "450737dd9b8307f498eddb2ac7f4e567",
"Best Prebuilt TV Show Collection/Season 02/s02.e99052499 - Mock Entry 21-1.nfo": "ee3a0c5681e7bf7923f72c323e7a9795",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/season02-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "61943a17325c5188475651ca7c68446d"
}

View file

@ -0,0 +1,23 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "8f0390db6223f1c48072c28aabdc4162",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080701 - Mock Entry 20-3.mp4": "680b08a1e5032d89188def2784e62146",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080701 - Mock Entry 20-3.nfo": "2ecfb16f4216df21e9485b90b14e355f",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080801 - Mock Entry 20-2.mp4": "3c0dcdd38605f032c98aac9068732329",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080801 - Mock Entry 20-2.nfo": "090cfb9795b2ddd210afb93873cb60ce",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080802 - Mock Entry 20-1.mp4": "29d7648807690a4af93541ae340dd7bc",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080802 - Mock Entry 20-1.nfo": "e4aa8129a961c47e3151062b95f39b73",
"Best Prebuilt TV Show Collection/Season 01/s01.e01080801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e01080801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e01080801 - Mock Entry 21-1.mp4": "9abd6ecaf194553e9486e1bf02f552cf",
"Best Prebuilt TV Show Collection/Season 01/s01.e01080801 - Mock Entry 21-1.nfo": "34704ea06cbdfaa1c9939e061fd1633a",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "a3f1061135b7aa0c25e5176a271d32f0"
}

View file

@ -0,0 +1,40 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "b6ee943d41ca8b0f9b54bb9cb04da7de",
"Best Prebuilt TV Show Collection/Season 01/s01.e00060601 - Mock Entry 20-7-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e00060601 - Mock Entry 20-7.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e00060601 - Mock Entry 20-7.mp4": "a75f9d2b28d82b78ae30da3962f57f61",
"Best Prebuilt TV Show Collection/Season 01/s01.e00060601 - Mock Entry 20-7.nfo": "29b6f491d1d7cb5e62f6a3dd290ba74a",
"Best Prebuilt TV Show Collection/Season 01/s01.e00070601 - Mock Entry 20-6-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e00070601 - Mock Entry 20-6.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e00070601 - Mock Entry 20-6.mp4": "18226f783f8c3fac3089d61307bee3ba",
"Best Prebuilt TV Show Collection/Season 01/s01.e00070601 - Mock Entry 20-6.nfo": "8ce2b32b9371cc45908acb73a5c5d5e6",
"Best Prebuilt TV Show Collection/Season 01/s01.e00070602 - Mock Entry 20-5-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e00070602 - Mock Entry 20-5.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e00070602 - Mock Entry 20-5.mp4": "180e86031e6af3ea1a54714460a6bf1f",
"Best Prebuilt TV Show Collection/Season 01/s01.e00070602 - Mock Entry 20-5.nfo": "4567dfd30fc7287eea63f28e465d9c54",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080601 - Mock Entry 20-4-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080601 - Mock Entry 20-4.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080601 - Mock Entry 20-4.mp4": "09fba17ad8bf9deb8157ff83880185c3",
"Best Prebuilt TV Show Collection/Season 01/s01.e00080601 - Mock Entry 20-4.nfo": "0fc29c2520ae80c4390a5b68d02e6794",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080701 - Mock Entry 20-3.mp4": "680b08a1e5032d89188def2784e62146",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080701 - Mock Entry 20-3.nfo": "7276a8c14720f76cf4e5e624eb1dea2d",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080801 - Mock Entry 20-2.mp4": "3c0dcdd38605f032c98aac9068732329",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080801 - Mock Entry 20-2.nfo": "028a4161b6bccda2cf8d2a0a3ff13f4f",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080802 - Mock Entry 20-1.mp4": "29d7648807690a4af93541ae340dd7bc",
"Best Prebuilt TV Show Collection/Season 02/s02.e00080802 - Mock Entry 20-1.nfo": "7303574842c9409a6521486ddeb7cde9",
"Best Prebuilt TV Show Collection/Season 02/s02.e01080801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e01080801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e01080801 - Mock Entry 21-1.mp4": "9abd6ecaf194553e9486e1bf02f552cf",
"Best Prebuilt TV Show Collection/Season 02/s02.e01080801 - Mock Entry 21-1.nfo": "5b9d248c2fd2460030aa590d8660d957",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/season02-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "61943a17325c5188475651ca7c68446d"
}

View file

@ -0,0 +1,23 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "e26598ffd47b150e6bf09e5490546e94",
"Best Prebuilt TV Show Collection/Season 01/s01.e79052499 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e79052499 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e79052499 - Mock Entry 21-1.mp4": "cda96931a134eeb6e05e0b5aec03ddfe",
"Best Prebuilt TV Show Collection/Season 01/s01.e79052499 - Mock Entry 21-1.nfo": "a7e21040a3fcb264f287e4eba1853f94",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052498 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052498 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052498 - Mock Entry 20-1.mp4": "a7f2ca37b817da0512af297b5652e5e8",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052498 - Mock Entry 20-1.nfo": "78c08ad76d004a74cdba012f0b746c22",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052499 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052499 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052499 - Mock Entry 20-2.mp4": "35ebd30923d8f321e023281ec6ef4e6f",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052499 - Mock Entry 20-2.nfo": "d9031d8608d12742c40a3279138c8371",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052599 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052599 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052599 - Mock Entry 20-3.mp4": "204ce9c28ab7b69c659f0f76b6c75bfa",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052599 - Mock Entry 20-3.nfo": "74bd8dc59c5b76ae112d90638977c464",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "a3f1061135b7aa0c25e5176a271d32f0"
}

View file

@ -0,0 +1,40 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "2990d95213df5a6c87825a28daa970f4",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052699 - Mock Entry 20-4-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052699 - Mock Entry 20-4.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052699 - Mock Entry 20-4.mp4": "611d34cf77a2123181a7c4096623f6b9",
"Best Prebuilt TV Show Collection/Season 01/s01.e80052699 - Mock Entry 20-4.nfo": "0242d92e5e0d26745948b42c25eecfba",
"Best Prebuilt TV Show Collection/Season 01/s01.e80062698 - Mock Entry 20-5-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e80062698 - Mock Entry 20-5.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e80062698 - Mock Entry 20-5.mp4": "f09ce6574a040cea52b7177f74b9ccea",
"Best Prebuilt TV Show Collection/Season 01/s01.e80062698 - Mock Entry 20-5.nfo": "52b17bcad30adb6eaef4c3b21c1af76b",
"Best Prebuilt TV Show Collection/Season 01/s01.e80062699 - Mock Entry 20-6-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e80062699 - Mock Entry 20-6.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e80062699 - Mock Entry 20-6.mp4": "435692c75994d929807b5f09726728ad",
"Best Prebuilt TV Show Collection/Season 01/s01.e80062699 - Mock Entry 20-6.nfo": "31fb72d089fd680fea1340f3d3c41d64",
"Best Prebuilt TV Show Collection/Season 01/s01.e80072599 - Mock Entry 20-7-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e80072599 - Mock Entry 20-7.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e80072599 - Mock Entry 20-7.mp4": "613c386e00bf1c87e4f1d1acbbd98281",
"Best Prebuilt TV Show Collection/Season 01/s01.e80072599 - Mock Entry 20-7.nfo": "df65227ee260219883ff618c9fbdf1d4",
"Best Prebuilt TV Show Collection/Season 02/s02.e79052499 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e79052499 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e79052499 - Mock Entry 21-1.mp4": "cda96931a134eeb6e05e0b5aec03ddfe",
"Best Prebuilt TV Show Collection/Season 02/s02.e79052499 - Mock Entry 21-1.nfo": "0e4ea058d6a1e8760c80d3a02d25aafc",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052498 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052498 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052498 - Mock Entry 20-1.mp4": "a7f2ca37b817da0512af297b5652e5e8",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052498 - Mock Entry 20-1.nfo": "1095322364e512dff963a44b2beffe30",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052499 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052499 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052499 - Mock Entry 20-2.mp4": "35ebd30923d8f321e023281ec6ef4e6f",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052499 - Mock Entry 20-2.nfo": "5130c71d99f1c0726fee8cb8c9d82ba7",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052599 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052599 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052599 - Mock Entry 20-3.mp4": "204ce9c28ab7b69c659f0f76b6c75bfa",
"Best Prebuilt TV Show Collection/Season 02/s02.e80052599 - Mock Entry 20-3.nfo": "bde2a5401c49b58de0eacea4145d91ae",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/season02-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "61943a17325c5188475651ca7c68446d"
}

View file

@ -0,0 +1,23 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "ede1deeba072bae50f7f7039deca0543",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry 20-3.mp4": "a874866ad7096eb3bf31c72ea3315354",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry 20-3.nfo": "043c6de15ab09f32e64414d1f4a8a89c",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry 20-2.mp4": "fe6fcf8bfc13c0913ad1509703b447f1",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry 20-2.nfo": "4fb018858502fe2c7a0d3dcb89145a8d",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry 20-1.mp4": "1d0f1081b402ebea9aeedf75f891c811",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry 20-1.nfo": "6ac2ee3bd87b453d56bd90b42bbf52a0",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry 21-1.mp4": "e5a844cc2c8fe19037afdf0692731414",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry 21-1.nfo": "d54ba1888f0083f8de6f401e399ad379",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "a3f1061135b7aa0c25e5176a271d32f0"
}

View file

@ -0,0 +1,40 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "9b9e26ff3d036f450501bf5adb979aeb",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry 20-7-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry 20-7.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry 20-7.mp4": "fab0f2d0a74558a86b3935c6b962ebe3",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry 20-7.nfo": "9ceac835c0166bf4f7354106379e6d17",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry 20-6-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry 20-6.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry 20-6.mp4": "9c2569c62ff81f067fe3d91324942c11",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry 20-6.nfo": "35f15e5437cae813095fe2ca3bd2ca18",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry 20-5-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry 20-5.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry 20-5.mp4": "921e686edbf2a2e11f8d3eba9ef32592",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry 20-5.nfo": "9aa69e4c86bb282ac57b2024ec2fbd34",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry 20-4-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry 20-4.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry 20-4.mp4": "a9fae3eca046637910a6e79aae4a8354",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry 20-4.nfo": "709533b6b8f501196db0d98dfe2b2b16",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry 20-3.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry 20-3.mp4": "a874866ad7096eb3bf31c72ea3315354",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry 20-3.nfo": "05f34cd0e6be4b09ea96134faec892f6",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry 20-2.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry 20-2.mp4": "fe6fcf8bfc13c0913ad1509703b447f1",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry 20-2.nfo": "54b4f84daf25626703f7b3a36b0dea49",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry 20-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry 20-1.mp4": "1d0f1081b402ebea9aeedf75f891c811",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry 20-1.nfo": "3d2644161e57b94cfac5e95732864cc3",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry 21-1.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry 21-1.mp4": "e5a844cc2c8fe19037afdf0692731414",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry 21-1.nfo": "fd91adb2089d0fcdbb2e568a10ac880e",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a",
"Best Prebuilt TV Show Collection/season01-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/season02-poster.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/tvshow.nfo": "61943a17325c5188475651ca7c68446d"
}

View file

@ -0,0 +1,18 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json": "ba324b2c6532fe9d8fbe03e0dd6d0410",
"Best Prebuilt TV Show Collection/Season 01/Season01.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry -.mp4": "e107a93fa177c5f8594ccc3e023db8e8",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry -.mp4": "2788e6b165908e6d52d7d771859b085e",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry -.mp4": "6f0fd7cb3b3c49a8cf7baa6657cb5148",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry -.mp4": "6a983aa1b5dff65c54bd5f0b78003e07",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a"
}

View file

@ -0,0 +1,31 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-Best Prebuilt TV Show Collection-download-archive.json": "6ec6913047c175cedcbd41cb3ef402fc",
"Best Prebuilt TV Show Collection/Season 01/Season01.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000001 - Mock Entry -.mp4": "6390603e1f9642ea573f25179b42f6af",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000002 - Mock Entry -.mp4": "179e310bd4de1638d84b6da787ff9fa8",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000003 - Mock Entry -.mp4": "3c412f47cb87e820138fb8909c672216",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e000004 - Mock Entry -.mp4": "8409a144e67c50573bae29bf76431d76",
"Best Prebuilt TV Show Collection/Season 02/Season02.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000001 - Mock Entry -.mp4": "e107a93fa177c5f8594ccc3e023db8e8",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000002 - Mock Entry -.mp4": "2788e6b165908e6d52d7d771859b085e",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000003 - Mock Entry -.mp4": "6f0fd7cb3b3c49a8cf7baa6657cb5148",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e000004 - Mock Entry -.mp4": "6a983aa1b5dff65c54bd5f0b78003e07",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a"
}

View file

@ -0,0 +1,18 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "87f96a1e055447383bfea0a12680438d",
"Best Prebuilt TV Show Collection/Season 01/Season01.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080701 - Mock Entry -.mp4": "a874866ad7096eb3bf31c72ea3315354",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080801 - Mock Entry -.mp4": "fe6fcf8bfc13c0913ad1509703b447f1",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080802 - Mock Entry -.mp4": "1d0f1081b402ebea9aeedf75f891c811",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e21080801 - Mock Entry -.mp4": "e5a844cc2c8fe19037afdf0692731414",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a"
}

View file

@ -0,0 +1,31 @@
{
"Best Prebuilt TV Show Collection/.ytdl-sub-subscription_test-download-archive.json": "04d06f293fcb5164266bedf66b4d4264",
"Best Prebuilt TV Show Collection/Season 01/Season01.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20060601 - Mock Entry -.mp4": "fab0f2d0a74558a86b3935c6b962ebe3",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070601 - Mock Entry -.mp4": "9c2569c62ff81f067fe3d91324942c11",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20070602 - Mock Entry -.mp4": "921e686edbf2a2e11f8d3eba9ef32592",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 01/s01.e20080601 - Mock Entry -.mp4": "a9fae3eca046637910a6e79aae4a8354",
"Best Prebuilt TV Show Collection/Season 02/Season02.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080701 - Mock Entry -.mp4": "a874866ad7096eb3bf31c72ea3315354",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080801 - Mock Entry -.mp4": "fe6fcf8bfc13c0913ad1509703b447f1",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e20080802 - Mock Entry -.mp4": "1d0f1081b402ebea9aeedf75f891c811",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry --thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry -.info.json": "INFO_JSON",
"Best Prebuilt TV Show Collection/Season 02/s02.e21080801 - Mock Entry -.mp4": "e5a844cc2c8fe19037afdf0692731414",
"Best Prebuilt TV Show Collection/fanart.jpg": "15c9a67b554f415a662fdf7a3340cd61",
"Best Prebuilt TV Show Collection/poster.jpg": "0b0fdbe56bab3e3fdda18730588d742a"
}

View file

@ -2,18 +2,18 @@
".ytdl-sub-subscription_test-download-archive.json": "76e202bd03ceef93daaffffee2cfa193", ".ytdl-sub-subscription_test-download-archive.json": "76e202bd03ceef93daaffffee2cfa193",
"JMC/Mock Entry 20-1.info.json": "INFO_JSON", "JMC/Mock Entry 20-1.info.json": "INFO_JSON",
"JMC/Mock Entry 20-1.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 20-1.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 20-1.mp4": "40efe3f597c40c11d0d3f2f87cc7650b", "JMC/Mock Entry 20-1.mp4": "f9f1542629edf78fb9d80f2517e6530e",
"JMC/Mock Entry 20-1.nfo": "fefcf0b3e4f4ff80ad636584d50dadec", "JMC/Mock Entry 20-1.nfo": "fefcf0b3e4f4ff80ad636584d50dadec",
"JMC/Mock Entry 20-2.info.json": "INFO_JSON", "JMC/Mock Entry 20-2.info.json": "INFO_JSON",
"JMC/Mock Entry 20-2.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 20-2.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 20-2.mp4": "0747f56d03e720128b1fa5ef25bcf3c8", "JMC/Mock Entry 20-2.mp4": "48a46efb360b908a5c9653ef266f6574",
"JMC/Mock Entry 20-2.nfo": "025c0b631da5ff5470382b38fce78d2d", "JMC/Mock Entry 20-2.nfo": "025c0b631da5ff5470382b38fce78d2d",
"JMC/Mock Entry 20-3.info.json": "INFO_JSON", "JMC/Mock Entry 20-3.info.json": "INFO_JSON",
"JMC/Mock Entry 20-3.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 20-3.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 20-3.mp4": "eb776b8e6da77a848666d88623600fd6", "JMC/Mock Entry 20-3.mp4": "69b89a35ca5554d047d7ea375635e557",
"JMC/Mock Entry 20-3.nfo": "618b0ff948d9de2e10cf1da8c0dd6615", "JMC/Mock Entry 20-3.nfo": "618b0ff948d9de2e10cf1da8c0dd6615",
"JMC/Mock Entry 21-1.info.json": "INFO_JSON", "JMC/Mock Entry 21-1.info.json": "INFO_JSON",
"JMC/Mock Entry 21-1.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 21-1.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 21-1.mp4": "5d56530dc8a4cfe55efb791d82bae0f0", "JMC/Mock Entry 21-1.mp4": "a3a18a3acf6e28ab921b8458d1526886",
"JMC/Mock Entry 21-1.nfo": "e5c715749efc1603a6e2f59244d87aba" "JMC/Mock Entry 21-1.nfo": "e5c715749efc1603a6e2f59244d87aba"
} }

View file

@ -2,18 +2,18 @@
".ytdl-sub-subscription_test-download-archive.json": "19cf39d57914ba9cbd1e57ba6f1e0683", ".ytdl-sub-subscription_test-download-archive.json": "19cf39d57914ba9cbd1e57ba6f1e0683",
"JMC/Mock Entry 20-1.info.json": "INFO_JSON", "JMC/Mock Entry 20-1.info.json": "INFO_JSON",
"JMC/Mock Entry 20-1.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 20-1.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 20-1.mkv": "1514f89a141f7b5ee3fdde9a70e63f57", "JMC/Mock Entry 20-1.mkv": "eb9a8ff61701ab673c2f06147fe29a4e",
"JMC/Mock Entry 20-1.nfo": "fefcf0b3e4f4ff80ad636584d50dadec", "JMC/Mock Entry 20-1.nfo": "fefcf0b3e4f4ff80ad636584d50dadec",
"JMC/Mock Entry 20-2.info.json": "INFO_JSON", "JMC/Mock Entry 20-2.info.json": "INFO_JSON",
"JMC/Mock Entry 20-2.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 20-2.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 20-2.mkv": "da089645cc944fdb411ecc46facfe9f6", "JMC/Mock Entry 20-2.mkv": "65d047562c61a068e8a92bd8df8801fa",
"JMC/Mock Entry 20-2.nfo": "025c0b631da5ff5470382b38fce78d2d", "JMC/Mock Entry 20-2.nfo": "025c0b631da5ff5470382b38fce78d2d",
"JMC/Mock Entry 20-3.info.json": "INFO_JSON", "JMC/Mock Entry 20-3.info.json": "INFO_JSON",
"JMC/Mock Entry 20-3.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 20-3.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 20-3.mkv": "5dbae183aa57c4062af9d5a2e0e0f8fe", "JMC/Mock Entry 20-3.mkv": "98582526fcb3bea10ab7b3df7a5bc65d",
"JMC/Mock Entry 20-3.nfo": "618b0ff948d9de2e10cf1da8c0dd6615", "JMC/Mock Entry 20-3.nfo": "618b0ff948d9de2e10cf1da8c0dd6615",
"JMC/Mock Entry 21-1.info.json": "INFO_JSON", "JMC/Mock Entry 21-1.info.json": "INFO_JSON",
"JMC/Mock Entry 21-1.jpg": "e80c508c4818454300133fe1dc1a9cd7", "JMC/Mock Entry 21-1.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"JMC/Mock Entry 21-1.mkv": "61910bf74a0175fbdf595095109d1a1c", "JMC/Mock Entry 21-1.mkv": "3fe368f6d4bd9662a24abca9215b4d26",
"JMC/Mock Entry 21-1.nfo": "e5c715749efc1603a6e2f59244d87aba" "JMC/Mock Entry 21-1.nfo": "e5c715749efc1603a6e2f59244d87aba"
} }

View file

@ -1,12 +1,12 @@
{ {
".ytdl-sub-subscription_test-download-archive.json": "7c6fbd0631d7d02025f3da457ba203e3", ".ytdl-sub-subscription_test-download-archive.json": "7c6fbd0631d7d02025f3da457ba203e3",
"Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "7d2ee7fe8003ea63ece37dd2a441c123", "Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "06e7e5beab0b68e4ee3535c801448b4c",
"Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726", "Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726",
"Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "699017cd1b67ae216eda769bef413415", "Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "dfe89d82ae778bae164e161f10ac9adc",
"Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681", "Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681",
"Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "a16741d4fbf657d1de4c50493db14062", "Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "273785dcc28969f4de4a6e3e5ff1e61e",
"Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9", "Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9",
"Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "7433153952b069a5674e2a3ed529b49b", "Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "2170bfbc2b85057ba0ec403e7e5ea66d",
"Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c", "Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
"tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8" "tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8"
} }

View file

@ -2,19 +2,19 @@
".ytdl-sub-JMC-migrated-download-archive.json": "456d8882fc5e35d74f19b386d1d9a059", ".ytdl-sub-JMC-migrated-download-archive.json": "456d8882fc5e35d74f19b386d1d9a059",
"Season 2020/s2020.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7", "Season 2020/s2020.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Season 2020/s2020.e000001 - Mock Entry 20-3.info.json": "INFO_JSON", "Season 2020/s2020.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "7d2ee7fe8003ea63ece37dd2a441c123", "Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "06e7e5beab0b68e4ee3535c801448b4c",
"Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726", "Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726",
"Season 2020/s2020.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7", "Season 2020/s2020.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Season 2020/s2020.e000002 - Mock Entry 20-2.info.json": "INFO_JSON", "Season 2020/s2020.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "699017cd1b67ae216eda769bef413415", "Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "dfe89d82ae778bae164e161f10ac9adc",
"Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681", "Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681",
"Season 2020/s2020.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7", "Season 2020/s2020.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Season 2020/s2020.e000003 - Mock Entry 20-1.info.json": "INFO_JSON", "Season 2020/s2020.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "a16741d4fbf657d1de4c50493db14062", "Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "273785dcc28969f4de4a6e3e5ff1e61e",
"Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9", "Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9",
"Season 2021/s2021.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7", "Season 2021/s2021.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Season 2021/s2021.e000004 - Mock Entry 21-1.info.json": "INFO_JSON", "Season 2021/s2021.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "7433153952b069a5674e2a3ed529b49b", "Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "2170bfbc2b85057ba0ec403e7e5ea66d",
"Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c", "Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
"tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8" "tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8"
} }

View file

@ -2,19 +2,19 @@
".ytdl-sub-subscription_test-download-archive.json": "456d8882fc5e35d74f19b386d1d9a059", ".ytdl-sub-subscription_test-download-archive.json": "456d8882fc5e35d74f19b386d1d9a059",
"Season 2020/s2020.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7", "Season 2020/s2020.e000001 - Mock Entry 20-3-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Season 2020/s2020.e000001 - Mock Entry 20-3.info.json": "INFO_JSON", "Season 2020/s2020.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "7d2ee7fe8003ea63ece37dd2a441c123", "Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "06e7e5beab0b68e4ee3535c801448b4c",
"Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726", "Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726",
"Season 2020/s2020.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7", "Season 2020/s2020.e000002 - Mock Entry 20-2-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Season 2020/s2020.e000002 - Mock Entry 20-2.info.json": "INFO_JSON", "Season 2020/s2020.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "699017cd1b67ae216eda769bef413415", "Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "dfe89d82ae778bae164e161f10ac9adc",
"Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681", "Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681",
"Season 2020/s2020.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7", "Season 2020/s2020.e000003 - Mock Entry 20-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Season 2020/s2020.e000003 - Mock Entry 20-1.info.json": "INFO_JSON", "Season 2020/s2020.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "a16741d4fbf657d1de4c50493db14062", "Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "273785dcc28969f4de4a6e3e5ff1e61e",
"Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9", "Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9",
"Season 2021/s2021.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7", "Season 2021/s2021.e000004 - Mock Entry 21-1-thumb.jpg": "e80c508c4818454300133fe1dc1a9cd7",
"Season 2021/s2021.e000004 - Mock Entry 21-1.info.json": "INFO_JSON", "Season 2021/s2021.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "7433153952b069a5674e2a3ed529b49b", "Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "2170bfbc2b85057ba0ec403e7e5ea66d",
"Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c", "Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
"tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8" "tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8"
} }

View file

@ -1,16 +1,16 @@
{ {
".ytdl-sub-subscription_test-download-archive.json": "e049068ba7f5bdc27c474b18f4c6b402", ".ytdl-sub-subscription_test-download-archive.json": "e049068ba7f5bdc27c474b18f4c6b402",
"Season 2020/s2020.e000001 - Mock Entry 20-3.info.json": "INFO_JSON", "Season 2020/s2020.e000001 - Mock Entry 20-3.info.json": "INFO_JSON",
"Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "7d2ee7fe8003ea63ece37dd2a441c123", "Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "06e7e5beab0b68e4ee3535c801448b4c",
"Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726", "Season 2020/s2020.e000001 - Mock Entry 20-3.nfo": "dbe61f2c8ae41041773f713ba5376726",
"Season 2020/s2020.e000002 - Mock Entry 20-2.info.json": "INFO_JSON", "Season 2020/s2020.e000002 - Mock Entry 20-2.info.json": "INFO_JSON",
"Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "699017cd1b67ae216eda769bef413415", "Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "dfe89d82ae778bae164e161f10ac9adc",
"Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681", "Season 2020/s2020.e000002 - Mock Entry 20-2.nfo": "0f071078c9fa2569bbcb998664d40681",
"Season 2020/s2020.e000003 - Mock Entry 20-1.info.json": "INFO_JSON", "Season 2020/s2020.e000003 - Mock Entry 20-1.info.json": "INFO_JSON",
"Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "a16741d4fbf657d1de4c50493db14062", "Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "273785dcc28969f4de4a6e3e5ff1e61e",
"Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9", "Season 2020/s2020.e000003 - Mock Entry 20-1.nfo": "837a61dca11bbe1874ea07cb8ef8a7c9",
"Season 2021/s2021.e000004 - Mock Entry 21-1.info.json": "INFO_JSON", "Season 2021/s2021.e000004 - Mock Entry 21-1.info.json": "INFO_JSON",
"Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "7433153952b069a5674e2a3ed529b49b", "Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "2170bfbc2b85057ba0ec403e7e5ea66d",
"Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c", "Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
"tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8" "tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8"
} }

View file

@ -1,19 +1,19 @@
{ {
".ytdl-sub-j_b-download-archive.json": "1a99156e9ece62539fb2608416a07200", ".ytdl-sub-j_b-download-archive.json": "1a99156e9ece62539fb2608416a07200",
"j_b/[2021] Baby Santana's Dorian Groove/01 - Baby Santana's Dorian Groove.mp3": "b35d01351e5dd1195e90a41b2edeb5b7", "j_b/[2021] Baby Santana's Dorian Groove/01 - Baby Santana's Dorian Groove.mp3": "0aecc0e9832768002f41e09a7e70b305",
"j_b/[2021] Baby Santana's Dorian Groove/folder.jpg": "967892be44b8c47e1be73f055a7c6f08", "j_b/[2021] Baby Santana's Dorian Groove/folder.jpg": "967892be44b8c47e1be73f055a7c6f08",
"j_b/[2021] Purple Clouds/01 - Purple Clouds.mp3": "5c107c122048389aa40936c4fba0436e", "j_b/[2021] Purple Clouds/01 - Purple Clouds.mp3": "2d4154dfa596c905d71cfd970f5dc9d3",
"j_b/[2021] Purple Clouds/folder.jpg": "967892be44b8c47e1be73f055a7c6f08", "j_b/[2021] Purple Clouds/folder.jpg": "967892be44b8c47e1be73f055a7c6f08",
"j_b/[2022] Acoustic Treats/01 - 20160426 184214.mp3": "e737c2ba118920643cec9eac09a283ea", "j_b/[2022] Acoustic Treats/01 - 20160426 184214.mp3": "10c925fc97629ca788c44ae8082ee889",
"j_b/[2022] Acoustic Treats/02 - 20160502 123150.mp3": "d5def3e4329b9b95c5354c838dd3bc91", "j_b/[2022] Acoustic Treats/02 - 20160502 123150.mp3": "da0e8a7db660adbfef255ba1763c384c",
"j_b/[2022] Acoustic Treats/03 - 20160504 143832.mp3": "1694c4a2889aa7dacafc5f2fdb4fe1d7", "j_b/[2022] Acoustic Treats/03 - 20160504 143832.mp3": "42284493f75a55bd1b1d3f1ac8d1ffe1",
"j_b/[2022] Acoustic Treats/04 - 20160601 221234.mp3": "9c8ddee6aaf88b38706ce6bbdf907878", "j_b/[2022] Acoustic Treats/04 - 20160601 221234.mp3": "d660abefa257a10cbeae434c770bf3e5",
"j_b/[2022] Acoustic Treats/05 - 20160601 222440.mp3": "4bf9e700137cb1b58e730dd312e00bb4", "j_b/[2022] Acoustic Treats/05 - 20160601 222440.mp3": "565d4dcf92e40e1485ed77fa79b52c17",
"j_b/[2022] Acoustic Treats/06 - 20170604 190236.mp3": "c1920254046eda0d84b36dd34f9bafdb", "j_b/[2022] Acoustic Treats/06 - 20170604 190236.mp3": "7c5903693230ca9ac8b44b76ed004eb4",
"j_b/[2022] Acoustic Treats/07 - 20170612 193646.mp3": "26f2eb8395b23cecc4000e2fd58304bf", "j_b/[2022] Acoustic Treats/07 - 20170612 193646.mp3": "dfec9909cb8156ec8d5c28cfce858f36",
"j_b/[2022] Acoustic Treats/08 - 20170628 215206.mp3": "e3ceb5ae25c115d037a298b4620bce9f", "j_b/[2022] Acoustic Treats/08 - 20170628 215206.mp3": "2c8769a9de3d1033d0e152c642512e40",
"j_b/[2022] Acoustic Treats/09 - Finding Home.mp3": "f3dcfe7c8569853a02eb12ad21b0e248", "j_b/[2022] Acoustic Treats/09 - Finding Home.mp3": "b3920d39deb847be938e03e3d74d4ac9",
"j_b/[2022] Acoustic Treats/10 - Shallow Water WIP.mp3": "4fb9a18b3fdd788ce9d9f4c8a07b6028", "j_b/[2022] Acoustic Treats/10 - Shallow Water WIP.mp3": "759394ebe3db9ccef564fa9d7c0dfd09",
"j_b/[2022] Acoustic Treats/11 - Untold History.mp3": "ad186ee1d0029f8de02357e8b8712a4b", "j_b/[2022] Acoustic Treats/11 - Untold History.mp3": "477af6c479263539f244db8d9de04201",
"j_b/[2022] Acoustic Treats/folder.jpg": "967892be44b8c47e1be73f055a7c6f08" "j_b/[2022] Acoustic Treats/folder.jpg": "967892be44b8c47e1be73f055a7c6f08"
} }

Some files were not shown because too many files have changed in this diff Show more