From bda338348c0d223afe802821429a4db5557dc0f2 Mon Sep 17 00:00:00 2001 From: 0xcodepunk <1329474@gmail.com> Date: Sun, 12 Jul 2026 19:03:44 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20MP3-=D1=84=D0=BE=D0=BB=D0=B1=D1=8D=D0=BA?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=81=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D0=B5=D1=82=20AAC-=D0=BF=D0=BE=D1=82=D0=BE=D0=BA?= =?UTF-8?q?=20=D0=B2=20MP3-=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=B9=D0=BD?= =?UTF-8?q?=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - get_track_source запрашивает get_file_info с фильтром codecs=mp3: без него «лучшим по битрейту» для треков без MP3-320 оказывался AAC, и ffmpeg падал в crash-loop на -c:a copy -f mp3 (Could not write header) - добавлен разовый фолбэк на lossless для треков, у которых MP3 нет вовсе, даже при APP_PREFER_LOSSLESS=False - добавлены тесты: AAC не выбирается вместо лучшего MP3; трек без MP3 спасается lossless-ссылкой Примечание: регрессия проявилась после отключения FLAC на проде — весь трафик пошёл через MP3-фолбэк, и треки без MP3-320 (у которых Яндекс отдаёт AAC-256 как максимум) роняли поток в бесконечный перезапуск. --- src/main_stream_service/yandex_music_api.py | 12 ++++++- tests/test_yandex_music_api.py | 40 +++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/main_stream_service/yandex_music_api.py b/src/main_stream_service/yandex_music_api.py index ca1ffeb..a9ed8bc 100644 --- a/src/main_stream_service/yandex_music_api.py +++ b/src/main_stream_service/yandex_music_api.py @@ -58,9 +58,19 @@ async def get_track_source( if lossless_url: return TrackSource(url=lossless_url, codec="flac") - mp3_url = await self.get_file_info(track_id, quality=quality) + # Только MP3: без фильтра «лучшим по битрейту» может оказаться + # AAC, а его копирование в MP3-контейнер роняет FFmpeg + mp3_url = await self.get_file_info( + track_id, quality=quality, codecs="mp3" + ) if mp3_url: return TrackSource(url=mp3_url, codec="mp3") + + # У трека нет MP3 вовсе — разово спасаемся lossless + if not settings.prefer_lossless: + lossless_url = await self._get_lossless_url(track_id) + if lossless_url: + return TrackSource(url=lossless_url, codec="flac") return None async def _get_lossless_url(self, track_id: str | int) -> str | None: diff --git a/tests/test_yandex_music_api.py b/tests/test_yandex_music_api.py index c06b561..722921b 100644 --- a/tests/test_yandex_music_api.py +++ b/tests/test_yandex_music_api.py @@ -80,6 +80,46 @@ async def test_get_track_source_falls_back_to_mp3(monkeypatch): assert source.url == "link-320" +async def test_get_track_source_ignores_aac_masquerading_as_best( + monkeypatch, +): + """Без MP3-320 лучшим по битрейту был бы AAC — берём лучший MP3.""" + from core.config.settings import settings + + monkeypatch.setattr(settings, "prefer_lossless", False) + api = make_api( + [ + info("aac", 256, "link-aac-256"), + info("mp3", 192, "link-mp3-192"), + ] + ) + + source = await api.get_track_source("42", quality="320") + + assert source is not None + assert source.codec == "mp3" + assert source.url == "link-mp3-192" + + +async def test_get_track_source_rescues_with_lossless_without_mp3( + monkeypatch, +): + """У трека нет MP3 вовсе — разовый фолбэк на lossless.""" + from core.config.settings import settings + + monkeypatch.setattr(settings, "prefer_lossless", False) + api = make_api([info("aac", 256, "link-aac-256")]) + monkeypatch.setattr( + api, "_get_lossless_url", AsyncMock(return_value="https://cdn/flac") + ) + + source = await api.get_track_source("42", quality="320") + + assert source is not None + assert source.codec == "flac" + assert source.url == "https://cdn/flac" + + async def test_get_track_source_skips_lossless_when_disabled(monkeypatch): from core.config.settings import settings