Add --dry-run summary (#92)

* tests updated with new class

* fix nfo tags, no tests only manual inspection

* txt

* dry-run output tested

* readability

* all tests now have files except rolling recent

* better

* almost fixed

* updated txt files w/indents

* files updated, looking good

* rolling recent dry-run test

* Print in main
This commit is contained in:
Jesse Bannon 2022-07-05 22:51:23 -07:00 committed by GitHub
parent cb31156b42
commit a0b46c0982
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1167 additions and 334 deletions

View file

@ -8,6 +8,7 @@ from typing import Dict
from typing import Generic
from typing import List
from typing import Optional
from typing import Tuple
from typing import Type
from typing import TypeVar
@ -18,6 +19,7 @@ from yt_dlp.utils import RejectedVideoReached
from ytdl_sub.config.preset_options import Overrides
from ytdl_sub.entries.base_entry import BaseEntry
from ytdl_sub.entries.entry import Entry
from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.utils.logger import Logger
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
from ytdl_sub.ytdl_additions.enhanced_download_archive import DownloadArchiver
@ -197,7 +199,7 @@ class Downloader(DownloadArchiver, Generic[DownloaderOptionsT, DownloaderEntryT]
return self._get_entry_dicts_from_info_json_files()
@abc.abstractmethod
def download(self) -> List[DownloaderEntryT]:
def download(self) -> List[DownloaderEntryT] | List[Tuple[DownloaderEntryT, FileMetadata]]:
"""The function to perform the download of all media entries"""
def post_download(self, overrides: Overrides):

View file

@ -1,6 +1,7 @@
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from ytdl_sub.downloaders.youtube.abc import YoutubeDownloader
from ytdl_sub.downloaders.youtube.playlist import YoutubePlaylistDownloaderOptions
@ -8,6 +9,7 @@ from ytdl_sub.entries.youtube import YoutubeVideo
from ytdl_sub.utils.chapters import Chapters
from ytdl_sub.utils.chapters import Timestamp
from ytdl_sub.utils.ffmpeg import add_ffmpeg_metadata
from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.validators.validators import BoolValidator
@ -100,7 +102,7 @@ class YoutubeMergePlaylistDownloader(
},
)
def _add_chapters(self, merged_video: YoutubeVideo) -> None:
def _get_chapters(self, merged_video: YoutubeVideo, add_chapters: bool) -> FileMetadata:
titles: List[str] = []
timestamps: List[Timestamp] = []
@ -111,14 +113,17 @@ class YoutubeMergePlaylistDownloader(
current_timestamp_sec += video_entry["duration"]
# TODO: return chapter metadata here
if not self.is_dry_run:
chapters = Chapters(timestamps=timestamps, titles=titles)
if not self.is_dry_run and add_chapters:
add_ffmpeg_metadata(
file_path=merged_video.get_download_file_path(),
chapters=Chapters(timestamps=timestamps, titles=titles),
chapters=chapters,
file_duration_sec=merged_video.kwargs("duration"),
)
return chapters.to_file_metadata(title="Timestamps of playlist videos in the merged file:")
def _to_merged_video(self, entry_dict: Dict) -> YoutubeVideo:
"""
Adds a few entries not included in a playlist entry to make it look like a merged video
@ -138,13 +143,12 @@ class YoutubeMergePlaylistDownloader(
)
return YoutubeVideo(entry_dict=entry_dict, working_directory=self.working_directory)
def download(self) -> List[YoutubeVideo]:
def download(self) -> List[Tuple[YoutubeVideo, FileMetadata]]:
"""Download a single Youtube video, then split it into multiple videos"""
merged_video = self._to_merged_video(
entry_dict=self.extract_info(url=self.download_options.playlist_url)
)
if self.download_options.add_chapters:
self._add_chapters(merged_video=merged_video)
return [merged_video]
merged_video_metadata = self._get_chapters(
merged_video=merged_video, add_chapters=self.download_options.add_chapters
)
return [(merged_video, merged_video_metadata)]

View file

@ -3,6 +3,7 @@ import re
from pathlib import Path
from typing import Dict
from typing import List
from typing import Tuple
from ytdl_sub.downloaders.youtube.abc import YoutubeDownloader
from ytdl_sub.downloaders.youtube.video import YoutubeVideoDownloaderOptions
@ -19,6 +20,7 @@ from ytdl_sub.utils.ffmpeg import FFMPEG
# 01:00:00 title
# where capture group 1 and 2 are the timestamp and title, respectively
from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.utils.thumbnail import convert_download_thumbnail
from ytdl_sub.validators.validators import StringValidator
@ -32,8 +34,8 @@ def _split_video_uid(source_uid: str, idx: int) -> str:
def _split_video_ffmpeg_cmd(
input_file: str, output_file: str, timestamps: List[Timestamp], idx: int
) -> List[str]:
timestamp_begin = timestamps[idx].timestamp_str
timestamp_end = timestamps[idx + 1].timestamp_str if idx + 1 < len(timestamps) else ""
timestamp_begin = timestamps[idx].standardized_str
timestamp_end = timestamps[idx + 1].standardized_str if idx + 1 < len(timestamps) else ""
cmd = ["-i", input_file, "-ss", timestamp_begin]
if timestamp_end:
@ -121,15 +123,15 @@ class YoutubeSplitVideoDownloader(
)
def _create_split_video_entry(
self, source_entry_dict: Dict, title: str, idx: int, split_video_count: int
) -> YoutubePlaylistVideo:
self, source_entry_dict: Dict, title: str, idx: int, chapters: Chapters
) -> Tuple[YoutubePlaylistVideo, FileMetadata]:
"""
Runs ffmpeg to create the split video
"""
entry_dict = copy.deepcopy(source_entry_dict)
entry_dict["title"] = title
entry_dict["playlist_index"] = idx + 1
entry_dict["playlist_count"] = split_video_count
entry_dict["playlist_count"] = len(chapters.timestamps)
entry_dict["id"] = _split_video_uid(source_uid=entry_dict["id"], idx=idx)
# Remove track and artist since its now split
@ -138,11 +140,20 @@ class YoutubeSplitVideoDownloader(
if "artist" in entry_dict:
del entry_dict["artist"]
return YoutubePlaylistVideo(entry_dict=entry_dict, working_directory=self.working_directory)
timestamp_begin = chapters.timestamps[idx].readable_str
timestamp_end = Timestamp(source_entry_dict["duration"]).readable_str
if idx + 1 < len(chapters.timestamps):
timestamp_end = chapters.timestamps[idx + 1].readable_str
def download(self) -> List[YoutubePlaylistVideo]:
metadata = FileMetadata(metadata=f"{timestamp_begin} - {timestamp_end}")
return (
YoutubePlaylistVideo(entry_dict=entry_dict, working_directory=self.working_directory),
metadata,
)
def download(self) -> List[Tuple[YoutubePlaylistVideo, FileMetadata]]:
"""Download a single Youtube video, then split it into multiple videos"""
split_videos: List[YoutubePlaylistVideo] = []
split_videos_and_metadata: List[Tuple[YoutubePlaylistVideo, FileMetadata]] = []
chapters = Chapters.from_file(chapters_file_path=self.download_options.split_timestamps)
entry_dict = self.extract_info(url=self.download_options.video_url)
@ -178,13 +189,10 @@ class YoutubeSplitVideoDownloader(
)
# Format the split video as a YoutubePlaylistVideo
split_videos.append(
split_videos_and_metadata.append(
self._create_split_video_entry(
source_entry_dict=entry_dict,
title=title,
idx=idx,
split_video_count=len(chapters.timestamps),
source_entry_dict=entry_dict, title=title, idx=idx, chapters=chapters
)
)
return split_videos
return split_videos_and_metadata

View file

@ -1,6 +1,7 @@
import argparse
import sys
from typing import List
from typing import Tuple
from ytdl_sub.cli.download_args_parser import DownloadArgsParser
from ytdl_sub.cli.main_args_parser import parser
@ -8,12 +9,15 @@ from ytdl_sub.config.config_file import ConfigFile
from ytdl_sub.config.preset import Preset
from ytdl_sub.subscriptions.subscription import Subscription
from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
from ytdl_sub.utils.logger import Logger
logger = Logger.get()
def _download_subscriptions_from_yaml_files(config: ConfigFile, args: argparse.Namespace) -> None:
def _download_subscriptions_from_yaml_files(
config: ConfigFile, args: argparse.Namespace
) -> List[Tuple[Subscription, FileHandlerTransactionLog]]:
"""
Downloads all subscriptions from one or many subscription yaml files.
@ -23,10 +27,16 @@ def _download_subscriptions_from_yaml_files(config: ConfigFile, args: argparse.N
Configuration file
args
Arguments from argparse
Returns
-------
List of (subscription, transaction_log)
"""
preset_paths: List[str] = args.subscription_paths
presets: List[Preset] = []
output: List[Tuple[Subscription, FileHandlerTransactionLog]] = []
# Load all of the presets first to perform all validation before downloading
for preset_path in preset_paths:
presets += Preset.from_file_path(config=config, subscription_path=preset_path)
@ -34,12 +44,16 @@ def _download_subscriptions_from_yaml_files(config: ConfigFile, args: argparse.N
subscription = Subscription.from_preset(preset=preset, config=config)
logger.info("Beginning subscription download for %s", subscription.name)
subscription.download(dry_run=args.dry_run)
transaction_log = subscription.download(dry_run=args.dry_run)
output.append((subscription, transaction_log))
return output
def _download_subscription_from_cli(
config: ConfigFile, args: argparse.Namespace, extra_args: List[str]
) -> None:
) -> Tuple[Subscription, FileHandlerTransactionLog]:
"""
Downloads a one-off subscription using the CLI
@ -51,6 +65,10 @@ def _download_subscription_from_cli(
Arguments from argparse
extra_args
Extra arguments from argparse that contain dynamic subscription options
Returns
-------
Subscription and its download transaction log
"""
dl_args_parser = DownloadArgsParser(
extra_arguments=extra_args, config_options=config.config_options
@ -69,7 +87,7 @@ def _download_subscription_from_cli(
config=config,
)
subscription.download(dry_run=args.dry_run)
return subscription, subscription.download(dry_run=args.dry_run)
def _main():
@ -86,14 +104,22 @@ def _main():
config: ConfigFile = ConfigFile.from_file_path(args.config).initialize()
Logger.set_log_level(log_level_name=args.log_level)
transaction_logs: List[Tuple[Subscription, FileHandlerTransactionLog]] = []
if args.subparser == "sub":
_download_subscriptions_from_yaml_files(config=config, args=args)
logger.info("Subscription download complete!")
transaction_logs = _download_subscriptions_from_yaml_files(config=config, args=args)
# One-off download
if args.subparser == "dl":
_download_subscription_from_cli(config=config, args=args, extra_args=extra_args)
logger.info("Download complete!")
transaction_logs.append(
_download_subscription_from_cli(config=config, args=args, extra_args=extra_args)
)
for subscription, transaction_log in transaction_logs:
logger.info(
"Downloads for %s:\n%s\n",
subscription.name,
transaction_log.to_output_message(subscription.output_directory),
)
# Ran successfully, so we can delete the debug file
Logger.cleanup(delete_debug_file=True)

View file

@ -56,7 +56,7 @@ class MusicTagsOptions(PluginOptions):
class MusicTagsPlugin(Plugin[MusicTagsOptions]):
plugin_options_type = MusicTagsOptions
def post_process_entry(self, entry: Entry):
def post_process_entry(self, entry: Entry) -> FileMetadata:
"""
Tags the entry's audio file using values defined in the metadata options
"""

View file

@ -80,7 +80,7 @@ class NfoTagsOptions(PluginOptions):
class NfoTagsPlugin(Plugin[NfoTagsOptions]):
plugin_options_type = NfoTagsOptions
def post_process_entry(self, entry: Entry):
def post_process_entry(self, entry: Entry) -> None:
"""
Creates an entry's NFO file using values defined in the metadata options
@ -115,7 +115,6 @@ class NfoTagsPlugin(Plugin[NfoTagsOptions]):
with open(nfo_file_path, "wb") as nfo_file:
nfo_file.write(xml)
# Archive the nfo's file name
self.save_file(file_name=nfo_file_name, entry=entry)
return FileMetadata.from_dict(value_dict={nfo_root: nfo}, title="NFO tags:")
# Save the nfo file and log its metadata
nfo_metadata = FileMetadata.from_dict(value_dict={nfo_root: nfo}, title="NFO tags:")
self.save_file(file_name=nfo_file_name, file_metadata=nfo_metadata, entry=entry)

View file

@ -106,5 +106,5 @@ class OutputDirectoryNfoTagsPlugin(Plugin[OutputDirectoryNfoTagsOptions]):
with open(nfo_file_path, "wb") as nfo_file:
nfo_file.write(xml)
self.save_file(file_name=nfo_file_name)
return FileMetadata.from_dict(value_dict={nfo_root: nfo}, title="NFO tags:")
nfo_metadata = FileMetadata.from_dict(value_dict={nfo_root: nfo}, title="NFO tags:")
self.save_file(file_name=nfo_file_name, file_metadata=nfo_metadata)

View file

@ -267,10 +267,14 @@ class Subscription:
)
for entry in downloader.download():
# TODO: Add entry metadata from the downloader.download function
entry_metadata = FileMetadata()
if isinstance(entry, tuple):
entry, entry_metadata = entry
for plugin in plugins:
entry_metadata.extend(plugin.post_process_entry(entry))
optional_plugin_entry_metadata = plugin.post_process_entry(entry)
if optional_plugin_entry_metadata:
entry_metadata.extend(optional_plugin_entry_metadata)
self._copy_entry_files_to_output_directory(
entry=entry, entry_metadata=entry_metadata

View file

@ -1,8 +1,11 @@
import os
import re
from typing import List
from typing import Optional
from typing import Tuple
from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.file_handler import FileMetadata
class Timestamp:
@ -48,12 +51,7 @@ class Timestamp:
return self._timestamp_sec
@property
def timestamp_str(self) -> str:
"""
Returns
-------
The timestamp in 'HH:MM:SS' format
"""
def _hours_minutes_seconds(self) -> Tuple[int, int, int]:
seconds = self.timestamp_sec
hours = int(seconds / 3600)
@ -62,6 +60,30 @@ class Timestamp:
minutes = int(seconds / 60)
seconds -= minutes * 60
return hours, minutes, seconds
@property
def readable_str(self) -> str:
"""
Returns
-------
The timestamp in '0:SS' format (min trim).
"""
hours, minutes, seconds = self._hours_minutes_seconds
if hours:
return f"{str(hours)}:{str(minutes).zfill(2)}:{str(seconds).zfill(2)}"
if minutes:
return f"{str(minutes)}:{str(seconds).zfill(2)}"
return f"0:{str(seconds).zfill(2)}"
@property
def standardized_str(self) -> str:
"""
Returns
-------
The timestamp in 'HH:MM:SS' format
"""
hours, minutes, seconds = self._hours_minutes_seconds
return f"{str(hours).zfill(2)}:{str(minutes).zfill(2)}:{str(seconds).zfill(2)}"
@classmethod
@ -125,6 +147,22 @@ class Chapters:
"""
return self.timestamps[0].timestamp_sec == 0
def to_file_metadata(self, title: Optional[str] = None) -> FileMetadata:
"""
Parameters
----------
title
Optional title
Returns
-------
Chapter metadata in the format of { readable_timestamp_str: title }
"""
return FileMetadata.from_dict(
value_dict={ts.readable_str: title for ts, title in zip(self.timestamps, self.titles)},
title=title,
)
@classmethod
def from_file(cls, chapters_file_path: str) -> "Chapters":
"""

View file

@ -14,8 +14,12 @@ class FileMetadata:
Stores pretty-printed information about a file. Each line in the metadata represents a newline
"""
def __init__(self, metadata: Optional[List[str]] = None):
self.metadata: List[str] = metadata if metadata else []
def __init__(self, metadata: Optional[Union[str, List[str]]] = None):
self.metadata = []
if isinstance(metadata, str):
self.metadata = [metadata]
elif isinstance(metadata, list):
self.metadata = metadata
def append(self, line: str) -> "FileMetadata":
"""
@ -54,11 +58,19 @@ class FileMetadata:
def _recursive_add_dict_lines(rdict: Dict, indent: int):
for key, value in sorted(rdict.items()):
if isinstance(value, Dict):
_recursive_add_dict_lines(rdict=value, indent=indent + 2)
_indent = " " * indent
lines.append(f"{_indent}{key}: {value}")
if isinstance(value, Dict):
lines.append(f"{_indent}{key}:")
_recursive_add_dict_lines(rdict=value, indent=indent + 2)
else:
value = str(value)
# If there are newlines in the value, print them indented
if "\n" in value:
lines.append(f"{_indent}{key}:")
for value_line in value.split("\n"):
lines.append(f" {_indent}{value_line.strip()}")
else:
lines.append(f"{_indent}{key}: {value}")
_recursive_add_dict_lines(rdict=value_dict, indent=2)
return cls(metadata=lines)
@ -103,6 +115,46 @@ class FileHandlerTransactionLog:
self.files_removed.add(file_name)
return self
def to_output_message(self, output_directory: str) -> str:
"""
Parameters
----------
output_directory
Path to the output directory. Included in the output message
Returns
-------
The output message to show users what was recorded in the transaction log
"""
lines: List[str] = []
def _indent_metadata_line(line: str) -> str:
# Do not indent empty lines
rstrip_line = line.rstrip()
return f" {rstrip_line}" if rstrip_line else ""
if self.files_created:
created_line = f"Files created in '{output_directory}'"
created_line_dash = "-" * 40
lines.extend([created_line, created_line_dash])
for file_path, file_metadata in sorted(self.files_created.items()):
lines.append(file_path)
if file_metadata:
lines.extend([_indent_metadata_line(line) for line in file_metadata.metadata])
if self.files_removed:
# Add a blank line to separate created/removed files
if self.files_created:
lines.append("")
removed_line = f"Files removed from '{output_directory}'"
removed_line_dash = "-" * 40
lines.extend([removed_line, removed_line_dash])
for file_path in sorted(self.files_removed):
lines.append(file_path)
return "\n".join(lines)
class FileHandler:
"""

View file

@ -618,7 +618,11 @@ class DownloadArchiver:
return self.__enhanced_download_archive.is_dry_run
def save_file(
self, file_name: str, output_file_name: Optional[str] = None, entry: Optional[Entry] = None
self,
file_name: str,
file_metadata: Optional[FileMetadata] = None,
output_file_name: Optional[str] = None,
entry: Optional[Entry] = None,
) -> None:
"""
Saves a file in the working directory to the output directory.
@ -627,6 +631,8 @@ class DownloadArchiver:
----------
file_name
Name of the file relative to the working directory
file_metadata
Optional. Metadata to record to the transaction log for this file
output_file_name
Optional. Final name of the file in the output directory (does not include output
directory path). If None, use the same working_directory file_name
@ -634,5 +640,8 @@ class DownloadArchiver:
Optional. Entry that the file belongs to
"""
self.__enhanced_download_archive.save_file_to_output_directory(
file_name=file_name, output_file_name=output_file_name, entry=entry
file_name=file_name,
file_metadata=file_metadata,
output_file_name=output_file_name,
entry=entry,
)

View file

@ -1,15 +1,22 @@
import hashlib
import os.path
from dataclasses import dataclass
from pathlib import Path
from typing import Dict
from typing import List
from typing import Optional
from typing import Union
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
from ytdl_sub.utils.file_handler import FileMetadata
class ExpectedDownload:
@dataclass
class ExpectedDownloadFile:
path: Path
md5: Optional[Union[str, List[str]]] = None
metadata: Optional[FileMetadata] = None
class ExpectedDownloads:
"""
To test ytdl-sub downloads work, we compare each downloaded file's md5 hash to an
expected md5 hash defined in this class.
@ -18,12 +25,15 @@ class ExpectedDownload:
try all the hashes (used in case the GitHub env produces different deterministic value).
"""
def __init__(self, expected_md5_file_hashes: Dict[Path, Optional[Union[str, List[str]]]]):
self.expected_md5_file_hashes = expected_md5_file_hashes
def __init__(self, expected_downloads: List[ExpectedDownloadFile]):
self.expected_downloads = expected_downloads
@property
def file_count(self) -> int:
return len(self.expected_md5_file_hashes)
return len(self.expected_downloads)
def contains(self, relative_path: Path) -> bool:
return sum(relative_path == download.path for download in self.expected_downloads) == 1
def assert_files_exist(self, relative_directory: Path):
"""
@ -35,38 +45,29 @@ class ExpectedDownload:
num_files += 1
relative_path = Path(*path.parts[3:])
assert (
relative_path in self.expected_md5_file_hashes
assert self.contains(
relative_path
), f"File {relative_path} was created but not expected"
assert num_files == self.file_count, "Mismatch in number of created files"
for relative_path, expected_md5_hash in self.expected_md5_file_hashes.items():
full_path = Path(relative_directory) / relative_path
for expected_download in self.expected_downloads:
full_path = Path(relative_directory) / expected_download.path
assert os.path.isfile(
full_path
), f"Expected {str(relative_path)} to be a file but it is not"
), f"Expected {str(expected_download.path)} to be a file but it is not"
if expected_md5_hash is None:
if expected_download.md5 is None:
continue
with open(full_path, "rb") as file:
md5_hash = hashlib.md5(file.read()).hexdigest()
if isinstance(expected_md5_hash, str):
expected_md5_hash = [expected_md5_hash]
expected_md5_hash = expected_download.md5
if isinstance(expected_download.md5, str):
expected_md5_hash = [expected_download.md5]
assert md5_hash in expected_md5_hash, (
f"MD5 hash for {str(relative_path)} does not match: "
f"MD5 hash for {str(expected_download.path)} does not match: "
f"{md5_hash} != {expected_md5_hash}"
)
def assert_dry_run_files_logged(self, transaction_log: FileHandlerTransactionLog):
assert (
len(transaction_log.files_created) == self.file_count
), "Mismatch in number of created files"
for relative_path in self.expected_md5_file_hashes.keys():
assert (
str(relative_path) in transaction_log.files_created
), f"Expected {str(relative_path)} to be a file but it is not"

View file

@ -0,0 +1,55 @@
from pathlib import Path
from typing import List
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
_TRANSACTION_LOG_SUMMARY_PATH = Path("tests/e2e/resources/transaction_log_summaries")
def assert_transaction_log_matches(
output_directory: str,
transaction_log: FileHandlerTransactionLog,
transaction_log_summary_file_name: str,
regenerate_transaction_log: bool = False,
):
"""
Parameters
----------
output_directory
Output directory the files are saved to
transaction_log
Transaction log to check
transaction_log_summary_file_name
Name if the transaction log summary to compare.
Lives in tests/e2e/resources/transaction_log_summaries
regenerate_transaction_log
USE WITH CAUTION - MANUALLY INSPECT CHANGED FILES TO ENSURE THEY LOOK GOOD!
Updates the file with the input transaction log. Should only be used to update
tests after an expected change is made.
"""
transaction_log_path = _TRANSACTION_LOG_SUMMARY_PATH / transaction_log_summary_file_name
summary = transaction_log.to_output_message(output_directory=output_directory)
# Write the expected summary file if regenerate is True
if regenerate_transaction_log:
with open(transaction_log_path, "w", encoding="utf-8") as summary_file:
summary_file.write(
transaction_log.to_output_message(output_directory="{output_directory}")
)
# Read the expected summary file
with open(transaction_log_path, "r", encoding="utf-8") as summary_file:
expected_summary = summary_file.read().format(output_directory=output_directory)
# Split, ensure there are the same number of new lines
summary_lines: List[str] = summary.split("\n")
expected_summary_lines: List[str] = expected_summary.split("\n")
assert len(summary_lines) == len(
expected_summary_lines
), f"Summary number of lines differ: {len(summary_lines) != len(expected_summary_lines)}"
# Ensure each line equals
for idx in range(len(summary_lines)):
line = summary_lines[idx]
expected_line = expected_summary_lines[idx]
assert line == expected_line, f"Summary line {idx} differs: '{line}' != '{expected_line}'"

View file

@ -0,0 +1,123 @@
Files created in '{output_directory}'
----------------------------------------
.ytdl-sub-jb-download-archive.json
j_b/[2021] Baby Santana's Dorian Groove/01 - Baby Santana's Dorian Groove.mp3
Music Tags:
album: Baby Santana's Dorian Groove
albumartist: j_b
artist: j_b
genre: Unset
title: Baby Santana's Dorian Groove
track: 1
year: 2021
j_b/[2021] Baby Santana's Dorian Groove/folder.jpg
j_b/[2021] Purple Clouds/01 - Purple Clouds.mp3
Music Tags:
album: Purple Clouds
albumartist: j_b
artist: j_b
genre: Unset
title: Purple Clouds
track: 1
year: 2021
j_b/[2021] Purple Clouds/folder.jpg
j_b/[2022] Acoustic Treats/01 - 20160426 184214.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: 20160426 184214
track: 1
year: 2022
j_b/[2022] Acoustic Treats/02 - 20160502 123150.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: 20160502 123150
track: 2
year: 2022
j_b/[2022] Acoustic Treats/03 - 20160504 143832.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: 20160504 143832
track: 3
year: 2022
j_b/[2022] Acoustic Treats/04 - 20160601 221234.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: 20160601 221234
track: 4
year: 2022
j_b/[2022] Acoustic Treats/05 - 20160601 222440.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: 20160601 222440
track: 5
year: 2022
j_b/[2022] Acoustic Treats/06 - 20170604 190236.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: 20170604 190236
track: 6
year: 2022
j_b/[2022] Acoustic Treats/07 - 20170612 193646.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: 20170612 193646
track: 7
year: 2022
j_b/[2022] Acoustic Treats/08 - 20170628 215206.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: 20170628 215206
track: 8
year: 2022
j_b/[2022] Acoustic Treats/09 - Finding Home.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: Finding Home
track: 9
year: 2022
j_b/[2022] Acoustic Treats/10 - Shallow Water WIP.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: Shallow Water WIP
track: 10
year: 2022
j_b/[2022] Acoustic Treats/11 - Untold History.mp3
Music Tags:
album: Acoustic Treats
albumartist: j_b
artist: j_b
genre: Unset
title: Untold History
track: 11
year: 2022
j_b/[2022] Acoustic Treats/folder.jpg

View file

@ -0,0 +1,273 @@
Files created in '{output_directory}'
----------------------------------------
.ytdl-sub-pz-download-archive.json
Season 2010/s2010.e0813 - Oblivion Mod 'Falcor' p.1-thumb.jpg
Season 2010/s2010.e0813 - Oblivion Mod 'Falcor' p.1.mp4
Season 2010/s2010.e0813 - Oblivion Mod 'Falcor' p.1.nfo
NFO tags:
episodedetails:
aired: 2010-08-13
episode: 813
plot:
PLEASE WATCH IN HD!
This is the castle in the mod "Falcor" which we are currently working on.
You can find some more information about the mod @ http://www.oblivionfalcormod.webs.com/
This is the first time I've really used the editor, and the beginning of our first mod. Hope you enjoy.
season: 2010
title: Oblivion Mod "Falcor" p.1
year: 2010
Season 2010/s2010.e1202 - Oblivion Mod 'Falcor' p.2-thumb.jpg
Season 2010/s2010.e1202 - Oblivion Mod 'Falcor' p.2.mp4
Season 2010/s2010.e1202 - Oblivion Mod 'Falcor' p.2.nfo
NFO tags:
episodedetails:
aired: 2010-12-02
episode: 1202
plot:
PLEASE WATCH IN HD.
Download Link:
http://www.tesnexus.com/downloads/file.php?id=36306
Please keep in mind that the mod is not complete, so you'll see some blank areas during the video. I haven't made LOD (Level of Distance) yet either, so areas far away will not appear.
season: 2010
title: Oblivion Mod "Falcor" p.2
year: 2010
Season 2011/s2011.e0201 - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg
Season 2011/s2011.e0201 - Jesse's Minecraft Server [Trailer - Feb.1].mp4
Season 2011/s2011.e0201 - Jesse's Minecraft Server [Trailer - Feb.1].nfo
NFO tags:
episodedetails:
aired: 2011-02-01
episode: 201
plot:
To join the server, you must apply at:
http://www.jesseminecraft.webs.com/
This is just a brief video of the server as of Feb. 1, 2011.
Texture Pack I Use:
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
season: 2011
title: Jesse's Minecraft Server [Trailer - Feb.1]
year: 2011
Season 2011/s2011.e0227 - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg
Season 2011/s2011.e0227 - Jesse's Minecraft Server [Trailer - Feb.27].mp4
Season 2011/s2011.e0227 - Jesse's Minecraft Server [Trailer - Feb.27].nfo
NFO tags:
episodedetails:
aired: 2011-02-27
episode: 227
plot:
Website Link:
http://jesseminecraft.webs.com/
All you have to do is read the rules, and fill out a quick, little application to join the Server so we know you read them. We do this to keep the griefers/newbs out, it only takes three minutes, it's not that big of a deal.
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 750 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
There are over 200 empty properties that are ready for anyone to own! Join now!
This is the server state as of Feb. 27, 2011.
----------------------------------------------------------------------------------
Texture Pack:
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
Recording Software:
http://www.fraps.com/download.php
Video Editing Software:
http://explore.live.com/windows-live-movie-maker?os=other
Song:
Given to Fly - Pearl Jam
(Off of the 'Yield' album)
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
season: 2011
title: Jesse's Minecraft Server [Trailer - Feb.27]
year: 2011
Season 2011/s2011.e0321 - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg
Season 2011/s2011.e0321 - Jesse's Minecraft Server [Trailer - Mar.21].mp4
Season 2011/s2011.e0321 - Jesse's Minecraft Server [Trailer - Mar.21].nfo
NFO tags:
episodedetails:
aired: 2011-03-21
episode: 321
plot:
Website Link:
http://jesseminecraft.webs.com/
To get on the whitelist, please look at the website linked above (^^^). Due to the overwhelming amount of people trying to join, I've made it so it costs $2 to become a member through paypal. All of it is explained on the website.
Jesse's Minecraft Server is a City Roleplay/Survival Server, there is a currency, public transportation, strict rules for immersive gameplay, and has a decent size population. Over 1000 people have logged onto the server. It is very important you read the rules, we don't tolerate idiots.
There are over 300 empty properties that are ready for anyone to own! Join now!
This is the server state as of Mar. 21, 2011.
--------------------------------------------------------------------------------­--
Texture Pack:
http://www.minecraftforum.net/viewtopic.php?f=25&t=29164
Recording Software:
http://www.fraps.com/download.php
Video Editing Software:
http://explore.live.com/windows-live-movie-maker?os=other
Song:
Indifference - Pearl Jam
(Off of the 'Vs.' album)
I claim no ownership of this song, all the credit goes to Pearl Jam and their producers.
season: 2011
title: Jesse's Minecraft Server [Trailer - Mar.21]
year: 2011
Season 2011/s2011.e0529 - Project Zombie _Official Trailer_ (IP - mc.projectzombie.beastnode.net)-thumb.jpg
Season 2011/s2011.e0529 - Project Zombie _Official Trailer_ (IP - mc.projectzombie.beastnode.net).mp4
Season 2011/s2011.e0529 - Project Zombie _Official Trailer_ (IP - mc.projectzombie.beastnode.net).nfo
NFO tags:
episodedetails:
aired: 2011-05-29
episode: 529
plot:
Currently in alpha testing.
http://www.projectzombie.net/
Credits:
-Master_Queef (Jesse)
-Cheeseducksg
-Vsalaka
-Edwingsd
-All Members on my Roleplay Server
-All Mercenaries in Project Zombie
Song:
Johnny Cash - God's Gonna Cut You Down
Texture Pack:
http://www.minecraftforum.net/topic/26727-johnsmith-texture-pack-v73-32x32-16x-with-customizer/
season: 2011
title: Project Zombie |Official Trailer| (IP: mc.projectzombie.beastnode.net)
year: 2011
Season 2011/s2011.e0630 - Project Zombie _Fin-thumb.jpg
Season 2011/s2011.e0630 - Project Zombie _Fin.mp4
Season 2011/s2011.e0630 - Project Zombie _Fin.nfo
NFO tags:
episodedetails:
aired: 2011-06-30
episode: 630
plot:
Twas a good run while it lasted. If you're still interested in playing PZ, you can join my Roleplay Server, yes, Project Zombie is completely imported onto that one:
http://www.jesseminecraft.webs.com/
season: 2011
title: Project Zombie |Fin|
year: 2011
Season 2011/s2011.e1121 - Skyrim 'Ultra HD w_Mods' [PC]-thumb.jpg
Season 2011/s2011.e1121 - Skyrim 'Ultra HD w_Mods' [PC].mp4
Season 2011/s2011.e1121 - Skyrim 'Ultra HD w_Mods' [PC].nfo
NFO tags:
episodedetails:
aired: 2011-11-21
episode: 1121
plot:
Mods I have used:
http://skyrimnexus.com/downloads/file.php?id=329
http://skyrimnexus.com/downloads/file.php?id=607
http://skyrimnexus.com/downloads/file.php?id=711
http://skyrimnexus.com/downloads/file.php?id=302
http://skyrimnexus.com/downloads/file.php?id=141
http://skyrimnexus.com/downloads/file.php?id=131
http://skyrimnexus.com/downloads/file.php?id=85
http://skyrimnexus.com/downloads/file.php?id=191
season: 2011
title: Skyrim 'Ultra HD w/Mods' [PC]
year: 2011
Season 2012/s2012.e0123 - Project Zombie _Map Trailer-thumb.jpg
Season 2012/s2012.e0123 - Project Zombie _Map Trailer.mp4
Season 2012/s2012.e0123 - Project Zombie _Map Trailer.nfo
NFO tags:
episodedetails:
aired: 2012-01-23
episode: 123
plot:
Faction Server IP: 72.9.157.88:25652
Project Zombie IP: mc.projectzombie.beastnode.net
Project Zombie Map: http://tinyurl.com/projectzombie1
Website: http://jesseminecraft.webs.com/
Map Download: http://www.planetminecraft.com/project/project-zombie-511865/
A few little mistakes, but awh well, the dynamic shadows look boss.
And the song is Comfortably Numb by Pink Floyd. If you didn't know that after the first 10 seconds of listening to it then you may need some help.
season: 2012
title: Project Zombie |Map Trailer|
year: 2012
Season 2013/s2013.e0719 - Project Zombie Rewind _Trailer-thumb.jpg
Season 2013/s2013.e0719 - Project Zombie Rewind _Trailer.mp4
Season 2013/s2013.e0719 - Project Zombie Rewind _Trailer.nfo
NFO tags:
episodedetails:
aired: 2013-07-19
episode: 719
plot:
IP: 172.245.24.118
http://www.projectzombie.net/
With almost a year of downtime, Project Zombie rises from its ashes. Rewind back to May 2011 and immerse yourself in the original Project Zombie with expansions. Open July 20th, 2013.
season: 2013
title: Project Zombie Rewind |Trailer|
year: 2013
Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer-thumb.jpg
Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.mp4
Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.nfo
NFO tags:
episodedetails:
aired: 2018-10-29
episode: 1029
plot:
See the full trailer here: https://youtu.be/LN2e6idGluI
Discord: https://discord.gg/BQN7SSe
Website: https://www.projectzombie.net/
It has been about six years since the glory days of the original RP server. Hope to see many familiar faces in the revival of our great server.
Credits:
ALISON - Space Echo
https://www.youtube.com/watch?v=lPldcjlv3_Y
season: 2018
title: Jesse's Minecraft Server | Teaser Trailer
year: 2018
Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id-thumb.jpg
Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.mp4
Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.nfo
NFO tags:
episodedetails:
aired: 2018-11-02
episode: 1102
plot:
IP: mc.jesse.id
Live Map: http://mc.jesse.id:8123
Discord: https://discord.gg/BQN7SSe
Website: https://www.projectzombie.net/
It has been about six years since the glory days of the original RP server. Hope to see many familiar faces in the revival of our great server.
The MC version is 1.13.x AND/OR 1.12.x
Credits:
ALISON - Space Echo
https://www.youtube.com/watch?v=lPldcjlv3_Y
season: 2018
title: Jesse's Minecraft Server | IP mc.jesse.id
year: 2018
fanart.jpg
poster.jpg
tvshow.nfo
NFO tags:
tvshow:
title: Project / Zombie

View file

@ -0,0 +1,9 @@
Files created in '{output_directory}'
----------------------------------------
.ytdl-sub-pz-download-archive.json
fanart.jpg
poster.jpg
tvshow.nfo
NFO tags:
tvshow:
title: Project / Zombie

View file

@ -0,0 +1,52 @@
Files created in '{output_directory}'
----------------------------------------
.ytdl-sub-pz-download-archive.json
Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer-thumb.jpg
Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.mp4
Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.nfo
NFO tags:
episodedetails:
aired: 2018-10-29
episode: 1029
plot:
See the full trailer here: https://youtu.be/LN2e6idGluI
Discord: https://discord.gg/BQN7SSe
Website: https://www.projectzombie.net/
It has been about six years since the glory days of the original RP server. Hope to see many familiar faces in the revival of our great server.
Credits:
ALISON - Space Echo
https://www.youtube.com/watch?v=lPldcjlv3_Y
season: 2018
title: Jesse's Minecraft Server | Teaser Trailer
year: 2018
Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id-thumb.jpg
Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.mp4
Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.nfo
NFO tags:
episodedetails:
aired: 2018-11-02
episode: 1102
plot:
IP: mc.jesse.id
Live Map: http://mc.jesse.id:8123
Discord: https://discord.gg/BQN7SSe
Website: https://www.projectzombie.net/
It has been about six years since the glory days of the original RP server. Hope to see many familiar faces in the revival of our great server.
The MC version is 1.13.x AND/OR 1.12.x
Credits:
ALISON - Space Echo
https://www.youtube.com/watch?v=lPldcjlv3_Y
season: 2018
title: Jesse's Minecraft Server | IP mc.jesse.id
year: 2018
fanart.jpg
poster.jpg
tvshow.nfo
NFO tags:
tvshow:
title: Project / Zombie

View file

@ -0,0 +1,15 @@
Files created in '{output_directory}'
----------------------------------------
.ytdl-sub-pz-download-archive.json
fanart.jpg
poster.jpg
tvshow.nfo
NFO tags:
tvshow:
title: Project / Zombie
Files removed from '{output_directory}'
----------------------------------------
Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer-thumb.jpg
Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.mp4
Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.nfo

View file

@ -0,0 +1,15 @@
Files created in '{output_directory}'
----------------------------------------
JMC - Jesse's Minecraft Server-thumb.jpg
JMC - Jesse's Minecraft Server.mkv
Timestamps of playlist videos in the merged file:
0:00: Jesse's Minecraft Server [Trailer - Feb.1]
5:23: Jesse's Minecraft Server [Trailer - Feb.27]
9:23: Jesse's Minecraft Server [Trailer - Mar.21]
JMC - Jesse's Minecraft Server.nfo
NFO tags:
musicvideo:
album: Music Videos
artist: JMC
title: Jesse's Minecraft Server
year: 2011

View file

@ -0,0 +1,30 @@
Files created in '{output_directory}'
----------------------------------------
.ytdl-sub-jmc-download-archive.json
JMC - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg
JMC - Jesse's Minecraft Server [Trailer - Feb.1].mp4
JMC - Jesse's Minecraft Server [Trailer - Feb.1].nfo
NFO tags:
musicvideo:
album: Music Videos
artist: JMC
title: Jesse's Minecraft Server [Trailer - Feb.1]
year: 2011
JMC - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg
JMC - Jesse's Minecraft Server [Trailer - Feb.27].mp4
JMC - Jesse's Minecraft Server [Trailer - Feb.27].nfo
NFO tags:
musicvideo:
album: Music Videos
artist: JMC
title: Jesse's Minecraft Server [Trailer - Feb.27]
year: 2011
JMC - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg
JMC - Jesse's Minecraft Server [Trailer - Mar.21].mp4
JMC - Jesse's Minecraft Server [Trailer - Mar.21].nfo
NFO tags:
musicvideo:
album: Music Videos
artist: JMC
title: Jesse's Minecraft Server [Trailer - Mar.21]
year: 2011

View file

@ -0,0 +1,62 @@
Files created in '{output_directory}'
----------------------------------------
Project Zombie - 1-6.Intro.mp4
0:00 - 0:10
Project Zombie - 2-6.Part 1.mp4
0:10 - 0:20
Project Zombie - 3-6.Part 2.mp4
0:20 - 0:30
Project Zombie - 4-6.Part 3.mp4
0:30 - 0:40
Project Zombie - 5-6.Part 4.mp4
0:40 - 1:01
Project Zombie - 6-6.Part 5.mp4
1:01 - 1:28
Project Zombie - Intro-thumb.jpg
Project Zombie - Intro.nfo
NFO tags:
musicvideo:
album: Music Videos
artist: Project Zombie
title: Intro
year: 2010
Project Zombie - Part 1-thumb.jpg
Project Zombie - Part 1.nfo
NFO tags:
musicvideo:
album: Music Videos
artist: Project Zombie
title: Part 1
year: 2010
Project Zombie - Part 2-thumb.jpg
Project Zombie - Part 2.nfo
NFO tags:
musicvideo:
album: Music Videos
artist: Project Zombie
title: Part 2
year: 2010
Project Zombie - Part 3-thumb.jpg
Project Zombie - Part 3.nfo
NFO tags:
musicvideo:
album: Music Videos
artist: Project Zombie
title: Part 3
year: 2010
Project Zombie - Part 4-thumb.jpg
Project Zombie - Part 4.nfo
NFO tags:
musicvideo:
album: Music Videos
artist: Project Zombie
title: Part 4
year: 2010
Project Zombie - Part 5-thumb.jpg
Project Zombie - Part 5.nfo
NFO tags:
musicvideo:
album: Music Videos
artist: Project Zombie
title: Part 5
year: 2010

View file

@ -0,0 +1,11 @@
Files created in '{output_directory}'
----------------------------------------
JMC - Oblivion Mod 'Falcor' p.1-thumb.jpg
JMC - Oblivion Mod 'Falcor' p.1.mp4
JMC - Oblivion Mod 'Falcor' p.1.nfo
NFO tags:
musicvideo:
album: Music Videos
artist: JMC
title: Oblivion Mod "Falcor" p.1
year: 2010

View file

@ -1,7 +1,9 @@
from pathlib import Path
import pytest
from e2e.expected_download import ExpectedDownload
from e2e.expected_download import ExpectedDownloadFile
from e2e.expected_download import ExpectedDownloads
from e2e.expected_transaction_log import assert_transaction_log_matches
from ytdl_sub.config.config_file import ConfigFile
from ytdl_sub.config.preset import Preset
@ -56,34 +58,32 @@ def discography_subscription(config, subscription_name, subscription_dict):
def expected_discography_download():
# turn off black formatter here for readability
# fmt: off
return ExpectedDownload(
expected_md5_file_hashes={
return ExpectedDownloads(
expected_downloads=[
# Download mapping
Path(".ytdl-sub-jb-download-archive.json"): "1a99156e9ece62539fb2608416a07200",
ExpectedDownloadFile(path=Path(".ytdl-sub-jb-download-archive.json"), md5="1a99156e9ece62539fb2608416a07200"),
# Entry files (singles)
Path("j_b/[2021] Baby Santana's Dorian Groove/01 - Baby Santana's Dorian Groove.mp3"): "bffbd558e12c6a9e029dc136a88342c4",
Path("j_b/[2021] Baby Santana's Dorian Groove/folder.jpg"): "967892be44b8c47e1be73f055a7c6f08",
ExpectedDownloadFile(path=Path("j_b/[2021] Baby Santana's Dorian Groove/01 - Baby Santana's Dorian Groove.mp3"), md5="bffbd558e12c6a9e029dc136a88342c4"),
ExpectedDownloadFile(path=Path("j_b/[2021] Baby Santana's Dorian Groove/folder.jpg"), md5="967892be44b8c47e1be73f055a7c6f08"),
Path("j_b/[2021] Purple Clouds/01 - Purple Clouds.mp3"): "038db58aebe2ba875b733932b42a94d6",
Path("j_b/[2021] Purple Clouds/folder.jpg"): "967892be44b8c47e1be73f055a7c6f08",
ExpectedDownloadFile(path=Path("j_b/[2021] Purple Clouds/01 - Purple Clouds.mp3"), md5="038db58aebe2ba875b733932b42a94d6"),
ExpectedDownloadFile(path=Path("j_b/[2021] Purple Clouds/folder.jpg"), md5="967892be44b8c47e1be73f055a7c6f08"),
# Entry files (albums)
Path("j_b/[2022] Acoustic Treats/01 - 20160426 184214.mp3"): "e145f0a2f6012768280c38655ca58065",
Path("j_b/[2022] Acoustic Treats/02 - 20160502 123150.mp3"): "60c8b8817a197a13e4bb90903af612c5",
Path("j_b/[2022] Acoustic Treats/03 - 20160504 143832.mp3"): "8265b7e4f79878af877bc6ecd9757efe",
Path("j_b/[2022] Acoustic Treats/04 - 20160601 221234.mp3"): "accf46b76891d2954b893d0f91d82816",
Path("j_b/[2022] Acoustic Treats/05 - 20160601 222440.mp3"): "e1f584f523336160d5c1104a61de77f3",
Path("j_b/[2022] Acoustic Treats/06 - 20170604 190236.mp3"): "f6885b25901177f0357649afe97328cc",
Path("j_b/[2022] Acoustic Treats/07 - 20170612 193646.mp3"): "fa057f221cbe4cf2442cd2fdb960743e",
Path("j_b/[2022] Acoustic Treats/08 - 20170628 215206.mp3"): "7794ae812c64580e2ac8fc457d5cc85f",
Path("j_b/[2022] Acoustic Treats/09 - Finding Home.mp3"): "adbf02eddb2090c008eb497d13ff84b9",
Path("j_b/[2022] Acoustic Treats/10 - Shallow Water WIP.mp3"): "65bb10c84366c71498161734f953e93d",
Path("j_b/[2022] Acoustic Treats/11 - Untold History.mp3"): "6904b2918e5dc38d9a9f72d967eb74bf",
Path("j_b/[2022] Acoustic Treats/folder.jpg"): "967892be44b8c47e1be73f055a7c6f08",
}
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/01 - 20160426 184214.mp3"), md5="e145f0a2f6012768280c38655ca58065"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/02 - 20160502 123150.mp3"), md5="60c8b8817a197a13e4bb90903af612c5"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/03 - 20160504 143832.mp3"), md5="8265b7e4f79878af877bc6ecd9757efe"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/04 - 20160601 221234.mp3"), md5="accf46b76891d2954b893d0f91d82816"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/05 - 20160601 222440.mp3"), md5="e1f584f523336160d5c1104a61de77f3"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/06 - 20170604 190236.mp3"), md5="f6885b25901177f0357649afe97328cc"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/07 - 20170612 193646.mp3"), md5="fa057f221cbe4cf2442cd2fdb960743e"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/08 - 20170628 215206.mp3"), md5="7794ae812c64580e2ac8fc457d5cc85f"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/09 - Finding Home.mp3"), md5="adbf02eddb2090c008eb497d13ff84b9"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/10 - Shallow Water WIP.mp3"), md5="65bb10c84366c71498161734f953e93d"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/11 - Untold History.mp3"), md5="6904b2918e5dc38d9a9f72d967eb74bf"),
ExpectedDownloadFile(path=Path("j_b/[2022] Acoustic Treats/folder.jpg"), md5="967892be44b8c47e1be73f055a7c6f08"),
]
)
# fmt: on
@ -94,14 +94,15 @@ class TestSoundcloudDiscography:
expected md5 file hashes.
"""
@pytest.mark.parametrize("dry_run", [True, False])
def test_discography_download(
self, discography_subscription, expected_discography_download, output_directory
self, discography_subscription, expected_discography_download, output_directory, dry_run
):
discography_subscription.download()
expected_discography_download.assert_files_exist(relative_directory=output_directory)
def test_discography_dry_run(
self, discography_subscription, expected_discography_download, output_directory
):
transaction_log = discography_subscription.download(dry_run=True)
expected_discography_download.assert_dry_run_files_logged(transaction_log=transaction_log)
transaction_log = discography_subscription.download(dry_run=dry_run)
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="soundcloud/test_soundcloud_discography.txt",
)
if not dry_run:
expected_discography_download.assert_files_exist(relative_directory=output_directory)

View file

@ -3,7 +3,9 @@ from pathlib import Path
import mergedeep
import pytest
from conftest import assert_debug_log
from e2e.expected_download import ExpectedDownload
from e2e.expected_download import ExpectedDownloadFile
from e2e.expected_download import ExpectedDownloads
from e2e.expected_transaction_log import assert_transaction_log_matches
import ytdl_sub.downloaders.downloader
from ytdl_sub.config.config_file import ConfigFile
@ -65,65 +67,65 @@ def full_channel_subscription(config, subscription_name, subscription_dict):
def expected_full_channel_download():
# turn off black formatter here for readability
# fmt: off
return ExpectedDownload(
expected_md5_file_hashes={
return ExpectedDownloads(
expected_downloads=[
# Download mapping
Path(".ytdl-sub-pz-download-archive.json"): "dd3c6236a107a665b884f701b8d14d4d",
ExpectedDownloadFile(path=Path(".ytdl-sub-pz-download-archive.json"), md5="dd3c6236a107a665b884f701b8d14d4d"),
# Output directory files
Path("fanart.jpg"): "c16b8b88a82cbd47d217ee80f6a8b5f3",
Path("poster.jpg"): "e92872ff94c96ad49e9579501c791578",
Path("tvshow.nfo"): "83c7db96081ac5bdf289fcf396bec157",
ExpectedDownloadFile(path=Path("fanart.jpg"), md5="c16b8b88a82cbd47d217ee80f6a8b5f3"),
ExpectedDownloadFile(path=Path("poster.jpg"), md5="e92872ff94c96ad49e9579501c791578"),
ExpectedDownloadFile(path=Path("tvshow.nfo"), md5="83c7db96081ac5bdf289fcf396bec157"),
# Entry files
Path("Season 2010/s2010.e0813 - Oblivion Mod 'Falcor' p.1-thumb.jpg"): "fb95b510681676e81c321171fc23143e",
Path("Season 2010/s2010.e0813 - Oblivion Mod 'Falcor' p.1.mp4"): "931a705864c57d21d6fedebed4af6bbc",
Path("Season 2010/s2010.e0813 - Oblivion Mod 'Falcor' p.1.nfo"): "67d8d71d048039080acbba3bce4febaa",
ExpectedDownloadFile(path=Path("Season 2010/s2010.e0813 - Oblivion Mod 'Falcor' p.1-thumb.jpg"), md5="fb95b510681676e81c321171fc23143e"),
ExpectedDownloadFile(path=Path("Season 2010/s2010.e0813 - Oblivion Mod 'Falcor' p.1.mp4"), md5="931a705864c57d21d6fedebed4af6bbc"),
ExpectedDownloadFile(path=Path("Season 2010/s2010.e0813 - Oblivion Mod 'Falcor' p.1.nfo"), md5="67d8d71d048039080acbba3bce4febaa"),
Path("Season 2010/s2010.e1202 - Oblivion Mod 'Falcor' p.2-thumb.jpg"): "8b32ee9c037fa669e444a0ac181525a1",
Path("Season 2010/s2010.e1202 - Oblivion Mod 'Falcor' p.2.mp4"): "d3469b4dca7139cb3dbc38712b6796bf",
Path("Season 2010/s2010.e1202 - Oblivion Mod 'Falcor' p.2.nfo"): "d81f49cedbd7edaee987521e89b37904",
ExpectedDownloadFile(path=Path("Season 2010/s2010.e1202 - Oblivion Mod 'Falcor' p.2-thumb.jpg"), md5="8b32ee9c037fa669e444a0ac181525a1"),
ExpectedDownloadFile(path=Path("Season 2010/s2010.e1202 - Oblivion Mod 'Falcor' p.2.mp4"), md5="d3469b4dca7139cb3dbc38712b6796bf"),
ExpectedDownloadFile(path=Path("Season 2010/s2010.e1202 - Oblivion Mod 'Falcor' p.2.nfo"), md5="d81f49cedbd7edaee987521e89b37904"),
Path("Season 2011/s2011.e0201 - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg"): "b232d253df621aa770b780c1301d364d",
Path("Season 2011/s2011.e0201 - Jesse's Minecraft Server [Trailer - Feb.1].mp4"): "e66287b9832277b6a4d1554e29d9fdcc",
Path("Season 2011/s2011.e0201 - Jesse's Minecraft Server [Trailer - Feb.1].nfo"): "f7c0de89038f8c491bded8a3968720a2",
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0201 - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg"), md5="b232d253df621aa770b780c1301d364d"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0201 - Jesse's Minecraft Server [Trailer - Feb.1].mp4"), md5="e66287b9832277b6a4d1554e29d9fdcc"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0201 - Jesse's Minecraft Server [Trailer - Feb.1].nfo"), md5="f7c0de89038f8c491bded8a3968720a2"),
Path("Season 2011/s2011.e0227 - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg"): "d17c379ea8b362f5b97c6b213b0342cb",
Path("Season 2011/s2011.e0227 - Jesse's Minecraft Server [Trailer - Feb.27].mp4"): "04ab5cb3cc12325d0c96a7cd04a8b91d",
Path("Season 2011/s2011.e0227 - Jesse's Minecraft Server [Trailer - Feb.27].nfo"): "ee1eda78fa0980bc703e602b5012dd1f",
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0227 - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg"), md5="d17c379ea8b362f5b97c6b213b0342cb"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0227 - Jesse's Minecraft Server [Trailer - Feb.27].mp4"), md5="04ab5cb3cc12325d0c96a7cd04a8b91d"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0227 - Jesse's Minecraft Server [Trailer - Feb.27].nfo"), md5="ee1eda78fa0980bc703e602b5012dd1f"),
Path("Season 2011/s2011.e0321 - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg"): "e7830aa8a64b0cde65ba3f7e5fc56530",
Path("Season 2011/s2011.e0321 - Jesse's Minecraft Server [Trailer - Mar.21].mp4"): "025de6099a5c98e6397153c7a62d517d",
Path("Season 2011/s2011.e0321 - Jesse's Minecraft Server [Trailer - Mar.21].nfo"): "61eb6369430da0ab6134d78829a7621b",
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0321 - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg"), md5="e7830aa8a64b0cde65ba3f7e5fc56530"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0321 - Jesse's Minecraft Server [Trailer - Mar.21].mp4"), md5="025de6099a5c98e6397153c7a62d517d"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0321 - Jesse's Minecraft Server [Trailer - Mar.21].nfo"), md5="61eb6369430da0ab6134d78829a7621b"),
Path("Season 2011/s2011.e0529 - Project Zombie _Official Trailer_ (IP - mc.projectzombie.beastnode.net)-thumb.jpg"): "c956192a379b3661595c9920972d4819",
Path("Season 2011/s2011.e0529 - Project Zombie _Official Trailer_ (IP - mc.projectzombie.beastnode.net).mp4"): "3d9c19835b03355d6fd5d00cd59dbe5b",
Path("Season 2011/s2011.e0529 - Project Zombie _Official Trailer_ (IP - mc.projectzombie.beastnode.net).nfo"): "60f72b99f5c69f9e03a071a12160928f",
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0529 - Project Zombie _Official Trailer_ (IP - mc.projectzombie.beastnode.net)-thumb.jpg"), md5="c956192a379b3661595c9920972d4819"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0529 - Project Zombie _Official Trailer_ (IP - mc.projectzombie.beastnode.net).mp4"), md5="3d9c19835b03355d6fd5d00cd59dbe5b"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0529 - Project Zombie _Official Trailer_ (IP - mc.projectzombie.beastnode.net).nfo"), md5="60f72b99f5c69f9e03a071a12160928f"),
Path("Season 2011/s2011.e0630 - Project Zombie _Fin-thumb.jpg"): "00ed383591779ffe98291de60f198fe9",
Path("Season 2011/s2011.e0630 - Project Zombie _Fin.mp4"): "4971cb2d4fa29460361031f3fa8e1ea9",
Path("Season 2011/s2011.e0630 - Project Zombie _Fin.nfo"): "a7b5d9e57d20852f5daf360a1373bb7a",
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0630 - Project Zombie _Fin-thumb.jpg"), md5="00ed383591779ffe98291de60f198fe9"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0630 - Project Zombie _Fin.mp4"), md5="4971cb2d4fa29460361031f3fa8e1ea9"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e0630 - Project Zombie _Fin.nfo"), md5="a7b5d9e57d20852f5daf360a1373bb7a"),
Path("Season 2011/s2011.e1121 - Skyrim 'Ultra HD w_Mods' [PC]-thumb.jpg"): "1718599d5189c65f7d8cf6acfa5ea851",
Path("Season 2011/s2011.e1121 - Skyrim 'Ultra HD w_Mods' [PC].mp4"): "55e9b0add08c48c9c66105da0def2426",
Path("Season 2011/s2011.e1121 - Skyrim 'Ultra HD w_Mods' [PC].nfo"): "fe60e2b6b564f9316b6c7c183e1cf300",
ExpectedDownloadFile(path=Path("Season 2011/s2011.e1121 - Skyrim 'Ultra HD w_Mods' [PC]-thumb.jpg"), md5="1718599d5189c65f7d8cf6acfa5ea851"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e1121 - Skyrim 'Ultra HD w_Mods' [PC].mp4"), md5="55e9b0add08c48c9c66105da0def2426"),
ExpectedDownloadFile(path=Path("Season 2011/s2011.e1121 - Skyrim 'Ultra HD w_Mods' [PC].nfo"), md5="fe60e2b6b564f9316b6c7c183e1cf300"),
Path("Season 2012/s2012.e0123 - Project Zombie _Map Trailer-thumb.jpg"): "54ebe9df801b278fdd17b21afa8373a6",
Path("Season 2012/s2012.e0123 - Project Zombie _Map Trailer.mp4"): "65e4ce53ed5ec4139995469f99477a50",
Path("Season 2012/s2012.e0123 - Project Zombie _Map Trailer.nfo"): "c8900adcca83c473c79a4afbc7ad2de1",
ExpectedDownloadFile(path=Path("Season 2012/s2012.e0123 - Project Zombie _Map Trailer-thumb.jpg"), md5="54ebe9df801b278fdd17b21afa8373a6"),
ExpectedDownloadFile(path=Path("Season 2012/s2012.e0123 - Project Zombie _Map Trailer.mp4"), md5="65e4ce53ed5ec4139995469f99477a50"),
ExpectedDownloadFile(path=Path("Season 2012/s2012.e0123 - Project Zombie _Map Trailer.nfo"), md5="c8900adcca83c473c79a4afbc7ad2de1"),
Path("Season 2013/s2013.e0719 - Project Zombie Rewind _Trailer-thumb.jpg"): "e29d49433175de8a761af35c5307791f",
Path("Season 2013/s2013.e0719 - Project Zombie Rewind _Trailer.mp4"): "18620a8257a686beda65e54add4d4cd1",
Path("Season 2013/s2013.e0719 - Project Zombie Rewind _Trailer.nfo"): "1c993c41d4308a6049333154d0adee16",
ExpectedDownloadFile(path=Path("Season 2013/s2013.e0719 - Project Zombie Rewind _Trailer-thumb.jpg"), md5="e29d49433175de8a761af35c5307791f"),
ExpectedDownloadFile(path=Path("Season 2013/s2013.e0719 - Project Zombie Rewind _Trailer.mp4"), md5="18620a8257a686beda65e54add4d4cd1"),
ExpectedDownloadFile(path=Path("Season 2013/s2013.e0719 - Project Zombie Rewind _Trailer.nfo"), md5="1c993c41d4308a6049333154d0adee16"),
Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer-thumb.jpg"): "6f8f5e1e031ec2a04b0a4906c04a19ee",
Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.mp4"): "82f6ee7253e1dbb83ae7215af08ffacc",
Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.nfo"): "cc7886aae3af6b7b0facd82f95390242",
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer-thumb.jpg"), md5="6f8f5e1e031ec2a04b0a4906c04a19ee"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.mp4"), md5="82f6ee7253e1dbb83ae7215af08ffacc"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.nfo"), md5="cc7886aae3af6b7b0facd82f95390242"),
Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id-thumb.jpg"): "49cc64b25314155c1b8ab0361ac0c34f",
Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.mp4"): "e733b4cc385b953b08c8eb0f47e03c1e",
Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.nfo"): "2b3ccb3f1ef81ee49fe1afb88f275a09",
}
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id-thumb.jpg"), md5="49cc64b25314155c1b8ab0361ac0c34f"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.mp4"), md5="e733b4cc385b953b08c8eb0f47e03c1e"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.nfo"), md5="2b3ccb3f1ef81ee49fe1afb88f275a09"),
]
)
# fmt: on
@ -161,25 +163,25 @@ def recent_channel_subscription(config, subscription_name, recent_channel_subscr
def expected_recent_channel_download():
# turn off black formatter here for readability
# fmt: off
return ExpectedDownload(
expected_md5_file_hashes={
return ExpectedDownloads(
expected_downloads=[
# Download mapping
Path(".ytdl-sub-pz-download-archive.json"): "91534d1c5921d121aa35d7a197ba1940",
ExpectedDownloadFile(path=Path(".ytdl-sub-pz-download-archive.json"), md5="91534d1c5921d121aa35d7a197ba1940"),
# Output directory files
Path("fanart.jpg"): "c16b8b88a82cbd47d217ee80f6a8b5f3",
Path("poster.jpg"): "e92872ff94c96ad49e9579501c791578",
Path("tvshow.nfo"): "83c7db96081ac5bdf289fcf396bec157",
ExpectedDownloadFile(path=Path("fanart.jpg"), md5="c16b8b88a82cbd47d217ee80f6a8b5f3"),
ExpectedDownloadFile(path=Path("poster.jpg"), md5="e92872ff94c96ad49e9579501c791578"),
ExpectedDownloadFile(path=Path("tvshow.nfo"), md5="83c7db96081ac5bdf289fcf396bec157"),
# Recent Entry files
Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer-thumb.jpg"): "6f8f5e1e031ec2a04b0a4906c04a19ee",
Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.mp4"): "82f6ee7253e1dbb83ae7215af08ffacc",
Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.nfo"): "cc7886aae3af6b7b0facd82f95390242",
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer-thumb.jpg"), md5="6f8f5e1e031ec2a04b0a4906c04a19ee"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.mp4"), md5="82f6ee7253e1dbb83ae7215af08ffacc"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1029 - Jesse's Minecraft Server _ Teaser Trailer.nfo"), md5="cc7886aae3af6b7b0facd82f95390242"),
Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id-thumb.jpg"): "49cc64b25314155c1b8ab0361ac0c34f",
Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.mp4"): "e733b4cc385b953b08c8eb0f47e03c1e",
Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.nfo"): "2b3ccb3f1ef81ee49fe1afb88f275a09",
}
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id-thumb.jpg"), md5="49cc64b25314155c1b8ab0361ac0c34f"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.mp4"), md5="e733b4cc385b953b08c8eb0f47e03c1e"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.nfo"), md5="2b3ccb3f1ef81ee49fe1afb88f275a09"),
]
)
# fmt: on
@ -219,16 +221,16 @@ def recent_channel_no_vids_in_range_subscription(
def expected_recent_channel_no_vids_in_range_download():
# turn off black formatter here for readability
# fmt: off
return ExpectedDownload(
expected_md5_file_hashes={
return ExpectedDownloads(
expected_downloads=[
# Download mapping
Path(".ytdl-sub-pz-download-archive.json"): "99914b932bd37a50b983c5e7c90ae93b",
ExpectedDownloadFile(path=Path(".ytdl-sub-pz-download-archive.json"), md5="99914b932bd37a50b983c5e7c90ae93b"),
# Output directory files
Path("fanart.jpg"): "c16b8b88a82cbd47d217ee80f6a8b5f3",
Path("poster.jpg"): "e92872ff94c96ad49e9579501c791578",
Path("tvshow.nfo"): "83c7db96081ac5bdf289fcf396bec157",
}
ExpectedDownloadFile(path=Path("fanart.jpg"), md5="c16b8b88a82cbd47d217ee80f6a8b5f3"),
ExpectedDownloadFile(path=Path("poster.jpg"), md5="e92872ff94c96ad49e9579501c791578"),
ExpectedDownloadFile(path=Path("tvshow.nfo"), md5="83c7db96081ac5bdf289fcf396bec157"),
]
)
# fmt: on
@ -266,21 +268,21 @@ def rolling_recent_channel_subscription(
def expected_rolling_recent_channel_download():
# turn off black formatter here for readability
# fmt: off
return ExpectedDownload(
expected_md5_file_hashes={
return ExpectedDownloads(
expected_downloads=[
# Download mapping
Path(".ytdl-sub-pz-download-archive.json"): "97ff47a7c5d89a426a653493ad1a3f06",
ExpectedDownloadFile(path=Path(".ytdl-sub-pz-download-archive.json"), md5="97ff47a7c5d89a426a653493ad1a3f06"),
# Output directory files
Path("fanart.jpg"): "c16b8b88a82cbd47d217ee80f6a8b5f3",
Path("poster.jpg"): "e92872ff94c96ad49e9579501c791578",
Path("tvshow.nfo"): "83c7db96081ac5bdf289fcf396bec157",
ExpectedDownloadFile(path=Path("fanart.jpg"), md5="c16b8b88a82cbd47d217ee80f6a8b5f3"),
ExpectedDownloadFile(path=Path("poster.jpg"), md5="e92872ff94c96ad49e9579501c791578"),
ExpectedDownloadFile(path=Path("tvshow.nfo"), md5="83c7db96081ac5bdf289fcf396bec157"),
# Rolling Recent Entry files
Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id-thumb.jpg"): "49cc64b25314155c1b8ab0361ac0c34f",
Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.mp4"): "e733b4cc385b953b08c8eb0f47e03c1e",
Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.nfo"): "2b3ccb3f1ef81ee49fe1afb88f275a09",
}
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id-thumb.jpg"), md5="49cc64b25314155c1b8ab0361ac0c34f"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.mp4"), md5="e733b4cc385b953b08c8eb0f47e03c1e"),
ExpectedDownloadFile(path=Path("Season 2018/s2018.e1102 - Jesse's Minecraft Server _ IP mc.jesse.id.nfo"), md5="2b3ccb3f1ef81ee49fe1afb88f275a09"),
]
)
# fmt: on
@ -291,68 +293,75 @@ class TestChannelAsKodiTvShow:
expected md5 file hashes.
"""
@pytest.mark.parametrize("dry_run", [True, False])
def test_full_channel_download(
self, full_channel_subscription, expected_full_channel_download, output_directory
self, full_channel_subscription, expected_full_channel_download, output_directory, dry_run
):
full_channel_subscription.download()
expected_full_channel_download.assert_files_exist(relative_directory=output_directory)
def test_full_channel_dry_run(
self, full_channel_subscription, expected_full_channel_download, output_directory
):
transaction_log = full_channel_subscription.download(dry_run=True)
expected_full_channel_download.assert_dry_run_files_logged(transaction_log=transaction_log)
transaction_log = full_channel_subscription.download(dry_run=dry_run)
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_channel_full.txt",
)
if not dry_run:
expected_full_channel_download.assert_files_exist(relative_directory=output_directory)
@pytest.mark.parametrize("dry_run", [True, False])
def test_recent_channel_download(
self, recent_channel_subscription, expected_recent_channel_download, output_directory
self,
recent_channel_subscription,
expected_recent_channel_download,
output_directory,
dry_run,
):
recent_channel_subscription.download()
expected_recent_channel_download.assert_files_exist(relative_directory=output_directory)
# try downloading again, ensure nothing more was downloaded
with assert_debug_log(
logger=ytdl_sub.downloaders.downloader.logger,
expected_message="ExistingVideoReached, stopping additional downloads",
):
recent_channel_subscription.download()
transaction_log = recent_channel_subscription.download(dry_run=dry_run)
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_channel_recent.txt",
)
if not dry_run:
expected_recent_channel_download.assert_files_exist(relative_directory=output_directory)
def test_recent_channel_dry_run(
self, recent_channel_subscription, expected_recent_channel_download, output_directory
):
transaction_log = recent_channel_subscription.download(dry_run=True)
expected_recent_channel_download.assert_dry_run_files_logged(
transaction_log=transaction_log
)
# try downloading again, ensure nothing more was downloaded
with assert_debug_log(
logger=ytdl_sub.downloaders.downloader.logger,
expected_message="ExistingVideoReached, stopping additional downloads",
):
transaction_log = recent_channel_subscription.download()
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name=(
"youtube/test_channel_no_additional_downloads.txt"
),
)
expected_recent_channel_download.assert_files_exist(
relative_directory=output_directory
)
@pytest.mark.parametrize("dry_run", [True, False])
def test_recent_channel_download__no_vids_in_range(
self,
recent_channel_no_vids_in_range_subscription,
expected_recent_channel_no_vids_in_range_download,
output_directory,
dry_run,
):
recent_channel_no_vids_in_range_subscription.download()
expected_recent_channel_no_vids_in_range_download.assert_files_exist(
relative_directory=output_directory
)
# Try again, make sure its the same
recent_channel_no_vids_in_range_subscription.download()
expected_recent_channel_no_vids_in_range_download.assert_files_exist(
relative_directory=output_directory
)
def test_recent_channel_dry_run__no_vids_in_range(
self,
recent_channel_no_vids_in_range_subscription,
expected_recent_channel_no_vids_in_range_download,
output_directory,
):
transaction_log = recent_channel_no_vids_in_range_subscription.download(dry_run=True)
expected_recent_channel_no_vids_in_range_download.assert_dry_run_files_logged(
transaction_log=transaction_log
)
# Run twice, ensure nothing changes between runs
for _ in range(2):
transaction_log = recent_channel_no_vids_in_range_subscription.download(dry_run=dry_run)
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_channel_no_additional_downloads.txt",
)
if not dry_run:
expected_recent_channel_no_vids_in_range_download.assert_files_exist(
relative_directory=output_directory
)
@pytest.mark.parametrize("dry_run", [True, False])
def test_rolling_recent_channel_download(
self,
recent_channel_subscription,
@ -360,14 +369,22 @@ class TestChannelAsKodiTvShow:
expected_recent_channel_download,
expected_rolling_recent_channel_download,
output_directory,
dry_run,
):
# First, download recent vids
# First, download recent vids. Always download since we want to test dry-run
# on the rolling recent portion.
with assert_debug_log(
logger=ytdl_sub.downloaders.downloader.logger,
expected_message="RejectedVideoReached, stopping additional downloads",
):
recent_channel_subscription.download()
expected_recent_channel_download.assert_files_exist(relative_directory=output_directory)
transaction_log = recent_channel_subscription.download()
expected_recent_channel_download.assert_files_exist(relative_directory=output_directory)
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_channel_recent.txt",
)
# Then, download the rolling recent vids subscription. This should remove one of the
# two videos
@ -375,18 +392,33 @@ class TestChannelAsKodiTvShow:
logger=ytdl_sub.downloaders.downloader.logger,
expected_message="ExistingVideoReached, stopping additional downloads",
):
rolling_recent_channel_subscription.download()
transaction_log = rolling_recent_channel_subscription.download(dry_run=dry_run)
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_channel_rolling_recent.txt",
regenerate_transaction_log=True,
)
if not dry_run:
expected_rolling_recent_channel_download.assert_files_exist(
relative_directory=output_directory
)
# Invoke the rolling download again, ensure downloading stopped early from it already
# existing
with assert_debug_log(
logger=ytdl_sub.downloaders.downloader.logger,
expected_message="ExistingVideoReached, stopping additional downloads",
):
rolling_recent_channel_subscription.download()
if not dry_run:
with assert_debug_log(
logger=ytdl_sub.downloaders.downloader.logger,
expected_message="ExistingVideoReached, stopping additional downloads",
):
transaction_log = rolling_recent_channel_subscription.download()
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_channel_no_additional_downloads.txt",
)
expected_rolling_recent_channel_download.assert_files_exist(
relative_directory=output_directory
)

