nm/README.md
2026-07-25 21:00:32 +03:00

23 KiB
Raw Blame History

nsm

Терминальный музыкальный плеер на Rust — что-то вроде cmus, но с нуля, с эквалайзером, поддержкой cue-листов, интернет-радио и SMB-шар.

Read this in English

Возможности

  • Библиотека: рекурсивное сканирование вложенных папок, поиск (нечёткий, по названию/исполнителю/альбому), группировка по альбомам
  • Воспроизведение: открыть файл напрямую (весь альбом подряд) или добавить в плейлист; несколько плейлистов с возможностью переставлять и удалять треки
  • Транспорт: 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 в листе