Merge branch 'jesse/ffmpeg' into jesse/xml
This commit is contained in:
commit
37e2136c96
26 changed files with 205 additions and 241 deletions
3
.github/workflows/ci.yaml
vendored
3
.github/workflows/ci.yaml
vendored
|
|
@ -9,7 +9,7 @@ on:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10"]
|
python-version: ["3.10"]
|
||||||
|
|
@ -21,6 +21,7 @@ jobs:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
sudo apt-get install -y ffmpeg
|
sudo apt-get install -y ffmpeg
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -e .[lint,test]
|
pip install -e .[lint,test]
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
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.abc import YoutubeDownloader
|
||||||
from ytdl_sub.downloaders.youtube.abc import YoutubeDownloaderOptions
|
from ytdl_sub.downloaders.youtube.abc import YoutubeDownloaderOptions
|
||||||
from ytdl_sub.entries.youtube import YoutubeVideo
|
from ytdl_sub.entries.youtube import YoutubeVideo
|
||||||
from ytdl_sub.utils.chapters import Chapters
|
|
||||||
from ytdl_sub.utils.ffmpeg import set_ffmpeg_metadata_chapters
|
|
||||||
from ytdl_sub.utils.file_handler import FileMetadata
|
|
||||||
from ytdl_sub.validators.url_validator import YoutubeVideoUrlValidator
|
from ytdl_sub.validators.url_validator import YoutubeVideoUrlValidator
|
||||||
from ytdl_sub.validators.validators import StringValidator
|
|
||||||
|
|
||||||
|
|
||||||
class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
||||||
|
|
@ -28,8 +22,6 @@ class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
||||||
# required
|
# required
|
||||||
download_strategy: "video"
|
download_strategy: "video"
|
||||||
video_url: "youtube.com/watch?v=VMAPTo7RVDo"
|
video_url: "youtube.com/watch?v=VMAPTo7RVDo"
|
||||||
# optional
|
|
||||||
chapter_timestamps: path/to/timestamps.txt
|
|
||||||
|
|
||||||
CLI usage:
|
CLI usage:
|
||||||
|
|
||||||
|
|
@ -39,14 +31,10 @@ class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_required_keys = {"video_url"}
|
_required_keys = {"video_url"}
|
||||||
_optional_keys = {"chapter_timestamps"}
|
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
self._video_url = self._validate_key("video_url", YoutubeVideoUrlValidator).video_url
|
self._video_url = self._validate_key("video_url", YoutubeVideoUrlValidator).video_url
|
||||||
self._chapter_timestamps = self._validate_key_if_present(
|
|
||||||
"chapter_timestamps", StringValidator
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def video_url(self) -> str:
|
def video_url(self) -> str:
|
||||||
|
|
@ -55,24 +43,6 @@ class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
||||||
"""
|
"""
|
||||||
return self._video_url
|
return self._video_url
|
||||||
|
|
||||||
@property
|
|
||||||
def chapter_timestamps(self) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
Optional. The path to the file containing the timestamps to embed into the video as
|
|
||||||
chapters. Should be formatted as:
|
|
||||||
|
|
||||||
.. code-block:: markdown
|
|
||||||
|
|
||||||
0:00 Intro
|
|
||||||
0:24 Blackwater Park
|
|
||||||
10:23 Bleak
|
|
||||||
16:39 Jokes
|
|
||||||
1:02:23 Ending
|
|
||||||
"""
|
|
||||||
if self._chapter_timestamps:
|
|
||||||
return self._chapter_timestamps.value
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions, YoutubeVideo]):
|
class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions, YoutubeVideo]):
|
||||||
downloader_options_type = YoutubeVideoDownloaderOptions
|
downloader_options_type = YoutubeVideoDownloaderOptions
|
||||||
|
|
@ -93,26 +63,8 @@ class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions, Yo
|
||||||
**{"break_on_existing": True},
|
**{"break_on_existing": True},
|
||||||
)
|
)
|
||||||
|
|
||||||
def download(self) -> List[YoutubeVideo] | List[Tuple[YoutubeVideo, FileMetadata]]:
|
def download(self) -> List[YoutubeVideo]:
|
||||||
"""Download a single Youtube video"""
|
"""Download a single Youtube video"""
|
||||||
entry_dict = self.extract_info(url=self.download_options.video_url)
|
entry_dict = self.extract_info(url=self.download_options.video_url)
|
||||||
video = YoutubeVideo(entry_dict=entry_dict, working_directory=self.working_directory)
|
video = YoutubeVideo(entry_dict=entry_dict, working_directory=self.working_directory)
|
||||||
|
return [video]
|
||||||
# If no chapters, just return the video
|
|
||||||
if not self.download_options.chapter_timestamps:
|
|
||||||
return [video]
|
|
||||||
|
|
||||||
# Otherwise, add the chapters and return the video + chapter metadata
|
|
||||||
chapters = Chapters.from_timestamps_file(
|
|
||||||
chapters_file_path=self.download_options.chapter_timestamps
|
|
||||||
)
|
|
||||||
if not self.is_dry_run:
|
|
||||||
set_ffmpeg_metadata_chapters(
|
|
||||||
file_path=video.get_download_file_path(),
|
|
||||||
chapters=chapters,
|
|
||||||
file_duration_sec=video.kwargs("duration"),
|
|
||||||
)
|
|
||||||
|
|
||||||
file_metadata = chapters.to_file_metadata(title="Chapters embedded into the video:")
|
|
||||||
|
|
||||||
return [(video, file_metadata)]
|
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,14 @@ from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder
|
||||||
from ytdl_sub.entries.entry import Entry
|
from ytdl_sub.entries.entry import Entry
|
||||||
from ytdl_sub.plugins.plugin import Plugin
|
from ytdl_sub.plugins.plugin import Plugin
|
||||||
from ytdl_sub.plugins.plugin import PluginOptions
|
from ytdl_sub.plugins.plugin import PluginOptions
|
||||||
|
from ytdl_sub.utils.chapters import Chapters
|
||||||
|
from ytdl_sub.utils.ffmpeg import set_ffmpeg_metadata_chapters
|
||||||
from ytdl_sub.utils.file_handler import FileMetadata
|
from ytdl_sub.utils.file_handler import FileMetadata
|
||||||
from ytdl_sub.validators.regex_validator import RegexListValidator
|
from ytdl_sub.validators.regex_validator import RegexListValidator
|
||||||
from ytdl_sub.validators.string_select_validator import StringSelectValidator
|
from ytdl_sub.validators.string_select_validator import StringSelectValidator
|
||||||
from ytdl_sub.validators.validators import BoolValidator
|
from ytdl_sub.validators.validators import BoolValidator
|
||||||
from ytdl_sub.validators.validators import ListValidator
|
from ytdl_sub.validators.validators import ListValidator
|
||||||
|
from ytdl_sub.validators.validators import StringValidator
|
||||||
|
|
||||||
SPONSORBLOCK_HIGHLIGHT_CATEGORIES: Set[str] = {"poi_highlight"}
|
SPONSORBLOCK_HIGHLIGHT_CATEGORIES: Set[str] = {"poi_highlight"}
|
||||||
SPONSORBLOCK_CATEGORIES: Set[str] = SPONSORBLOCK_HIGHLIGHT_CATEGORIES | {
|
SPONSORBLOCK_CATEGORIES: Set[str] = SPONSORBLOCK_HIGHLIGHT_CATEGORIES | {
|
||||||
|
|
@ -87,6 +90,7 @@ class ChaptersOptions(PluginOptions):
|
||||||
|
|
||||||
_optional_keys = {
|
_optional_keys = {
|
||||||
"embed_chapters",
|
"embed_chapters",
|
||||||
|
"embed_chapter_timestamps",
|
||||||
"sponsorblock_categories",
|
"sponsorblock_categories",
|
||||||
"remove_sponsorblock_categories",
|
"remove_sponsorblock_categories",
|
||||||
"remove_chapters_regex",
|
"remove_chapters_regex",
|
||||||
|
|
@ -110,12 +114,20 @@ class ChaptersOptions(PluginOptions):
|
||||||
self._force_key_frames = self._validate_key_if_present(
|
self._force_key_frames = self._validate_key_if_present(
|
||||||
key="force_key_frames", validator=BoolValidator, default=False
|
key="force_key_frames", validator=BoolValidator, default=False
|
||||||
).value
|
).value
|
||||||
|
self._embed_chapter_timestamps = self._validate_key_if_present(
|
||||||
|
"embed_chapter_timestamps", StringValidator
|
||||||
|
)
|
||||||
|
|
||||||
if self._remove_sponsorblock_categories and not self._sponsorblock_categories:
|
if self._remove_sponsorblock_categories and not self._sponsorblock_categories:
|
||||||
raise self._validation_exception(
|
raise self._validation_exception(
|
||||||
"Must specify sponsorblock_categories if you are going to remove any of them"
|
"Must specify sponsorblock_categories if you are going to remove any of them"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self._embed_chapters and self._embed_chapter_timestamps:
|
||||||
|
raise self._validation_exception(
|
||||||
|
"Cannot embed chapters from the source and from a timestamp file"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def embed_chapters(self) -> Optional[bool]:
|
def embed_chapters(self) -> Optional[bool]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -172,6 +184,27 @@ class ChaptersOptions(PluginOptions):
|
||||||
"""
|
"""
|
||||||
return self._force_key_frames
|
return self._force_key_frames
|
||||||
|
|
||||||
|
@property
|
||||||
|
def embed_chapter_timestamps(self) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Optional. The path to the file containing the timestamps to embed into the file as
|
||||||
|
chapters. Should be formatted as:
|
||||||
|
|
||||||
|
.. code-block:: markdown
|
||||||
|
|
||||||
|
0:00 Intro
|
||||||
|
0:24 Blackwater Park
|
||||||
|
10:23 Bleak
|
||||||
|
16:39 Jokes
|
||||||
|
1:02:23 Ending
|
||||||
|
|
||||||
|
This should only be used with single entity download strategies. Otherwise, an entire
|
||||||
|
playlist or channel would all the same embedded chapters.
|
||||||
|
"""
|
||||||
|
if self._embed_chapter_timestamps:
|
||||||
|
return self._embed_chapter_timestamps.value
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ChaptersPlugin(Plugin[ChaptersOptions]):
|
class ChaptersPlugin(Plugin[ChaptersOptions]):
|
||||||
plugin_options_type = ChaptersOptions
|
plugin_options_type = ChaptersOptions
|
||||||
|
|
@ -263,6 +296,29 @@ class ChaptersPlugin(Plugin[ChaptersOptions]):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def modify_entry(self, entry: Entry) -> Entry:
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
entry
|
||||||
|
Entry to add custom chapters using timestamps if present
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
entry
|
||||||
|
"""
|
||||||
|
if self.plugin_options.embed_chapter_timestamps and not self.is_dry_run:
|
||||||
|
chapters = Chapters.from_timestamps_file(
|
||||||
|
chapters_file_path=self.plugin_options.embed_chapter_timestamps
|
||||||
|
)
|
||||||
|
set_ffmpeg_metadata_chapters(
|
||||||
|
file_path=entry.get_download_file_path(),
|
||||||
|
chapters=chapters,
|
||||||
|
file_duration_sec=entry.kwargs("duration"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return entry
|
||||||
|
|
||||||
def post_process_entry(self, entry: Entry) -> Optional[FileMetadata]:
|
def post_process_entry(self, entry: Entry) -> Optional[FileMetadata]:
|
||||||
"""
|
"""
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -274,20 +330,29 @@ class ChaptersPlugin(Plugin[ChaptersOptions]):
|
||||||
-------
|
-------
|
||||||
FileMetadata outlining which chapters/SponsorBlock segments got removed
|
FileMetadata outlining which chapters/SponsorBlock segments got removed
|
||||||
"""
|
"""
|
||||||
metadata_dict = {}
|
if self.plugin_options.embed_chapter_timestamps:
|
||||||
removed_chapters = self._get_removed_chapters(entry)
|
chapters = Chapters.from_timestamps_file(
|
||||||
removed_sponsorblock = self._get_removed_sponsorblock_category_counts(entry)
|
chapters_file_path=self.plugin_options.embed_chapter_timestamps
|
||||||
|
)
|
||||||
|
return chapters.to_file_metadata(title="Chapters embedded from timestamp file:")
|
||||||
|
|
||||||
# If no chapters are on the entry, do not report any embedded chapters
|
if self.plugin_options.embed_chapters:
|
||||||
if not _contains_any_chapters(entry):
|
metadata_dict = {}
|
||||||
return None
|
removed_chapters = self._get_removed_chapters(entry)
|
||||||
|
removed_sponsorblock = self._get_removed_sponsorblock_category_counts(entry)
|
||||||
|
|
||||||
if removed_chapters:
|
# If no chapters are on the entry, do not report any embedded chapters
|
||||||
metadata_dict["Removed Chapter(s)"] = ", ".join(removed_chapters)
|
if not _contains_any_chapters(entry):
|
||||||
if removed_sponsorblock:
|
return None
|
||||||
metadata_dict["Removed SponsorBlock Category Count(s)"] = removed_sponsorblock
|
|
||||||
|
|
||||||
# TODO: check if file actually has embedded chapters
|
if removed_chapters:
|
||||||
return FileMetadata.from_dict(
|
metadata_dict["Removed Chapter(s)"] = ", ".join(removed_chapters)
|
||||||
value_dict=metadata_dict, title="Embedded Chapters", sort_dict=False
|
if removed_sponsorblock:
|
||||||
)
|
metadata_dict["Removed SponsorBlock Category Count(s)"] = removed_sponsorblock
|
||||||
|
|
||||||
|
# TODO: check if file actually has embedded chapters
|
||||||
|
return FileMetadata.from_dict(
|
||||||
|
value_dict=metadata_dict, title="Embedded Chapters", sort_dict=False
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ class Plugin(DownloadArchiver, Generic[PluginOptionsT], ABC):
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def modify_entry(self, entry: Entry) -> Optional[Entry]:
|
def modify_entry(self, entry: Entry) -> Optional[Entry | Tuple[Entry, FileMetadata]]:
|
||||||
"""
|
"""
|
||||||
For each entry downloaded, modify the entry in some way before sending it to
|
For each entry downloaded, modify the entry in some way before sending it to
|
||||||
post-processing.
|
post-processing.
|
||||||
|
|
|
||||||
|
|
@ -71,3 +71,42 @@ class TestChapters:
|
||||||
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.mp4"
|
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.mp4"
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("dry_run", [True, False])
|
||||||
|
def test_chapters_from_timestamp_file_with_subs(
|
||||||
|
self,
|
||||||
|
music_video_config,
|
||||||
|
single_video_sponsorblock_and_embedded_subs_preset_dict,
|
||||||
|
timestamps_file_path,
|
||||||
|
output_directory,
|
||||||
|
dry_run,
|
||||||
|
):
|
||||||
|
# Test chapters and video tags, throw in a video tag with special chars while we are at it
|
||||||
|
single_video_sponsorblock_and_embedded_subs_preset_dict["chapters"] = {
|
||||||
|
"embed_chapters": False,
|
||||||
|
"embed_chapter_timestamps": timestamps_file_path,
|
||||||
|
}
|
||||||
|
single_video_sponsorblock_and_embedded_subs_preset_dict["video_tags"] = {
|
||||||
|
"tags": {"description": "🎸 / ' \" \n newline?"}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription = Subscription.from_dict(
|
||||||
|
config=music_video_config,
|
||||||
|
preset_name="chapters_from_timestamps_with_subs",
|
||||||
|
preset_dict=single_video_sponsorblock_and_embedded_subs_preset_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction_log = subscription.download(dry_run=dry_run)
|
||||||
|
assert_transaction_log_matches(
|
||||||
|
output_directory=output_directory,
|
||||||
|
transaction_log=transaction_log,
|
||||||
|
transaction_log_summary_file_name="plugins/test_chapters_from_ts_with_subs.txt",
|
||||||
|
)
|
||||||
|
assert_expected_downloads(
|
||||||
|
output_directory=output_directory,
|
||||||
|
dry_run=dry_run,
|
||||||
|
expected_download_summary_file_name="plugins/test_chapters_from_ts_with_subs.json",
|
||||||
|
ignore_md5_hashes_for=[
|
||||||
|
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.mp4"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -86,39 +86,3 @@ class TestSubtitles:
|
||||||
dry_run=dry_run,
|
dry_run=dry_run,
|
||||||
expected_download_summary_file_name="plugins/test_subtitles_embedded_and_file.json",
|
expected_download_summary_file_name="plugins/test_subtitles_embedded_and_file.json",
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.parametrize("dry_run", [True, False])
|
|
||||||
def test_subtitles_chapters_tags_embedded(
|
|
||||||
self,
|
|
||||||
music_video_config,
|
|
||||||
timestamps_file_path,
|
|
||||||
single_video_subs_embed_preset_dict,
|
|
||||||
output_directory,
|
|
||||||
dry_run,
|
|
||||||
):
|
|
||||||
# Test chapters and video tags in addition to subtitles
|
|
||||||
mergedeep.merge(
|
|
||||||
single_video_subs_embed_preset_dict,
|
|
||||||
{
|
|
||||||
"youtube": {"chapter_timestamps": timestamps_file_path},
|
|
||||||
"video_tags": {"tags": {"title": "{title}"}},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
subscription = Subscription.from_dict(
|
|
||||||
config=music_video_config,
|
|
||||||
preset_name="subtitles_embedded_test",
|
|
||||||
preset_dict=single_video_subs_embed_preset_dict,
|
|
||||||
)
|
|
||||||
|
|
||||||
transaction_log = subscription.download(dry_run=dry_run)
|
|
||||||
assert_transaction_log_matches(
|
|
||||||
output_directory=output_directory,
|
|
||||||
transaction_log=transaction_log,
|
|
||||||
transaction_log_summary_file_name="plugins/test_subtitles_tags_chapters.txt",
|
|
||||||
)
|
|
||||||
assert_expected_downloads(
|
|
||||||
output_directory=output_directory,
|
|
||||||
dry_run=dry_run,
|
|
||||||
expected_download_summary_file_name="plugins/test_subtitles_tags_chapters.json",
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
{
|
{
|
||||||
"Alfa Mist - Nocturne [Full Album]/01 - 01. Intro (Feat. Racheal Ofori & Barney Artist).mp3": "9313382b938547fa9cc02c708068ae42",
|
"Alfa Mist - Nocturne [Full Album]/01 - 01. Intro (Feat. Racheal Ofori & Barney Artist).mp3": "ddc24257729f24055bf1b8dc06f8224c",
|
||||||
"Alfa Mist - Nocturne [Full Album]/02 - 02. Answers (Feat. Rick David & Kaya Thomas - Dyke).mp3": "c38c7186eb52e89246ed8c79be8485e7",
|
"Alfa Mist - Nocturne [Full Album]/02 - 02. Answers (Feat. Rick David & Kaya Thomas - Dyke).mp3": "10dd7f13c469bd51ffcf3f8ff3ed3d69",
|
||||||
"Alfa Mist - Nocturne [Full Album]/03 - 03. Blaze (Feat. Kaya Thomas - Dyke).mp3": "3d873907e61d4af8e20539961913d486",
|
"Alfa Mist - Nocturne [Full Album]/03 - 03. Blaze (Feat. Kaya Thomas - Dyke).mp3": "376722aaf08d1ef6dcba0aaf7a3b7a79",
|
||||||
"Alfa Mist - Nocturne [Full Album]/04 - 04. What If (Interlude).mp3": "846898842d60b7d1d3b455dfa44821bd",
|
"Alfa Mist - Nocturne [Full Album]/04 - 04. What If (Interlude).mp3": "2432e53f206149bb48d96eeb10eb1fb0",
|
||||||
"Alfa Mist - Nocturne [Full Album]/05 - 05. No Peace (Feat. Tom Misch).mp3": "23e40cccc86e106ea576dc78dc8e6376",
|
"Alfa Mist - Nocturne [Full Album]/05 - 05. No Peace (Feat. Tom Misch).mp3": "f01f0f7777cb90656eb83c685b4e2942",
|
||||||
"Alfa Mist - Nocturne [Full Album]/06 - 06. Closer (Feat. Lester Duval).mp3": "cf0fe6eea473f64bbeedc3ee82d91e17",
|
"Alfa Mist - Nocturne [Full Album]/06 - 06. Closer (Feat. Lester Duval).mp3": "232c28cb707b3be59f866c02a75e760f",
|
||||||
"Alfa Mist - Nocturne [Full Album]/07 - 07. Delusions: Rumination (Interlude) (Feat. Racheal Ofori).mp3": "d9eae099a8b25f603c0491c9184092da",
|
"Alfa Mist - Nocturne [Full Album]/07 - 07. Delusions: Rumination (Interlude) (Feat. Racheal Ofori).mp3": "2371a376778e5b34265db6b1a68a2c52",
|
||||||
"Alfa Mist - Nocturne [Full Album]/08 - 08. Dreams (Feat. Carmody).mp3": "e07de2c95fb2561540048948f1478c25",
|
"Alfa Mist - Nocturne [Full Album]/08 - 08. Dreams (Feat. Carmody).mp3": "a1fad1e44c847ff2c4ef385b15efac64",
|
||||||
"Alfa Mist - Nocturne [Full Album]/09 - 09. Dreaming (Interlude) (Feat. Racheal Ofori).mp3": "218386492a03e912a29dfa1a56fd983c",
|
"Alfa Mist - Nocturne [Full Album]/09 - 09. Dreaming (Interlude) (Feat. Racheal Ofori).mp3": "a60179ce5580943e7db8aad8251aa69e",
|
||||||
"Alfa Mist - Nocturne [Full Album]/10 - 10. Hopeful (Feat. Jordan Rakei).mp3": "0fb815e3648ad55c62328a3621aadd88",
|
"Alfa Mist - Nocturne [Full Album]/10 - 10. Hopeful (Feat. Jordan Rakei).mp3": "f34d9b5efd737327b3b1484a59a49544",
|
||||||
"Alfa Mist - Nocturne [Full Album]/11 - 11. Sunrise (Pillows) (Feat. Emmavie).mp3": "860154a2aa2b0b0b31daca0a481ceb87",
|
"Alfa Mist - Nocturne [Full Album]/11 - 11. Sunrise (Pillows) (Feat. Emmavie).mp3": "88341b6f9164754151d54f504bed3eca",
|
||||||
"Alfa Mist - Nocturne [Full Album]/folder.jpg": "13be3c1a9ba600f7cab82a042cffbf72"
|
"Alfa Mist - Nocturne [Full Album]/folder.jpg": "bd3685acc53072e591bae2505ecb0648"
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
{
|
{
|
||||||
"Nocturne/01 - Intro (Feat. Racheal Ofori & Barney Artist).mp3": "75e53ffa551ab9aebe5d25a7d7bc6757",
|
"Nocturne/01 - Intro (Feat. Racheal Ofori & Barney Artist).mp3": "15157be58e0f72485de1e7e10961321f",
|
||||||
"Nocturne/02 - Answers (Feat. Rick David & Kaya Thomas - Dyke).mp3": "76f5e0a03e0984ed959809721ad2ae4a",
|
"Nocturne/02 - Answers (Feat. Rick David & Kaya Thomas - Dyke).mp3": "28ff5e2dda45771f1c6d5265dd03095e",
|
||||||
"Nocturne/03 - Blaze (Feat. Kaya Thomas - Dyke).mp3": "bd6495ad5b46ee7e46215e5687ac7e7a",
|
"Nocturne/03 - Blaze (Feat. Kaya Thomas - Dyke).mp3": "705d7fc1799c1a14aa0656bd6e2bf260",
|
||||||
"Nocturne/04 - What If (Interlude).mp3": "928a7668b9e0d653cba66cb4f9e3474a",
|
"Nocturne/04 - What If (Interlude).mp3": "9fc7715a2eadec6303a2952cabc8047d",
|
||||||
"Nocturne/05 - No Peace (Feat. Tom Misch).mp3": "563d0416c1c9b1eeedb3bd6fdc9f648a",
|
"Nocturne/05 - No Peace (Feat. Tom Misch).mp3": "5c5ca334e214d5dabaa2d8788ed07675",
|
||||||
"Nocturne/06 - Closer (Feat. Lester Duval).mp3": "eb36390ef7064ee0145b4b42e6745a1e",
|
"Nocturne/06 - Closer (Feat. Lester Duval).mp3": "91208771580610afaa010dbe235c0556",
|
||||||
"Nocturne/07 - Delusions: Rumination (Interlude) (Feat. Racheal Ofori).mp3": "9cba05a39738aadf2cd36d7941484a02",
|
"Nocturne/07 - Delusions: Rumination (Interlude) (Feat. Racheal Ofori).mp3": "cc02d8ae63e7eb056d5354576cd6e718",
|
||||||
"Nocturne/08 - Dreams (Feat. Carmody).mp3": "6833e149f8ec0c98dbd0c3b3caf9362c",
|
"Nocturne/08 - Dreams (Feat. Carmody).mp3": "bacdc9d1cf26ee0e3c0154785556a2e2",
|
||||||
"Nocturne/09 - Dreaming (Interlude) (Feat. Racheal Ofori).mp3": "2bce7f06baadb6c9cc9742e2a0288c6b",
|
"Nocturne/09 - Dreaming (Interlude) (Feat. Racheal Ofori).mp3": "cd213379cf53a8c1e4eb9134da257cd8",
|
||||||
"Nocturne/10 - Hopeful (Feat. Jordan Rakei).mp3": "2e359012ffa86e231ec6694508da1455",
|
"Nocturne/10 - Hopeful (Feat. Jordan Rakei).mp3": "ef7e06690ac849e1dd35771936d6ddb6",
|
||||||
"Nocturne/11 - Sunrise (Pillows) (Feat. Emmavie).mp3": "dded786c10aade8bb0821507225b9bd2",
|
"Nocturne/11 - Sunrise (Pillows) (Feat. Emmavie).mp3": "56293fb70edc1ef7decc2d417d87ff1d",
|
||||||
"Nocturne/folder.jpg": "13be3c1a9ba600f7cab82a042cffbf72"
|
"Nocturne/folder.jpg": "bd3685acc53072e591bae2505ecb0648"
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"Jesse's Minecraft Server [Trailer - Feb.1].ogg": "c37c289bc9b7c79464aa8bcc6df423e3",
|
"Jesse's Minecraft Server [Trailer - Feb.1].ogg": "16f66f1d81541a1f18b62bc08ad01d16",
|
||||||
"Jesse's Minecraft Server [Trailer - Feb.27].ogg": "37def0736bb5c0a7fba0c1685e90bf3c",
|
"Jesse's Minecraft Server [Trailer - Feb.27].ogg": "6f322d842a1e43b4c9981b42423d9b4c",
|
||||||
"Jesse's Minecraft Server [Trailer - Mar.21].ogg": "1fcc317ee5ce24675f6e4e6c2eae40d3"
|
"Jesse's Minecraft Server [Trailer - Mar.21].ogg": "fe1dc3361a102661c27020d5b95a2a81"
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case-thumb.jpg": "b5353a824a4800cc26f884e3025ed969",
|
||||||
|
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.mp4": "2d40822bf4c0527f9080f00357b26ce0",
|
||||||
|
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.nfo": "0c06fe6874588209fccbd9276a446750"
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case-thumb.jpg": "a81457393418b5abed785a82122f3352",
|
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case-thumb.jpg": "b5353a824a4800cc26f884e3025ed969",
|
||||||
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.mp4": "2c417b31c9f5eb8cbecf0bf1fc1f9e53",
|
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.mp4": "6f0bac1c364ff3bb13d3e8a955aaa002",
|
||||||
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.nfo": "0c06fe6874588209fccbd9276a446750"
|
"JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.nfo": "0c06fe6874588209fccbd9276a446750"
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind-thumb.jpg": "704246dd78074e8a0ec001dd8d03fd60",
|
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind-thumb.jpg": "50ee47c80f679029f5d3503bb91b045a",
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.mp4": "90ae944811ca3312fcb3175ea32a0aa5",
|
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.mp4": "8562853314b75c1e47abd4f5ba97315c",
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.nfo": "ffa10f1cbc098ace7b1c7a8fbe3097a8"
|
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.nfo": "ffa10f1cbc098ace7b1c7a8fbe3097a8"
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind-thumb.jpg": "704246dd78074e8a0ec001dd8d03fd60",
|
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind-thumb.jpg": "50ee47c80f679029f5d3503bb91b045a",
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.de.srt": "b343c3bb9257b7ee7ba38f570a115b37",
|
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.de.srt": "b343c3bb9257b7ee7ba38f570a115b37",
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.en.srt": "fe8c6ee92cae6e059fd80fd61691adbe",
|
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.en.srt": "fe8c6ee92cae6e059fd80fd61691adbe",
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.mp4": "90ae944811ca3312fcb3175ea32a0aa5",
|
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.mp4": "8562853314b75c1e47abd4f5ba97315c",
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.nfo": "ffa10f1cbc098ace7b1c7a8fbe3097a8"
|
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.nfo": "ffa10f1cbc098ace7b1c7a8fbe3097a8"
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind-thumb.jpg": "704246dd78074e8a0ec001dd8d03fd60",
|
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.mp4": "e2e60d3e3ff7739d071aa953642980af",
|
|
||||||
"JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.nfo": "ffa10f1cbc098ace7b1c7a8fbe3097a8"
|
|
||||||
}
|
|
||||||
|
|
@ -30,14 +30,14 @@
|
||||||
"Season 2013/s2013.e0719 - Project Zombie Rewind |Trailer|-thumb.jpg": "e29d49433175de8a761af35c5307791f",
|
"Season 2013/s2013.e0719 - Project Zombie Rewind |Trailer|-thumb.jpg": "e29d49433175de8a761af35c5307791f",
|
||||||
"Season 2013/s2013.e0719 - Project Zombie Rewind |Trailer|.mp4": "18620a8257a686beda65e54add4d4cd1",
|
"Season 2013/s2013.e0719 - Project Zombie Rewind |Trailer|.mp4": "18620a8257a686beda65e54add4d4cd1",
|
||||||
"Season 2013/s2013.e0719 - Project Zombie Rewind |Trailer|.nfo": "1c993c41d4308a6049333154d0adee16",
|
"Season 2013/s2013.e0719 - Project Zombie Rewind |Trailer|.nfo": "1c993c41d4308a6049333154d0adee16",
|
||||||
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer-thumb.jpg": "6f8f5e1e031ec2a04b0a4906c04a19ee",
|
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer-thumb.jpg": "705ca4e0d99b37e9ecdf6bfe4b90c59b",
|
||||||
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer.mp4": "82f6ee7253e1dbb83ae7215af08ffacc",
|
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer.mp4": "82f6ee7253e1dbb83ae7215af08ffacc",
|
||||||
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer.nfo": "cc7886aae3af6b7b0facd82f95390242",
|
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer.nfo": "cc7886aae3af6b7b0facd82f95390242",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "49cc64b25314155c1b8ab0361ac0c34f",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "28d852ede73b879b9ebf9a061cfc7d46",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt": "3d2c4e7f65d2ca5e96da38ce7eecfc4e",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt": "3d2c4e7f65d2ca5e96da38ce7eecfc4e",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "e733b4cc385b953b08c8eb0f47e03c1e",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "e733b4cc385b953b08c8eb0f47e03c1e",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "2b3ccb3f1ef81ee49fe1afb88f275a09",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "2b3ccb3f1ef81ee49fe1afb88f275a09",
|
||||||
"fanart.jpg": "c16b8b88a82cbd47d217ee80f6a8b5f3",
|
"fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
||||||
"poster.jpg": "e92872ff94c96ad49e9579501c791578",
|
"poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
||||||
"tvshow.nfo": "83c7db96081ac5bdf289fcf396bec157"
|
"tvshow.nfo": "83c7db96081ac5bdf289fcf396bec157"
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
".ytdl-sub-pz-download-archive.json": "99914b932bd37a50b983c5e7c90ae93b",
|
".ytdl-sub-pz-download-archive.json": "99914b932bd37a50b983c5e7c90ae93b",
|
||||||
"fanart.jpg": "c16b8b88a82cbd47d217ee80f6a8b5f3",
|
"fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
||||||
"poster.jpg": "e92872ff94c96ad49e9579501c791578",
|
"poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
||||||
"tvshow.nfo": "83c7db96081ac5bdf289fcf396bec157"
|
"tvshow.nfo": "83c7db96081ac5bdf289fcf396bec157"
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
".ytdl-sub-pz-download-archive.json": "756b60d7a6c47d8163e3283404493a8d",
|
".ytdl-sub-pz-download-archive.json": "756b60d7a6c47d8163e3283404493a8d",
|
||||||
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer-thumb.jpg": "6f8f5e1e031ec2a04b0a4906c04a19ee",
|
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer-thumb.jpg": "705ca4e0d99b37e9ecdf6bfe4b90c59b",
|
||||||
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer.mp4": "82f6ee7253e1dbb83ae7215af08ffacc",
|
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer.mp4": "82f6ee7253e1dbb83ae7215af08ffacc",
|
||||||
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer.nfo": "cc7886aae3af6b7b0facd82f95390242",
|
"Season 2018/s2018.e1029 - Jesse's Minecraft Server | Teaser Trailer.nfo": "cc7886aae3af6b7b0facd82f95390242",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "49cc64b25314155c1b8ab0361ac0c34f",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "28d852ede73b879b9ebf9a061cfc7d46",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt": "3d2c4e7f65d2ca5e96da38ce7eecfc4e",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt": "3d2c4e7f65d2ca5e96da38ce7eecfc4e",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "e733b4cc385b953b08c8eb0f47e03c1e",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "e733b4cc385b953b08c8eb0f47e03c1e",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "2b3ccb3f1ef81ee49fe1afb88f275a09",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "2b3ccb3f1ef81ee49fe1afb88f275a09",
|
||||||
"fanart.jpg": "c16b8b88a82cbd47d217ee80f6a8b5f3",
|
"fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
||||||
"poster.jpg": "e92872ff94c96ad49e9579501c791578",
|
"poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
||||||
"tvshow.nfo": "83c7db96081ac5bdf289fcf396bec157"
|
"tvshow.nfo": "83c7db96081ac5bdf289fcf396bec157"
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
".ytdl-sub-pz-download-archive.json": "68e164a35d1541ce761a6d7fef19ee08",
|
".ytdl-sub-pz-download-archive.json": "68e164a35d1541ce761a6d7fef19ee08",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "49cc64b25314155c1b8ab0361ac0c34f",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "28d852ede73b879b9ebf9a061cfc7d46",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt": "3d2c4e7f65d2ca5e96da38ce7eecfc4e",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt": "3d2c4e7f65d2ca5e96da38ce7eecfc4e",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "e733b4cc385b953b08c8eb0f47e03c1e",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "e733b4cc385b953b08c8eb0f47e03c1e",
|
||||||
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "2b3ccb3f1ef81ee49fe1afb88f275a09",
|
"Season 2018/s2018.e1102 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "2b3ccb3f1ef81ee49fe1afb88f275a09",
|
||||||
"fanart.jpg": "c16b8b88a82cbd47d217ee80f6a8b5f3",
|
"fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
||||||
"poster.jpg": "e92872ff94c96ad49e9579501c791578",
|
"poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
||||||
"tvshow.nfo": "83c7db96081ac5bdf289fcf396bec157"
|
"tvshow.nfo": "83c7db96081ac5bdf289fcf396bec157"
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"JMC - Jesse's Minecraft Server-thumb.jpg": "a3f1910f9c51f6442f845a528e190829",
|
"JMC - Jesse's Minecraft Server-thumb.jpg": "a3f1910f9c51f6442f845a528e190829",
|
||||||
"JMC - Jesse's Minecraft Server.mkv": "f523ac968dd9dfbd1954cbca72ad4108",
|
"JMC - Jesse's Minecraft Server.mkv": "21f246b1c922e11add509ea26c43c53d",
|
||||||
"JMC - Jesse's Minecraft Server.nfo": "10df5dcdb65ab18ecf21b3503c77e48b"
|
"JMC - Jesse's Minecraft Server.nfo": "10df5dcdb65ab18ecf21b3503c77e48b"
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"Project Zombie - 1-6.Intro.mp4": "eaec6f50f364b13ef1a201e736ec9c05",
|
"Project Zombie - 1-6.Intro.mp4": "59f09bbdb1582890529241a0333663a6",
|
||||||
"Project Zombie - 2-6.Part 1.mp4": "5850b19acb250cc13db36f80fa1bba5a",
|
"Project Zombie - 2-6.Part 1.mp4": "8789e4e55a0c3f76c54528c8029d1581",
|
||||||
"Project Zombie - 3-6.Part 2.mp4": "445d95eba437db6df284df7e1ab633e8",
|
"Project Zombie - 3-6.Part 2.mp4": "3c9021548247e9e7c7a6682ecd4d30ec",
|
||||||
"Project Zombie - 4-6.Part 3.mp4": "2b6e7532d515c9e64ed2a33d850cf199",
|
"Project Zombie - 4-6.Part 3.mp4": "16e0ce13790a998b3a5b3860c10f4dd8",
|
||||||
"Project Zombie - 5-6.Part 4.mp4": "842bf3c4d1fcc4c5ab110635935dac66",
|
"Project Zombie - 5-6.Part 4.mp4": "de9afd0b6fa5f845b086e2d4915f2f14",
|
||||||
"Project Zombie - 6-6.Part 5.mp4": "238de99f00f829ab72f042b79da9a33a",
|
"Project Zombie - 6-6.Part 5.mp4": "d485c2a5df9a2d5bf9037a7a96a04d10",
|
||||||
"Project Zombie - Intro-thumb.jpg": "fb95b510681676e81c321171fc23143e",
|
"Project Zombie - Intro-thumb.jpg": "fb95b510681676e81c321171fc23143e",
|
||||||
"Project Zombie - Intro.nfo": "ded59ac906f579312cc3cf98a57e7ea3",
|
"Project Zombie - Intro.nfo": "ded59ac906f579312cc3cf98a57e7ea3",
|
||||||
"Project Zombie - Part 1-thumb.jpg": "fb95b510681676e81c321171fc23143e",
|
"Project Zombie - Part 1-thumb.jpg": "fb95b510681676e81c321171fc23143e",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"JMC - Oblivion Mod "Falcor" p.1-thumb.jpg": "fb95b510681676e81c321171fc23143e",
|
"JMC - Oblivion Mod "Falcor" p.1-thumb.jpg": "fb95b510681676e81c321171fc23143e",
|
||||||
"JMC - Oblivion Mod "Falcor" p.1.mp4": "170bec01308f639da7459c51ec4a1d7e",
|
"JMC - Oblivion Mod "Falcor" p.1.mp4": "28c14cdac05c803efe71abb9454ab306",
|
||||||
"JMC - Oblivion Mod "Falcor" p.1.nfo": "89f509a8a3d9003e22a9091abeeae5dc"
|
"JMC - Oblivion Mod "Falcor" p.1.nfo": "89f509a8a3d9003e22a9091abeeae5dc"
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"JMC - Oblivion Mod "Falcor" p.1-thumb.jpg": "fb95b510681676e81c321171fc23143e",
|
|
||||||
"JMC - Oblivion Mod "Falcor" p.1.mp4": "567631875d95fee5899e2ff407b74fd8",
|
|
||||||
"JMC - Oblivion Mod "Falcor" p.1.nfo": "89f509a8a3d9003e22a9091abeeae5dc"
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
Files created in '{output_directory}'
|
||||||
|
----------------------------------------
|
||||||
|
JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case-thumb.jpg
|
||||||
|
JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.mp4
|
||||||
|
Chapters embedded from timestamp file:
|
||||||
|
0:00: Intro
|
||||||
|
0:10: Part 1
|
||||||
|
0:20: Part 2
|
||||||
|
0:30: Part 3
|
||||||
|
0:40: Part 4
|
||||||
|
1:01: Part 5
|
||||||
|
Embedded subtitles with lang(s) en, de
|
||||||
|
Video Tags:
|
||||||
|
description:
|
||||||
|
🎸 / ' "
|
||||||
|
newline?
|
||||||
|
JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.nfo
|
||||||
|
NFO tags:
|
||||||
|
musicvideo:
|
||||||
|
album: Music Videos
|
||||||
|
artist: JMC
|
||||||
|
title: This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case
|
||||||
|
year: 2021
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
Files created in '{output_directory}'
|
|
||||||
----------------------------------------
|
|
||||||
JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind-thumb.jpg
|
|
||||||
JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.mp4
|
|
||||||
Chapters embedded into the video:
|
|
||||||
0:00: Intro
|
|
||||||
0:10: Part 1
|
|
||||||
0:20: Part 2
|
|
||||||
0:30: Part 3
|
|
||||||
0:40: Part 4
|
|
||||||
1:01: Part 5
|
|
||||||
Embedded subtitles with lang(s) en, de
|
|
||||||
Video Tags:
|
|
||||||
title: YouTube Rewind 2019: For the Record | #YouTubeRewind
|
|
||||||
JMC - YouTube Rewind 2019: For the Record | #YouTubeRewind.nfo
|
|
||||||
NFO tags:
|
|
||||||
musicvideo:
|
|
||||||
album: Music Videos
|
|
||||||
artist: JMC
|
|
||||||
title: YouTube Rewind 2019: For the Record | #YouTubeRewind
|
|
||||||
year: 2019
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
Files created in '{output_directory}'
|
|
||||||
----------------------------------------
|
|
||||||
JMC - Oblivion Mod "Falcor" p.1-thumb.jpg
|
|
||||||
JMC - Oblivion Mod "Falcor" p.1.mp4
|
|
||||||
Chapters embedded into the video:
|
|
||||||
0:00: Intro
|
|
||||||
0:10: Part 1
|
|
||||||
0:20: Part 2
|
|
||||||
0:30: Part 3
|
|
||||||
0:40: Part 4
|
|
||||||
1:01: Part 5
|
|
||||||
Video Tags:
|
|
||||||
description:
|
|
||||||
🎸 / ' "
|
|
||||||
newline?
|
|
||||||
title: Oblivion Mod "Falcor" p.1
|
|
||||||
JMC - Oblivion Mod "Falcor" p.1.nfo
|
|
||||||
NFO tags:
|
|
||||||
musicvideo:
|
|
||||||
album: Music Videos
|
|
||||||
artist: JMC
|
|
||||||
title: Oblivion Mod "Falcor" p.1
|
|
||||||
year: 2010
|
|
||||||
|
|
@ -86,34 +86,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",
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.parametrize("dry_run", [True, False])
|
|
||||||
def test_single_video_with_timestamp_chapters_download(
|
|
||||||
self,
|
|
||||||
timestamps_file_path,
|
|
||||||
music_video_config,
|
|
||||||
single_video_preset_dict,
|
|
||||||
output_directory,
|
|
||||||
dry_run,
|
|
||||||
):
|
|
||||||
# Test chapters and video tags, throw in a video tag with special chars while we are at it
|
|
||||||
single_video_preset_dict["youtube"]["chapter_timestamps"] = timestamps_file_path
|
|
||||||
single_video_preset_dict["video_tags"]["tags"]["description"] = "🎸 / ' \" \n newline?"
|
|
||||||
|
|
||||||
single_video_subscription = Subscription.from_dict(
|
|
||||||
config=music_video_config,
|
|
||||||
preset_name="music_video_single_video_test",
|
|
||||||
preset_dict=single_video_preset_dict,
|
|
||||||
)
|
|
||||||
|
|
||||||
transaction_log = single_video_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_video_with_chapter_timestamps.txt",
|
|
||||||
)
|
|
||||||
assert_expected_downloads(
|
|
||||||
output_directory=output_directory,
|
|
||||||
dry_run=dry_run,
|
|
||||||
expected_download_summary_file_name="youtube/test_video_with_chapter_timestamps.json",
|
|
||||||
)
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue