Мне нужна была утилита под macOS, которая берёт образ Super Audio CD и делает из него FLAC. Обычный путь «открыть ISO в ffmpeg» здесь не работает: внутри не PCM, а однобитный DSD, часто ещё сжатый в DST. В итоге получился Кедр. Окно на Electron, само преобразование делает локальный Python, альбом с машины никуда не уходит.

И сразу честно, как это писалось. Я не набирал проект руками файл за файлом. Это вайбкодинг в Cursor: я говорил, что должно получиться, смотрел результат и возвращал в чат то, что сломалось уже на настоящем диске. Модель хорошо собирает каркас и плохо угадывает, как себя ведёт Homebrew ffmpeg 9 на конкретном DSF. Ниже как раз про это. Без тех прогонов на своём Mac утилита бы так и осталась «ну вроде открывается».

Окно Кедра
Окно Кедра

Что я вообще просил

Сначала запрос был короткий: утилита для macOS, DSD ISO в FLAC. Потом стало ясно, что иногда нужен MP3, и что хочется нормальное окно, а не страницу в браузере. Потом окно оказалось длинной простынёй, и его пришлось собрать в две колонки. Потом русский и английский, с языком системы по умолчанию. Потом скрипт сборки под Windows, потому что «поставь на Маке» — это ещё не программа.

Каждый такой заход я формулировал обычным текстом. Cursor правил код, я собирал на Маке и приносил обратно текст ошибки. Так проект и вырос до версии 1.1.0.

Почему ISO не открывается

SACD — это Scarlet Book. Снаружи файл похож на .iso, внутри поток DSD. У диска DSD64 частота 2 822 400 Гц, один бит на отсчёт. Чтобы альбом влез, дорожки часто лежат в DST: это сжатие без потерь, но только для DSD. На одном диске бывают две зоны, стерео и 5.1, у каждой своё оглавление, исполнитель и длительности.

ffmpeg такое не читает. Он умеет уже готовый DSF или DFF. Сектора образа, выбор зоны и снятие DST — это работа sacd_extract из sacd-ripper. Программа под GPL-2.0, в репозиторий Кедра она не входит. Установщик сам скачивает исходники и собирает бинарник рядом. Код Кедра под MIT, и смешивать эти две лицензии в одном дереве не хотелось.

Дальше неприятная, но честная часть. FLAC и MP3 хранят PCM. Бит-в-бит SACD в них не положить, иначе на выходе был бы DSF. Кедр делает файл, который открывает обычный плеер, и переносит теги с диска.

Как это склеивается

sacd_extract печатает оглавление и вытаскивает выбранные дорожки в DSF, по дороге снимая DST. Потом ffmpeg переводит DSD в PCM.

FLAC по умолчанию: 176,4 кГц, 24 бита, срез 40 кГц, сжатие 8. Срез здесь не «для красоты». У DSD выше слышимой полосы сидит шум модуляции, и если тащить его в файл целиком, получается странный спектр и лишний размер. Можно оставить 352,8 кГц, без дополнительного понижения, файлы просто толстые.

MP3 — LAME, по умолчанию 320 кбит/с, 44,1 кГц и срез 20 кГц. Выше 48 кГц MPEG не умеет. 5.1 в MP3 тоже не умеет, поэтому многоканальную зону Кедр даже не начинает извлекать, если на выходе выбран MP3. Промежуточные DSF после успеха удаляются. Если нажать «стоп», уже записанное остаётся в папке.

Где вайбкодинг закончился и начался диск

На синтетическом тоне всё было зелёное. На настоящем SACD первая дорожка умерла с текстом Дорожка 1: Conversion failed! У ffmpeg 9.0 из Homebrew это последняя строка почти на любую беду, так что сообщение пришлось выкидывать и искать выше по журналу. Следующий заход дал уже другое: Terminating thread with return code -22, то есть Invalid argument из потока фильтра. Под этим сидели три отдельные вещи.

Обложка. В DSF часто лежит картинка ID3. ffmpeg видит её как второе видео. Если отправить в FLAC всё подряд и сказать «24 бита», глубина применяется и к картинке, и кодировщик падает. В команду уходит только звук, -map 0:a:0.

Каналы. В заголовке DSF есть тип раскладки и число каналов. На части многоканальных зон sacd_extract пишет тип «стерео», а каналов в файле пять или шесть. ffmpeg 9 такое не прощает. Если тип и счётчик спорят, Кедр ставит тип 0, «смотри на счётчик».

Частота и SoX. Декодер DSD отдаёт PCM на частоте из заголовка, делённой на восемь. Для DSD64 это 352 800 Гц. Срез 40 кГц, посчитанный целочисленным фильтром на такой частоте, и SoX в слишком высоком качестве дают тот самый -22. Срез считается в double, точность SoX опущена до уровня, на котором он ещё жив, а если он всё равно отказывается, дорожка один раз перекодируется встроенным ресемплером. Срез выше Найквиста выбранной частоты программа опускает сама.

Вот это в чат не придумаешь. Пока на столе нет диска и конкретного ffmpeg, модель уверенно собирает «правильную» команду, которая на 6.1 проходит, а на 9.0 нет.

Окно

Electron здесь только рамка: список дорожек, FLAC или MP3, частота, срез, папка. Python из стандартной библиотеки, без своих пакетов, поднимает страницу на 127.0.0.1 и делает всю работу. Диалоги файла — системные.

Первая вёрстка была одной длинной страницей, её приходилось крутить, чтобы добраться до кнопки. Сейчас слева диск и дорожки, справа настройки, кнопка преобразования прибита внизу. На узком окне колонки снова складываются.

Язык при первом запуске берётся из системы. Есть русский и английский, всё остальное становится английским. Переключатель в настройках запоминается. Названия папок альбома остаются как на диске и от языка интерфейса не зависят: если на обложке по-русски, папка тоже по-русски.

Та же страница живёт и без Electron.

python3 -m kedr info "Album.iso"
python3 -m kedr convert "Album.iso" -o ~/Music --mode stereo --rate 176400
python3 -m kedr convert "Album.iso" -o ~/Music --format mp3 --bitrate 320

Сборка, в том числе та, что сначала не собралась

На Маке скрипт ставит cmake, libxml2, ffmpeg, Python и Node, собирает sacd_extract и кладёт ярлык в ~/Applications. Первый прогон на уже установленном Homebrew упал с «нужна libxml2», хотя brew install libxml2 говорил, что всё на месте. Формула keg-only: заголовки лежат не в /usr/include, а внутри префикса Homebrew. Скрипт теперь сам подставляет этот путь.

На Windows отдельный PowerShell-скрипт. Через winget ставятся MSYS2, Python, Node и ffmpeg, sacd_extract.exe собирается компилятором UCRT64, ярлык падает на рабочий стол. Подписанного установщика нет. Electron лежит в папке проекта, ярлык запускает его оттуда, так что папку после установки лучше не переезжать.

Исходники: github.com/PavelDzhagriev/sacd-convert.

Если коротко про способ. Cursor нормально тащит такой проект, пока вы приносите ему настоящие ошибки, а не просите «сделай хорошо». Каркас, окно, локали и установщики он собрал быстро. Три падения ffmpeg на живом SACD пришлось ловить ушами и журналом, а уже потом отдавать обратно в чат.