327 lines
18 KiB
Plaintext
Executable File
327 lines
18 KiB
Plaintext
Executable File
---
|
||
title: Music Tags Repair — TrueNAS / Navidrome
|
||
created: '2026-09-27'
|
||
updated: '2026-09-27'
|
||
type: plan
|
||
namespace: personal
|
||
status: canon-diff-v3-built / genre-gap-open
|
||
tags:
|
||
- music
|
||
- metadata
|
||
- id3
|
||
- album
|
||
- albumartist
|
||
- repair
|
||
- truenas
|
||
- navidrome
|
||
related:
|
||
- '[[family/how-to/truenas-access]]'
|
||
- '[[personal/tech/music-tags-repair-truenas]]'
|
||
- '[[personal/plans/tagwriter-container]]'
|
||
- '[[family/how-to/navidrome-truenas-android]]'
|
||
- '[[family/plans/music-jellyfin-lidarr-taiga]]'
|
||
---
|
||
|
||
# Music Tags Repair — TrueNAS / Navidrome
|
||
|
||
> **Эта дока — выжимка.** Полная хронология 50 сессий — в бэкапе
|
||
> `music-artist-albumartist-repair.md.bak-20260927-210643` (7529 строк).
|
||
> Здесь: майлстоуны, текущий стейт, инструкции.
|
||
|
||
---
|
||
|
||
## 🔑 СТАРТ ЗДЕСЬ
|
||
|
||
**Что делаем:** привести ID3-теги библиотеки `/mnt/RED_2TB/storage/Music` (7861 файл)
|
||
в порядок, чтобы Navidrome показывал правильные альбомы/исполнителей/жанры.
|
||
|
||
**Главный артефакт на сейчас:**
|
||
|
||
```
|
||
Дифф: ~/Downloads/music-tags/DIFF-base-vs-applyplan-v3.csv
|
||
md5 bdb20c6ae00e7dfab4da2f76653fe084
|
||
945 строк · genre 427 · album 28 · albumartist 6
|
||
План: /tmp/apply-plan-v3.csv (7861 строка, md5 6b9e49b2baa3f1d1708e4566972266a9)
|
||
Канон: ~/Downloads/music-tags/APPLY-PLAN.csv (7861 строка — НЕ ПЕРЕЗАПИСЫВАТЬ)
|
||
```
|
||
|
||
**Открытый дефект:** жанры (`TCON`) не записаны в теги — прогон шёл старым скриптом.
|
||
Скрипт уже исправлен, **прогон не сделан**.
|
||
|
||
**Доступ к NAS:** `ssh -i ~/.ssh/id_rsa truenas_admin@mallexxx.duckdns.org`
|
||
⚠️ `192.168.1.100` — ЧУЖАЯ подсеть, не использовать. Подробнее → [[family/how-to/truenas-access]]
|
||
|
||
---
|
||
|
||
## 📍 МАЙЛСТОУНЫ
|
||
|
||
| # | Дата | Что сделано | Артефакт |
|
||
|---|------|-------------|----------|
|
||
| 1 | 27 сен | Navidrome развёрнут на TrueNAS, класс дефекта вскрыт | — |
|
||
| 2 | 27 сен | Собраны правила Alex из Zulip DB (правило корня, сборники) | — |
|
||
| 3 | 27 сен | `APPLY-PLAN.csv` = 7861 строка, канон | `APPLY-PLAN.csv` |
|
||
| 4 | 27 сен | Боевой прогон тегов: 7649/7649 ok, err=0 | `tagwriter:latest` |
|
||
| 5 | 27 сен | Генератор `build_fix_plan2.py` пофикшен (обрезка категории) | — |
|
||
| 6 | 27 сен | 38-я волна: `tag-fix-plan.csv` + ручные правки (транслит, треки, опечатки) | `tag-fix-plan.csv` |
|
||
| 7 | 27 сен | `.ndignore` — 6 бэкапов исключены, Navidrome их не видит | `.ndignore` |
|
||
| 8 | 27 сен | **Канонный дифф v3 собран** — сортировка по `field`, схлопывание, шум убран | `DIFF-base-vs-applyplan-v3.csv` |
|
||
| 9 | 27 сен | **Верификация артефактов v3 перед dry-run** — 3 расхождения вскрыты, см. § ВЕРИФИКАЦИЯ | `/tmp/verify_v3*.py` |
|
||
|
||
---
|
||
|
||
## 🧩 ПРАВИЛА (от Alex, обязательны)
|
||
|
||
### 1. Правило корня
|
||
Файл лежит **в корне папки артиста** (без подпапки-альбома) → это один альбом:
|
||
- `album` = имя папки артиста
|
||
- `albumartist` = титульный исполнитель
|
||
|
||
Сравнение по **вхождению** (`nleaf in nartist`), НЕ на равенство — иначе теряются
|
||
папки-огрызки (`Руки` vs `Руки Вверх`).
|
||
|
||
### 2. Сборники не трогаем
|
||
Если тег `artist` ≠ имя папки → это сборник, правило корня к нему не применяется.
|
||
Пример: `Sbornik_Novij_Rok` (`artist` = разные исполнители).
|
||
|
||
### 3. Папка-мусорка = альбом
|
||
`Разное`, `Избранное`, `Песни`, `misc` как имя папки → идут в `album` **как есть**.
|
||
Не разгребаем. Решение Alex от 27 сен.
|
||
|
||
### 4. `track` — только целое
|
||
База хранит `track_number` целым (`1`, `2` … `24`). Формат `N/M` и ведущие нули
|
||
(`6/14`, `08`) — **шум, не правка**: значение не меняется, парсеры ломаются.
|
||
|
||
### 5. Категории-контейнеры срезаются
|
||
`!Retro`, `!Rock&Folk`, `!Pop, Club, Любэ`, `!A Not sorted` — срезаются при разборе пути.
|
||
|
||
---
|
||
|
||
## 🛠 КАК ЧТО ДЕЛАЕТСЯ
|
||
|
||
### Доступ к NAS
|
||
```bash
|
||
ssh -i ~/.ssh/id_rsa truenas_admin@mallexxx.duckdns.org
|
||
```
|
||
- Библиотека: `/mnt/RED_2TB/storage/Music` (`/mnt/RED_2TB/Music` — ПУСТАЯ)
|
||
- Navidrome БД: `/mnt/RED_2TB/storage/docker/navidrome/data/navidrome.db`, порт 4533
|
||
- `sqlite3` на NAS есть; в контейнерах — НЕТ (читать БД через `strings`)
|
||
|
||
### Собрать дифф (`field → folder → path`, схлопывание, шум-фильтр)
|
||
```bash
|
||
python3 /tmp/build_canon_diff_v3.py
|
||
# → ~/Downloads/music-tags/DIFF-base-vs-applyplan-v3.csv
|
||
```
|
||
|
||
### Собрать план v3 (правило корня поверх канона)
|
||
```bash
|
||
python3 /tmp/make_applyplan_v3.py
|
||
# → /tmp/apply-plan-v3.csv
|
||
```
|
||
|
||
### Применить теги (образ tagwriter)
|
||
```bash
|
||
# сухой прогон
|
||
docker create --name twdry --entrypoint python3 \
|
||
-v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:rw \
|
||
tagwriter:latest /app/data/apply_tags_v2.py --dry-run && docker start -a twdry
|
||
```
|
||
- Образ `tagwriter:latest` (НЕ `v2`), uid 950:950, amd64
|
||
- План читается ТОЛЬКО как `/app/data/apply-plan.csv`
|
||
- Скрипт: `/app/apply_tags_v2.py` — пишет `TIT2 TPE1 TPE2 TALB TCON TRCK`
|
||
|
||
### Пересканить Navidrome после правки тегов
|
||
```bash
|
||
cd /mnt/RED_2TB/storage/docker/navidrome && docker compose stop
|
||
rm -f data/navidrome.db* # БД = КЭШ, теги не перечитываются иначе
|
||
docker compose up -d
|
||
```
|
||
⚠️ Рескан сам по себе теги НЕ перечитывает → старый альбом останется ДУБЛЕМ.
|
||
|
||
### Обязательный фильтр во ВСЕХ запросах к БД
|
||
```sql
|
||
WHERE path NOT LIKE 'backup%'
|
||
```
|
||
Иначе бэкап всплывает как живое дерево.
|
||
|
||
### Правило `.ndignore`
|
||
Любой новый `backup*` в `/mnt/RED_2TB/storage/Music/` → дописать в `.ndignore`
|
||
в том же прогоне. Текущее содержимое (6 записей):
|
||
```
|
||
backup
|
||
backup_album_root_20260927-album-root-229
|
||
backup_before_306
|
||
backup_conv40
|
||
backup_fix
|
||
backup_raw
|
||
```
|
||
|
||
---
|
||
|
||
## 📦 АРТЕФАКТЫ
|
||
|
||
### Рабочие (Mac)
|
||
```
|
||
~/Downloads/music-tags/APPLY-PLAN.csv канон, 7861 стр, md5 bc20f2a49e364ecd5ed57d9fc97bfbcb
|
||
~/Downloads/music-tags/DIFF-base-vs-applyplan-v3.csv дифф v3, 945 стр, md5 bdb20c6ae00e7dfab4da2f76653fe084
|
||
~/Downloads/music-tags/APPLY-PLAN-v3-changes.csv лог 31 правки v3, md5 2c6d2f0bb1d732d7e3e70ef1b210e7d8
|
||
~/Downloads/music-tags/tag-fix-plan.csv 38-я волна, 7905 стр
|
||
~/Downloads/music-tags/ALBUM-FIX-306.csv скобочный прогон, 306 стр
|
||
~/Downloads/navidrome-db/navidrome-merged.db БД Navidrome (локальная копия)
|
||
```
|
||
|
||
### Скрипты
|
||
```
|
||
/tmp/build_canon_diff_v3.py сборка канонного диффа (шум-фильтр внутри)
|
||
/tmp/make_applyplan_v3.py сборка плана v3 (правило корня)
|
||
/tmp/make_diff_rule.py детектор правила корня по БД
|
||
/tmp/rule_check_v2.py проверка правила (вхождение, не равенство)
|
||
/tmp/verify_v3.py верификация: лог vs план
|
||
/tmp/verify_v3b.py верификация: ПАПКА/ТРЕК-строки диффа из плана
|
||
/tmp/verify_v3c.py верификация: аномалии (genre-дубли, Sbornik, пропуск пути)
|
||
~/Developer/personal-os/tools/music-tags/build_fix_plan2.py генератор плана (пофикшен)
|
||
```
|
||
|
||
### Устаревшие диффы (не использовать)
|
||
```
|
||
DIFF-new-vs-38-v2/v3/v4/v5/v6.csv промежуточные, дефектные
|
||
DIFF-base-vs-applyplan-v2.csv дефектный (albumartist 7861, Sbornik)
|
||
DIFF-rule-album-v1.csv подмножество
|
||
DIFF-rule-vs-BASE.csv сверка правила с базой
|
||
```
|
||
|
||
---
|
||
|
||
## 🔎 ВЕРИФИКАЦИЯ v3 ПЕРЕД DRY-RUN (2026-09-27)
|
||
|
||
Проверено на Mac, три скрипта, все числа — из самих файлов.
|
||
|
||
### ✅ Что подтвердилось
|
||
```
|
||
План 7861 стр · дифф 945 стр · лог 31 стр — все три md5 совпали с докой
|
||
Дифф: ПАПКА 711 / ТРЕК 234 = 945; album 28 · albumartist 6 · genre 911
|
||
album + albumartist: все 234 ТРЕК-строки восстанавливаются из плана точно, 0 ошибок
|
||
```
|
||
|
||
### 🔴 РАСХОЖДЕНИЕ 1 — лог `APPLY-PLAN-v3-changes.csv` УСТАРЕЛ (план прав)
|
||
Лог писался ДО фиксов `fix_ruki.py` / `fix_rockopop_track.py`. Три строки врут:
|
||
|
||
| файл | лог говорит | в плане (верно) |
|
||
|---|---|---|
|
||
| `!Retro/Руки/Руки Вверх - Алешка.mp3` | `Руки` | **`Руки вверх`** |
|
||
| `!Rock&Folk/!rock_o_pop/АлисА - Стать севера.mp3` | `!rock_o_pop` | **`rock_o_pop`** |
|
||
| `!Rock&Folk/!rock_o_pop/Рок-Группа - Попса.mp3` | `!rock_o_pop` | **`rock_o_pop`** |
|
||
| `!Retro/Юрий Никулин/Постой паровоз.mp3` | есть | в плане путь `Постой паровоз.mp3`, файлов 3, все `Песни → Юрий Никулин` |
|
||
|
||
Плюс 3 строки с `old_album` в CP1251-кракозябрах (`Øàëàâû`) — план хранит битый `cur_*`
|
||
как есть, лог записал уже декодированное. **Лог не трогаем, план — эталон.**
|
||
|
||
### 🔴 РАСХОЖДЕНИЕ 2 — `genre` в диффе НЕ выдержан по правилу `scope`
|
||
Пример `!Pop, Club, Любэ/!Radio/w 2008-07` (38 файлов):
|
||
- ПАПКА-строки в диффе: `Blues · Dance · Electronic · House · Pop` (5)
|
||
- фактически `new_genre` в плане: **11 значений** — `Pop 16 · Dance 5 · House 5 ·
|
||
Electronic 4 · Blues 2 · Trance 1 · Club 1 · Progressive House 1 · Electro House 1 ·
|
||
Drum & Bass 1 · Hip-Hop 1`
|
||
- **7 значений не попали в дифф**: файл с таким значением в папке единственный →
|
||
правило «ПАПКА, если ≥2 файла» его отбросило.
|
||
|
||
Итог: из 234 ТРЕК-строк **211 — по `genre`** и они дублируют значения, которые уже лежат
|
||
в плане как `new_genre`. ПАПКА-строки по genre: 700 (из 711); 585 уникальных ключей
|
||
`(folder,field)` при 711 строках ⇒ **126 лишних строк** (65 ключей с >1 `new`).
|
||
`old` заполнен только у 11 из 711 (8×`<N разных>`, 3 конкретных), у 700 — пусто.
|
||
|
||
### 🔴 РАСХОЖДЕНИЕ 3 — цифры dry-run НЕ МОГУТ совпасть с диффом
|
||
```
|
||
Дифф считает ГРУППЫ: album 28 · albumartist 6 · genre 911 = 945 строк
|
||
Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (все файлы с пустым cur_genre)
|
||
```
|
||
Ожидаемый вывод dry-run — по счётчикам полей прогона 27 сен: `TCON 4359`.
|
||
**Поштучное совпадение с 945 недостижимо by design.**
|
||
|
||
### ⏳ Открытый вопрос к Alex (блокирует формулировку «dry-run совпал»)
|
||
1. Эталон = план (файлы, `genre 4359`) или дифф (группы, `genre 911`)? Дифф пересобираем?
|
||
2. 126 лишних строк диффа схлопывать до одной на `(folder,field)` или множественные
|
||
`new` внутри папки — намеренный показ «сколько разных значений осталось»?
|
||
|
||
---
|
||
|
||
## 🔴 ОТКРЫТЫЕ ДЕФЕКТЫ
|
||
|
||
| Дефект | Факт | Статус |
|
||
|--------|------|--------|
|
||
| **Жанры не в тегах** | В БД `genre` пусто у всех 7861; прогон 27 сен не писал `TCON` | Скрипт исправлен, **прогон нужен** |
|
||
| Жанры в диффе | 427 строк / 911 групп, покрытие 7861 — не «правка», а повторная запись | Учтено |
|
||
| Лог v3 устарел | 3 строки + 1 путь расходятся с планом после `fix_ruki`/`fix_rockopop` | Решено: план — эталон, лог не пересобираем |
|
||
| Дифф: genre-группы | 211 ТРЕК-строк дублируют `new_genre`; 7 одиночек не добраны | **Ждём решения Alex** |
|
||
|
||
---
|
||
|
||
## 📊 ЧИСЛА (факты)
|
||
|
||
```
|
||
Библиотека: 7861 живых файлов = строкам плана
|
||
Бэкапы: 535 файлов (6 папок, в .ndignore)
|
||
Боевой прогон: 27 сен, 7649/7649 ok, err=0, VERIFY 7649
|
||
Поля прогона: TPE2 7213 · TCON 4359 · TALB 1659 · TPE1 381 · TRCK 344 · TIT2 8
|
||
Поля плана (cur!=new): album 2031 · albumartist 7213 · genre 4359 · artist 381 ·
|
||
title 8 · track 441 ; plan.changed=yes 7653
|
||
Дифф v3: 945 строк (ПАПКА 711 · ТРЕК 234)
|
||
genre 911 · album 28 · albumartist 6
|
||
Дифф на 100%: 7861 файл сопоставлен, не найдено 0
|
||
Лог v3: 31 строка
|
||
```
|
||
|
||
---
|
||
|
||
## 🧨 ПИТФОЛЛЫ
|
||
|
||
1. **`192.168.1.100` — чужая подсеть.** NAS: `mallexxx.duckdns.org`. Агент объявлял
|
||
«NAS недоступен» — **ложно**.
|
||
2. **`grep` по всему файлу даёт ложную тревогу.** Опечатки в колонке `title` = названия
|
||
песен, они и должны остаться. Проверять по НУЖНОЙ колонке.
|
||
3. **Фильтр шума снаружи генератора = регрессия.** Пересборка возвращает `track N/M`.
|
||
Фильтр должен быть ВНУТРИ сборщика диффа.
|
||
4. **`cur_*` в плане = снимок ДО прогона**, не текущее состояние базы.
|
||
5. **Запрос к БД без `NOT LIKE 'backup%'`** → бэкап всплывает как живое дерево.
|
||
6. **Navidrome не перечитывает теги** при скане/рестарте → дубли. Лечение: stop →
|
||
`rm data/navidrome.db*` → up -d.
|
||
7. **Детектор «правило корня не сработало» на `new_album`** даёт тысячи ложных
|
||
срабатываний, если не отсеять осмысленные альбомы.
|
||
8. **`album` для сборника** — правило корня применять НЕЛЬЗЯ, только если план не дал
|
||
целевого значения.
|
||
9. **Не сравнивать дифф и dry-run по сырым числам.** Дифф = ГРУППЫ, dry-run = ФАЙЛЫ.
|
||
Единицу счёта называть явно, иначе «не совпало» на пустом месте.
|
||
10. **Дифф с `scope=ПАПКА` проверять агрегацией по плану, а не поиском пути в плане** —
|
||
у ПАПКА-строки в колонке `path` лежит ПАПКА, не файл. Наивный чек даёт 711 ложных
|
||
«DIFF-PATH-NOT-IN-PLAN».
|
||
11. **ТРЕК-строка диффа ≠ `cur!=new`.** У genre ТРЕК-строки могут нести значение,
|
||
совпадающее с `cur_genre` (дубль групповой строки). Не считать их списком правок.
|
||
12. **Лог правок пересобирать ПОСЛЕ последнего фикса плана.** Иначе лог расходится с
|
||
планом (так и вышло: `fix_ruki`/`fix_rockopop` прошли после генерации лога).
|
||
|
||
---
|
||
|
||
## ⚙️ ПОРЯДОК РАБОТЫ С БОЕВЫМИ ДАННЫМИ
|
||
|
||
```
|
||
коммит ДО → правка → заливка → ПРОВЕРКА ALEX → коммит ПОСЛЕ
|
||
```
|
||
- `apply` / запись тегов — **только по прямой команде Alex**
|
||
- Мой `read back` ≠ «работает». Приёмка = видно в UI
|
||
- Ничего сверх команды без апрува
|
||
|
||
---
|
||
|
||
## 📜 ИСТОРИЯ
|
||
|
||
Полная хронология (сессии 2–29, все правки, откаты, разборы) — в бэкапе:
|
||
```
|
||
~/.hermes/... — нет; файл рядом:
|
||
~/obsidian/personal/plans/music-artist-albumartist-repair.md.bak-20260927-210643
|
||
```
|
||
|
||
Смежные доки:
|
||
- [[personal/tech/music-tags-repair-truenas]] — детали ремонта тегов
|
||
- [[personal/plans/tagwriter-container]] — контейнер-писатель
|
||
- [[family/how-to/navidrome-truenas-android]] — Navidrome + клиенты
|
||
- [[family/how-to/truenas-access]] — доступ к NAS (**читать перед SSH**)
|