diff --git a/src/ytdl_sub/utils/ffmpeg.py b/src/ytdl_sub/utils/ffmpeg.py index affed520..f8ccdbfa 100644 --- a/src/ytdl_sub/utils/ffmpeg.py +++ b/src/ytdl_sub/utils/ffmpeg.py @@ -1,4 +1,3 @@ -import shutil import subprocess import tempfile from typing import Dict @@ -7,6 +6,7 @@ from typing import Optional from ytdl_sub.utils.chapters import Chapters from ytdl_sub.utils.exceptions import ValidationException +from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.logger import Logger logger = Logger.get(name="ffmpeg") @@ -132,8 +132,7 @@ def set_ffmpeg_metadata_chapters( output_file_path, ] ) - - shutil.move(src=output_file_path, dst=file_path) + FileHandler.move(output_file_path, file_path) def add_ffmpeg_metadata_key_values(file_path: str, key_values: Dict[str, str]) -> None: @@ -154,4 +153,4 @@ def add_ffmpeg_metadata_key_values(file_path: str, key_values: Dict[str, str]) - ffmpeg_args.extend(["-codec", "copy", output_file_path]) FFMPEG.run(ffmpeg_args) - shutil.move(src=output_file_path, dst=file_path) + FileHandler.move(output_file_path, file_path) diff --git a/src/ytdl_sub/utils/file_handler.py b/src/ytdl_sub/utils/file_handler.py index ddabadd4..6a3dc6c9 100644 --- a/src/ytdl_sub/utils/file_handler.py +++ b/src/ytdl_sub/utils/file_handler.py @@ -353,8 +353,23 @@ class FileHandler: Source file dst_file_path Destination file + + Raises + ------ + OSError + Cross-device link workaround """ - shutil.move(src=src_file_path, dst=dst_file_path) + try: + shutil.move(src=src_file_path, dst=dst_file_path) + except OSError as os_error_exc: + # Invalid cross-device link + # Can happen from using os.rename under the hood, which requires the two file on the + # same filesystem. Work around it by copying and deleting the file + if os_error_exc.errno == 18: + cls.copy(src_file_path, dst_file_path) + cls.delete(src_file_path) + else: + raise @classmethod def delete(cls, file_path: Union[str, Path]): diff --git a/tests/e2e/plugins/internal/test_view.py b/tests/e2e/plugins/internal/test_view.py index 08d92154..590bfc6e 100644 --- a/tests/e2e/plugins/internal/test_view.py +++ b/tests/e2e/plugins/internal/test_view.py @@ -1,6 +1,10 @@ +from typing import Optional + import pytest from e2e.conftest import mock_run_from_cli +from ytdl_sub.utils.file_handler import FileMetadata + class TestView: @pytest.mark.parametrize("split_chapters", [True, False]) @@ -22,12 +26,15 @@ class TestView: # Ensure the video and thumbnail are recognized assert len(transaction_log.files_created) == 2 - file_name = f"avUT-zd9v68{'___0' if split_chapters else ''}.webm" - video_file = transaction_log.files_created.get(file_name) - assert video_file is not None + video_metadata: Optional[FileMetadata] = None + for file_name, metadata in transaction_log.files_created.items(): + if file_name.endswith("webm"): + video_metadata = metadata + break - assert "Source Variables:" in video_file.metadata + assert video_metadata is not None + assert "Source Variables:" in video_metadata.metadata if split_chapters: - assert " chapter_index: 1" in video_file.metadata + assert " chapter_index: 1" in video_metadata.metadata else: - assert " chapter_index: 1" not in video_file.metadata + assert " chapter_index: 1" not in video_metadata.metadata diff --git a/tests/resources/expected_downloads_summaries/plugins/chapters/test_chapters_from_comments.json b/tests/resources/expected_downloads_summaries/plugins/chapters/test_chapters_from_comments.json index 2836f6a3..67e3fd84 100644 --- a/tests/resources/expected_downloads_summaries/plugins/chapters/test_chapters_from_comments.json +++ b/tests/resources/expected_downloads_summaries/plugins/chapters/test_chapters_from_comments.json @@ -1,7 +1,7 @@ { ".ytdl-sub-chapters_from_comments-download-archive.json": "122723ce8d257eebb05178daa26141f6", "JMC/JMC - Move 78 - Automated Improvisation [Full Album]-thumb.jpg": "c12e6a6f242680d1096a1a99d74a62c6", - "JMC/JMC - Move 78 - Automated Improvisation [Full Album].info.json": "228adc1dbc785852c8266457af0adadc", - "JMC/JMC - Move 78 - Automated Improvisation [Full Album].mp4": "68c1c4b20b89511b26a92b967e54891e", + "JMC/JMC - Move 78 - Automated Improvisation [Full Album].info.json": "ee8437e52186219ff510214138f00e28", + "JMC/JMC - Move 78 - Automated Improvisation [Full Album].mp4": "8b8a9a731a19bc37feb091559f97ebbc", "JMC/JMC - Move 78 - Automated Improvisation [Full Album].nfo": "7a65b184d24c68fc0ec5380432250f5b" -} \ No newline at end of file +} diff --git a/tests/resources/expected_downloads_summaries/youtube/test_channel_full.json b/tests/resources/expected_downloads_summaries/youtube/test_channel_full.json index b0d22ec7..a4dc9b7b 100644 --- a/tests/resources/expected_downloads_summaries/youtube/test_channel_full.json +++ b/tests/resources/expected_downloads_summaries/youtube/test_channel_full.json @@ -25,7 +25,7 @@ "Project ⧸ Zombie/Season 2011/s2011.e112101 - qbMJh2df1M4.mp4": "60c3221125afbef78990bdcead78a82d", "Project ⧸ Zombie/Season 2011/s2011.e112101 - qbMJh2df1M4.nfo": "3c5e407a428161b75474eea13a82e009", "Project ⧸ Zombie/Season 2012/s2012.e012301 - y5-3ovwQQ_U-thumb.jpg": "54ebe9df801b278fdd17b21afa8373a6", - "Project ⧸ Zombie/Season 2012/s2012.e012301 - y5-3ovwQQ_U.mp4": "cdf5d458e148bbadba6dfed5b05e378b", + "Project ⧸ Zombie/Season 2012/s2012.e012301 - y5-3ovwQQ_U.mp4": "2141f0f928d55e94fef7f7d3c7a4aca0", "Project ⧸ Zombie/Season 2012/s2012.e012301 - y5-3ovwQQ_U.nfo": "a0d59e102f06b93718ab20e0238b4f95", "Project ⧸ Zombie/Season 2013/s2013.e071901 - c_PZdc0Zi7M-thumb.jpg": "e29d49433175de8a761af35c5307791f", "Project ⧸ Zombie/Season 2013/s2013.e071901 - c_PZdc0Zi7M.mp4": "3fb239f0a646e515bbaaeb6f6d5cdd69",