From 9802eb202a107480b3c9e08c239f4679124770dd Mon Sep 17 00:00:00 2001 From: jbannon Date: Sun, 19 Jun 2022 05:49:19 +0000 Subject: [PATCH] working test --- .../downloaders/youtube/split_video.py | 85 +++++++++++++------ tests/e2e/youtube/test_split_video.py | 34 ++++++-- 2 files changed, 84 insertions(+), 35 deletions(-) diff --git a/src/ytdl_sub/downloaders/youtube/split_video.py b/src/ytdl_sub/downloaders/youtube/split_video.py index d205aa70..4b9be824 100644 --- a/src/ytdl_sub/downloaders/youtube/split_video.py +++ b/src/ytdl_sub/downloaders/youtube/split_video.py @@ -25,9 +25,13 @@ from ytdl_sub.validators.validators import StringValidator _SPLIT_TIMESTAMP_REGEX = re.compile(r"^((?:\d\d:)?(?:\d:)?(?:\d)?\d:\d\d) (.+)$") -def _parse_split_timestamp_file(split_timestamp_path: str) -> List[Tuple[str, str]]: +def _split_video_uid(source_uid: str, idx: int) -> str: + return f"{source_uid}___{idx}" + + +def _parse_split_timestamp_file(split_timestamp_path: str) -> Tuple[List[str], List[str]]: """ - Returns list of (00:00:00 title), each timestamp as HH:MM:SS + Returns two lists, one containing timestamps in HH:MM:SS format, and the other titles """ if not os.path.isfile(split_timestamp_path): raise ValidationException( @@ -37,7 +41,8 @@ def _parse_split_timestamp_file(split_timestamp_path: str) -> List[Tuple[str, st with open(split_timestamp_path, "r", encoding="utf-8") as file: lines = file.readlines() - timestamp_titles: List[Tuple[str, str]] = [] + timestamps: List[str] = [] + titles: List[str] = [] idx = 0 for idx, line in enumerate(lines): match = _SPLIT_TIMESTAMP_REGEX.match(line) @@ -57,7 +62,8 @@ def _parse_split_timestamp_file(split_timestamp_path: str) -> List[Tuple[str, st pass assert len(timestamp) == 8 - timestamp_titles.append((timestamp, title)) + timestamps.append(timestamp) + titles.append(title) if idx not in (len(lines) - 1, len(lines) - 2): raise ValidationException( @@ -65,7 +71,20 @@ def _parse_split_timestamp_file(split_timestamp_path: str) -> List[Tuple[str, st f"Each line must be formatted as '0:00 title' - a timestamp, space, then title." ) - return timestamp_titles + return timestamps, titles + + +def _split_video_ffmpeg_cmd( + input_file: str, output_file: str, timestamps: List[str], idx: int +) -> List[str]: + timestamp_begin = timestamps[idx] + timestamp_end = timestamps[idx + 1] if idx + 1 < len(timestamps) else "" + + cmd = ["-i", input_file, "-ss", timestamp_begin] + if timestamp_end: + cmd += ["-to", timestamp_end] + cmd += ["-vcodec", "copy", "-acodec", "copy", output_file] + return cmd ############################################################################### @@ -151,11 +170,29 @@ class YoutubeSplitVideoDownloader( **{"break_on_existing": True}, ) + def _create_split_video_entry( + self, source_entry_dict: Dict, title: str, idx: int, split_video_count: int + ) -> YoutubePlaylistVideo: + """ + 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["id"] = _split_video_uid(source_uid=entry_dict["id"], idx=idx) + + # Remove track and artist since its now split + del entry_dict["track"] + del entry_dict["artist"] + + return YoutubePlaylistVideo(entry_dict=entry_dict, working_directory=self.working_directory) + def download(self) -> List[YoutubePlaylistVideo]: """Download a single Youtube video, then split it into multiple videos""" split_videos: List[YoutubePlaylistVideo] = [] - timestamp_titles = _parse_split_timestamp_file( + timestamps, titles = _parse_split_timestamp_file( split_timestamp_path=self.download_options.split_timestamps ) entry_dict = self.extract_info(url=self.download_options.video_url) @@ -165,36 +202,32 @@ class YoutubeSplitVideoDownloader( # when copying it convert_download_thumbnail(entry=entry) - for idx, timestamp_title in enumerate(timestamp_titles): - timestamp_begin, title = timestamp_title - timestamp_end = timestamp_titles[idx + 1][0] if idx + 1 < len(timestamp_titles) else "" - - new_uid = f"{entry.uid}___{idx}" + for idx, title in enumerate(titles): + new_uid = _split_video_uid(source_uid=entry.uid, idx=idx) + # Get the input/output file paths input_file = entry.get_download_file_path() output_file = str(Path(self.working_directory) / f"{new_uid}.{entry.ext}") output_thumbnail_file = str( Path(self.working_directory) / f"{new_uid}.{entry.thumbnail_ext}" ) - entry_dict_ = copy.deepcopy(entry_dict) - entry_dict_["title"] = title - entry_dict_["playlist_index"] = idx + 1 - entry_dict_["playlist_count"] = len(timestamp_titles) - entry_dict_["id"] = new_uid - - cmd = ["-i", input_file, "-ss", timestamp_begin] - if timestamp_end: - cmd += ["-to", timestamp_end] - cmd += ["-vcodec", "copy", "-acodec", "copy", output_file] - - FFMPEG.run(cmd) - + # Run ffmpeg to create the split the video + FFMPEG.run( + _split_video_ffmpeg_cmd( + input_file=input_file, output_file=output_file, timestamps=timestamps, idx=idx + ) + ) + # Copy the thumbnail copyfile(src=entry.get_download_thumbnail_path(), dst=output_thumbnail_file) + # Format the split video as a YoutubePlaylistVideo split_videos.append( - YoutubePlaylistVideo( - entry_dict=entry_dict_, working_directory=self.working_directory + self._create_split_video_entry( + source_entry_dict=entry_dict, + title=title, + idx=idx, + split_video_count=len(timestamps), ) ) diff --git a/tests/e2e/youtube/test_split_video.py b/tests/e2e/youtube/test_split_video.py index cb2b170c..4a71de93 100644 --- a/tests/e2e/youtube/test_split_video.py +++ b/tests/e2e/youtube/test_split_video.py @@ -21,11 +21,11 @@ def config_path(): def split_timestamps_file_path(): timestamps = [ "0:00 Intro\n", - "00:15 Part 1\n", - "1:01 Part 2\n", - "01:24 Part 3\n", - "0:02:01 Part 4\n", - "00:02:33 Part 5\n", + "00:10 Part 1\n", + "0:20 Part 2\n", + "00:30 Part 3\n", + "0:00:40 Part 4\n", + "00:01:01 Part 5\n", ] with NamedTemporaryFile(mode="w", encoding="utf-8", suffix=".txt") as tmp: @@ -56,7 +56,8 @@ def subscription_dict(output_directory, subscription_name, split_timestamps_file # override the output directory with our fixture-generated dir "output_options": { "output_directory": output_directory, - "file_name": "{playlist_index}.{title_sanitized}.{ext}", + "file_name": "{channel_sanitized} - {playlist_index}-{playlist_size}" + ".{title_sanitized}.{ext}", }, # download the worst format so it is fast "ytdl_options": { @@ -89,9 +90,24 @@ def expected_single_video_download(): # fmt: off return ExpectedDownload( expected_md5_file_hashes={ - Path("JMC - Whale & Wasp.jpg"): "b58377dfe7c39527e1990a24b36bbd77", - Path("JMC - Whale & Wasp.mp4"): "931a705864c57d21d6fedebed4af6bbc", - Path("JMC - Whale & Wasp.nfo"): "6c2f085adb847c1dcc47c19514c454d8", + 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.jpg'): "e87282e4115baa8b5c727fb4de15316d", + Path('Project Zombie - Intro.nfo'): "ded59ac906f579312cc3cf98a57e7ea3", + Path('Project Zombie - Part 1.jpg'): "e87282e4115baa8b5c727fb4de15316d", + Path('Project Zombie - Part 1.nfo'): "70ff5cd0092b8bc22dc4db93a824789b", + Path('Project Zombie - Part 2.jpg'): "e87282e4115baa8b5c727fb4de15316d", + Path('Project Zombie - Part 2.nfo'): "54450c18a2cbb9d6d2ee5d0a1fb3f279", + Path('Project Zombie - Part 3.jpg'): "e87282e4115baa8b5c727fb4de15316d", + Path('Project Zombie - Part 3.nfo'): "0effb13fc4039363a95969d1048dde57", + Path('Project Zombie - Part 4.jpg'): "e87282e4115baa8b5c727fb4de15316d", + Path('Project Zombie - Part 4.nfo'): "74bd0d7c12105469838768a0cc323a8c", + Path('Project Zombie - Part 5.jpg'): "e87282e4115baa8b5c727fb4de15316d", + Path('Project Zombie - Part 5.nfo'): "a8cf2e77721335ea7c18e22734e7996c", } ) # fmt: on