View file

@ -1,7 +1,9 @@
from pathlib import Path
import pytest
from e2e.expected_download import ExpectedDownload
from e2e.expected_download import ExpectedDownloadFile
from e2e.expected_download import ExpectedDownloads
from e2e.expected_transaction_log import assert_transaction_log_matches
from ytdl_sub.config.config_file import ConfigFile
from ytdl_sub.config.preset import Preset
@ -63,16 +65,15 @@ def playlist_subscription(config, subscription_name, subscription_dict):
@pytest.fixture
def expected_playlist_download():
return ExpectedDownload(
expected_md5_file_hashes={
Path("JMC - Jesse's Minecraft Server-thumb.jpg"): "a3f1910f9c51f6442f845a528e190829",
Path("JMC - Jesse's Minecraft Server.mkv"): [
"6053c47a8690519b0a33c13fa4b01ac0",
"3ab42b3e6be0a44deb3a9a28e6ebaf16",
],
Path("JMC - Jesse's Minecraft Server.nfo"): "10df5dcdb65ab18ecf21b3503c77e48b",
}
# fmt: off
return ExpectedDownloads(
expected_downloads=[
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server-thumb.jpg"), md5="a3f1910f9c51f6442f845a528e190829"),
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server.mkv"), md5=["6053c47a8690519b0a33c13fa4b01ac0", "3ab42b3e6be0a44deb3a9a28e6ebaf16"]),
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server.nfo"), md5="10df5dcdb65ab18ecf21b3503c77e48b"),
]
)
# fmt: on
class TestYoutubeMergePlaylist:
@ -81,14 +82,16 @@ class TestYoutubeMergePlaylist:
files exist and have the expected md5 file hashes.
"""
@pytest.mark.parametrize("dry_run", [True, False])
def test_merge_playlist_download(
self, playlist_subscription, expected_playlist_download, output_directory
self, playlist_subscription, expected_playlist_download, output_directory, dry_run
):
playlist_subscription.download()
expected_playlist_download.assert_files_exist(relative_directory=output_directory)
transaction_log = playlist_subscription.download()
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_merge_playlist.txt",
)
def test_merge_playlist_dry_run(
self, playlist_subscription, expected_playlist_download, output_directory
):
transaction_log = playlist_subscription.download(dry_run=True)
expected_playlist_download.assert_dry_run_files_logged(transaction_log=transaction_log)
if not dry_run:
expected_playlist_download.assert_files_exist(relative_directory=output_directory)

View file

@ -3,7 +3,9 @@ from pathlib import Path
import mergedeep
import pytest
from conftest import assert_debug_log
from e2e.expected_download import ExpectedDownload
from e2e.expected_download import ExpectedDownloadFile
from e2e.expected_download import ExpectedDownloads
from e2e.expected_transaction_log import assert_transaction_log_matches
import ytdl_sub.downloaders.downloader
from ytdl_sub.config.config_file import ConfigFile
@ -63,24 +65,24 @@ def playlist_subscription(config, subscription_name, subscription_dict):
def expected_playlist_download():
# turn off black formatter here for readability
# fmt: off
return ExpectedDownload(
expected_md5_file_hashes={
return ExpectedDownloads(
expected_downloads=[
# Download mapping
Path(".ytdl-sub-jmc-download-archive.json"): "9f785c29194a6ecfba6a6b4018763ddc",
ExpectedDownloadFile(path=Path(".ytdl-sub-jmc-download-archive.json"), md5="9f785c29194a6ecfba6a6b4018763ddc"),
# Entry files
Path("JMC - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg"): "b232d253df621aa770b780c1301d364d",
Path("JMC - Jesse's Minecraft Server [Trailer - Feb.1].mp4"): "e66287b9832277b6a4d1554e29d9fdcc",
Path("JMC - Jesse's Minecraft Server [Trailer - Feb.1].nfo"): "3d272fe58487b6011ad049b6000b046f",
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg"), md5="b232d253df621aa770b780c1301d364d"),
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Feb.1].mp4"), md5="e66287b9832277b6a4d1554e29d9fdcc"),
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Feb.1].nfo"), md5="3d272fe58487b6011ad049b6000b046f"),
Path("JMC - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg"): "d17c379ea8b362f5b97c6b213b0342cb",
Path("JMC - Jesse's Minecraft Server [Trailer - Feb.27].mp4"): "04ab5cb3cc12325d0c96a7cd04a8b91d",
Path("JMC - Jesse's Minecraft Server [Trailer - Feb.27].nfo"): "6f99af10bef67276a507d1d9770c5e92",
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg"), md5="d17c379ea8b362f5b97c6b213b0342cb"),
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Feb.27].mp4"), md5="04ab5cb3cc12325d0c96a7cd04a8b91d"),
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Feb.27].nfo"), md5="6f99af10bef67276a507d1d9770c5e92"),
Path("JMC - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg"): "e7830aa8a64b0cde65ba3f7e5fc56530",
Path("JMC - Jesse's Minecraft Server [Trailer - Mar.21].mp4"): "025de6099a5c98e6397153c7a62d517d",
Path("JMC - Jesse's Minecraft Server [Trailer - Mar.21].nfo"): "beec3c1326654bd8c858cecf4e40977a",
}
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg"), md5="e7830aa8a64b0cde65ba3f7e5fc56530"),
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Mar.21].mp4"), md5="025de6099a5c98e6397153c7a62d517d"),
ExpectedDownloadFile(path=Path("JMC - Jesse's Minecraft Server [Trailer - Mar.21].nfo"), md5="beec3c1326654bd8c858cecf4e40977a"),
]
)
# fmt: on
@ -118,12 +120,12 @@ def single_video_subscription(config, subscription_name, single_video_subscripti
def expected_single_video_download():
# turn off black formatter here for readability
# fmt: off
return ExpectedDownload(
expected_md5_file_hashes={
Path("JMC - Oblivion Mod 'Falcor' p.1-thumb.jpg"): "fb95b510681676e81c321171fc23143e",
Path("JMC - Oblivion Mod 'Falcor' p.1.mp4"): "931a705864c57d21d6fedebed4af6bbc",
Path("JMC - Oblivion Mod 'Falcor' p.1.nfo"): "89f509a8a3d9003e22a9091abeeae5dc",
}
return ExpectedDownloads(
expected_downloads=[
ExpectedDownloadFile(path=Path("JMC - Oblivion Mod 'Falcor' p.1-thumb.jpg"), md5="fb95b510681676e81c321171fc23143e"),
ExpectedDownloadFile(path=Path("JMC - Oblivion Mod 'Falcor' p.1.mp4"), md5="931a705864c57d21d6fedebed4af6bbc"),
ExpectedDownloadFile(path=Path("JMC - Oblivion Mod 'Falcor' p.1.nfo"), md5="89f509a8a3d9003e22a9091abeeae5dc"),
]
)
# fmt: on
@ -134,36 +136,37 @@ class TestPlaylistAsKodiMusicVideo:
files exist and have the expected md5 file hashes.
"""
@pytest.mark.parametrize("dry_run", [True, False])
def test_playlist_download(
self, playlist_subscription, expected_playlist_download, output_directory
self, playlist_subscription, expected_playlist_download, output_directory, dry_run
):
playlist_subscription.download()
expected_playlist_download.assert_files_exist(relative_directory=output_directory)
transaction_log = playlist_subscription.download()
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_playlist.txt",
)
# After the playlist is downloaded, ensure another invocation will hit ExistingVideoReached
with assert_debug_log(
logger=ytdl_sub.downloaders.downloader.logger,
expected_message="ExistingVideoReached, stopping additional downloads",
):
playlist_subscription.download()
if not dry_run:
expected_playlist_download.assert_files_exist(relative_directory=output_directory)
def test_playlist_dry_run(
self, playlist_subscription, expected_playlist_download, output_directory
):
file_transaction_log = playlist_subscription.download(dry_run=True)
expected_playlist_download.assert_dry_run_files_logged(transaction_log=file_transaction_log)
# Ensure another invocation will hit ExistingVideoReached
with assert_debug_log(
logger=ytdl_sub.downloaders.downloader.logger,
expected_message="ExistingVideoReached, stopping additional downloads",
):
playlist_subscription.download()
expected_playlist_download.assert_files_exist(relative_directory=output_directory)
@pytest.mark.parametrize("dry_run", [True, False])
def test_single_video_download(
self, single_video_subscription, expected_single_video_download, output_directory
self, single_video_subscription, expected_single_video_download, output_directory, dry_run
):
single_video_subscription.download()
expected_single_video_download.assert_files_exist(relative_directory=output_directory)
def test_single_video_dry_run(
self, single_video_subscription, expected_single_video_download, output_directory
):
file_transaction_log = single_video_subscription.download(dry_run=True)
expected_single_video_download.assert_dry_run_files_logged(
transaction_log=file_transaction_log
transaction_log = single_video_subscription.download()
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_video.txt",
)
if not dry_run:
expected_single_video_download.assert_files_exist(relative_directory=output_directory)

