# nsm Терминальный музыкальный плеер на Rust — что-то вроде [cmus](https://cmus.github.io/), но с нуля, с эквалайзером, поддержкой cue-листов, интернет-радио и SMB-шар. *[Read this in English](README.en.md)* ## Возможности - **Библиотека**: рекурсивное сканирование вложенных папок, поиск (нечёткий, по названию/исполнителю/альбому), группировка по альбомам - **Воспроизведение**: открыть файл напрямую (весь альбом подряд) или добавить в плейлист; несколько плейлистов с возможностью переставлять и удалять треки - **Транспорт**: shuffle, повтор (выкл/один трек/всё), изменение скорости воспроизведения (0.25×–1.75×), mute, честный seek (перемотка) с сохранением позиции - **`.cue`-листы**: один физический файл (например, целый альбом одним FLAC) корректно распознаётся как несколько треков с правильными названиями и границами - **Эквалайзер**: 8 полос, свои биквадратные фильтры - **Обложка**: отображается прямо в терминале (kitty/sixel/iTerm2-протоколы с fallback на блочную графику) - **Анализатор спектра**: настоящий FFT в реальном времени (не анимация) — переключение обложка/спектр клавишей `v`, разворот на весь экран клавишей `z` (транспорт — пробел/next/prev/seek/mute — работает и в полноэкранном режиме) - **Затухание в конце трека**: последние 4 секунды перед концом плавно уводятся в тишину — включается клавишей `F`, по умолчанию выключено - **Выравнивание громкости**: тег ReplayGain в приоритете (если есть в файле), иначе — адаптивный левеллер (AGC), подстраивающий громкость на лету — клавиша `G`, по умолчанию выключено - **Интернет-радио**: свои станции по URL, встроенный список (SomaFM, Radio Paradise), поиск станций через открытый [Radio Browser API](https://api.radio-browser.info) (с автопереключением между несколькими зеркалами, если одно недоступно), ICY-метаданные ("сейчас играет" прямо из потока) - **SMB-шары**: подключение к сетевым папкам (в т.ч. анонимно/гостевой доступ) через системный gvfs — без реализации протокола внутри плеера - **История прослушиваний**: при первом запуске спрашивает, запоминать ли прослушанное; статистика (артист/трек/дата) — диаграммой по часам за сегодня, по дням за неделю/месяц; включается/выключается в любой момент, сбрасывается с подтверждением - **Новые релизы**: проверка новых альбомов/EP/синглов **у артистов, уже имеющихся в твоей библиотеке**, через открытую базу [MusicBrainz](https://musicbrainz.org) (без ключей API) — не глобальный чарт, а именно "что вышло нового у тех, кого ты слушаешь" - **Раскладка**: хоткеи работают одинаково что на английской, что на русской раскладке ## Форматы Поддерживается всё, что умеет декодировать [symphonia](https://github.com/pdeltuvia/symphonia): **MP3, FLAC, ALAC/AAC (в `.m4a`), WAV, AIFF, OGG (Vorbis), MKV/WebM**. **Не поддерживается: Opus** — у symphonia нет собственного Opus-декодера ни в одном наборе фич (только сторонние биндинги к libopus, которых в проекте нет). `.opus`-файлы не индексируются сканером библиотеки. ## Сборка ### Системные зависимости **Linux** — нужны заголовки ALSA (для аудио-вывода через [cpal](https://github.com/RustAudio/cpal)): ```bash # Debian/Ubuntu sudo apt-get install libasound2-dev pkg-config # ALT Linux sudo apt-get install libalsa-devel # Fedora sudo dnf install alsa-lib-devel ``` **macOS** *(не проверено — собиралось и тестировалось только на Linux)* — отдельных аудио-зависимостей не нужно, `cpal` использует системный CoreAudio напрямую. Нужны стандартные инструменты сборки: ```bash xcode-select --install ``` **Windows** *(не проверено — собиралось и тестировалось только на Linux)* — отдельных аудио-зависимостей тоже не нужно (`cpal` использует WASAPI), достаточно обычной установки Rust через [rustup](https://rustup.rs) (MSVC toolchain). Для нормального отображения обложки в терминале нужен современный эмулятор с поддержкой true color — рекомендуется Windows Terminal; протоколы отображения картинок (kitty/sixel/iTerm2) там, скорее всего, не поддерживаются, так что обложка будет через block-графику как fallback. **SMB-фича (`c` в Library) работает только на Linux** — она целиком построена на `gio`/gvfs (GNOME-стек), которого нет ни на macOS, ни на Windows. На этих системах при попытке подключить SMB-шару через nsm просто вернётся понятная ошибка ("is gvfs installed?"), без краша — сетевые папки там штатно монтируются через Finder/Проводник, не через nsm. **Если получаешь ошибку "volume doesn't implement mount" / "том не поддерживает монтирование"** — это gvfs-специфичная ошибка, при том что сама SMB-шара доступна и работает. Обычно значит, что не хватает именно **SMB-бэкенда gvfs** (сам `gio` может быть установлен как чужая зависимость, а конкретно SMB-плагин к нему — нет). Проверить и починить: ```bash # проверить руками, минуя nsm gio mount smb://сервер/шара # на ALT/Fedora-подобных — убедиться, что бэкенд установлен sudo apt-get install gvfs-backends # (или свой пакетный менеджер) # проверить, что D-Bus сессия вообще есть (без неё gvfs не активируется) echo $DBUS_SESSION_BUS_ADDRESS ``` Если это не помогло — причина может быть глубже, в согласовании версии протокола SMB между клиентом и сервером (см. `journalctl` на предмет строк вида `smbXcli_negprot` от `gvfsd`); в этом случае поможет `client min protocol` в `/etc/samba/smb.conf`, как описано выше. Для интернет-радио (**SomaFM**, **Radio Paradise** и т.д.) и MusicBrainz дополнительных системных зависимостей не нужно ни на одной ОС — TLS обеспечивает `rustls` (чистый Rust, без OpenSSL). ### Сборка проекта ```bash git clone cd nsm cargo build --release ``` Бинарник появится в `target/release/nsm` (на Windows — `target\release\nsm.exe`). ### Запуск ```bash ./target/release/nsm /путь/к/музыке ``` Если путь не указан, по умолчанию используется `~/Music`. ## Управление ### Глобальные клавиши (любая вкладка) | Клавиша | Действие | |---|---| | `Space` | play/pause | | `N` / `P` | следующий / предыдущий трек | | `s` | shuffle вкл/выкл | | `r` | repeat (выкл → всё → один трек → выкл) | | `m` | mute | | `[` / `]` | скорость воспроизведения вниз/вверх | | `←` / `→` | перемотка −5с / +5с | | `e` | фокус на эквалайзер | | `h` | включить/выключить запись истории прослушиваний | | `v` | переключить обложка/спектр | | `z` | развернуть обложку/спектр на весь экран | | `F` | затухание в конце трека вкл/выкл | | `G` | выравнивание громкости вкл/выкл | | `I` | переключить протокол отображения обложки (если автоопределение ошиблось) | | `Tab` | следующая вкладка | | `q` | выход | ### Эквалайзер (после `e`) | Клавиша | Действие | |---|---| | `←` / `→` | выбор полосы | | `↑` / `↓` | ±1dB на выбранной полосе | | `0` | сброс текущей полосы | | `R` | сброс всех полос | | `Esc` / `e` | выйти из фокуса | ### Library | Клавиша | Действие | |---|---| | `j`/`k`, `↓`/`↑` | навигация | | `Enter` | раскрыть альбом / сыграть | | `Esc` | свернуть альбом | | `/` | поиск | | `a` | добавить в плейлист | | `c` | подключить SMB-шару | | `L` | добавить ещё одну папку к библиотеке | | `C` | очистить библиотеку (не трогает файлы на диске) | ### Playlists | Клавиша | Действие | |---|---| | `j`/`k` | навигация | | `Enter` | открыть плейлист / играть с этого места | | `n` | новый плейлист | | `J`/`K` | переместить трек вниз/вверх | | `d` | удалить трек из плейлиста | | `Esc` | назад к списку плейлистов | ### Queue | Клавиша | Действие | |---|---| | `j`/`k` | навигация | | `Enter` | играть с выбранного места | ### Radio | Клавиша | Действие | |---|---| | `j`/`k` | навигация | | `Enter` | играть станцию | | `u` | добавить свою станцию по URL | | `o` | поиск станций онлайн | | `d` | удалить (только свою) станцию | ### Stats | Клавиша | Действие | |---|---| | `p` | переключить период (день → неделя → месяц) | | `x` | сбросить историю (нужно нажать дважды подряд для подтверждения) | ### New Releases | Клавиша | Действие | |---|---| | `p` | переключить период (день → неделя → месяц) | | `f` | проверить новые релизы у всех артистов из библиотеки (может занять время, см. ниже) | | `a` | вручную проверить одного артиста по имени (не обязательно есть в библиотеке) — добавляется к уже показанному списку | В полях ввода текста — `Enter` подтверждает, `Esc` отменяет, `Backspace` стирает. ## Архитектура Cargo-workspace из четырёх крейтов: ``` crates/ ├── player/ — движок воспроизведения: декодирование (symphonia), вывод звука (cpal), │ эквалайзер, ресемплинг для скорости, очередь, интернет-радио ├── library/ — сканирование библиотеки, теги (lofty), cue-листы, плейлисты, │ радиостанции, SMB ├── tui/ — интерфейс на ratatui: вкладки, обложка, эквалайзер, event loop └── nsm/ — тонкий бинарник, связывающий всё вместе ``` `player` и `library` не знают друг о друге — `tui` связывает их через `player::QueueItem`. ## История прослушиваний Хранится локально в `~/.config/nsm/history.jsonl` (одна запись — одна строка JSON: артист, трек, дата/время). Никуда не отправляется. При первом запуске спрашивается разрешение; в любой момент можно включить/выключить клавишей `h`, а на вкладке **Stats** — посмотреть диаграмму по часам/дням, список конкретных прослушанных треков и сбросить всё (двойным нажатием `x`). ## Кракозябры в старых тегах Многие старые mp3 (особенно с тегами из 2000-х) хранят кириллицу в ID3v1 в кодировке CP1251, но она формально не объявлена — большинство библиотек (включая нашу) по умолчанию читают такие теги как Latin-1, получая "Ïåñíÿ" вместо "Песня". `nsm` определяет этот случай эвристически (по code points ≤0xFF и доле кириллических букв после перекодировки в CP1251) и чинит автоматически, не трогая уже нормальный UTF-8 текст. ## Обложка Протокол отображения обложки (kitty/sixel/iTerm2/halfblocks) определяется автоматически по терминалу. Если автоопределение угадало неверно (например, показывает низкое разрешение на терминале, который на самом деле поддерживает kitty-графику) — жми `I`, чтобы вручную перебрать протоколы; выбор запоминается и применяется при следующих запусках. ## Новые релизы Вкладка **New Releases** явно показывает релизы **только для артистов, уже имеющихся в твоей библиотеке** — это не глобальный чарт новинок (RYM активно блокирует ботов, и честно скрапить его не вышло — см. обсуждение). Вместо этого используется открытая база [MusicBrainz](https://musicbrainz.org), без ключей API. Из-за ограничения MusicBrainz (не больше одного запроса в секунду) проверка большой библиотеки может занять время — по прогресс-бару видно, какой артист сейчас проверяется. Для очень плодовитых артистов (много синглов/переизданий за много лет) может понадобиться несколько запросов подряд на одного артиста — MusicBrainz не гарантирует сортировку результатов по дате, поэтому вычитывается вся дискография постранично, а не только первые 100 записей (иначе новый релиз мог случайно не попасть в выборку). Результаты кэшируются в `~/.config/nsm/new_releases_cache.json`, повторный запуск `nsm` сразу покажет последний результат без обращения к сети; обновление — вручную клавишей `f`. **Важно понимать масштаб**: вкладка показывает релизы **только за последний день/неделю/месяц** (переключение `p`) — это буквально "что вышло недавно", а не вся дискография артиста. Если твой любимый артист не выпускал ничего последние 30 дней (для большинства артистов это норма), список для него будет пустым — это ожидаемое поведение, не баг. ## Выравнивание громкости Клавиша `G`. Если в файле есть тег ReplayGain (`REPLAYGAIN_TRACK_GAIN` и аналоги — читается универсально через `lofty` для FLAC/MP3/OGG/MP4) — используется точное значение из тега. Если тега нет — включается адаптивный левеллер (AGC): он постоянно измеряет громкость последних 1-3 секунд и плавно подстраивает усиление к цели. Это **не то же самое**, что настоящий ReplayGain (посчитанный заранее по всему треку) — первые секунды трека выравниваются не идеально, дальше подстройка сходится. ## Известные ограничения - Минимальный размер терминала — 70×18; на меньшем показывается предупреждение вместо сломанного интерфейса - **Собиралось и тестировалось только на Linux** — сборки под macOS/Windows не проверялись; SMB-фича на них принципиально не будет работать (см. выше) - **Opus не декодируется** (см. раздел «Форматы») - **Плейлисты не хранят диапазоны cue-треков** — добавление cue-трека в плейлист сохраняет файл целиком, а не конкретный виртуальный трек - Подключение к SMB — блокирующий вызов в UI-потоке; интерфейс на секунду-другую подвиснет во время операции (поиск радиостанций и проверка новых релизов уже работают в фоне и не блокируют) - Мульти-файловые cue-листы (редкость) поддерживаются только по первому `FILE` в листе