22 KiB
nsm
Терминальный музыкальный плеер на Rust — что-то вроде cmus, но с нуля, с эквалайзером, поддержкой cue-листов, интернет-радио и SMB-шар.
Возможности
- Библиотека: рекурсивное сканирование вложенных папок, поиск (нечёткий, по названию/исполнителю/альбому), группировка по альбомам
- Воспроизведение: открыть файл напрямую (весь альбом подряд) или добавить в плейлист; несколько плейлистов с возможностью переставлять и удалять треки
- Транспорт: 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 (с автопереключением между несколькими зеркалами, если одно недоступно), ICY-метаданные ("сейчас играет" прямо из потока)
- SMB-шары: подключение к сетевым папкам (в т.ч. анонимно/гостевой доступ) через системный gvfs — без реализации протокола внутри плеера
- История прослушиваний: при первом запуске спрашивает, запоминать ли прослушанное; статистика (артист/трек/дата) — диаграммой по часам за сегодня, по дням за неделю/месяц; включается/выключается в любой момент, сбрасывается с подтверждением
- Новые релизы: проверка новых альбомов/EP/синглов у артистов, уже имеющихся в твоей библиотеке, через открытую базу MusicBrainz (без ключей API) — не глобальный чарт, а именно "что вышло нового у тех, кого ты слушаешь"
- Раскладка: хоткеи работают одинаково что на английской, что на русской раскладке
Форматы
Поддерживается всё, что умеет декодировать symphonia: MP3, FLAC, ALAC/AAC (в .m4a), WAV, AIFF, OGG (Vorbis), MKV/WebM.
Не поддерживается: Opus — у symphonia нет собственного Opus-декодера ни в одном наборе фич (только сторонние биндинги к libopus, которых в проекте нет). .opus-файлы не индексируются сканером библиотеки.
Сборка
Системные зависимости
Linux — нужны заголовки ALSA (для аудио-вывода через cpal):
# 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 напрямую. Нужны стандартные инструменты сборки:
xcode-select --install
Windows (не проверено — собиралось и тестировалось только на Linux) — отдельных аудио-зависимостей тоже не нужно (cpal использует WASAPI), достаточно обычной установки Rust через rustup (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-плагин к нему — нет). Проверить и починить:
# проверить руками, минуя 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).
Сборка проекта
git clone <URL-этого-репозитория>
cd nsm
cargo build --release
Бинарник появится в target/release/nsm (на Windows — target\release\nsm.exe).
Сборка Windows-бинарника с Linux (кросс-компиляция)
# 1. Добавить Windows-таргет (нужен rustup — обычная установка Rust с rustup.rs)
rustup target add x86_64-pc-windows-gnu
# 2. Поставить mingw-w64 — кросс-компилятор/линкер под Windows
sudo apt-get install gcc-mingw-w64-x86-64 # Debian/Ubuntu; на других — свой пакетный менеджер
# 3. Указать cargo, каким линкером пользоваться для этого таргета
mkdir -p .cargo
cat >> .cargo/config.toml <<EOF
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
EOF
# 4. Собрать
cargo build --release --target x86_64-pc-windows-gnu
Бинарник появится в target/x86_64-pc-windows-gnu/release/nsm.exe.
Проверено частично: сам линкер (mingw-w64) реально протестирован — собрал им тестовую C-программу, получился настоящий Windows PE-исполняемый файл. Полную сборку nsm под Windows-таргет от начала до конца проверить не удалось (нужен rustup для загрузки Windows std-библиотеки, а в этом окружении к нему нет доступа) — но по ходу проверки нашёлся и уже исправлен реальный баг: код использовал Unix-специфичный способ определения размера ячейки терминала для обложки, из-за чего сборка под Windows не прошла бы вообще. Теперь на не-Unix платформах используется разумное значение по умолчанию.
Запуск
./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, без ключей 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в листе