[2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/tech/music-tags-repair-truenas.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-27 21:34:55 +06:00
1 parent 7932f70274
commit 68356f1677
2 files changed
+166 -20

No files matched your search

+133 -14
View File
@@ -4,7 +4,7 @@ created: '2026-09-27'
updated: '2026-09-27'
type: plan
namespace: personal
status: v3-verified-3-discrepancies / dry-run-blocked-on-alex-decision
status: dry-run-done / boevoy-zapusk-pravilnyy-cmd-ne-podtverzhden
tags:
- music
- metadata
@@ -46,12 +46,17 @@ related:
Канон: ~/Downloads/music-tags/APPLY-PLAN.csv (7861 строка — НЕ ПЕРЕЗАПИСЫВАТЬ)
```
**Состояние:** артефакты v3 проверифицированы 27 сен. **Dry-run НЕ запущен** —
блокирует решение Alex: дифф считает группы (genre 911), dry-run считает файлы
(genre 4359). См. § ВЕРИФИКАЦИЯ v3.
**Состояние (27 сен, сессия ~30):** ✅ **dry-run ВЫПОЛНЕН УСПЕШНО** — `7653 к правке`,
`mp3 на диске 7861`, `не найдено 0`. Требование Alex «совпасть по цифрам с
APPLY-PLAN-v3-changes» **снято самим Alex** («нихуя не понял… очевидно что полный набор»):
дифф считает группы, dry-run — файлы, совпадение недостижимо by design. Решение Alex:
**пишем полный набор полей** (не только `TCON`).
**Открытый дефект:** жанры (`TCON`) не записаны в теги — прогон шёл старым скриптом.
Скрипт уже исправлен, **прогон не сделан**.
🔴 **БОЕВОЙ ПРОГОН НЕ СОСТОЯЛСЯ — запущен с неверным аргументом.** См. § БОЕВОЙ ПРОГОН,
немедленно читать перед повтором.
**Открытый дефект:** жанры (`TCON`) в тегах отсутствуют. Скрипт исправлен, dry-run доказал,
что `TCON` пишется. Боевой прогон — ждёт корректной команды.
**Доступ к NAS:** `ssh -i ~/.ssh/id_rsa truenas_admin@mallexxx.duckdns.org`
⚠️ `192.168.1.100` — ЧУЖАЯ подсеть, не использовать. Подробнее → [[family/how-to/truenas-access]]
@@ -71,6 +76,8 @@ related:
| 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` |
| 10 | 27 сен | **Dry-run выполнен: 7653 к правке, не найдено 0** — `TCON` пишется | `/tmp/dryrun.log` |
| 11 | 27 сен | 🔴 **Боевой прогон не состоялся** — `CMD=--dry-run` образа подставился | `/tmp/tw306/run.log` |
---
@@ -123,16 +130,33 @@ python3 /tmp/make_applyplan_v3.py
# → /tmp/apply-plan-v3.csv
```
### Применить теги (образ tagwriter)
### Применить теги (образ tagwriter) — 🔴 ЧИТАТЬ `CMD` ОБРАЗА ПЕРЕД ЗАПУСКОМ
```bash
# сухой прогон
# 1. ВСЕГДА сначала посмотреть, что в CMD образа:
docker inspect tagwriter:latest --format 'CMD={{json .Config.Cmd}} ENTRY={{json .Config.Entrypoint}}'
# ФАКТ 27 сен: CMD=["--dry-run"] — то есть --dry-run ПОДСТАВЛЯЕТСЯ АВТОМАТИЧЕСКИ.
# 2. СУХОЙ ПРОГОН — скрипт лежит в /app/, НЕ в /app/data/:
docker rm -f twdry >/dev/null 2>&1
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 /app/apply_tags_v2.py --dry-run
docker start -a twdry
# 3. БОЕВОЙ ПРОГОН — CMD переопределить явно, иначе будет dry-run (см. ПИТФОЛЛ 13):
docker rm -f twrun >/dev/null 2>&1
docker create --name twrun --entrypoint python3 \
-v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:rw \
tagwriter:latest /app/apply_tags_v2.py --apply # ← ФЛАГ ПОДТВЕРДИТЬ В СКРИПТЕ
docker start -a twrun
```
- Образ `tagwriter:latest` (НЕ `v2`), uid 950:950, amd64
- План читается ТОЛЬКО как `/app/data/apply-plan.csv`
- Скрипт: `/app/apply_tags_v2.py` — пишет `TIT2 TPE1 TPE2 TALB TCON TRCK`
- Образ `tagwriter:latest` (НЕ `v2`), uid 950:950, amd64 · 25971 байт `apply_tags_v2.py`
- **План читается ТОЛЬКО как `/app/data/apply-plan.csv`** → подкладывать
`/tmp/apply-plan-v3.csv` под этим именем
- **Скрипт лежит в `/app/`**, план — в `/app/data/`. Путать нельзя (см. ПИТФОЛЛ 14)
- Скрипт пишет `TIT2 TPE1 TPE2 TALB TCON TRCK` (все 6 полей сразу, режима «только genre» нет)
- Строки скрипта: 76 `("new_genre","TCON","genre")` · 206 `TCON(...)` · 351 MP3-ветка ·
515 верификация читает `TCON` обратно
### Пересканить Navidrome после правки тегов
```bash
@@ -183,6 +207,8 @@ backup_raw
/tmp/verify_v3.py верификация: лог vs план
/tmp/verify_v3b.py верификация: ПАПКА/ТРЕК-строки диффа из плана
/tmp/verify_v3c.py верификация: аномалии (genre-дубли, Sbornik, пропуск пути)
/tmp/parse_dry.py разбор лога dry-run по полям (только показанные 40 файлов!)
/tmp/dryrun.log вывод успешного dry-run (копия с NAS)
~/Developer/personal-os/tools/music-tags/build_fix_plan2.py генератор плана (пофикшен)
```
@@ -253,10 +279,77 @@ Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (в
| Дефект | Факт | Статус |
|--------|------|--------|
| **Жанры не в тегах** | В БД `genre` пусто у всех 7861; прогон 27 сен не писал `TCON` | Скрипт исправлен, **прогон нужен** |
| **Жанры не в тегах** | В БД `genre` пусто у всех 7861; прогон 27 сен не писал `TCON` | Скрипт исправлен, dry-run доказал `TCON`. **Боевой прогон нужен** |
| 🔴 **Боевой прогон сорвался** | Запущен без переопределения `CMD` → подставился `--dry-run` образа | **Ничего не записано. Повтор с явным CMD** |
| Жанры в диффе | 427 строк / 911 групп, покрытие 7861 — не «правка», а повторная запись | Учтено |
| Лог v3 устарел | 3 строки + 1 путь расходятся с планом после `fix_ruki`/`fix_rockopop` | Решено: план — эталон, лог не пересобираем |
| Дифф: genre-группы | 211 ТРЕК-строк дублируют `new_genre`; 7 одиночек не добраны | **Ждём решения Alex** |
| Дифф: genre-группы | 211 ТРЕК-строк дублируют `new_genre`; 7 одиночек не добраны | **Alex снял вопрос:** пишем полный набор, дифф не пересобираем |
---
## 🚀 DRY-RUN и БОЕВОЙ ПРОГОН (27 сен, сессия ~30)
### ✅ DRY-RUN — ВЫПОЛНЕН УСПЕШНО
```bash
scp -i ~/.ssh/id_rsa /tmp/apply-plan-v3.csv \
truenas_admin@mallexxx.duckdns.org:/tmp/tw306/apply-plan.csv
# md5 на NAS = 6b9e49b2baa3f1d1708e4566972266a9 (= локальному)
```
Вывод (`/tmp/dryrun.log`, 21955 B; на NAS `/tmp/tw306/dryrun.log`):
```
CSV: /app/data/apply-plan.csv
строк в плане: 7861
changed=yes требующих правки, но помеченных no: 4
mp3 на диске: 7861 пропущено не-mp3: 562
пропущено changed=no: 208
не найдено на диске: 0 ← ни одного потерянного пути
к обработке: 7653
=== DRY-RUN — ничего не записано (7653 файлов) ===
ИТОГО к правке: 7653
```
**`TCON` доказан рабочим** — живой пример из лога:
```
!Барды/Владимир Высоцкий/Кони привередливые.mp3
albumartist 'Vladimir Vysockij' -> 'Владимир Высоцкий'
album '(Высоцкий)' -> 'Владимир Высоцкий'
genre 'Разное' -> 'Бардовская песня'
```
⚠️ **Лог печатает только первые 40 файлов** из 7653 (`... и ещё 7613`), **итога по полям
в логе НЕТ** — только `ИТОГО к правке: 7653`. Не считать счётчики по 40 показанным
файлам: получается ложное «genre меняется у 37», хотя в плане genre меняется у **4359**.
**Итог по полям берётся ИЗ ПЛАНА, а не из лога:**
```
genre 4359 · albumartist 7213 · album 2031 · track 441 · artist 381 · title 8
plan.changed=yes 7653
```
### 🔴 БОЕВОЙ ПРОГОН — НЕ СОСТОЯЛСЯ (запущен с неверным аргументом)
Запущено:
```bash
docker create --name twrun --entrypoint python3 \
-v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:rw \
tagwriter:latest /app/apply_tags_v2.py # ← CMD НЕ переопределён
docker start -a twrun
```
Результат: `exit=0`, но в `/tmp/tw306/run.log` строка
`=== DRY-RUN — ничего не записано (7653 файлов) ===`.
**Причина:** у `tagwriter:latest` в `CMD` уже прописан `--dry-run`:
```
ENTRYPOINT = ["python3", "/app/apply_tags_v2.py"]
CMD = ["--dry-run"]
```
При `docker create` без своего CMD образ подставил свой → скрипт получил `--dry-run`.
**✅ Ничего не записано, музыка не тронута, бэкап не потребовался.**
**Осталось перед повтором:** подтвердить в скрипте флаг боевого режима (в доках он
не зафиксирован — проверялись только `--dry-run`), затем `docker create` **с явным CMD**.
---
@@ -273,10 +366,23 @@ Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (в
genre 911 · album 28 · albumartist 6
Дифф на 100%: 7861 файл сопоставлен, не найдено 0
Лог v3: 31 строка
Dry-run 27 сен: план 7861 · mp3 на диске 7861 · не найдено 0 · к обработке 7653
(пропущено changed=no 208, не-mp3 562) · лог печатает 40 файлов
changed=yes но расхождений нет: 4 файла (Infected Mushroom ×3, Пушной)
Боевой прогон: НЕ СОСТОЯЛСЯ — подставился `CMD=--dry-run` образа
```
---
## 📁 ФАЙЛЫ НА NAS (рабочие, `/tmp/tw306/`)
```
apply-plan.csv md5 6b9e49b2baa3f1d1708e4566972266a9 (= /tmp/apply-plan-v3.csv)
dryrun.log 21955 B — вывод успешного dry-run (есть копия на Mac /tmp/dryrun.log)
run.log вывод сорвавшегося боевого прогона (снова DRY-RUN)
check_album_applied*.py / verify_306*.py вспомогательные проверки
---
## 🧨 ПИТФОЛЛЫ
1. **`192.168.1.100` — чужая подсеть.** NAS: `mallexxx.duckdns.org`. Агент объявлял
@@ -302,6 +408,19 @@ Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (в
совпадающее с `cur_genre` (дубль групповой строки). Не считать их списком правок.
12. **Лог правок пересобирать ПОСЛЕ последнего фикса плана.** Иначе лог расходится с
планом (так и вышло: `fix_ruki`/`fix_rockopop` прошли после генерации лога).
13. 🔴 **`CMD` ОБРАЗА ПОДСТАВЛЯЕТСЯ АВТОМАТИЧЕСКИ.** У `tagwriter:latest` прописан
`CMD=["--dry-run"]` → `docker create` БЕЗ своего CMD запускает dry-run, а не боевой
прогон. Внешне выглядит как успех (`exit=0`), в логе — «ничего не записано».
**Всегда:** `docker inspect --format 'CMD={{json .Config.Cmd}}'` ПЕРЕД `create`
и явный флаг в `create`. Проверять в логе строку про DRY-RUN, не только `exit=0`.
14. 🔴 **Скрипт в `/app/`, план в `/app/data/`.** `/app/data/apply_tags_v2.py` →
`[Errno 2] No such file or directory`. Только `/app/data/` — это точка монтирования,
скрипт туда не кладётся.
15. **Лог dry-run неполный (40 файлов из 7653) и без итогов по полям.** Цифры по полям
брать из плана (`cur_* != new_*`), не считать по обрезанному логу.
16. **Не предлагать Alex выбор, которого он не делал.** «Только жанры или полный набор?»
при живом плане = лишний круг и раздражение: набор очевиден из плана. Молчаливые
умолчания не выдумывать, но и очевидное не переспрашивать.
---