Files
obsidian-vault/personal/plans/music-artist-albumartist-repair.md.bak-verify-v3-20260927

327 lines
18 KiB
Plaintext
Executable File
Raw Permalink 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.
---
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**)