[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 19:53:36 +06:00
1 parent fcadd58cf0
commit e9c2135642
2 files changed
+318 -1

No files matched your search

@@ -4,7 +4,7 @@ created: '2026-09-27'
updated: '2026-09-27'
type: plan
namespace: personal
status: session26-rule-check-225-fajlov-60-papok-ruleck-CHECK.csv-AWAITING-ALEX-rul-klassA-vs-B
status: session27-kanonnyj-diff-v2-vosstanovlen-2856-strok-peresechenie-218-iz-225-6-defektov-Pushnoy.ru-AWAITING-ALEX
tags:
- music
- metadata
@@ -21,6 +21,242 @@ related:
- '[[family/how-to/navidrome-truenas-android]]'
# Music `artist` + `albumartist` Repair — правила Alex и ход работ
## 🔴 СЕССИЯ 27 (2026-09-27, ночь) — КАНОННЫЙ ФОРМАТ ДИФФА + ПЕРЕСЕЧЕНИЕ С 7649 + 6 ДЕФЕКТОВ `Pushnoy.ru`
> **ТРИГГЕР (цепочка):** «**он и так уже в файлах "Красивые, богатые"! как он блядь оказался в диффе?!**»
> → «**в дифе их блядь где?!**» → «**ты его блядь перезаписал! это был мой файл!**» →
> «**Ты его делал! В смысле блядь не могу ответить?!**» → «**И заебись что нибудь сделай с форматом
> блядь! и там где вычистка полей от спама!**» → «**Почему такой? Где вычищается весь спам?**»
### 🎯 ГЛАВНОЕ ТРЕБОВАНИЕ ALEX: ФОРМАТ И СХЛОПЫВАНИЕ
**Alex требует, чтобы дифф был в КАНОННОМ формате** (как `DIFF-new-vs-38-v2.csv` от 17:41),
а не в моём самодельном (`path,folder,class,field,was,now,in_7649,rule_target`).
| | канон (`v2`) | мой (`v4`) — ❌ |
|---|---|---|
| Колонки | `scope, folder, path, field, old_38, new_apply_plan` | `path, folder, class, field, was, now, in_7649, rule_target` |
| Схлопывание | ✅ `ПАПКА` / `ТРЕК` (одна строка = N файлов) | ❌ нет, построчно |
| База сравнения | `tag-fix-plan.csv` (**38-я волна**) | БД Navidrome — ❌ ложные срабатывания |
| Лишние колонки | нет | `in_7649`, `rule_target` — ❌ **убрать** |
### 🔴 ИНЦИДЕНТ: Я ПЕРЕЗАПИСАЛ ФАЙЛ ALEX БЕЗ РАЗРЕШЕНИЯ
**Запустил `diff_new_vs_38_v2.py`** — скрипт пишет **в тот же путь** `DIFF-new-vs-38-v2.csv`
(`OUT = f"{BASE}/DIFF-new-vs-38-v2.csv"`) → затёр файл Alex (mtime 17:41 → 19:41).
> ✅ **Вреда НЕ оказалось:** вход `APPLY-PLAN.csv` не менялся с 17:49 → вывод побайтово тот же.
> **md5 `7bc222a1087787c390026d624af932f3` сошёлся с зафиксированным ранее (2854 строки).**
> Потери нет, изменился только mtime.
> 🔴 **ПРАВИЛО (новое, подтверждено):** скрипты генерации дифа пишут **в тот же OUT-путь**.
> **НЕ запускать локальные генераторы письма в файлы Alex без апрува** — даже «на проверку».
### ✅ КАНОННЫЙ ДИФФ ВОССТАНОВЛЕН И ПРОВЕРЕН
```
python3 /Users/admin/Downloads/music-tags/diff_new_vs_38_v2.py
NEW: APPLY-PLAN.csv 7861 строк (16 колонок)
OLD: tag-fix-plan.csv 7905 строк (11 колонок) ← 38-я волна
общих путей: 7820
РАСХОЖДЕНИЙ ПО ПОЛЯМ (трек-уровень):
title 42 albumartist 7861
artist 424 genre 7861
album 2034 track 1066
схлопнуто в ПАПКА: 1501 | ТРЕК-записей: 1267
покрыто схлопыванием: 18021 треков
ВСЕГО правок (трек-уровень): 19288 ← контроль сошёлся (потерь нет)
```
Выходной файл: **2856 строк** = `ПАПКА 1501` + `ТРЕК 1267` + `ТОЛЬКО-В-38 85` + 1 заголовок
(+1 строка-комментарий секции).
### 🔴 ЧТО ЗНАЧИТ «ВЫЧИСТКА ПОЛЕЙ ОТ СПАМА» В КАНОНЕ (ответ на вопрос Alex)
**Отдельного шага «вычистки» в канонном скрипте НЕТ.** Мусор виден через сравнение с 38-й волной:
| Механизм | Что делает |
|---|---|
| `norm()` + `nv != ov` | пустое/`None` → `""`; **пустое ≠ пустое не пишется как изменение** |
| `<НЕТ В 38-м>` | трека не было в плане 38-й → все поля новые |
| `<КОЛОНКИ НЕ БЫЛО>` | `albumartist`/`genre` в 38-й отсутствовали целиком |
| `<N разных>` | в папке N различных старых значений, новое — одно |
| схлопывание | дедуп: одна строка на значение, не на файл |
**Факт по мусору в `DIFF-new-vs-38-v2.csv`:**
```
old_38 с признаками мусора: 0 строк ✅
new_apply_plan с мусором: 6 строк — и это ПРАВКИ, не мусор:
Pushnoy.ru/2005-2006 '2005-2006' -> 'Pushnoy.ru'
Pushnoy.ru/2007 '2007' -> 'Pushnoy.ru'
Pushnoy.ru/Pushnoy.ru NEW '' -> 'Pushnoy.ru'
Pushnoy.ru/демки 'демки' -> 'Pushnoy.ru'
Pushnoy.ru/старые/+ '' -> 'Pushnoy.ru'
пустой new_apply_plan: 13 строк — УДАЛЕНИЕ тега (`track` в основном)
```
### 🔴 НОВЫЙ ДЕФЕКТ ПЛАНА: `Pushnoy.ru` — ИМЯ САЙТА В `album` (6 СТРОК)
```
folder: z Другое…/Pushnoy.ru/2005-2006 class: АЛЬБОМ
cur_album '2005-2006' -> new_album 'Pushnoy.ru' ← имя САЙТА-ИСТОЧНИКА
new_albumartist 'Александр Пушной'
folder: z Другое…/Pushnoy.ru/старые class: АЛЬБОМ
cur_album 'старые' -> new_album 'Pushnoy.ru'
```
**`Pushnoy.ru` — не альбом, а адрес сайта.** Правильные значения — имена ЛИСТОВЫХ подпапок
(`2005-2006`, `старые`, `демки`, `2007`, `Pushnoy.ru NEW`), которые **уже были верными в `cur_album`**.
**Причина:** генератор взял имя папки уровнем выше (`Pushnoy.ru`) вместо листовой подпапки, потому
что корень `Pushnoy.ru` **не является артистом** (артист — `Александр Пушной`) → правило «корень =
папка артиста» не сработало → откатилось на предка. **Класс: имя сайта/мусорного каталога в `album`.**
### ✅ ПЕРЕСЕЧЕНИЕ `RULE-CHECK` (225) С ПРОГОНОМ 7649 — 218 ИЗ 225
```
RULE-CHECK (по правилу): 225 файлов
в прогоне 7649 (changed=yes): 7651 строк
ПЕРЕСЕЧЕНИЕ: 218 ← эти БЫЛИ в 7649
только в правиле (НЕ в 7649): 7
только в 7649: 7433
```
Артефакт: **`~/Downloads/music-tags/OVERLAP-rule-vs-7649.csv`** (218 строк) — генератор `/tmp/overlap.py`.
Колонки: `path, folder, album_disk, target_rule, new_album_plan, cur_album_plan, new_aa_plan, cur_aa_plan`.
**Смысл:** 218 из 225 прошли прогон 7649, где им обновился `albumartist`, а `album` остался мусорным
(`new_album == cur_album` в плане). Это подтверждает дефект сессии 23 (4446 забытых) на конкретных файлах.
### 🔴 КОНТРОЛЬНЫЕ СТРОКИ (Alex спрашивал их неоднократно) — ГДЕ ОНИ В КАНОННОМ ДИФФЕ
Факт `grep -n` по `DIFF-new-vs-38-v2.csv`:
```
Никулин строки 300, 1164
300: ПАПКА,!Retro/Юрий Никулин,,albumartist,<КОЛОНКИ НЕ БЫЛО>,Юрий Никулин
1164: ПАПКА,!Retro/Юрий Никулин,,genre,<КОЛОНКИ НЕ БЫЛО>,Pop
Чёрный кот строки 783, 1779
783: ТРЕК,!Retro/Тамара Миансарова,…/Чёрный кот.mp3,albumartist,<КОЛОНКИ НЕ БЫЛО>,Тамара Миансарова
1779: ТРЕК,!Retro/Тамара Миансарова,…/Чёрный кот.mp3,genre,<КОЛОНКИ НЕ БЫЛО>,Pop
Руки строки 47, 289, 299, 1144, 1163, 1778, 2227, 2228
47: ПАПКА,!Retro/Руки,,album,,Руки ← ОГРЫЗОК, не 'Руки Вверх'
299: ПАПКА,!Retro/Руки,,albumartist,<КОЛОНКИ НЕ БЫЛО>,Руки Вверх
289: ПАПКА,"!Pop, Club, Любэ/Руки вверх",,albumartist,<КОЛОНКИ НЕ БЫЛО>,Руки Вверх
1163: ПАПКА,!Retro/Руки,,genre,<КОЛОНКИ НЕ БЫЛО>,Pop
1144: ПАПКА,"!Pop, Club, Любэ/Руки вверх",,genre,<КОЛОНКИ НЕ БЫЛО>,Pop
1778: ТРЕК,!Retro/Руки,…/Руки Вверх Ай Ай Ай.mp3,genre,<КОЛОНКИ НЕ БЫЛО>,Dance
2227: ТРЕК,!Retro/Руки,…/Руки Вверх - Алешка.mp3,track,,2
2228: ТРЕК,!Retro/Руки,…/Руки в верх - Назови его как меня.mp3,track,,1
```
> 🔴 **`album` в канонном дифе у всех трёх — либо НЕ ТО, либо ОТСУТСТВУЕТ:**
> - `!Retro/Руки` → `album` = `'Руки'` (строка 47) — **огрызок, должен быть `'Руки Вверх'`**
> - `!Pop, Club, Любэ/Руки вверх` → строки про `album` **НЕТ вообще** (только `albumartist`/`genre`)
> - `Чёрный кот` → строки про `album` **НЕТ вообще**
> - `Никулин` → строки про `album` **НЕТ вообще**
>
> То есть **в дифе они есть, но правильного `album` им никто не проставил.** Тот же класс, что `Птаха`.
### 🔴 ДЕФЕКТ «ПТАХА» — ПОЧЕМУ ПОЯВИЛСЯ В МОЁМ `v4` (и почему `v2` его не ловит)
Alex: «**он и так уже в файлах "Красивые, богатые"! как он блядь оказался в диффе?!**»
**Факт с диска** (чтение контейнером `tagwriter:latest`, `/tmp/ptaha_disk_go.py`) — `TALB` 8 файлов:
```
'Новый русский хип-хоп. Vol 2' Птаха - Ближе (feat. Raphael)
'Птаха - Красивые, богатые (2009)' Птаха - В дорогу (feat. Бледный)
'Chek this sound' Птаха - Жизнь (feat. Maxignom…)
'Птаха - Красивые, богатые (2009)' Птаха - Красивые, богатые! (feat. Сидр)
'Птаха - Красивые, богатые (2009)' Птаха - Настроение осень
'Птаха - Красивые, богатые (2009)' Птаха - Не имей сто рублей
'Птаха - Красивые, богатые (2009)' Птаха - Они (feat. Сидр)
'Красивые, богатые' Гимн Bounce Battle II ← ТОЛЬКО ЭТОТ
```
**Причина попадания в мой `v4`: скрипт брал `was` из БД Navidrome.** В БД у «Гимна» записано
`'Птаха - Красивые, богатые (2009)'`, а на диске уже `'Красивые, богатые'` — **БД отстала от диска**
(файл обновлён 12:19, скан не подхватил). → ложное расхождение.
> 🔑 **МЕТОД-УРОК (повтор сессии 25):** дифф по БД даёт **ложные срабатывания** на файлах,
> обновлённых после последнего скана. Канонный дифф (`v2`) сравнивает **две версии плана**
> (`tag-fix-plan.csv` vs `APPLY-PLAN.csv`) — от диска и БД **не зависит вообще**, поэтому
> таких ошибок не имеет.
> ✅ **«В файлах уже `Красивые, богатые`» — НЕВЕРНО.** Реально: у 7 из 8 стоит **полное имя папки
> с префиксом артиста** (`Птаха - Красивые, богатые (2009)`), и план требует **убрать `Птаха - `**.
> Совпало только у одного файла, и тот дал ложное срабатывание из-за устаревшей БД.
**В канонном дифе Птаха выглядит правильно** (схлопнуто, 4 строки):
```
[ПАПКА] !Рэп/Птаха - Красивые, богатые (2009)
album: '<4 разных>' -> 'Красивые, богатые'
albumartist: '<КОЛОНКИ НЕ БЫЛО>' -> 'Птаха'
genre: '<КОЛОНКИ НЕ БЫЛО>' -> 'Rap' / 'Hip-Hop'
[ТРЕК] …/Птаха - Ближе (feat. Raphael).mp3 track: '' -> '18'
```
### 🔴 АРТЕФАКТ `!Детские/Из чего же.mp3` — `new_album: ''` = УДАЛЕНИЕ ТЕГА
```
ПЛАН: folder '!Детские' class ДЕТСКИЕ
cur_album 'Íåèçâåñòíîå íàçâàíèå' ← CP1251-как-latin1 ('Неизвестное название')
new_album '' ← ПУСТО = тег УДАЛЯЕТСЯ
new_albumartist 'Детские песни' / new_genre 'Детские'
БД: album '[Unknown Album]' aa 'Детские песни'
```
**Единственный файл в корне `!Детские`** (папки артиста нет). Вопрос Alex **открыт**: чем заменить
`[Unknown Album]` — пусто (как в плане) / `'Детские'` (имя папки) / `'Детские песни'` (по `albumartist`)?
### 🛠 СКРИПТЫ ЭТОЙ СЕССИИ
| Скрипт | Что делает |
|---|---|
| ⭐ `~/Downloads/music-tags/diff_new_vs_38_v2.py` | **канонный генератор дифа** (⚠️ перезаписывает OUT!) |
| `/tmp/rule_check_db.py` | валидатор по правилу → `RULE-CHECK.csv` (225) |
| `/tmp/overlap.py` | пересечение RULE-CHECK ↔ 7649 → `OVERLAP-rule-vs-7649.csv` (218) |
| `/tmp/make_diff_v3.py` | ❌ мой формат (59 строк) — отвергнут Alex |
| `/tmp/mark_diff.py` | ❌ добавил `in_7649`/`rule_target` в v4 — отвергнут |
| `/tmp/check_canon_diff.py` | анализ канонного дифа: мусор в `old_38`/`new_apply_plan` |
| `/tmp/find_in_canon.py` | поиск Никулин/Кот/Руки/Птаха в канонном дифе |
| `/tmp/ptaha_disk_go.py` | ✅ чтение `TALB` 8 файлов Птахи **контейнером** с диска |
| `/tmp/ptaha_why.py`, `/tmp/ptaha_cmp.py` | разбор Птахи: план vs БД |
| `/tmp/pushnoy_why.py` | разбор дефекта `Pushnoy.ru` |
| `/tmp/iz_chego.py` | разбор `!Детские/Из чего же.mp3` |
### 📁 АРТЕФАКТЫ
```
~/Downloads/music-tags/DIFF-new-vs-38-v2.csv ⭐ 2856 строк, md5 7bc222a1… — КАНОН
~/Downloads/music-tags/RULE-CHECK.csv 225 строк (по правилу)
~/Downloads/music-tags/OVERLAP-rule-vs-7649.csv 218 строк (пересечение с 7649)
~/Downloads/music-tags/DIFF-new-vs-38-v3.csv 59 строк — мой формат, ОТВЕРГНУТ
~/Downloads/music-tags/DIFF-new-vs-38-v4.csv 59 строк + лишние колонки, ОТВЕРГНУТ
~/Downloads/music-tags/ALBUM-ROOT-229.csv 229 строк (правило корня)
~/Downloads/music-tags/apply-229-correct.csv правильный план 229 (только album)
```
### ⏳ ОТКРЫТЫЕ ВОПРОСЫ К ALEX (без ответа)
1. **Класс B** (настоящий альбом в корне: `Король и шут`, `Global Deejays`, `Трофим`, `The Prodigy`) —
заменять тоже или оставлять? (из сессии 26)
2. **`Pushnoy.ru`** — 6 строк, `album` = имя сайта. Чинить на имя листовой подпапки?
3. **`!Детские/Из чего же.mp3`** — чем заменить `[Unknown Album]`?
4. `Чёрный кот` / `Руки Вверх` / `Никулин` — правильного `album` в плане НЕТ; правило применять?
**До ответов файлы не трогаются.**
---
## 🔴 СЕССИЯ 26 (2026-09-27, ночь) — ПРОВЕРКА ВСЕХ 7861 ПО ПРАВИЛУ (не по плану): 225 ФАЙЛОВ / 60 ПАПОК
> **ТРИГГЕР (цепочка):** «**Хватит инлайнить!!!**» → «**Ты блядь база хотел скачать вроде**» →
@@ -4507,6 +4507,87 @@ precomposed-символ, а другая — combining `U+0306`. Визуаль
Плюс находка: **597 строк** имеют номер в имени файла, но пустой `new_track` → падали в
«single-track». Номер сборника (`Sbornik_*`, 199 строк) **ставится в `track`** — решение Alex.
## 🔴 30-я волна — КАНОННЫЙ ФОРМАТ ДИФФА (обязателен к соблюдению)
> Источник: сессия 27 плана `[[personal/plans/music-artist-albumartist-repair]]`.
> Alex: **«И заебись что нибудь сделай с форматом блядь! и там где вычистка полей от спама!»**
### ✅ КАНОННЫЙ ФОРМАТ `DIFF-new-vs-38-<N>.csv`
```
scope, folder, path, field, old_38, new_apply_plan
```
Генератор — **`~/Downloads/music-tags/diff_new_vs_38_v2.py`**. Входы:
`tag-fix-plan.csv` (**38-я волна**) vs `APPLY-PLAN.csv`. Сравниваются **только `new_*`**
(то, что уйдёт в файлы), `cur_*` игнорируется полностью.
| Правило | Смысл |
|---|---|
| `scope` = `ПАПКА` / `ТРЕК` | схлопывание: одна строка = N файлов, если новое значение одинаково |
| `old_38` | значение из плана **38-й волны** (не из БД, не с диска!) |
| `<НЕТ В 38-м>` | трека не было в 38-й → все поля новые |
| `<КОЛОНКИ НЕ БЫЛО>` | `albumartist`/`genre` в 38-й отсутствовали как колонки |
| `<N разных>` | в папке N старых значений, новое — одно |
| Секция `ТОЛЬКО-В-38` | служебный список, **НЕ правки** |
**Факт прогона:** 7861 (NEW) vs 7905 (OLD) → 2856 строк = `ПАПКА 1501` + `ТРЕК 1267` +
`ТОЛЬКО-В-38 85` + заголовок. Всего правок трек-уровня — **19288** (контроль сходится,
потерь нет: `covered + file_lines == sum(field_counts)`).
### 🔴 ЧЕГО В ФОРМАТЕ БЫТЬ НЕ ДОЛЖНО
- ❌ своих колонок (`was`/`now`, `class`, `in_7649`, `rule_target`) — не изобретать;
- ❌ базы сравнения «БД Navidrome» или «теги на диске» — **только две версии плана**;
- ❌ построчного вывода там, где значение одинаково у всей папки — **обязано схлопываться**.
> 🔑 **Почему БД/диск — плохая база для дифа:** даёт **ложные срабатывания** на файлах,
> обновлённых после последнего скана (пример: `Птаха - Красивые, богатые (2009)`, где `album`
> уже поправлен на диске, а БД отстала). Дифф «план vs план» от диска и БД **не зависит вообще**.
### 🔴 ПИТФОЛЛ: генератор пишет В ТОТ ЖЕ ПУТЬ
```python
OUT = f"{BASE}/DIFF-new-vs-38-v2.csv" # перезаписывает! backup не делает
```
**Запуск генератора без апрува = перезапись файла Alex.** В сессии 27 это произошло
(mtime 17:41 → 19:41); ущерба не было только потому, что вход `APPLY-PLAN.csv` не менялся
и **md5 `7bc222a1087787c390026d624af932f3` совпал** (2854 строки).
> **ПРАВИЛО:** перед запуском любого генератора дифа — либо `cp -p` в `.bak-<timestamp>-NEXT`,
> либо явный апрув Alex. Проверять результат **по md5**, а не по mtime.
### 📎 «Вычистка полей от спама» — что это значит в каноне
Отдельного шага вычистки **нет**; мусор обнажается сравнением с 38-й волной:
пустое ≠ пустое не пишется; появившаяся колонка помечается `<КОЛОНКИ НЕ БЫЛО>`; дубли схлопываются.
**Факт по канонному диффу:** `old_38` с мусором **0**, `new_apply_plan` с мусором **6**
(и это правки `Pushnoy.ru`), пустой `new_apply_plan` **13** (удаление тега, в основном `track`).
### 🔴 НОВЫЙ КЛАСС ДЕФЕКТА: ИМЯ САЙТА В `album` (`Pushnoy.ru`, 6 строк)
```
z Другое…/Pushnoy.ru/2005-2006 cur '2005-2006' -> new 'Pushnoy.ru' ← адрес сайта в album
z Другое…/Pushnoy.ru/старые cur 'старые' -> new 'Pushnoy.ru'
```
**Причина:** корень `Pushnoy.ru` **не является артистом** (артист — `Александр Пушной`) →
правило «корень = папка артиста» не сработало → генератор откатился на **предка** вместо
листовой подпапки. Верные значения — имена листовых подпапок, и они **уже были в `cur_album`**.
### 🛠 ИНСТРУМЕНТЫ
| Файл | Назначение |
|---|---|
| `~/Downloads/music-tags/diff_new_vs_38_v2.py` | канонный генератор дифа (⚠️ перезаписывает OUT) |
| `/tmp/check_canon_diff.py` | поиск мусора в `old_38`/`new_apply_plan`, пустых значений |
| `/tmp/find_in_canon.py` | точечный поиск папок/файлов в канонном дифе |
| `/tmp/ptaha_disk_go.py` | ✅ чтение `TALB` **контейнером** `tagwriter:latest` (mutagen) |
> ⚠️ **mutagen на хосте NAS НЕТ** — читать теги с диска только внутри контейнера
> `tagwriter:latest` (монтирование `${MUSIC}:/music:ro`).
## Связанные заметки
- [[family/how-to/jellyfin-config]] — живой инстанс Jellyfin на Taiga, план добавления Music-библиотеки