From f0b5cdeeb408205c4d15b7670017e835e9e50036 Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:58:05 -0700 Subject: [PATCH 1/2] fix: do not delete .mp3 file remuxed from MPEGLAYER3 WAV remux_mpeglayer3_wav() writes the extracted MP3 stream to path.with_suffix('.mp3') and then removes the source. When the source file is already named .mp3 (a WAVE_FORMAT_MPEGLAYER3 file with an mp3 extension), both paths are identical, so the freshly written file was immediately deleted and the import silently lost the track. Only remove the source when it is a distinct path from the remuxed output. Adds a regression test covering the .mp3-named input. Closes #6748 --- beets/util/extension.py | 6 +++++- docs/changelog.rst | 3 +++ test/test_importer.py | 11 +++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/beets/util/extension.py b/beets/util/extension.py index 7b87a98c93..2a44dfeaae 100644 --- a/beets/util/extension.py +++ b/beets/util/extension.py @@ -170,7 +170,11 @@ def remux_mpeglayer3_wav(path: AnyPath) -> AnyPath | None: mp3_path = syspath.with_suffix(".mp3") mp3_path.write_bytes(mp3_data) - util.remove(path) + # When the source is already named `.mp3`, it has just been rewritten in + # place with the extracted stream, so there is no separate original left + # to remove. + if mp3_path != syspath: + util.remove(path) if isinstance(path, str): return str(mp3_path) diff --git a/docs/changelog.rst b/docs/changelog.rst index acbd3294fe..d7c3e774a4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -95,6 +95,9 @@ Bug fixes valid date/time string" error instead of crashing with an uncaught ``KeyError``. A ``|`` was being accepted as a relative-date unit due to a regular expression character-class typo. +- ``remux_mp3_in_wav`` no longer deletes the imported file when a + MPEGLAYER3 WAV is named ``.mp3``: the remuxed stream was written back to the + same path and then removed as if it were a leftover original. :bug:`6748` .. For plugin developers diff --git a/test/test_importer.py b/test/test_importer.py index c626202c7b..ef1e72197b 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -1894,6 +1894,17 @@ def test_remux_mpeglayer3_wav(self): assert mp3_path.exists() assert not dest.exists() + def test_remux_mpeglayer3_wav_mp3_extension(self): + """A MPEGLAYER3 WAV named `.mp3` must not be deleted (#6748).""" + src = _common.RSRC / "mpeglayer3.wav" + dest = self.temp_path / "mpeglayer3.mp3" + shutil.copy(syspath(src), syspath(dest)) + + mp3_path = remux_mpeglayer3_wav(dest) + + assert mp3_path == dest + assert dest.exists() + def test_remux_mpeglayer3_wav_disabled(self): """When remux_mp3_in_wav is disabled, WAV file should not be remuxed.""" self.config["import"]["remux_mp3_in_wav"] = False From 8cf0e87d2d182c99f114e3319528b791e53d516b Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Thu, 6 Aug 2026 01:56:40 -0700 Subject: [PATCH 2/2] fix: use samefile for the remux self-delete guard Path inequality misses a case-only difference on a case-insensitive filesystem, so a .MP3 source was still deleted. Also clarify the changelog and test wording to say .mp3 extension rather than name. --- beets/util/extension.py | 9 +++++---- docs/changelog.rst | 5 +++-- test/test_importer.py | 17 ++++++++++++++++- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/beets/util/extension.py b/beets/util/extension.py index 2a44dfeaae..fb3d7da493 100644 --- a/beets/util/extension.py +++ b/beets/util/extension.py @@ -170,10 +170,11 @@ def remux_mpeglayer3_wav(path: AnyPath) -> AnyPath | None: mp3_path = syspath.with_suffix(".mp3") mp3_path.write_bytes(mp3_data) - # When the source is already named `.mp3`, it has just been rewritten in - # place with the extracted stream, so there is no separate original left - # to remove. - if mp3_path != syspath: + # When the source already has an `.mp3` extension, it has just been + # rewritten in place with the extracted stream, so there is no separate + # original left to remove. Compare with `samefile` so that a case-only + # difference on a case-insensitive filesystem is caught too. + if not util.samefile(mp3_path, syspath): util.remove(path) if isinstance(path, str): diff --git a/docs/changelog.rst b/docs/changelog.rst index d7c3e774a4..b5b318366e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -96,8 +96,9 @@ Bug fixes ``KeyError``. A ``|`` was being accepted as a relative-date unit due to a regular expression character-class typo. - ``remux_mp3_in_wav`` no longer deletes the imported file when a - MPEGLAYER3 WAV is named ``.mp3``: the remuxed stream was written back to the - same path and then removed as if it were a leftover original. :bug:`6748` + MPEGLAYER3 WAV has an ``.mp3`` extension: the remuxed stream was written back + to the same path and then removed as if it were a leftover original. + :bug:`6748` .. For plugin developers diff --git a/test/test_importer.py b/test/test_importer.py index ef1e72197b..44a2a32bfc 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -1895,7 +1895,10 @@ def test_remux_mpeglayer3_wav(self): assert not dest.exists() def test_remux_mpeglayer3_wav_mp3_extension(self): - """A MPEGLAYER3 WAV named `.mp3` must not be deleted (#6748).""" + """A MPEGLAYER3 WAV with an `.mp3` extension must not be deleted. + + See #6748. + """ src = _common.RSRC / "mpeglayer3.wav" dest = self.temp_path / "mpeglayer3.mp3" shutil.copy(syspath(src), syspath(dest)) @@ -1905,6 +1908,18 @@ def test_remux_mpeglayer3_wav_mp3_extension(self): assert mp3_path == dest assert dest.exists() + def test_remux_mpeglayer3_wav_mp3_extension_uppercase(self): + """The same holds when the extension differs only by case.""" + src = _common.RSRC / "mpeglayer3.wav" + dest = self.temp_path / "mpeglayer3.MP3" + shutil.copy(syspath(src), syspath(dest)) + + mp3_path = remux_mpeglayer3_wav(dest) + + assert dest.exists() + assert mp3_path is not None + assert mp3_path.exists() + def test_remux_mpeglayer3_wav_disabled(self): """When remux_mp3_in_wav is disabled, WAV file should not be remuxed.""" self.config["import"]["remux_mp3_in_wav"] = False