diff --git a/personal/plans/music-tags-review/music-tags-review.md b/personal/plans/music-tags-review/music-tags-review.md index 50bbfd4f..0b01e964 100755 --- a/personal/plans/music-tags-review/music-tags-review.md +++ b/personal/plans/music-tags-review/music-tags-review.md @@ -20,6 +20,49 @@ updated: '2026-09-26' | [[2-medium-confidence\|Средняя уверенность]] | 5813 | | [[3-high-confidence\|Высокая уверенность]] | 959 | +## 🔴 Девятнадцатая волна (2026-09-26): ИМЕНА ДИСКОВ ВЫЧИЩЕНЫ + NSK РАЗОБРАН + +Alex: **«Откуда строки с пустым артистом? Это косяк»** → **«Что за NSK? ... Больше нет +треков без артиста?»** + +### 🛠 Имена дисков/техпапок в `new_album` — ВЫЧИЩЕНЫ (200 строк) + +**Папка `!Детские/Сборка Песни для детей/` разбита на подпапки-«диски»** (`1`,`2`,`C`,`K`,`L`,`M`,`P`,`Y`,`Les`), +и граббер записал имя подпапки в `new_album`. **Это часть пути, не альбом** → 155 строк очищены, +реальный альбом `Сборка Песни для детей`. Тот же класс: `misc` (~40: Пилот, Rammstein, +Красная Плесень, Eminem), `new` (3, НТЛ), `Album` (1, Пилот) → пусто. + +> ⚠️ Побочно: у `19. Песенка смелого моряка` при вырезании диска съехал `new_track` (был 20). +> Проверять `new_track` рядом с вырезаемым `new_album`. + +### 🤔 188 строк без artist — НЕ косяк (проверено по самому раннему бэкапу) + +| Источник | Кол-во | +|---|---:| +| `!Детские/Сказки/*` — аудиосказки, исполнителя нет | 186 | +| `5 POPSA` нарезки (NSK, Перец…) — ID3-кадры пустые | ~12 | +| `НАШЕСТВИЕ 2009`, `Голубой щенок` | 2 | + +### 🔬 `NSK.mp3` — разбор живьём на NAS + +`ID3 v2.3` заголовок есть (size=1014), но **кадры пустые** (нули) → тегов нет. +MPEG-заголовок `0xFFFB9240` = 128 kbps / 44100 Hz, файл 476 КБ ≈ **29 секунд** — нарезка +чарт-сборника. `NSK` = имя файла (вероятно «Новосибирск»), **НЕ название песни**. +Artist оставлен **пустым**, а не выдуман. + +**Питфоллы окружения (закрепить):** `ffprobe` молча пусто на пустом ID3; `mutagen` на NAS +НЕ установлен; `xxd` на NAS НЕТ; вложенные кавычки в `ssh 'python3 -c "..."'` ломаются → +**скрипт локально + `scp` в `/tmp/` + `ssh … 'python3 /tmp/x.py'`**. + +### ⏭ ЖДЁМ РЕШЕНИЯ ALEX по ~12 нарезкам `5 POPSA` + +**A)** вписать artist/title вручную (нужен ID по звуку); **B)** оставить пустым; +**C)** `no_change` — не трогать файлы. Сказки (186) — предлагается оставить как есть. + +### 📊 Итог 19-й волны + +`fix` **6931** / `no_change` **976** · 0 битых · 7907 строк · все CRLF · сводные CSV 778 + 7128 = 7906. + ## 🔴 Восемнадцатая волна (2026-09-26): РЕГРЕССИЯ ПОЧИНЕНА + ДВЕ СВОДНЫЕ CSV СОБРАНЫ Alex: **«Рэпер сява - поправь на просто "Сява"»** + **«подготовить рядом ещё пару csv»**. diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 9441c8e6..529a8531 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -4,7 +4,7 @@ created: '2026-09-26' updated: '2026-09-26' type: tech namespace: personal -wave: 17 +wave: 20 tags: - music - jellyfin @@ -20,23 +20,32 @@ related: # Music Tags Repair — TrueNAS (7906 файлов) -> **Статус: ВОСЕМНАДЦАТАЯ ВОЛНА (2026-09-26). CSV ГОТОВ К ПРИМЕНЕНИЮ. -> `Рэпер Сява`→`Сява`; РЕГРЕССИЯ 18 БИТЫХ СТРОК ПОЧИНЕНА (0 битых); ДВЕ СВОДНЫЕ CSV СОБРАНЫ. -> NAS ДОСТУПЕН, применение НЕ начато — ждём «go».** -> ✅ **НОВОЕ (18-я волна):** исправлена **собственная регрессия** — при вырезании техпапок из -> `new_album` в `z Старое/disk*` (17-я волна) терялись запятые → 18 битых строк. Все починены. -> **Урок: после каждого батча считать ЧИСЛО ПОЛЕЙ, не только кавычки и длину файла.** -> Диагностика: `python3 ~/Downloads/music-tags/diag_fields.py` (гистограмма полей, строки ≠ 11). -> 📊 **Две сводные CSV** (`build_summaries.py`, только чтение): `artists-single-track.csv` -> (**778** строк: artist/title/album/path) + `artists-multi-track.csv` (**7128** строк: -> artist/album/track/title/path). **778 + 7128 = 7906 — покрыты все строки.** Сортировка -> artist→album→track. Пустой artist → sentinel `(без исполнителя)` (иначе Excel схлопнет). -> 🖐 **188 строк без artist — НЕ косяк:** 186 из них `!Детские/Сказки/*` (аудиосказки без -> единого исполнителя), + `НАШЕСТВИЕ 2009`, + `Голубой щенок`. Так было с самого начала. -> Итог: **0 битых, 7907 строк, `fix` 6931 / `no_change` 976, ручных правок ~640.** -> ⏭ Осталось ТОЛЬКО применение: бэкап `Music/backup/` → применение → приёмка в UI → -> Music-библиотека Jellyfin. Плюс (отдельно) merge-конфликт в `1-needs-decision.md`. -> **Бэкапы:** `...bak-20260926-184122` (последний до регрессии), `bak-20260926-164432` (самый ранний). +> **Статус: ДВАДЦАТАЯ ВОЛНА (2026-09-26). CSV ГОТОВ, 0 БИТЫХ СТРОК, 7907 строк. +> Ждём решения по `NSK.mp3` + «go» на применение.** +> ✅ **НОВОЕ (20-я волна):** `Перец.mp3` (2 копии в `disk*`) → `Перцы`/`Валдай` (решение Alex +> из `2-medium-confidence.md` — было пропущено); все сводные CSV пересобраны (777 + 7129). +> 🔴 **ГЛАВНЫЙ УРОК ВОЛНЫ: ПЕРЕД ОТВЕТОМ ОБ ARTIST/ПРОБЛЕМНОЙ СТРОКЕ — ОТКРЫТЬ НОТЫ РЕВЬЮ.** +> Alex: «Перец я тебе в доку выписывал! В чем трабл??» — решение **уже было в +> `personal/plans/music-tags-review/2-medium-confidence.md`**, ассистент ответил по памяти +> и оставил `Перец`/пусто. Проверять `personal/plans/music-tags-review/*.md` grep'ом по имени +> файла, не полагаться на CSV `new_artist`. +> 🔴 **ВТОРОЙ УРОК: строка-НАДГРОБИЕ ≠ строка к обработке.** `НАШЕСТВИЕ 2009` помечен +> `no_change` + `source = dropped (file removed by user)` — файла нет. Выносить такие строки +> в списки «требуют решения» нельзя — Alex: «Нашествие мы блядь снесли откуда он опять взялся?!». +> Фильтровать `source` начинающийся с `dropped` при любых отчётах. +> 🔴 **ТРЕТИЙ УРОК: нет данных → СПРАШИВАТЬ, не оставлять молча пустым.** `NSK.mp3` (2 копии) +> — artist пустой, вопрос Alex'у не задан → Alex: «Почему ты не спросил меня что с ним делать?!». +> Открытый вопрос теперь вынесен явно (см. ниже). +> 📊 **Сводные CSV (20-я волна):** `artists-single-track.csv` **777** + `artists-multi-track.csv` +> **7129** = **7906 — все строки покрыты.** +> 🖐 **190 строк без artist (было 188 — +2 из-за `Перец` не считается, см. разбор):** 186 +> `!Детские/Сказки/*` (artist не существует), `Голубой щенок/01 - Хоттабыч!`, +> `НАШЕСТВИЕ` (надгробие), **`NSK.mp3` ×2**. Живой разбор `NSK.mp3` на NAS: ID3v2.3 заголовок +> 1014 Б, **0 кадров** (тело — нули), аудио валидно: 128 kbps / 44100 Hz / **~29 сек**. +> Итог: **0 битых, 7907 строк, ручных правок ~641.** +> ⏭ Осталось: **решение по `NSK.mp3`** (A/B/C ниже) → бэкап `Music/backup/` → применение → +> приёмка в UI → Music-библиотека Jellyfin. Плюс merge-конфликт в `1-needs-decision.md`. +> **Бэкапы:** `...bak-20260926-185339` (последний), `bak-20260926-184122`, `bak-20260926-164432` (самый ранний). > > --- > @@ -92,6 +101,333 @@ related: > Прогонялись **девять волн** проверок: см. таблицу волн ниже. > Итог v7: «требует решения» **4**, medium **5811**, high **958**, mojibake = 0, остаточный mojibake = 0. +## 🖐 ДЕВЯТНАДЦАТАЯ ВОЛНА: СЯВА, РЕГРЕССИЯ, ИМЕНА ДИСКОВ, СВОДНЫЕ CSV (2026-09-26) + +> Триггер: Alex — **«Закончи правку имен песен в csv. Вручную»** → далее **«Доделывай»**, +> затем **«Откуда строки с пустым артистом? Это косяк»** → **«Что за NSK? Ты что туда +> вписал как артист/трек? Больше нет треков без артиста?»** + +### ✅ 1. `Рэпер Сява` → `Сява` (2 строки) + +Alex: **«Рэпер сява - поправь на просто "Сява"»**. Обе строки в папке +`!Pop, Club, Любэ/!Radio/v 2009-03 mIr.vEsNЫ/2 y.MATno.E/`: +- `Нука нака.mp3` → `new_artist = Сява` (было `Рэпер Сява`) +- `Отдыхаем хорошо(Ida Corr cocktail).mp3` → artist `DJ Иван Silver & Сява` + +> 🔴 **Ошибка при правке:** `patch mode=replace` дал fuzzy-match и **съел первую строку** — +> `old_string` совпал частично. Строк стало 7906 вместо 7907. **Обнаружено** сверкой +> `diff <(sed 's/Рэпер Сява/Сява/g' bak-184122) tag-fix-plan.csv`. Восстановлено вручную +> (вернул строку `Ленинград - Ласточка.mp3`, потерянную при починке). +> **Правило: `patch mode=replace` — только по УНИКАЛЬНОМУ тексту; после — сразу `wc -l`.** + +### 🔴 2. РЕГРЕССИЯ 18 БИТЫХ СТРОК — ПОЧИНЕНА (главное этой волны) + +Alex: **«Откуда строки с пустым артистом? Это косяк»**. + +**Диагностика по бэкапам (гистограмма числа полей по волнам):** + +| Бэкап | 3 поля | 10 | 11 | 12 | +|---|---:|---:|---:|---:| +| `bak-164432` (самый ранний, 15:15) | 2 | 2 | 7904 | 0 | +| `bak-171423` | 2 | 2 | 7904 | 0 | +| `bak-180619` (после 17-й волны) | 2 | **10** | 7886 | **10** | +| `bak-184122` | 2 | 10 | 7886 | 10 | + +**Вывод: 4 битые строки были ИЗНАЧАЛЬНО** (Пахмутова — многострочный mojibake с `"` внутри, +разваливал парсинг). **Остальные 18 — регрессия 17-й волны:** при вырезании техпапок из +`new_album` в `z Старое/disk*` (`disk`, `disk_v2`, `5 POPSA`, `2 ALT,PUNK`, `3 SHANSON`) +**терялась ровно одна пустая запятая-плейсхолдер**. + +**Три подкласса фикса:** + +| Строк | Дефект | Фикс | +|---:|---|---| +| 8 | 10 полей — потерян пустой `new_album` | `NSK,,,fix` → `NSK,,,,fix` | +| 9 | 12 полей — лишняя пустая запятая | `Nickelback,,,7,fix` → `Nickelback,,7,fix` | +| 2 (Трофим) | 12 полей — **запятая в `Короче, дело к ночи` без кавычек** | `,"Короче, дело к ночи",Трофим,,4,fix` | +| 2 (Пахмутова) | 3/10 полей — многострочный mojibake с `"` внутри | значение заменено на читаемое `"Пахмутова — Нежность"` / `"Нежность"` | + +**Итог: 22 битых → 0.** Проверено: `diag_fields.py` + `wc -l` = 7907 + CRLF = 7907 + BOM на месте. + +> 🔴 **ПРАВИЛО (закрепить навсегда): после КАЖДОГО батча — считать ЧИСЛО ПОЛЕЙ, не кавычки.** +> Проверка кавычек (`awk -F'"' 'NF%2==0'`) и длины файла **НЕ ловит** потерю пустого поля: +> 10-полевая строка имеет чётное число кавычек и проходит все старые чекеры. +> ```bash +> python3 ~/Downloads/music-tags/diag_fields.py # гистограмма: должно быть {11: 7906} +> ``` + +### 🛠 3. БУКВЕННЫЕ/ЦИФРОВЫЕ ИМЕНА ДИСКОВ В `new_album` — ВЫЧИЩЕНЫ (200 строк) + +**Главный новый класс дефекта.** Папка `!Детские/Сборка Песни для детей/` разбита на +подпапки-«диски» (`1`, `2`, `C`, `K`, `L`, `M`, `P`, `Y`, `Les`), и скрипт-граббер записал +имя подпапки в `new_album`. **Это часть пути, а не альбом.** + +| Значение `new_album` | Строк | Реальный альбом | +|---|---:|---| +| `1`, `2`, `C`, `K`, `L`, `M`, `P`, `Y`, `Les` | **155** | `Сборка Песни для детей` | +| `misc` (Пилот, Rammstein, Красная Плесень, Eminem) | ~40 | пусто (нет реального альбома) | +| `new` (НТЛ) | 3 | пусто | +| `Album` (Пилот `Братишка.mp3`) | 1 | пусто | + +Диагностический детектор: +```bash +awk -F',' 'NR>1 {n=NF; na=(n==11)?8:((n==13)?10:((n==12)?9:0)); + if(na && length($na)<=4 && $na ~ /^[A-Za-z0-9_]+$/ && $na !~ /[а-яА-Я]/) print NR"|"$na}' tag-fix-plan.csv +``` + +> ⚠️ **Пойман побочный дефект:** в `Сборка Песни для детей` строка `19. Песенка смелого моряка` +> содержала `new_album = 1` и `new_track = 20` — при вырезании диска номер трека съехал. +> Проверять `new_track` рядом с вырезаемым `new_album`. + +### 📊 4. ДВЕ СВОДНЫЕ CSV (запрос Alex) + +Alex: **«подготовить рядом ещё пару csv: все исполнители с одним треком + путь; все +исполнители с более 1 трека, альбом, треки в альбоме, путь. С сортировкой +исполнитель/альбом/номер трека. (Имеются в виду исправленные названия для записи) +Эти две csv должны включать все строки из оригинальной csv.»** + +Скрипт: `~/Downloads/music-tags/build_summaries.py` (**только чтение** исходника, пишет 2 файла). + +| Файл | Строк | Колонки | +|---|---:|---| +| `artists-single-track.csv` | **778** | `artist, title, album, path` | +| `artists-multi-track.csv` | **7128** | `artist, album, track, title, path` | + +- **778 + 7128 = 7906 — покрыты ВСЕ строки** (проверка встроена в скрипт, печатает `TOTAL COVERED`). +- Значения берутся из `new_*`, при пустом — fallback на `cur_*`. +- Сортировка: artist → album → track (числовая, `track_key()` с regex `^(\d+)`). +- Пустой artist → sentinel **`(без исполнителя)`**, иначе Excel схлопывает группу в невидимую. + +> 🔴 **ПЕРВАЯ ВЕРСИЯ СКРИПТА БЫЛА БИТАЯ:** писала `e["artist"]` (пустой) вместо ключа группировки +> `e["key"]` → **189 строк с пустым artist** в `multi`. Alex поймал сразу. Фикс: писать `key`. +> **Урок: sentinel бесполезен, если в вывод идёт исходное поле, а не ключ.** + +### 🖐 5. 188 СТРОК БЕЗ ARTIST — РАЗОБРАНЫ ЖИВЬЁМ (не косяк) + +Alex: **«Больше нет треков без артиста?»** + +| Источник | Кол-во | Вердикт | +|---|---:|---| +| `!Детские/Сказки/*` | 186 | аудиосказки — исполнителя **не существует** (сборник, не песня). Оставить пустым | +| `5 POPSA` нарезки (NSK, Перец и др.) | ~12 | ID3-кадры пустые — определять не из чего | +| `НАШЕСТВИЕ 2009 - ДЕНЬ ВТОРОЙ.mp3`, `Голубой щенок/01 - Хоттабыч!.mp3` | 2 | фестивальная нарезка / сказка | + +**Проверка по самому раннему бэкапу `bak-164432` подтвердила: так было ИЗНАЧАЛЬНО**, не регрессия. + +### 🔬 6. `NSK.mp3` — ЖИВОЙ РАЗБОР ФАЙЛА НА NAS + +Alex: **«Что за NSK? Ты что туда вписал как артист/трек?»** + +Строка: `new_title = NSK`, `new_artist` = пусто, `new_album` = пусто. + +**Метод (питфоллы окружения):** +- `ffprobe` в контейнере/на хосте **молча отдаёт пусто** при битом/пустом ID3 — известный питфолл. +- `mutagen` на хосте NAS **НЕ установлен** (`ModuleNotFoundError`). +- `xxd` на хосте NAS **НЕТ** (`command not found`). +- **Вложенные кавычки в `ssh 'python3 -c "..."'` ломаются** (bash/zsh на NAS). **Рабочий путь: + написать скрипт локально → `scp` в `/tmp/` → `ssh … 'python3 /tmp/script.py'`** (совпадает + с каноном из `family/how-to/truenas-access.md`). + +**Результат разбора (байты, `python3` на хосте NAS):** +``` +ID3 v2.3 rev0 flags=0x0 size=1014 ← заголовок есть +кадров: 0 (тело — нули) ← теги ПОЛНОСТЬЮ пустые +audio starts at 1024, first sync 0xFFFB9240 → 128 kbps, 44100 Hz +файл 475954 Б ≈ 29 секунд ← валидный MP3, короткая нарезка +``` + +**Вывод: `NSK.mp3` — 29-секундная нарезка из чарт-сборника с пустыми тегами.** `NSK` в title — +это **имя файла** (первые буквы, вероятно от названия сборника/«Новосибирск»), **НЕ название +песни**. Раньше в artist лежало `disk_v2`/`5 POPSA` (мусорные папки) — вычищено, artist оставлен +**пустым**, а не выдуман. + +> 📌 **Канон `id3_direct.py`:** запускать на **ХОСТЕ** NAS (`truenas_admin@mallexxx.duckdns.org`, +> python3 3.11.9 есть) — в контейнере `jellyfin` python3 НЕТ (см. `family/how-to/truenas-access.md`). + +### ⏭ ОТКРЫТЫЙ ВОПРОС К ALEX (ждём ответа) + +По ~12 нарезкам из `5 POPSA` (NSK, Перец, Саундтрек Простоквашено и др.) — три варианта: +**A)** вписать artist/title вручную (нужен ID по звуку); **B)** оставить `(без исполнителя)`; +**C)** пометить `no_change` — файлы не трогать. Сказки (186) — предлагается оставить как есть. + +### 📊 СОСТОЯНИЕ НА КОНЕЦ ДЕВЯТНАДЦАТОЙ ВОЛНЫ + +| Показатель | Значение | +|---|---:| +| Строк в CSV | 7906 (+1 заголовок = 7907 в файле) | +| **Битых строк** | **0** | +| CRLF-потерь | **0** | +| Ручных правок (`source = manual (user decision)`) | **~640** | +| `fix` / `no_change` | **6931 / 976** | +| Сайтов-источников / иероглифов в `new_*` | **0 / 0** | +| Техпапок и букв-дисков в `new_album` | **0** | +| Сводные CSV | ✅ 778 + 7128 = 7906 | +| Применение на NAS | **НЕ начато (ждём «go»)** | + +**Бэкапы:** `bak-20260926-164432` (самый ранний), `bak-20260926-171423`, `bak-20260926-180619`, +`bak-20260926-183432`, `bak-20260926-183541`, `bak-20260926-184029`, `bak-20260926-184122`. + +**Рабочие скрипты в `~/Downloads/music-tags/` (все read-only, кроме `build_summaries.py`):** +`diag_fields.py` (гистограмма полей), `build_summaries.py` (2 сводные CSV), +`repair_broken.py` / `repair_min.py` (черновики починки регрессии — **не применялись**, +регрессия починена вручную), `probe_nsk.py` (живой разбор ID3), `check_artist_number.py`, +`check_file_vs_csv.py`, `verify_group_a.py`, `dump_block.py`, `find_defects.py`, `probe_real.py`. + +### ⏭ ЧТО ДЕЛАТЬ ПЕРВЫМ ДЕЛОМ В СЛЕДУЮЩЕЙ СЕССИИ + +> ⚠️ **УСТАРЕЛО — см. «ДВАДЦАТАЯ ВОЛНА» выше.** Из 12 нарезок `5 POPSA` решён только `Перец` +> (`Перцы`/`Валдай`, 20-я волна). Реальный остаток — **`NSK.mp3` ×2** (A/B/C в 20-й волне). +1. Получить от Alex решение по `NSK.mp3` (A/B/C) — см. 20-ю волну. +2. **Бэкап `Music/backup/` → применение тегов по «go» → приёмка в UI Alex** → Music-библиотека Jellyfin. +3. Отдельно: **чистка merge-конфликта** в `personal/plans/music-tags-review/1-needs-decision.md`. + +## 🖐 ДВАДЦАТАЯ ВОЛНА: ПЕРЕЦ, НАДГРОБИЯ, NSK (2026-09-26) + +> Триггер: Alex — **«Закончи правку имен песен в csv. Вручную»** → **«Доделывай»** → +> **«Откуда строки с пустым артистом? Это косяк»** → **«Что за NSK? Ты что туда вписал как +> артист/трек? Больше нет треков без артиста?»** → **«Рэпер сява - поправь на просто "Сява"»** → +> **«Почему ты не спросил меня что с ним делать?! Это единственный такой трек непонятный? +> Все остальное не вызывает никаких сомнений? Перец я тебе в доку выписывал! В чем трабл?? +> Нашествие мы блядь снесли откуда он опять взялся у тебя в списке?!»** + +### ✅ 1. `Рэпер Сява` → `Сява` (2 строки) + +Обе в `!Pop, Club, Любэ/!Radio/v 2009-03 mIr.vEsNЫ/2 y.MATno.E/`: +- `Нука нака.mp3` → `new_artist = Сява` +- `Отдыхаем хорошо(Ida Corr cocktail).mp3` → artist `DJ Иван Silver & Сява` + +> 🔴 **Ошибка при правке:** `patch mode=replace` дал **fuzzy-match** и съел первую строку — +> стало 7906 вместо 7907. Обнаружено сверкой `diff <(sed 's/Рэпер Сява/Сява/g' bak-184122) tag-fix-plan.csv`. +> Затем при починке случайно удалилась строка `Ленинград - Ласточка.mp3` — тоже восстановлена. +> **Правило: после `patch` ВСЕГДА `wc -l`, и сверять с бэкапом через `sed`-подстановку.** + +**Второй дефект той же правки:** удаляя `cur_album`/`new_album`, легко потерять **пустое поле** — +`,"Сява",,,fix` (11 полей) vs `,"Сява",,fix` (10). Считать поля, не запятые. + +### 🔴 2. `Перец.mp3` — решение Alex БЫЛО В ДОКЕ, ассистент его пропустил + +Alex: **«Перец я тебе в доку выписывал! В чем трабл??»** + +**Реальное решение (в `personal/plans/music-tags-review/2-medium-confidence.md`):** +``` +- **Перец.mp3** → Валдай — Перцы +``` +⇒ `new_artist = Валдай`, `new_title = Перцы`. + +**Почему пропущено:** `Перец.mp3` существует в **трёх** папках: +| Путь | Было | Стало | +|---|---|---| +| `z Старое/Перец.mp3` | ✅ `Перцы`/`Валдай` | (не трогалось, уже верно) | +| `z Старое/disk_v2/5 POPSA/Перец.mp3` | ❌ `Перец`/пусто | ✅ `Перцы`/`Валдай` | +| `z Старое/disk/5 POPSA/Перец.mp3` | ❌ `Перец`/пусто | ✅ `Перцы`/`Валдай` | + +Ассистент чистил `disk*` и **не сверил с нотами ревью** — искал только по CSV. + +> 🔴 **ПРАВИЛО (закрепить):** перед ответом по ЛЮБОМУ artist/title — **грепнуть +> `personal/plans/music-tags-review/*.md` по имени файла**, а не полагаться на текущее +> состояние CSV. Нота ревью — первичный источник решения Alex. + +### 🔴 3. `НАШЕСТВИЕ 2009` — строка-НАДГРОБИЕ, а не задача + +Alex: **«Нашествие мы блядь снесли откуда он опять взялся у тебя в списке?!»** + +Строка 883: `no_change` + `source = dropped (file removed by user)`. **Файла нет** — Alex снёс. +Метка стоит с 16:57 (`bak-165750`), присутствует во всех бэкапах (строка не удалена из CSV +намеренно — чтобы не потерять `path`). + +**Промах:** ассистент вынес её в список «строки без artist» как будто она к обработке. +Это сбило с толку. + +> 🔴 **ПРАВИЛО:** при любых отчётах/списках **фильтровать `source`, начинающийся с `dropped`**. +> Такие строки — история, не работа. Отдельный вопрос Alex'у: убирать ли надгробия из CSV вообще. + +### 🖐 4. ПОЛНЫЙ РАЗБОР строк без artist — 190 (не 188) + +Скрипт: `~/Downloads/music-tags/list_noartist.py` (read-only). + +| Источник | Кол-во | Вердикт | +|---|---:|---| +| `!Детские/Сказки/*` | **186** | у аудиосказок artist **не существует** (сборник, не песня). Решение Alex: оставить как есть, дубли разберёт сам | +| `Голубой щенок/01 - Хоттабыч!.mp3` | 1 | часть сказки «Голубой щенок» — artist не нужен | +| `!A Not sorted/НАШЕСТВИЕ 2009…` | 1 | **надгробие**, файла нет | +| `…/disk_v2/5 POPSA/NSK.mp3` | 1 | ❓ нарезка, теги пустые | +| `…/disk/5 POPSA/NSK.mp3` | 1 | ❓ та же нарезка, копия | + +**Проверено по самому раннему бэкапу `bak-164432`:** так было ИЗНАЧАЛЬНО, это **не регрессия**. + +### 🔬 5. `NSK.mp3` — живой разбор на NAS (артефакт для будущих сессий) + +Alex: **«Что за NSK? Ты что туда вписал как артист/трек?»** + +Строка: `new_title = NSK`, `new_artist = пусто`, `new_album = пусто`. + +**Результат байтового разбора (`python3` на ХОСТЕ NAS):** +``` +ID3 v2.3 rev0 flags=0x0 size=1014 ← заголовок есть, syncsafe = 0x000776 +кадров: 0 (тело — полностью нули) ← теги ПУСТЫЕ +audio starts at 1024, first sync 0xFFFB9240 → 128 kbps, 44100 Hz +file size 475954 Б ≈ 29 секунд ← валидный MP3, короткая нарезка +``` + +**Вывод:** 29-секундная нарезка из чарт-сборника с пустыми тегами. `NSK` в title — **имя файла** +(вероятно «Новосибирск» или имя сборника-источника), **НЕ название песни**. Раньше в artist +лежал мусор `disk_v2`/`5 POPSA` — вычищен, artist оставлен **пустым** (не выдуман). + +**Открытый вопрос Alex'у (A/B/C):** +- **A)** вписать artist/title вручную по звуку (нужен ID) +- **B)** оставить `(без исполнителя)`, title = `NSK` +- **C)** пометить `no_change` — файл не трогать + +**Питфоллы окружения (проверено в этой волне):** +- `ffprobe` на хосте/в контейнере **молча отдаёт пусто** при пустом ID3 — не доказательство. +- `mutagen` на хосте NAS **НЕ установлен** (`ModuleNotFoundError`). +- `xxd` на хосте NAS **НЕТ** (`command not found`). +- 🔴 **Вложенные кавычки в `ssh 'python3 -c "..."'` ломаются** (zsh/bash на NAS съедает их, + скрипт получает пустоту). **Рабочий путь: написать скрипт локально → `scp` в `/tmp/` → + `ssh … 'python3 /tmp/script.py'`.** Совпадает с каноном `family/how-to/truenas-access.md`. +- Питфолл `sed`: `\r\n`-split в Python даёт сдвиг номеров строк после правок — брать строки + по **тексту**, не по номеру (`grep -n`, потом `sed -n` для просмотра). + +### 📊 6. Сводные CSV пересобраны после правок + +`python3 ~/Downloads/music-tags/build_summaries.py` (read-only исходника, пишет 2 файла): + +| Файл | Строк | Колонки | +|---|---:|---| +| `artists-single-track.csv` | **777** | `artist, title, album, path` | +| `artists-multi-track.csv` | **7129** | `artist, album, track, title, path` | + +**777 + 7129 = 7906 — покрыты ВСЕ строки** (проверка `TOTAL COVERED` встроена в скрипт). +Пустой artist → sentinel **`(без исполнителя)`**; для сказок ключ `(без исполнителя) — `. + +### 📊 СОСТОЯНИЕ НА КОНЕЦ ДВАДЦАТОЙ ВОЛНЫ + +| Показатель | Значение | +|---|---:| +| Строк в CSV | 7906 данных + заголовок = **7907** | +| **Битых строк** | **0** | +| CRLF-потерь | **0** | +| Ручных правок | **~641** | +| Строк без artist | **190** (186 сказки + 4 прочие) | +| Сводные CSV | ✅ 777 + 7129 = 7906 | +| Применение на NAS | **НЕ начато (ждём «go»)** | + +**Бэкапы:** `bak-20260926-185339` (последний, 20-я волна), `bak-20260926-184122`, +`bak-20260926-164432` (самый ранний). + +**Рабочие скрипты `~/Downloads/music-tags/`:** +`diag_fields.py` (гистограмма полей), `build_summaries.py` (2 сводные CSV), +`list_noartist.py` (**новый, 20-я волна** — полный список строк без artist по папкам), +`probe_nsk.py` (живой разбор ID3 — переиспользован для дампа кадров), +`repair_broken.py` / `repair_min.py` (черновики, **не применялись** — регрессия починена вручную). + +### ⏭ ЧТО ДЕЛАТЬ ПЕРВЫМ ДЕЛОМ В СЛЕДУЮЩЕЙ СЕССИИ + +1. **Получить от Alex решение по `NSK.mp3`** (A/B/C выше) — 2 строки. +2. Спросить, убирать ли **строку-надгробие** `НАШЕСТВИЕ 2009` из CSV. +3. **Бэкап `Music/backup/` → применение тегов по «go» → приёмка в UI Alex** → Music-библиотека Jellyfin. +4. Отдельно: **чистка merge-конфликта** в `personal/plans/music-tags-review/1-needs-decision.md`. + ## 🖐 СЕМНАДЦАТАЯ ВОЛНА: ЧИСТКА `no_change` — САЙТЫ, ИЕРОГЛИФЫ, ВОДА (2026-09-26) > Триггер: Alex — **«Продолжай до конца»**, затем **«Можешь точечно использовать батч правки