View file

@ -4,7 +4,9 @@ from tempfile import NamedTemporaryFile
import mergedeep
import pytest
from conftest import assert_debug_log
from e2e.expected_download import ExpectedDownload
from e2e.expected_download import ExpectedDownloadFile
from e2e.expected_download import ExpectedDownloads
from e2e.expected_transaction_log import assert_transaction_log_matches
import ytdl_sub.downloaders.downloader
from ytdl_sub.config.config_file import ConfigFile
@ -88,27 +90,27 @@ def single_video_subscription(config, subscription_name, subscription_dict):
def expected_single_video_download():
# turn off black formatter here for readability
# fmt: off
return ExpectedDownload(
expected_md5_file_hashes={
Path('Project Zombie - 1-6.Intro.mp4'): "eaec6f50f364b13ef1a201e736ec9c05",
Path('Project Zombie - 2-6.Part 1.mp4'): "5850b19acb250cc13db36f80fa1bba5a",
Path('Project Zombie - 3-6.Part 2.mp4'): "445d95eba437db6df284df7e1ab633e8",
Path('Project Zombie - 4-6.Part 3.mp4'): "2b6e7532d515c9e64ed2a33d850cf199",
Path('Project Zombie - 5-6.Part 4.mp4'): "842bf3c4d1fcc4c5ab110635935dac66",
Path('Project Zombie - 6-6.Part 5.mp4'): "238de99f00f829ab72f042b79da9a33a",
Path('Project Zombie - Intro-thumb.jpg'): "fb95b510681676e81c321171fc23143e",
Path('Project Zombie - Intro.nfo'): "ded59ac906f579312cc3cf98a57e7ea3",
Path('Project Zombie - Part 1-thumb.jpg'): "fb95b510681676e81c321171fc23143e",
Path('Project Zombie - Part 1.nfo'): "70ff5cd0092b8bc22dc4db93a824789b",
Path('Project Zombie - Part 2-thumb.jpg'): "fb95b510681676e81c321171fc23143e",
Path('Project Zombie - Part 2.nfo'): "54450c18a2cbb9d6d2ee5d0a1fb3f279",
Path('Project Zombie - Part 3-thumb.jpg'): "fb95b510681676e81c321171fc23143e",
Path('Project Zombie - Part 3.nfo'): "0effb13fc4039363a95969d1048dde57",
Path('Project Zombie - Part 4-thumb.jpg'): "fb95b510681676e81c321171fc23143e",
Path('Project Zombie - Part 4.nfo'): "74bd0d7c12105469838768a0cc323a8c",
Path('Project Zombie - Part 5-thumb.jpg'): "fb95b510681676e81c321171fc23143e",
Path('Project Zombie - Part 5.nfo'): "a8cf2e77721335ea7c18e22734e7996c",
}
return ExpectedDownloads(
expected_downloads=[
ExpectedDownloadFile(path=Path('Project Zombie - 1-6.Intro.mp4'), md5="eaec6f50f364b13ef1a201e736ec9c05"),
ExpectedDownloadFile(path=Path('Project Zombie - 2-6.Part 1.mp4'), md5="5850b19acb250cc13db36f80fa1bba5a"),
ExpectedDownloadFile(path=Path('Project Zombie - 3-6.Part 2.mp4'), md5="445d95eba437db6df284df7e1ab633e8"),
ExpectedDownloadFile(path=Path('Project Zombie - 4-6.Part 3.mp4'), md5="2b6e7532d515c9e64ed2a33d850cf199"),
ExpectedDownloadFile(path=Path('Project Zombie - 5-6.Part 4.mp4'), md5="842bf3c4d1fcc4c5ab110635935dac66"),
ExpectedDownloadFile(path=Path('Project Zombie - 6-6.Part 5.mp4'), md5="238de99f00f829ab72f042b79da9a33a"),
ExpectedDownloadFile(path=Path('Project Zombie - Intro-thumb.jpg'), md5="fb95b510681676e81c321171fc23143e"),
ExpectedDownloadFile(path=Path('Project Zombie - Intro.nfo'), md5="ded59ac906f579312cc3cf98a57e7ea3"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 1-thumb.jpg'), md5="fb95b510681676e81c321171fc23143e"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 1.nfo'), md5="70ff5cd0092b8bc22dc4db93a824789b"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 2-thumb.jpg'), md5="fb95b510681676e81c321171fc23143e"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 2.nfo'), md5="54450c18a2cbb9d6d2ee5d0a1fb3f279"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 3-thumb.jpg'), md5="fb95b510681676e81c321171fc23143e"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 3.nfo'), md5="0effb13fc4039363a95969d1048dde57"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 4-thumb.jpg'), md5="fb95b510681676e81c321171fc23143e"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 4.nfo'), md5="74bd0d7c12105469838768a0cc323a8c"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 5-thumb.jpg'), md5="fb95b510681676e81c321171fc23143e"),
ExpectedDownloadFile(path=Path('Project Zombie - Part 5.nfo'), md5="a8cf2e77721335ea7c18e22734e7996c"),
]
)
# fmt: on
@ -119,12 +121,16 @@ class TestPlaylistAsKodiMusicVideo:
files exist and have the expected md5 file hashes.
"""
@pytest.mark.parametrize("dry_run", [True, False])
def test_split_video_download(
self, single_video_subscription, expected_single_video_download, output_directory
self, single_video_subscription, expected_single_video_download, output_directory, dry_run
):
single_video_subscription.download()
expected_single_video_download.assert_files_exist(relative_directory=output_directory)
transaction_log = single_video_subscription.download()
assert_transaction_log_matches(
output_directory=output_directory,
transaction_log=transaction_log,
transaction_log_summary_file_name="youtube/test_split_video.txt",
)
def test_split_video_dry_run(self, single_video_subscription, expected_single_video_download):
transaction_log = single_video_subscription.download(dry_run=True)
expected_single_video_download.assert_dry_run_files_logged(transaction_log=transaction_log)
if not dry_run:
expected_single_video_download.assert_files_exist(relative_directory=output_directory)