diff --git a/test/test_postprocessors.py b/test/test_postprocessors.py index ecc73e39eb..a742872e55 100644 --- a/test/test_postprocessors.py +++ b/test/test_postprocessors.py @@ -14,6 +14,7 @@ from yt_dlp import YoutubeDL from yt_dlp.utils import shell_quote from yt_dlp.postprocessor import ( ExecPP, + FFmpegExtractAudioPP, FFmpegThumbnailsConvertorPP, MetadataFromFieldPP, MetadataParserPP, @@ -627,5 +628,76 @@ outpoint 10.000000 self._pp._quote_for_ffmpeg("special ' characters ' galore'''")) +class TestFFmpegExtractAudioPP(unittest.TestCase): + def setUp(self): + self.ydl = YoutubeDL() + + def test_no_overwrites_respected(self): + """Test that --no-overwrites prevents overwriting extracted audio files""" + pp = FFmpegExtractAudioPP(self.ydl, overwrites=False) + + # Create a mock info dict + info = { + 'filepath': 'test.webm', + 'ext': 'webm' + } + + # Mock the necessary methods to avoid ffmpeg dependency + import unittest.mock + with unittest.mock.patch.object(pp, 'get_audio_codec', return_value='opus'), \ + unittest.mock.patch('os.path.exists') as mock_exists, \ + unittest.mock.patch('yt_dlp.postprocessor.ffmpeg.replace_extension') as mock_replace_ext: + + # Mock replace_extension to return a different filename + mock_replace_ext.return_value = 'test.opus' + # Mock os.path.exists to return True for the target file + mock_exists.return_value = True + + # Should skip processing when target file exists and overwrites=False + files_to_delete, result_info = pp.run(info) + + # Should return empty list (no files to delete) and original info + self.assertEqual(files_to_delete, []) + self.assertEqual(result_info, info) + + def test_no_post_overwrites_respected(self): + """Test that --no-post-overwrites prevents overwriting extracted audio files""" + pp = FFmpegExtractAudioPP(self.ydl, nopostoverwrites=True) + + # Create a mock info dict + info = { + 'filepath': 'test.webm', + 'ext': 'webm' + } + + # Mock the necessary methods to avoid ffmpeg dependency + import unittest.mock + with unittest.mock.patch.object(pp, 'get_audio_codec', return_value='opus'), \ + unittest.mock.patch('os.path.exists') as mock_exists, \ + unittest.mock.patch('yt_dlp.postprocessor.ffmpeg.replace_extension') as mock_replace_ext: + + # Mock replace_extension to return a different filename + mock_replace_ext.return_value = 'test.opus' + # Mock os.path.exists to return True for the target file + mock_exists.return_value = True + + # Should skip processing when target file exists and nopostoverwrites=True + files_to_delete, result_info = pp.run(info) + + # Should return empty list (no files to delete) and original info + self.assertEqual(files_to_delete, []) + self.assertEqual(result_info, info) + + def test_overwrites_allowed(self): + """Test that overwriting works when neither option is set""" + pp = FFmpegExtractAudioPP(self.ydl, overwrites=None, nopostoverwrites=False) + + # This test would require more complex mocking to test the actual processing + # For now, just verify the constructor accepts the parameters + self.assertIsNotNone(pp) + self.assertFalse(pp._nopostoverwrites) + self.assertIsNone(pp._overwrites) + + if __name__ == '__main__': unittest.main() diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index 714d9ad5c2..afc0e74bcc 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -653,6 +653,7 @@ def get_postprocessors(opts): 'preferredcodec': opts.audioformat, 'preferredquality': opts.audioquality, 'nopostoverwrites': opts.nopostoverwrites, + 'overwrites': opts.overwrites, } if opts.remuxvideo: yield { diff --git a/yt_dlp/postprocessor/ffmpeg.py b/yt_dlp/postprocessor/ffmpeg.py index 59a49aa578..4013eae248 100644 --- a/yt_dlp/postprocessor/ffmpeg.py +++ b/yt_dlp/postprocessor/ffmpeg.py @@ -441,11 +441,12 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): SUPPORTED_EXTS = tuple(ACODECS.keys()) FORMAT_RE = create_mapping_re(('best', *SUPPORTED_EXTS)) - def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, nopostoverwrites=False): + def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, nopostoverwrites=False, overwrites=None): FFmpegPostProcessor.__init__(self, downloader) self.mapping = preferredcodec or 'best' self._preferredquality = float_or_none(preferredquality) self._nopostoverwrites = nopostoverwrites + self._overwrites = overwrites def _quality_args(self, codec): if self._preferredquality is None: @@ -521,8 +522,13 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor): return [], information orig_path = prepend_extension(path, 'orig') temp_path = prepend_extension(path, 'temp') - if (self._nopostoverwrites and os.path.exists(new_path) - and os.path.exists(orig_path)): + # Check if we should skip due to existing file + # Respect both --no-overwrites and --no-post-overwrites + should_skip_overwrite = ( + (self._nopostoverwrites or self._overwrites is False) + and os.path.exists(new_path) + ) + if should_skip_overwrite: self.to_screen(f'Post-process file {new_path} exists, skipping') return [], information