23 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-шары: подключение к сетевым папкам (в т.ч. анонимно/гостевой доступ) — свой SMB-клиент на чистом Rust (крейт
smb), без системных зависимостей вродеgio/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) реализована собственным SMB-клиентом на чистом Rust (крейт smb) — не зависит от системного gio/gvfs и его особенностей на конкретном дистрибутиве. Подключение и сканирование шары идут в фоновом потоке — интерфейс не подвисает даже если сеть медленная.
Требует Rust 1.85+ (edition 2024) — на момент написания в Ubuntu/Debian это отдельные версионированные пакеты (rustc-1.91/cargo-1.91 и т.п.), а не то, что стоит по умолчанию (там обычно 1.75). Если у тебя старый rustc — либо поставь такие версионированные пакеты через apt, если они есть в твоём дистрибутиве, либо используй rustup.
Честная оговорка по тестированию: сама SMB-логика (подключение, рекурсивный листинг папок, чтение файлов с точным seek, декодирование аудио через SMB) проверена на реальном Samba-сервере и работает. Но в песочнице, где это разрабатывалось, не было настоящей звуковой карты — cpal (аудио-библиотека) там гарантированно падает при старте, и в этом конкретном состоянии (плюс активный поток аудио-движка) обнаружилось зависание при листинге SMB-папки. Причина не докопана до конца (что-то на стыке ALSA/cpal и tokio-реактора при неудачной попытке cpal открыть устройство) — и не исключено, что это специфично именно для отсутствия звуковой карты, а не проявится там, где cpal открывает поток нормально. Само SMB-подключение теперь работает в фоновом потоке, так что даже если это зависание всё-таки проявится — интерфейс не замёрзнет целиком, зависнет только конкретная попытка подключения. Если у тебя это тоже случится — дай знать, будем разбираться дальше уже на работающем звуке, что сильно сузит круг подозреваемых.
Для интернет-радио (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 |
свернуть альбом / сбросить поиск и показать всю библиотеку |
/ |
поиск |
t |
переключить вид: по альбомам ↔ плоский список всех треков (Enter на любом треке — играть всё оттуда) |
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 теперь на чистом Rust и в теории кросс-платформенна, но не тестировалась вне Linux)
-
Opus не декодируется (см. раздел «Форматы»)
-
Плейлисты не хранят диапазоны cue-треков — добавление cue-трека в плейлист сохраняет файл целиком, а не конкретный виртуальный трек
-
Подключение к SMB, поиск радиостанций и проверка новых релизов — все работают в фоне и не блокируют интерфейс
-
Мульти-файловые cue-листы (редкость) поддерживаются только по первому
FILEв листе