Кроссплатформенный npm/npx CLI для переноса треков, лайков и плейлистов из Яндекс Музыки в Звук / SberZvuk без какого-либо LLM в рантайме.
Логика работы детерминированная:
exportвыгружает библиотеку Яндекс Музыки через прямые запросы кapi.music.yandex.net.matchищет соответствия в Звуке и классифицирует треки какexact,fuzzy,ambiguous,missingилиoverride.importвоспроизводит заранее захваченные write-mutation запросы Звука, поддерживает checkpoint/resume и не создаёт дубли при повторных запусках.verifyпроверяет итоговое состояние в Звуке, включая fallback-проверку канонизированных треков по названию и артистам.
Node-клиент Яндекс Музыки в проекте реализован по мотивам и с опорой на референсную Python-библиотеку MarshalX/yandex-music-api для того среза API, который нужен мигратору.
npm installПо умолчанию CLI хранит состояние в системной конфигурационной директории:
- Windows:
%APPDATA%/ym2zvuk - macOS:
~/Library/Application Support/ym2zvuk - Linux:
${XDG_CONFIG_HOME:-~/.config}/ym2zvuk
Основные файлы внутри этой директории:
config.jsonoverrides.csvzvuk-write-templates.jsonzvuk-probe-capture.jsonruns/<timestamp>/...
Для локальной разработки или изолированных запусков используйте --state-dir <dir>.
npx ym2zvuk initЧто делает init:
- сохраняет токены Яндекса и Звука в
config.json - создаёт
overrides.csv, если его ещё нет - при необходимости устанавливает Playwright Chromium
- запускает интерактивный probe Звука, если не передан
--skip-probe
Важно: --zvuk-token имеет смысл и действительно нужен. Probe через браузер нужен для захвата шаблонов write-запросов Звука, но текущая реализация не извлекает и не сохраняет X-Auth-Token автоматически из браузерной сессии. Для команд match, import, verify и migrate проект использует отдельную API-сессию Звука, которой нужен этот токен.
Можно инициализировать проект без браузерного шага, если шаблоны уже есть:
npx ym2zvuk init \
--yandex-token <token> \
--zvuk-token <token> \
--skip-probe \
--templates path/to/zvuk_write_templates.jsonФлаг --zvuk-token необязательно указывать именно в командной строке, но сам токен нужен. Если не передавать его флагом, init запросит его интерактивно и сохранит в config.json.
После авторизации в браузере токен Звука обычно можно увидеть в данных, связанных с запросом:
https://zvuk.com/api/tiny/profile
На практике проект использует этот же endpoint для priming anti-bot cookie, но сам токен из браузера автоматически не вытаскивает, поэтому его нужно либо передать в init, либо сохранить вручную в config.json.
С токеном Яндекс Музыки сложнее. Актуальные пользовательские способы и обсуждение собраны здесь:
Если кратко, в обсуждении описаны варианты через браузерный OAuth-flow и DevTools/Network. Так как способы со временем меняются, лучше ориентироваться именно на эту живую ветку обсуждения, а не на зафиксированную инструкцию внутри проекта.
Самая чувствительная часть проекта это первый probe Звука, потому что он открывает настоящий браузер и просит пользователя войти вручную. Это нормально вызывает вопросы, поэтому важно явно описать поведение.
Что происходит на самом деле:
- CLI запускает локальный Chromium через Playwright
- пользователь сам входит на реальный сайт
https://zvuk.com - скрипт ждёт подтверждения Enter после ручных действий
- во время probe записываются только API-запросы Звука, которые нужны для вывода шаблонов трёх операций:
- лайк трека
- создание плейлиста
- добавление трека в плейлист
Чего проект не делает:
- не спрашивает пароль в терминале
- не печатает логин и пароль за пользователя
- не читает поля формы логина
- не реализует keylogger
- не отправляет захваченные данные куда-либо сам по себе
- не использует никакие удалённые AI/LLM-сервисы в рантайме
Где это можно проверить в коде:
- bootstrap и probe-flow:
src/init.js - логика захвата запросов:
src/probe.js - сессия и replay запросов к Звуку:
src/zvuk/session.js - прямой HTTP-клиент Яндекса:
src/yandex/client.js
Что сохраняется локально:
config.jsonс токенами- профиль браузера Playwright в state-директории
zvuk-probe-capture.jsonс захваченными request/response примерамиzvuk-write-templates.jsonс шаблонами мутаций
Проект не обещает невозможного и не пытается «криптографически доказать», что он безопасен. Честная гарантия тут уже: код открыт, чувствительная логика небольшая и обозримая, а все данные лежат локально в state-директории, которую можно посмотреть и удалить через ym2zvuk reset.
Если вы осторожно относитесь к таким инструментам, лучший сценарий проверки такой:
- Прочитать
src/probe.jsи убедиться, что модуль только слушает ответыzvuk.com/api/и ждёт ручных подтверждений Enter. - Запустить проект с отдельной
--state-dirи посмотреть все созданные там файлы. - При желании параллельно наблюдать сетевую активность своими средствами.
- Для последующих запусков использовать
--skip-probe --templates <file>, чтобы не повторять браузерный шаг. - В любой момент удалить локальное состояние через
ym2zvuk reset.
Корректная формулировка для такого проекта не «доверьтесь на слово», а «чувствительная часть маленькая, локальная, открытая и проверяемая».
npx ym2zvuk export
npx ym2zvuk match --limit 25 --report-format console
npx ym2zvuk import
npx ym2zvuk verify
npx ym2zvuk migrateПоддерживаемые команды:
initexportprobematchimportverifymigrateresetversion
Алиасы для совместимости:
probe-zvuk->probedry-run-match->match
Ручные решения хранятся в overrides.csv:
source_id,zvuk_track_id,action,comment
54874190:6320030,131057283,force_match,Подтверждено вручную
104375865:41191790,,skip,Надёжного совпадения в Звуке нетПоддерживаемые действия:
force_matchskip
Повторные запуски должны быть воспроизводимы через этот файл.
Каждый запуск пишет отдельную директорию внутри runs/:
export.jsonexport-summary.jsonmatch-report.jsonunmatched.csvimport-checkpoint.jsonmigration-report.jsonmigration-report.mdverification-report.jsonrun-manifest.json
- Все запросы к сервисам идут с принудительным обходом прокси.
- Запись в Звук делается через replay реально захваченных браузерных мутаций, а не через угадывание private API.
importподдерживает resume и пишет checkpoint после каждого лайка и каждого элемента плейлиста.verifyсначала сверяет лайки по целевым id треков, а затем использует fallback поtitle + artist, если Звук канонизировал трек под другим внутренним id.- Если Звук временно возвращает HTML или maintenance-страницу вместо GraphQL JSON, слой сессии делает повторные попытки.
- В репозитории уже есть готовый workflow CodeQL в
.github/workflows/codeql.yml.
npm test- миграция Яндекс Музыка в Звук
- перенос треков из Яндекс Музыки в Звук
- перенос плейлистов из Яндекс Музыки в Звук
- перенос лайков из Яндекс Музыки в Звук
- yandex music to zvuk
- yandex music migration
- zvuk playlist migration