[DEV] More OutputOption unit tests (#1082)
This commit is contained in:
parent
ed4a2814b9
commit
c0ba30b220
24 changed files with 663 additions and 297 deletions
|
|
@ -161,7 +161,7 @@ def _download_subscription_from_cli(
|
||||||
extra_arguments=extra_args, config_options=config.config_options
|
extra_arguments=extra_args, config_options=config.config_options
|
||||||
)
|
)
|
||||||
subscription_args_dict = dl_args_parser.to_subscription_dict()
|
subscription_args_dict = dl_args_parser.to_subscription_dict()
|
||||||
subscription_name = f"cli-dl-{dl_args_parser.get_args_hash()}"
|
subscription_name = dl_args_parser.get_dl_subscription_name()
|
||||||
|
|
||||||
subscription = Subscription.from_dict(
|
subscription = Subscription.from_dict(
|
||||||
config=config, preset_name=subscription_name, preset_dict=subscription_args_dict
|
config=config, preset_name=subscription_name, preset_dict=subscription_args_dict
|
||||||
|
|
|
||||||
|
|
@ -242,12 +242,13 @@ class DownloadArgsParser:
|
||||||
|
|
||||||
return subscription_dict
|
return subscription_dict
|
||||||
|
|
||||||
def get_args_hash(self) -> str:
|
def get_dl_subscription_name(self) -> str:
|
||||||
"""
|
"""
|
||||||
:return: Hash of the arguments provided
|
Returns a deterministic name based on input args
|
||||||
"""
|
"""
|
||||||
hash_string = str(sorted(self._unknown_arguments))
|
to_hash = str(sorted(self._unknown_arguments))
|
||||||
return hashlib.sha256(hash_string.encode()).hexdigest()[-8:]
|
hash_str = hashlib.sha256(to_hash.encode()).hexdigest()[-8:]
|
||||||
|
return f"cli-dl-{hash_str}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dl_override(cls, override: str, config: ConfigFile) -> "DownloadArgsParser":
|
def from_dl_override(cls, override: str, config: ConfigFile) -> "DownloadArgsParser":
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ import contextlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
@ -18,8 +20,10 @@ from resources import copy_file_fixture
|
||||||
from resources import file_fixture_path
|
from resources import file_fixture_path
|
||||||
from yt_dlp.utils import sanitize_filename
|
from yt_dlp.utils import sanitize_filename
|
||||||
|
|
||||||
|
from ytdl_sub.cli.entrypoint import main
|
||||||
from ytdl_sub.config.config_file import ConfigFile
|
from ytdl_sub.config.config_file import ConfigFile
|
||||||
from ytdl_sub.entries.script.custom_functions import CustomFunctions
|
from ytdl_sub.entries.script.custom_functions import CustomFunctions
|
||||||
|
from ytdl_sub.subscriptions.subscription import Subscription
|
||||||
from ytdl_sub.subscriptions.subscription_download import SubscriptionDownload
|
from ytdl_sub.subscriptions.subscription_download import SubscriptionDownload
|
||||||
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
|
||||||
|
|
@ -262,3 +266,9 @@ def default_config_path(default_config) -> str:
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def music_subscriptions_path() -> Path:
|
def music_subscriptions_path() -> Path:
|
||||||
return Path("examples/music_subscriptions.yaml")
|
return Path("examples/music_subscriptions.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
def mock_run_from_cli(args: str) -> List[Subscription]:
|
||||||
|
args_list = ["ytdl-sub"] + shlex.split(args)
|
||||||
|
with patch.object(sys, "argv", args_list):
|
||||||
|
return main()
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
import shlex
|
|
||||||
import sys
|
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import List
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ytdl_sub.cli.entrypoint import main
|
|
||||||
from ytdl_sub.subscriptions.subscription import Subscription
|
|
||||||
from ytdl_sub.utils.file_handler import FileHandler
|
from ytdl_sub.utils.file_handler import FileHandler
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -31,9 +25,3 @@ def timestamps_file_path():
|
||||||
yield tmp.name
|
yield tmp.name
|
||||||
finally:
|
finally:
|
||||||
FileHandler.delete(tmp.name)
|
FileHandler.delete(tmp.name)
|
||||||
|
|
||||||
|
|
||||||
def mock_run_from_cli(args: str) -> List[Subscription]:
|
|
||||||
args_list = ["ytdl-sub"] + shlex.split(args)
|
|
||||||
with patch.object(sys, "argv", args_list):
|
|
||||||
return main()
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from e2e.conftest import mock_run_from_cli
|
from conftest import mock_run_from_cli
|
||||||
|
|
||||||
from ytdl_sub.utils.file_handler import FileMetadata
|
from ytdl_sub.utils.file_handler import FileMetadata
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from conftest import assert_logs
|
from conftest import assert_logs
|
||||||
from e2e.conftest import mock_run_from_cli
|
|
||||||
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 mergedeep import mergedeep
|
|
||||||
|
|
||||||
from ytdl_sub.config.config_file import ConfigFile
|
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.subscriptions.subscription import Subscription
|
from ytdl_sub.subscriptions.subscription import Subscription
|
||||||
from ytdl_sub.utils.system import IS_WINDOWS
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -100,50 +96,6 @@ class TestPlaylist:
|
||||||
files exist and have the expected md5 file hashes.
|
files exist and have the expected md5 file hashes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _ensure_subscription_migrates(
|
|
||||||
cls,
|
|
||||||
config: ConfigFile,
|
|
||||||
subscription_name: str,
|
|
||||||
subscription_dict: Dict,
|
|
||||||
output_directory: Path,
|
|
||||||
):
|
|
||||||
# Ensure download archive migrates
|
|
||||||
mergedeep.merge(
|
|
||||||
subscription_dict,
|
|
||||||
{
|
|
||||||
"output_options": {
|
|
||||||
"migrated_download_archive_name": ".ytdl-sub-{tv_show_name_sanitized}-download-archive.json"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
migrated_subscription = Subscription.from_dict(
|
|
||||||
config=config,
|
|
||||||
preset_name=subscription_name,
|
|
||||||
preset_dict=subscription_dict,
|
|
||||||
)
|
|
||||||
transaction_log = migrated_subscription.download()
|
|
||||||
|
|
||||||
assert_transaction_log_matches(
|
|
||||||
output_directory=output_directory,
|
|
||||||
transaction_log=transaction_log,
|
|
||||||
transaction_log_summary_file_name="youtube/test_playlist_archive_migrated.txt",
|
|
||||||
)
|
|
||||||
assert_expected_downloads(
|
|
||||||
output_directory=output_directory,
|
|
||||||
dry_run=False,
|
|
||||||
expected_download_summary_file_name="youtube/test_playlist_archive_migrated.json",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure no changes after migration
|
|
||||||
transaction_log = migrated_subscription.download()
|
|
||||||
assert transaction_log.is_empty
|
|
||||||
assert_expected_downloads(
|
|
||||||
output_directory=output_directory,
|
|
||||||
dry_run=False,
|
|
||||||
expected_download_summary_file_name="youtube/test_playlist_archive_migrated.json",
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("dry_run", [True, False])
|
@pytest.mark.parametrize("dry_run", [True, False])
|
||||||
def test_playlist_download(
|
def test_playlist_download(
|
||||||
self,
|
self,
|
||||||
|
|
@ -186,86 +138,6 @@ class TestPlaylist:
|
||||||
expected_download_summary_file_name="youtube/test_playlist.json",
|
expected_download_summary_file_name="youtube/test_playlist.json",
|
||||||
)
|
)
|
||||||
|
|
||||||
self._ensure_subscription_migrates(
|
|
||||||
config=default_config,
|
|
||||||
subscription_name="music_video_playlist_test",
|
|
||||||
subscription_dict=playlist_preset_dict,
|
|
||||||
output_directory=output_directory,
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("dry_run", [True, False])
|
|
||||||
def test_playlist_download_from_cli_sub_no_provided_config(
|
|
||||||
self,
|
|
||||||
preset_dict_to_subscription_yaml_generator,
|
|
||||||
playlist_preset_dict,
|
|
||||||
output_directory,
|
|
||||||
dry_run,
|
|
||||||
):
|
|
||||||
# TODO: Fix CLI parsing on windows when dealing with spaces
|
|
||||||
if IS_WINDOWS:
|
|
||||||
return
|
|
||||||
|
|
||||||
# No config needed when using only prebuilt presets
|
|
||||||
with preset_dict_to_subscription_yaml_generator(
|
|
||||||
subscription_name="music_video_playlist_test", preset_dict=playlist_preset_dict
|
|
||||||
) as subscription_path:
|
|
||||||
args = "--dry-run " if dry_run else ""
|
|
||||||
args += f"sub '{subscription_path}'"
|
|
||||||
subscriptions = mock_run_from_cli(args=args)
|
|
||||||
|
|
||||||
assert len(subscriptions) == 1
|
|
||||||
transaction_log = subscriptions[0].transaction_log
|
|
||||||
|
|
||||||
assert_transaction_log_matches(
|
|
||||||
output_directory=output_directory,
|
|
||||||
transaction_log=transaction_log,
|
|
||||||
transaction_log_summary_file_name="youtube/test_playlist.txt",
|
|
||||||
)
|
|
||||||
assert_expected_downloads(
|
|
||||||
output_directory=output_directory,
|
|
||||||
dry_run=dry_run,
|
|
||||||
expected_download_summary_file_name="youtube/test_playlist.json",
|
|
||||||
)
|
|
||||||
|
|
||||||
if not dry_run:
|
|
||||||
# Ensure another invocation will hit ExistingVideoReached
|
|
||||||
with assert_logs(
|
|
||||||
logger=YTDLP.logger,
|
|
||||||
expected_message="ExistingVideoReached, stopping additional downloads",
|
|
||||||
log_level="debug",
|
|
||||||
):
|
|
||||||
transaction_log = mock_run_from_cli(args=args)[0].transaction_log
|
|
||||||
|
|
||||||
assert transaction_log.is_empty
|
|
||||||
assert_expected_downloads(
|
|
||||||
output_directory=output_directory,
|
|
||||||
dry_run=dry_run,
|
|
||||||
expected_download_summary_file_name="youtube/test_playlist.json",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_playlist_download_from_cli_sub_with_override_arg(
|
|
||||||
self,
|
|
||||||
preset_dict_to_subscription_yaml_generator,
|
|
||||||
playlist_preset_dict,
|
|
||||||
output_directory,
|
|
||||||
):
|
|
||||||
# TODO: Fix CLI parsing on windows when dealing with spaces
|
|
||||||
if IS_WINDOWS:
|
|
||||||
return
|
|
||||||
|
|
||||||
# No config needed when using only prebuilt presets
|
|
||||||
with preset_dict_to_subscription_yaml_generator(
|
|
||||||
subscription_name="music_video_playlist_test", preset_dict=playlist_preset_dict
|
|
||||||
) as subscription_path:
|
|
||||||
args = (
|
|
||||||
f"--dry-run sub '{subscription_path}' --dl-override '--date_range.after 20240101'"
|
|
||||||
)
|
|
||||||
|
|
||||||
subscriptions = mock_run_from_cli(args=args)
|
|
||||||
|
|
||||||
assert len(subscriptions) == 1
|
|
||||||
assert subscriptions[0].transaction_log.is_empty
|
|
||||||
|
|
||||||
def test_tv_show_by_date_downloads_bilateral(
|
def test_tv_show_by_date_downloads_bilateral(
|
||||||
self,
|
self,
|
||||||
tv_show_by_date_bilateral_dict: Dict,
|
tv_show_by_date_bilateral_dict: Dict,
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,9 @@
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from conftest import preset_dict_to_dl_args
|
from conftest import preset_dict_to_dl_args
|
||||||
from e2e.conftest import mock_run_from_cli
|
|
||||||
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.downloaders.ytdlp import YTDLP
|
|
||||||
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.file_handler import FileHandler
|
|
||||||
from ytdl_sub.utils.system import IS_WINDOWS
|
|
||||||
from ytdl_sub.utils.thumbnail import try_convert_download_thumbnail
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -69,72 +61,3 @@ class TestYoutubeVideo:
|
||||||
dry_run=dry_run,
|
dry_run=dry_run,
|
||||||
expected_download_summary_file_name="youtube/test_video.json",
|
expected_download_summary_file_name="youtube/test_video.json",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_single_video_download_missing_thumbnail(
|
|
||||||
self,
|
|
||||||
default_config,
|
|
||||||
single_video_preset_dict,
|
|
||||||
working_directory,
|
|
||||||
output_directory,
|
|
||||||
):
|
|
||||||
single_video_subscription = Subscription.from_dict(
|
|
||||||
config=default_config,
|
|
||||||
preset_name="music_video_single_video_test",
|
|
||||||
preset_dict=single_video_preset_dict,
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete_entry_thumb(entry: Entry) -> None:
|
|
||||||
FileHandler.delete(entry.get_download_thumbnail_path())
|
|
||||||
try_convert_download_thumbnail(entry=entry)
|
|
||||||
|
|
||||||
# Pretend the thumbnail did not download via returning nothing for its downloaded path
|
|
||||||
with (
|
|
||||||
patch.object(YTDLP, "_EXTRACT_ENTRY_NUM_RETRIES", 1),
|
|
||||||
patch.object(Entry, "try_get_ytdlp_download_thumbnail_path") as mock_ytdlp_path,
|
|
||||||
patch(
|
|
||||||
"ytdl_sub.downloaders.url.downloader.try_convert_download_thumbnail",
|
|
||||||
side_effect=delete_entry_thumb,
|
|
||||||
),
|
|
||||||
):
|
|
||||||
mock_ytdlp_path.return_value = None
|
|
||||||
transaction_log = single_video_subscription.download(dry_run=False)
|
|
||||||
|
|
||||||
assert_transaction_log_matches(
|
|
||||||
output_directory=output_directory,
|
|
||||||
transaction_log=transaction_log,
|
|
||||||
transaction_log_summary_file_name="youtube/test_video_missing_thumb.txt",
|
|
||||||
)
|
|
||||||
assert_expected_downloads(
|
|
||||||
output_directory=output_directory,
|
|
||||||
dry_run=False,
|
|
||||||
expected_download_summary_file_name="youtube/test_video_missing_thumb.json",
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("dry_run", [True, False])
|
|
||||||
def test_single_video_download_from_cli_dl(
|
|
||||||
self,
|
|
||||||
default_config_path,
|
|
||||||
single_video_preset_dict_dl_args,
|
|
||||||
output_directory,
|
|
||||||
dry_run,
|
|
||||||
):
|
|
||||||
# TODO: Fix CLI parsing on windows when dealing with spaces
|
|
||||||
if IS_WINDOWS:
|
|
||||||
return
|
|
||||||
|
|
||||||
args = "--dry-run " if dry_run else ""
|
|
||||||
args += f"--config {default_config_path} "
|
|
||||||
args += f"dl {single_video_preset_dict_dl_args}"
|
|
||||||
subscriptions = mock_run_from_cli(args=args)
|
|
||||||
|
|
||||||
assert len(subscriptions) == 1
|
|
||||||
assert_transaction_log_matches(
|
|
||||||
output_directory=output_directory,
|
|
||||||
transaction_log=subscriptions[0].transaction_log,
|
|
||||||
transaction_log_summary_file_name="youtube/test_video_cli.txt",
|
|
||||||
)
|
|
||||||
assert_expected_downloads(
|
|
||||||
output_directory=output_directory,
|
|
||||||
dry_run=dry_run,
|
|
||||||
expected_download_summary_file_name="youtube/test_video_cli.json",
|
|
||||||
)
|
|
||||||
|
|
|
||||||
73
tests/integration/cli/test_dl.py
Normal file
73
tests/integration/cli/test_dl.py
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
from typing import Dict
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from conftest import mock_run_from_cli
|
||||||
|
from conftest import preset_dict_to_dl_args
|
||||||
|
from expected_download import assert_expected_downloads
|
||||||
|
from expected_transaction_log import assert_transaction_log_matches
|
||||||
|
|
||||||
|
from ytdl_sub.cli.parsers.dl import DownloadArgsParser
|
||||||
|
from ytdl_sub.utils.system import IS_WINDOWS
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dl_subscription_dict(output_directory) -> Dict:
|
||||||
|
return {
|
||||||
|
"preset": "Jellyfin Music Videos",
|
||||||
|
"overrides": {
|
||||||
|
"music_video_artist": "JMC",
|
||||||
|
"music_video_directory": output_directory,
|
||||||
|
"url": "https://your.name.here",
|
||||||
|
},
|
||||||
|
"nfo_tags": {"tags": {"custom_tag": "{%array_at( ['hi', 'mom'], 1 )}"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestCliDl:
|
||||||
|
@pytest.mark.parametrize("config_provided", [True, False])
|
||||||
|
@pytest.mark.parametrize("dry_run", [True, False])
|
||||||
|
def test_cli_dl_command(
|
||||||
|
self,
|
||||||
|
default_config_path: str,
|
||||||
|
subscription_name: str,
|
||||||
|
dl_subscription_dict: Dict,
|
||||||
|
output_directory: str,
|
||||||
|
mock_download_collection_entries,
|
||||||
|
config_provided: bool,
|
||||||
|
dry_run: bool,
|
||||||
|
):
|
||||||
|
# Skip this combo to save time and to avoid mocking working dir
|
||||||
|
if not config_provided and not dry_run:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: Fix CLI parsing on windows when dealing with spaces
|
||||||
|
if IS_WINDOWS:
|
||||||
|
return
|
||||||
|
|
||||||
|
args = "--dry-run " if dry_run else ""
|
||||||
|
args += f"--config {default_config_path} " if config_provided else ""
|
||||||
|
args += f"dl {preset_dict_to_dl_args(dl_subscription_dict)} "
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(DownloadArgsParser, "get_dl_subscription_name") as mock_subscription_name,
|
||||||
|
mock_download_collection_entries(
|
||||||
|
is_youtube_channel=False,
|
||||||
|
num_urls=1,
|
||||||
|
is_extracted_audio=False,
|
||||||
|
is_dry_run=dry_run,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
mock_subscription_name.return_value = subscription_name
|
||||||
|
subscriptions = mock_run_from_cli(args=args)
|
||||||
|
|
||||||
|
assert_transaction_log_matches(
|
||||||
|
output_directory=output_directory,
|
||||||
|
transaction_log=subscriptions[0].transaction_log,
|
||||||
|
transaction_log_summary_file_name="dl/test_cli_dl_command.txt",
|
||||||
|
)
|
||||||
|
assert_expected_downloads(
|
||||||
|
output_directory=output_directory,
|
||||||
|
dry_run=dry_run,
|
||||||
|
expected_download_summary_file_name="dl/test_cli_dl_command.json",
|
||||||
|
)
|
||||||
|
|
@ -145,6 +145,8 @@ def mock_download_collection_entries(
|
||||||
# Second TV URL or second soundcloud URL, which downloads first
|
# Second TV URL or second soundcloud URL, which downloads first
|
||||||
is_second_url = "2" in kwargs["url"] or kwargs["url"].endswith("/albums")
|
is_second_url = "2" in kwargs["url"] or kwargs["url"].endswith("/albums")
|
||||||
|
|
||||||
|
if num_urls == 0:
|
||||||
|
return []
|
||||||
if num_urls == 1 or (is_second_url and num_urls > 1):
|
if num_urls == 1 or (is_second_url and num_urls > 1):
|
||||||
return [
|
return [
|
||||||
mock_entry_dict_factory(
|
mock_entry_dict_factory(
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
|
from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
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 mergedeep import mergedeep
|
||||||
|
|
||||||
from ytdl_sub.config.config_file import ConfigFile
|
from ytdl_sub.config.config_file import ConfigFile
|
||||||
|
from ytdl_sub.downloaders.ytdlp import YTDLP
|
||||||
|
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.file_handler import FileHandler
|
||||||
|
from ytdl_sub.utils.thumbnail import try_convert_download_thumbnail
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -27,6 +34,110 @@ def output_options_subscription_dict(output_directory) -> Dict:
|
||||||
|
|
||||||
|
|
||||||
class TestOutputOptions:
|
class TestOutputOptions:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _ensure_subscription_migrates(
|
||||||
|
cls,
|
||||||
|
config: ConfigFile,
|
||||||
|
subscription_name: str,
|
||||||
|
subscription_dict: Dict,
|
||||||
|
output_directory: Path,
|
||||||
|
):
|
||||||
|
# Ensure download archive migrates
|
||||||
|
mergedeep.merge(
|
||||||
|
subscription_dict,
|
||||||
|
{
|
||||||
|
"output_options": {
|
||||||
|
"migrated_download_archive_name": ".ytdl-sub-{tv_show_name_sanitized}-download-archive.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
migrated_subscription = Subscription.from_dict(
|
||||||
|
config=config,
|
||||||
|
preset_name=subscription_name,
|
||||||
|
preset_dict=subscription_dict,
|
||||||
|
)
|
||||||
|
transaction_log = migrated_subscription.download()
|
||||||
|
|
||||||
|
assert_transaction_log_matches(
|
||||||
|
output_directory=output_directory,
|
||||||
|
transaction_log=transaction_log,
|
||||||
|
transaction_log_summary_file_name="youtube/test_playlist_archive_migrated.txt",
|
||||||
|
)
|
||||||
|
assert_expected_downloads(
|
||||||
|
output_directory=output_directory,
|
||||||
|
dry_run=False,
|
||||||
|
expected_download_summary_file_name="youtube/test_playlist_archive_migrated.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure no changes after migration
|
||||||
|
transaction_log = migrated_subscription.download()
|
||||||
|
assert transaction_log.is_empty
|
||||||
|
assert_expected_downloads(
|
||||||
|
output_directory=output_directory,
|
||||||
|
dry_run=False,
|
||||||
|
expected_download_summary_file_name="youtube/test_playlist_archive_migrated.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_download_archive_migration(
|
||||||
|
self,
|
||||||
|
config: ConfigFile,
|
||||||
|
subscription_name: str,
|
||||||
|
output_options_subscription_dict: Dict,
|
||||||
|
output_directory: str,
|
||||||
|
mock_download_collection_entries,
|
||||||
|
):
|
||||||
|
subscription = Subscription.from_dict(
|
||||||
|
config=config,
|
||||||
|
preset_name=subscription_name,
|
||||||
|
preset_dict=output_options_subscription_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock_download_collection_entries(
|
||||||
|
is_youtube_channel=False,
|
||||||
|
num_urls=1,
|
||||||
|
is_extracted_audio=False,
|
||||||
|
is_dry_run=False,
|
||||||
|
):
|
||||||
|
transaction_log = subscription.download(dry_run=False)
|
||||||
|
|
||||||
|
assert_transaction_log_matches(
|
||||||
|
output_directory=output_directory,
|
||||||
|
transaction_log=transaction_log,
|
||||||
|
transaction_log_summary_file_name="plugins/output_options/pre_migration.txt",
|
||||||
|
)
|
||||||
|
assert_expected_downloads(
|
||||||
|
output_directory=output_directory,
|
||||||
|
dry_run=False,
|
||||||
|
expected_download_summary_file_name="plugins/output_options/pre_migration.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
output_options_subscription_dict["output_options"][
|
||||||
|
"migrated_download_archive_name"
|
||||||
|
] = ".ytdl-sub-{tv_show_name_sanitized}-migrated-download-archive.json"
|
||||||
|
subscription = Subscription.from_dict(
|
||||||
|
config=config,
|
||||||
|
preset_name=subscription_name,
|
||||||
|
preset_dict=output_options_subscription_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock_download_collection_entries(
|
||||||
|
is_youtube_channel=False,
|
||||||
|
num_urls=0,
|
||||||
|
):
|
||||||
|
transaction_log = subscription.download(dry_run=False)
|
||||||
|
|
||||||
|
assert_transaction_log_matches(
|
||||||
|
output_directory=output_directory,
|
||||||
|
transaction_log=transaction_log,
|
||||||
|
transaction_log_summary_file_name="plugins/output_options/post_migration.txt",
|
||||||
|
)
|
||||||
|
assert_expected_downloads(
|
||||||
|
output_directory=output_directory,
|
||||||
|
dry_run=False,
|
||||||
|
expected_download_summary_file_name="plugins/output_options/post_migration.json",
|
||||||
|
)
|
||||||
|
|
||||||
@pytest.mark.parametrize("dry_run", [True, False])
|
@pytest.mark.parametrize("dry_run", [True, False])
|
||||||
def test_empty_info_json_and_thumb(
|
def test_empty_info_json_and_thumb(
|
||||||
self,
|
self,
|
||||||
|
|
@ -64,3 +175,51 @@ class TestOutputOptions:
|
||||||
dry_run=dry_run,
|
dry_run=dry_run,
|
||||||
expected_download_summary_file_name="plugins/output_options/empty_info_json_thumb.json",
|
expected_download_summary_file_name="plugins/output_options/empty_info_json_thumb.json",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_missing_thumbnail(
|
||||||
|
self,
|
||||||
|
config: ConfigFile,
|
||||||
|
subscription_name: str,
|
||||||
|
output_options_subscription_dict: Dict,
|
||||||
|
working_directory,
|
||||||
|
output_directory,
|
||||||
|
mock_download_collection_entries,
|
||||||
|
):
|
||||||
|
single_video_subscription = Subscription.from_dict(
|
||||||
|
config=config,
|
||||||
|
preset_name=subscription_name,
|
||||||
|
preset_dict=output_options_subscription_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete_entry_thumb(entry: Entry) -> None:
|
||||||
|
FileHandler.delete(entry.get_download_thumbnail_path())
|
||||||
|
try_convert_download_thumbnail(entry=entry)
|
||||||
|
|
||||||
|
# Pretend the thumbnail did not download via returning nothing for its downloaded path
|
||||||
|
with (
|
||||||
|
mock_download_collection_entries(
|
||||||
|
is_youtube_channel=False,
|
||||||
|
num_urls=1,
|
||||||
|
is_extracted_audio=False,
|
||||||
|
is_dry_run=False,
|
||||||
|
),
|
||||||
|
patch.object(YTDLP, "_EXTRACT_ENTRY_NUM_RETRIES", 1),
|
||||||
|
patch.object(Entry, "try_get_ytdlp_download_thumbnail_path") as mock_ytdlp_path,
|
||||||
|
patch(
|
||||||
|
"ytdl_sub.downloaders.url.downloader.try_convert_download_thumbnail",
|
||||||
|
side_effect=delete_entry_thumb,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
mock_ytdlp_path.return_value = None
|
||||||
|
transaction_log = single_video_subscription.download(dry_run=False)
|
||||||
|
|
||||||
|
assert_transaction_log_matches(
|
||||||
|
output_directory=output_directory,
|
||||||
|
transaction_log=transaction_log,
|
||||||
|
transaction_log_summary_file_name="plugins/output_options/test_missing_thumb.txt",
|
||||||
|
)
|
||||||
|
assert_expected_downloads(
|
||||||
|
output_directory=output_directory,
|
||||||
|
dry_run=False,
|
||||||
|
expected_download_summary_file_name="plugins/output_options/test_missing_thumb.json",
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
".ytdl-sub-subscription_test-download-archive.json": "76e202bd03ceef93daaffffee2cfa193",
|
||||||
|
"JMC/Mock Entry 20-1.info.json": "INFO_JSON",
|
||||||
|
"JMC/Mock Entry 20-1.jpg": "e80c508c4818454300133fe1dc1a9cd7",
|
||||||
|
"JMC/Mock Entry 20-1.mp4": "dbaeb2a3bfd1de1c7e9615e8bdacb910",
|
||||||
|
"JMC/Mock Entry 20-1.nfo": "5d001a105d2380844db181911d5df5c6",
|
||||||
|
"JMC/Mock Entry 20-2.info.json": "INFO_JSON",
|
||||||
|
"JMC/Mock Entry 20-2.jpg": "e80c508c4818454300133fe1dc1a9cd7",
|
||||||
|
"JMC/Mock Entry 20-2.mp4": "76c2c70572489c684923a51cb1b50687",
|
||||||
|
"JMC/Mock Entry 20-2.nfo": "dccdded1ca7ebb38d2c9a0e5130bea33",
|
||||||
|
"JMC/Mock Entry 20-3.info.json": "INFO_JSON",
|
||||||
|
"JMC/Mock Entry 20-3.jpg": "e80c508c4818454300133fe1dc1a9cd7",
|
||||||
|
"JMC/Mock Entry 20-3.mp4": "7bb4f21d59a6fb91836537b27a24e776",
|
||||||
|
"JMC/Mock Entry 20-3.nfo": "7b1eab48693161e83c21203b1308b64e",
|
||||||
|
"JMC/Mock Entry 21-1.info.json": "INFO_JSON",
|
||||||
|
"JMC/Mock Entry 21-1.jpg": "e80c508c4818454300133fe1dc1a9cd7",
|
||||||
|
"JMC/Mock Entry 21-1.mp4": "7b008531ca5660b51fb8adc83c799084",
|
||||||
|
"JMC/Mock Entry 21-1.nfo": "3b4b0e6820f80370d6738ce828f14a89"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
".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.info.json": "INFO_JSON",
|
||||||
|
"Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "7d2ee7fe8003ea63ece37dd2a441c123",
|
||||||
|
"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.info.json": "INFO_JSON",
|
||||||
|
"Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "699017cd1b67ae216eda769bef413415",
|
||||||
|
"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.info.json": "INFO_JSON",
|
||||||
|
"Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "a16741d4fbf657d1de4c50493db14062",
|
||||||
|
"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.info.json": "INFO_JSON",
|
||||||
|
"Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "7433153952b069a5674e2a3ed529b49b",
|
||||||
|
"Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
|
||||||
|
"tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
".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.info.json": "INFO_JSON",
|
||||||
|
"Season 2020/s2020.e000001 - Mock Entry 20-3.mp4": "7d2ee7fe8003ea63ece37dd2a441c123",
|
||||||
|
"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.info.json": "INFO_JSON",
|
||||||
|
"Season 2020/s2020.e000002 - Mock Entry 20-2.mp4": "699017cd1b67ae216eda769bef413415",
|
||||||
|
"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.info.json": "INFO_JSON",
|
||||||
|
"Season 2020/s2020.e000003 - Mock Entry 20-1.mp4": "a16741d4fbf657d1de4c50493db14062",
|
||||||
|
"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.info.json": "INFO_JSON",
|
||||||
|
"Season 2021/s2021.e000004 - Mock Entry 21-1.mp4": "7433153952b069a5674e2a3ed529b49b",
|
||||||
|
"Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
|
||||||
|
"tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
".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.mp4": "7d2ee7fe8003ea63ece37dd2a441c123",
|
||||||
|
"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.mp4": "699017cd1b67ae216eda769bef413415",
|
||||||
|
"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.mp4": "a16741d4fbf657d1de4c50493db14062",
|
||||||
|
"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.mp4": "7433153952b069a5674e2a3ed529b49b",
|
||||||
|
"Season 2021/s2021.e000004 - Mock Entry 21-1.nfo": "8ee3845c514a411425b7e9198666b61c",
|
||||||
|
"tvshow.nfo": "cccca1086b41af04d8ea004b1aa250e8"
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"JMC/.ytdl-sub-JMC-download-archive.json": "3fdab8d103e51aa70430b6da0ceb07e2",
|
|
||||||
"JMC/Season 01/s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg": "b232d253df621aa770b780c1301d364d",
|
|
||||||
"JMC/Season 01/s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1].info.json": "INFO_JSON",
|
|
||||||
"JMC/Season 01/s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1].mp4": "7fa67dd3a895da12c17669b0fa2f3763",
|
|
||||||
"JMC/Season 01/s01.e11020101 - Jesse's Minecraft Server [Trailer - Feb.1].nfo": "2a2997cbf16fb6b943d9933ad267331e",
|
|
||||||
"JMC/Season 01/s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg": "d17c379ea8b362f5b97c6b213b0342cb",
|
|
||||||
"JMC/Season 01/s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27].info.json": "INFO_JSON",
|
|
||||||
"JMC/Season 01/s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27].mp4": "5a3ffa5cee2f58f5001502ab3823d130",
|
|
||||||
"JMC/Season 01/s01.e11022701 - Jesse's Minecraft Server [Trailer - Feb.27].nfo": "da7645e8826586388ae0d8278ef6a1c1",
|
|
||||||
"JMC/Season 01/s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg": "e7830aa8a64b0cde65ba3f7e5fc56530",
|
|
||||||
"JMC/Season 01/s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21].info.json": "INFO_JSON",
|
|
||||||
"JMC/Season 01/s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21].mp4": "91f22944f797513fcda9856e244dd3b3",
|
|
||||||
"JMC/Season 01/s01.e11032101 - Jesse's Minecraft Server [Trailer - Mar.21].nfo": "c56083e2f3545fa2cafc4d67cbfdacf8",
|
|
||||||
"JMC/fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
|
||||||
"JMC/poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
|
||||||
"JMC/season01-poster.jpg": "e7830aa8a64b0cde65ba3f7e5fc56530",
|
|
||||||
"JMC/tvshow.nfo": "e92e4a2c01522dd9a9c3423f0f9304dc"
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"JMC/Oblivion Mod "Falcor" p.1.info.json": "INFO_JSON",
|
|
||||||
"JMC/Oblivion Mod "Falcor" p.1.jpg": "fb95b510681676e81c321171fc23143e",
|
|
||||||
"JMC/Oblivion Mod "Falcor" p.1.mp4": "318faf3eb1d7666491553cdfd2a2e9fb",
|
|
||||||
"JMC/Oblivion Mod "Falcor" p.1.nfo": "58c2be339869b5d071c1758d55c72ddb"
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"JMC/Oblivion Mod "Falcor" p.1.info.json": "INFO_JSON",
|
|
||||||
"JMC/Oblivion Mod "Falcor" p.1.mp4": "d3bdda5ec6822ea3b4b4bd8908eb327a",
|
|
||||||
"JMC/Oblivion Mod "Falcor" p.1.nfo": "58c2be339869b5d071c1758d55c72ddb"
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
Files created:
|
||||||
|
----------------------------------------
|
||||||
|
{output_directory}
|
||||||
|
.ytdl-sub-subscription_test-download-archive.json
|
||||||
|
{output_directory}/JMC
|
||||||
|
Mock Entry 20-1.info.json
|
||||||
|
Mock Entry 20-1.jpg
|
||||||
|
Mock Entry 20-1.mp4
|
||||||
|
Video Tags:
|
||||||
|
album: Music Videos
|
||||||
|
artist: JMC
|
||||||
|
genre: ytdl-sub
|
||||||
|
premiered: 2020-08-08
|
||||||
|
title: Mock Entry 20-1
|
||||||
|
year: 2020
|
||||||
|
Mock Entry 20-1.nfo
|
||||||
|
NFO tags:
|
||||||
|
musicvideo:
|
||||||
|
album: Music Videos
|
||||||
|
artist: JMC
|
||||||
|
custom_tag: mom
|
||||||
|
genre: ytdl-sub
|
||||||
|
premiered: 2020-08-08
|
||||||
|
title: Mock Entry 20-1
|
||||||
|
Mock Entry 20-2.info.json
|
||||||
|
Mock Entry 20-2.jpg
|
||||||
|
Mock Entry 20-2.mp4
|
||||||
|
Video Tags:
|
||||||
|
album: Music Videos
|
||||||
|
artist: JMC
|
||||||
|
genre: ytdl-sub
|
||||||
|
premiered: 2020-08-08
|
||||||
|
title: Mock Entry 20-2
|
||||||
|
year: 2020
|
||||||
|
Mock Entry 20-2.nfo
|
||||||
|
NFO tags:
|
||||||
|
musicvideo:
|
||||||
|
album: Music Videos
|
||||||
|
artist: JMC
|
||||||
|
custom_tag: mom
|
||||||
|
genre: ytdl-sub
|
||||||
|
premiered: 2020-08-08
|
||||||
|
title: Mock Entry 20-2
|
||||||
|
Mock Entry 20-3.info.json
|
||||||
|
Mock Entry 20-3.jpg
|
||||||
|
Mock Entry 20-3.mp4
|
||||||
|
Video Tags:
|
||||||
|
album: Music Videos
|
||||||
|
artist: JMC
|
||||||
|
genre: ytdl-sub
|
||||||
|
premiered: 2020-08-07
|
||||||
|
title: Mock Entry 20-3
|
||||||
|
year: 2020
|
||||||
|
Mock Entry 20-3.nfo
|
||||||
|
NFO tags:
|
||||||
|
musicvideo:
|
||||||
|
album: Music Videos
|
||||||
|
artist: JMC
|
||||||
|
custom_tag: mom
|
||||||
|
genre: ytdl-sub
|
||||||
|
premiered: 2020-08-07
|
||||||
|
title: Mock Entry 20-3
|
||||||
|
Mock Entry 21-1.info.json
|
||||||
|
Mock Entry 21-1.jpg
|
||||||
|
Mock Entry 21-1.mp4
|
||||||
|
Video Tags:
|
||||||
|
album: Music Videos
|
||||||
|
artist: JMC
|
||||||
|
genre: ytdl-sub
|
||||||
|
premiered: 2021-08-08
|
||||||
|
title: Mock Entry 21-1
|
||||||
|
year: 2021
|
||||||
|
Mock Entry 21-1.nfo
|
||||||
|
NFO tags:
|
||||||
|
musicvideo:
|
||||||
|
album: Music Videos
|
||||||
|
artist: JMC
|
||||||
|
custom_tag: mom
|
||||||
|
genre: ytdl-sub
|
||||||
|
premiered: 2021-08-08
|
||||||
|
title: Mock Entry 21-1
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
Files created:
|
Files created:
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
{output_directory}
|
{output_directory}
|
||||||
.ytdl-sub-JMC-download-archive.json
|
.ytdl-sub-JMC-migrated-download-archive.json
|
||||||
|
|
||||||
Files removed:
|
Files removed:
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
{output_directory}
|
{output_directory}
|
||||||
.ytdl-sub-music_video_playlist_test-download-archive.json
|
.ytdl-sub-subscription_test-download-archive.json
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
Files created:
|
||||||
|
----------------------------------------
|
||||||
|
{output_directory}
|
||||||
|
.ytdl-sub-subscription_test-download-archive.json
|
||||||
|
tvshow.nfo
|
||||||
|
NFO tags:
|
||||||
|
tvshow:
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
title: JMC
|
||||||
|
{output_directory}/Season 2020
|
||||||
|
s2020.e000001 - Mock Entry 20-3-thumb.jpg
|
||||||
|
s2020.e000001 - Mock Entry 20-3.info.json
|
||||||
|
s2020.e000001 - Mock Entry 20-3.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2020-08-07
|
||||||
|
episode_id: 1
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: JMC
|
||||||
|
synopsis:
|
||||||
|
https://20-3.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2020-08-07 - Mock Entry 20-3
|
||||||
|
year: 2020
|
||||||
|
s2020.e000001 - Mock Entry 20-3.nfo
|
||||||
|
NFO tags:
|
||||||
|
episodedetails:
|
||||||
|
aired: 2020-08-07
|
||||||
|
episode: 1
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
plot:
|
||||||
|
https://20-3.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
season: 2020
|
||||||
|
title: 2020-08-07 - Mock Entry 20-3
|
||||||
|
year: 2020
|
||||||
|
s2020.e000002 - Mock Entry 20-2-thumb.jpg
|
||||||
|
s2020.e000002 - Mock Entry 20-2.info.json
|
||||||
|
s2020.e000002 - Mock Entry 20-2.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2020-08-08
|
||||||
|
episode_id: 2
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: JMC
|
||||||
|
synopsis:
|
||||||
|
https://20-2.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2020-08-08 - Mock Entry 20-2
|
||||||
|
year: 2020
|
||||||
|
s2020.e000002 - Mock Entry 20-2.nfo
|
||||||
|
NFO tags:
|
||||||
|
episodedetails:
|
||||||
|
aired: 2020-08-08
|
||||||
|
episode: 2
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
plot:
|
||||||
|
https://20-2.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
season: 2020
|
||||||
|
title: 2020-08-08 - Mock Entry 20-2
|
||||||
|
year: 2020
|
||||||
|
s2020.e000003 - Mock Entry 20-1-thumb.jpg
|
||||||
|
s2020.e000003 - Mock Entry 20-1.info.json
|
||||||
|
s2020.e000003 - Mock Entry 20-1.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2020-08-08
|
||||||
|
episode_id: 3
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: JMC
|
||||||
|
synopsis:
|
||||||
|
https://20-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2020-08-08 - Mock Entry 20-1
|
||||||
|
year: 2020
|
||||||
|
s2020.e000003 - Mock Entry 20-1.nfo
|
||||||
|
NFO tags:
|
||||||
|
episodedetails:
|
||||||
|
aired: 2020-08-08
|
||||||
|
episode: 3
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
plot:
|
||||||
|
https://20-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
season: 2020
|
||||||
|
title: 2020-08-08 - Mock Entry 20-1
|
||||||
|
year: 2020
|
||||||
|
{output_directory}/Season 2021
|
||||||
|
s2021.e000004 - Mock Entry 21-1-thumb.jpg
|
||||||
|
s2021.e000004 - Mock Entry 21-1.info.json
|
||||||
|
s2021.e000004 - Mock Entry 21-1.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2021-08-08
|
||||||
|
episode_id: 4
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: JMC
|
||||||
|
synopsis:
|
||||||
|
https://21-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2021-08-08 - Mock Entry 21-1
|
||||||
|
year: 2021
|
||||||
|
s2021.e000004 - Mock Entry 21-1.nfo
|
||||||
|
NFO tags:
|
||||||
|
episodedetails:
|
||||||
|
aired: 2021-08-08
|
||||||
|
episode: 4
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
plot:
|
||||||
|
https://21-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
season: 2021
|
||||||
|
title: 2021-08-08 - Mock Entry 21-1
|
||||||
|
year: 2021
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
Files created:
|
||||||
|
----------------------------------------
|
||||||
|
{output_directory}
|
||||||
|
.ytdl-sub-subscription_test-download-archive.json
|
||||||
|
tvshow.nfo
|
||||||
|
NFO tags:
|
||||||
|
tvshow:
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
title: JMC
|
||||||
|
{output_directory}/Season 2020
|
||||||
|
s2020.e000001 - Mock Entry 20-3.info.json
|
||||||
|
s2020.e000001 - Mock Entry 20-3.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2020-08-07
|
||||||
|
episode_id: 1
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: JMC
|
||||||
|
synopsis:
|
||||||
|
https://20-3.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2020-08-07 - Mock Entry 20-3
|
||||||
|
year: 2020
|
||||||
|
s2020.e000001 - Mock Entry 20-3.nfo
|
||||||
|
NFO tags:
|
||||||
|
episodedetails:
|
||||||
|
aired: 2020-08-07
|
||||||
|
episode: 1
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
plot:
|
||||||
|
https://20-3.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
season: 2020
|
||||||
|
title: 2020-08-07 - Mock Entry 20-3
|
||||||
|
year: 2020
|
||||||
|
s2020.e000002 - Mock Entry 20-2.info.json
|
||||||
|
s2020.e000002 - Mock Entry 20-2.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2020-08-08
|
||||||
|
episode_id: 2
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: JMC
|
||||||
|
synopsis:
|
||||||
|
https://20-2.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2020-08-08 - Mock Entry 20-2
|
||||||
|
year: 2020
|
||||||
|
s2020.e000002 - Mock Entry 20-2.nfo
|
||||||
|
NFO tags:
|
||||||
|
episodedetails:
|
||||||
|
aired: 2020-08-08
|
||||||
|
episode: 2
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
plot:
|
||||||
|
https://20-2.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
season: 2020
|
||||||
|
title: 2020-08-08 - Mock Entry 20-2
|
||||||
|
year: 2020
|
||||||
|
s2020.e000003 - Mock Entry 20-1.info.json
|
||||||
|
s2020.e000003 - Mock Entry 20-1.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2020-08-08
|
||||||
|
episode_id: 3
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: JMC
|
||||||
|
synopsis:
|
||||||
|
https://20-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2020-08-08 - Mock Entry 20-1
|
||||||
|
year: 2020
|
||||||
|
s2020.e000003 - Mock Entry 20-1.nfo
|
||||||
|
NFO tags:
|
||||||
|
episodedetails:
|
||||||
|
aired: 2020-08-08
|
||||||
|
episode: 3
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
plot:
|
||||||
|
https://20-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
season: 2020
|
||||||
|
title: 2020-08-08 - Mock Entry 20-1
|
||||||
|
year: 2020
|
||||||
|
{output_directory}/Season 2021
|
||||||
|
s2021.e000004 - Mock Entry 21-1.info.json
|
||||||
|
s2021.e000004 - Mock Entry 21-1.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2021-08-08
|
||||||
|
episode_id: 4
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: JMC
|
||||||
|
synopsis:
|
||||||
|
https://21-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2021-08-08 - Mock Entry 21-1
|
||||||
|
year: 2021
|
||||||
|
s2021.e000004 - Mock Entry 21-1.nfo
|
||||||
|
NFO tags:
|
||||||
|
episodedetails:
|
||||||
|
aired: 2021-08-08
|
||||||
|
episode: 4
|
||||||
|
genre: ytdl-sub
|
||||||
|
mpaa: TV-14
|
||||||
|
plot:
|
||||||
|
https://21-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
season: 2021
|
||||||
|
title: 2021-08-08 - Mock Entry 21-1
|
||||||
|
year: 2021
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
Files created:
|
|
||||||
----------------------------------------
|
|
||||||
{output_directory}/JMC
|
|
||||||
Oblivion Mod "Falcor" p.1.info.json
|
|
||||||
Oblivion Mod "Falcor" p.1.jpg
|
|
||||||
Oblivion Mod "Falcor" p.1.mp4
|
|
||||||
Video Tags:
|
|
||||||
album: Music Videos
|
|
||||||
artist: JMC
|
|
||||||
genre: ytdl-sub
|
|
||||||
premiered: 2010-08-13
|
|
||||||
title: Oblivion Mod "Falcor" p.1
|
|
||||||
year: 2010
|
|
||||||
Embedded thumbnail
|
|
||||||
Oblivion Mod "Falcor" p.1.nfo
|
|
||||||
NFO tags:
|
|
||||||
musicvideo:
|
|
||||||
album: Music Videos
|
|
||||||
artist: JMC
|
|
||||||
genre: ytdl-sub
|
|
||||||
premiered: 2010-08-13
|
|
||||||
title: Oblivion Mod "Falcor" p.1
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
Files created:
|
|
||||||
----------------------------------------
|
|
||||||
{output_directory}/JMC
|
|
||||||
Oblivion Mod "Falcor" p.1.info.json
|
|
||||||
Oblivion Mod "Falcor" p.1.mp4
|
|
||||||
Video Tags:
|
|
||||||
album: Music Videos
|
|
||||||
artist: JMC
|
|
||||||
genre: ytdl-sub
|
|
||||||
premiered: 2010-08-13
|
|
||||||
title: Oblivion Mod "Falcor" p.1
|
|
||||||
year: 2010
|
|
||||||
Oblivion Mod "Falcor" p.1.nfo
|
|
||||||
NFO tags:
|
|
||||||
musicvideo:
|
|
||||||
album: Music Videos
|
|
||||||
artist: JMC
|
|
||||||
genre: ytdl-sub
|
|
||||||
premiered: 2010-08-13
|
|
||||||
title: Oblivion Mod "Falcor" p.1
|
|
||||||
|
|
@ -186,6 +186,7 @@ def preset_with_subscription_overrides_map(
|
||||||
"elem2",
|
"elem2",
|
||||||
"elem3",
|
"elem3",
|
||||||
],
|
],
|
||||||
|
"custom_map": {"custom_map_key": ["custom_map_list_value"]},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -290,6 +291,7 @@ def test_subscription_overrides_map(
|
||||||
"elem2",
|
"elem2",
|
||||||
"elem3",
|
"elem3",
|
||||||
],
|
],
|
||||||
|
"custom_map": {"custom_map_key": ["custom_map_list_value"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue