nsm/README.md
2026-07-13 09:31:20 +03:00

252 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <URL-этого-репозитория>
cd nsm
cargo build --release
```
Бинарник появится в `target/release/nsm` (на Windows — `target\release\nsm.exe`).
### Сборка Windows-бинарника с Linux (кросс-компиляция)
```bash
# 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 платформах используется разумное значение по умолчанию.
### Запуск
```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` в листе