252 lines
22 KiB
Markdown
252 lines
22 KiB
Markdown
# 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` в листе
|