diff --git a/personal/plans/music-tags-review/music-tags-review.md b/personal/plans/music-tags-review/music-tags-review.md index 53241cdf..c126ad8b 100755 --- a/personal/plans/music-tags-review/music-tags-review.md +++ b/personal/plans/music-tags-review/music-tags-review.md @@ -3,7 +3,7 @@ title: Music Tags — Review type: task namespace: personal tags: [music, jellyfin, tags, review, truenas] -status: awaiting-approval +status: in-progress updated: '2026-09-26' --- @@ -11,12 +11,28 @@ updated: '2026-09-26' **К правке:** 6773 файлов · **без изменений:** 1133 -> **✅ 21-я волна (2026-09-26): CSV ГОТОВ. 7907 строк, 0 битых. Ждём «go» на применение.** -> Разобраны блоки `Pushnoy.ru`/`Александр Пушной` (13 строк cur_artist) и `Infected Mushroom` -> (7 реальных дефектов: обрезки `Deeply Disturbed (Kobi Shalti-`, `elation station (beat hackers`; -> title = имя файла `Infected Mushoom - Saeed/Red Filter`; `Eyal Shechter` → title `Raash`; -> `U R So Fucked (GANDI Remix)`). 14 файлов `._*` (AppleDouble) помечены `skip`. -> Детали — [[personal/tech/music-tags-repair-truenas|tech/music-tags-repair-truenas]], 21-я волна. +> **⚠️ 22-я волна (2026-09-26): CSV НЕ ГОТОВ.** Alex спросил: «Ты прошёл вручную через весь список +> и АБСОЛЮТНО всё корректно? Нет дублей с разным написанием?» — честный ответ был **НЕТ**. Проверка +> нашла ещё ~900 дефектных строк сверх «зелёных» чекеров. +> +> Исправлено: **43 группы дублей артистов** (`Король и шут`/`Король и Шут`, `Земфира`/`земфира`, +> `Чиж & Cо` с латинской C, `Руки Вверх`/`Руки в Верх`) — 85 правок, свелось к 0 групп; +> **220 строк** с артистом-префиксом в title (`Король и Шут - Лесник` → `Лесник`, +> `Muse / Dead Star` → `Dead Star`); `album` case (`Король и шут-1` → `Король и Шут`), 36 строк. +> +> 🔴 **Два урока:** (1) «файл прошёл чекеры» ≠ «файл готов» — чекеры не видят дубли/префиксы; +> (2) **скрипт правки ОБРЕЗАЛ CSV до 4640 строк** (открыл источник на запись, упал на середине). +> Восстановлено из бэкапа. Теперь только схема tmp-файл + ревалидация + `os.replace`. +> +> **⏭ Осталось до «go»:** `album='2007'` (10), `album`=папка-плейсхолдер `NEW (Remix Album)` / +> `Unreleased Tracks` (~109), глазами не пройдены ~5000 строк прочих исполнителей. +> Детали и детекторы — [[personal/tech/music-tags-repair-truenas|tech/music-tags-repair-truenas]], 22-я волна. + +> **✅ 21-я волна (2026-09-26):** разобраны блоки `Pushnoy.ru`/`Александр Пушной` (13 строк cur_artist) +> и `Infected Mushroom` (7 реальных дефектов: обрезки `Deeply Disturbed (Kobi Shalti-`, +> `elation station (beat hackers`; title = имя файла `Infected Mushoom - Saeed/Red Filter`; +> `Eyal Shechter` → title `Raash`; `U R So Fucked (GANDI Remix)`). 14 файлов `._*` (AppleDouble) +> помечены `skip`. > [!warning] Ничего не изменено > Это план. Ставь галочки, что одобряешь. После «go» — бэкап в `Music/backup/` и применение. diff --git a/personal/tech/id3-tag-repair.md b/personal/tech/id3-tag-repair.md index 78c9e42b..4f62a9ee 100755 --- a/personal/tech/id3-tag-repair.md +++ b/personal/tech/id3-tag-repair.md @@ -76,6 +76,50 @@ TPE1 enc=0 payload = 00 FF FE 53 74 65 65 6C 20 44 65 6C 75 78 65 Правки человека в сгенерированной ноте **не возвращаются** в CSV. Следующая перегенерация их **затирает**. Лечится только явным переносом (`apply_manual_decisions.py`, список `DECISIONS`). +### 5. 🔴 Скрипт ОБРЕЗАЛ CSV до 4640 строк (22-я волна) + +`open(SRC, "w")` + `writerows(rows)` → падение на середине на битой строке +(`ValueError: dict contains fields not in fieldnames: None`) → **файл потерял половину**. +Режим `w` обрезает при открытии, всё недописанное исчезло. Восстановлено из `bak-194449-prefix`. + +**Обязательная схема правки CSV (tmp + ревалидация + `os.replace`):** + +```python +with open(SRC, encoding="utf-8-sig", newline="") as f: + rd = csv.DictReader(f); fields = rd.fieldnames; rows = list(rd) +bad = [i for i, r in enumerate(rows, 2) if None in r or len(r) != len(fields)] +if bad: raise SystemExit(f"ABORT: malformed source {bad[:10]}") # валидация ВХОДА +for r in rows: transform(r) +TMP = SRC + ".tmp" +with open(TMP, "w", encoding="utf-8-sig", newline="") as f: + w = csv.DictWriter(f, fieldnames=fields, quoting=csv.QUOTE_MINIMAL, lineterminator="\r\n") + w.writeheader(); w.writerows(rows) +# ревалидация tmp ТЕМИ ЖЕ проверками, затем атомарная подмена +os.replace(TMP, SRC) +``` + +Перед каждым запуском: `cp tag-fix-plan.csv bak-$(date +%H%M%S)-`. +Битые строки — **симптом**, а не причина: незакавыченная запятая (`Танцуй, Рублёвка`) +или лишние пробелы (`Áàíä'Ýðîñ ,2005 `) создают поля сверх заголовка. Чинить руками ДО скриптов. + +### 6. 🔴 «Чекеры зелёные» ≠ «файл готов» (22-я волна, ~900 строк) + +0 битых строк + 0 битых кавычек прошли — а в файле оставалось **~900** дефектных строк: +43 группы дублей написания артиста (676 строк), 220 строк с артистом-префиксом в title, +36 album с чужой раскладкой. Нашлось только после вопроса Alex «а дубли написаний?». + +Детекторы, которые ОБЯЗАТЕЛЬНО гонять перед заявлением «готово»: + +| Детектор | Ловит | Нюанс | +|---|---|---| +| Дубли артистов | `Король и шут`/`Король и Шут`, `Земфира`/`земфира` | нормализовать: lowercase → `ё→е` → маппинг латиница→кириллица-омоглифы (`C→С`, `o→о`…) | +| Артист-префикс в title | `Король и Шут - Лесник`, `Muse / Dead Star` | срезать ТОЛЬКО если после артиста идёт разделитель `[-–—/]`, иначе съест `Жуки Комарики`, `Тест Сабвуфера №2` | +| case/disc в album | `Король и шут-1` при artist `Король и Шут` | явный `ALBUM_MAP` | +| `album == artist` | **в основном ЛЕГИТИМНО** | одноимённые альбомы; НЕ массово менять | + +Плюс: `album` = год (`2007`) или имя папки-плейсхолдера (`NEW (Remix Album)`, `Unreleased Tracks`) +— **это** дефекты (папка протекла в album). + ## ⚪ Что НЕ является багом - **Рукописная транслитерация:** `КняZz`, `EMINЭM`, `Flёur`, `изBESTняк`, `Скаzки`, `Глюкоzа`, @@ -84,7 +128,8 @@ TPE1 enc=0 payload = 00 FF FE 53 74 65 65 6C 20 44 65 6C 75 78 65 `КняZz` оставил). - **`ÿþ` в середине слова:** `Òåðÿþ ñâåò → Теряю свет`, `Ïîçäðàâëÿþ → Поздравляю` — корректная CP1251-конверсия. Ложное срабатывание grep. Различать **по позиции**: начало значения ≠ середина слова. -- **`album == artist`** (101 файл) — одноимённые альбомы (`Рубль/Рубль`, `Би-2/2001 Би-2`). +- **`album == artist`** (138 файлов) — одноимённые альбомы (`Рубль/Рубль`, `Би-2`, `Земфира` (1999), + `НТЛ`, `Краденое Солнце`). Законно — **не менять без сверки дискографии**. - **`numeric_title`** (15 файлов) — `007` у Ленинграда, `48` у Пилота. Законно. ## 🚫 Запреты (жёсткие, от Alex) diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index 0a7e411e..c14b7c48 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: 21 +wave: 22 tags: - music - jellyfin @@ -20,8 +20,77 @@ related: # Music Tags Repair — TrueNAS (7906 файлов) -> **Статус: ДВАДЦАТЬ ПЕРВАЯ ВОЛНА (2026-09-26). CSV ГОТОВ, 0 БИТЫХ СТРОК, 7907 строк. -> Ждём «go» на применение.** +> **Статус: ДВАДЦАТЬ ВТОРАЯ ВОЛНА (2026-09-26). CSV: 7906 строк, 0 БИТЫХ, 0 ДУБЛЕЙ АРТИСТОВ. +> НЕ ГОТОВ к применению — остались неразобранные классы (см. «ОСТАЛОСЬ»).** +> ✅ **НОВОЕ (22-я волна) — ДЕДУП АРТИСТОВ + АРТИСТ В TITLE + case в album. Поймано триггером +> Alex: «Ты прошел вручную через весь список и абсолютно все выглядит корректно??? Нет дублей +> с разным написанием?» — ответ был НЕТ, и проверка нашла ещё ~900 дефектных строк.** +> 🔴 **ГЛАВНЫЙ УРОК 22-й ВОЛНЫ: «файл прошёл чекеры» ≠ «файл готов».** Чекеры ловят кодировку и +> пустые поля, но НЕ ловят (а) дубли написаний артиста, (б) артиста-префикс в title, +> (в) капс/диск-суффиксы в album. Перед любым заявлением «всё готово» — прогонять эти 4 детектора +> (скрипты ниже), иначе Alex ловит это вопросом и это выглядит как враньё. +> +> **Что исправлено в 22-й волне:** +> | Класс | Строк | Скрипт | Результат | +> |---|---|---|---| +> | Дубли написания артиста, 43 группы | 85 правок | `dedup_artists.py` (карта `CANON`) | 0 групп дублей | +> | Артист-префикс в `new_title` (`Король и Шут - Лесник`) | 220 | `strip_artist_prefix.py` | 21 остаток = ложные | +> | `album` со старой раскладкой (`Король и шут`, `Нечетный воин`) | 36 | `fix_albums.py` (`ALBUM_MAP`) | исправлено | +> | `Король и шут-1` — диск-суффикс в album | 17 | тот же | → `Король и Шут` | +> | `Иванушки Int - Тополиный пух`, `Алексин и группа Колючки` | 2 | вручную `patch` | исправлено | +> | **Структурные дефекты CSV** (`Áàíä'Ýðîñ ,2005 ` — незакавыченные пробелы; `Танцуй, Рублёвка` — запятая без кавычек) | 2 | вручную | это они РОНЯЛИ скрипты | +> +> 🔴 **КРИТИЧНЫЙ УРОК: СКРИПТ ОБРЕЗАЛ CSV ДО 4640 СТРОК.** `strip_artist_prefix.py` (v1) открыл +> `tag-fix-plan.csv` **на запись** и упал на середине (`ValueError: dict contains fields not in +> fieldnames: None` — строка с лишними полями) → файл потерял половину. Восстановлено `cp` из +> `bak-194449-prefix`. **Правило: НИКОГДА не открывать `tag-fix-plan.csv` на запись напрямую.** +> Схема безопасного скрипта (v2, `strip_artist_prefix.py` / `fix_albums.py`): +> 1) `csv.DictReader` читает источник; 2) валидация входа (нет `None`-ключей, все строки = `len(fields)`); +> 3) запись в `SRC + ".tmp"`; 4) **ревалидация tmp** теми же проверками; 5) `os.replace(tmp, src)`. +> Перед каждым запуском — `cp tag-fix-plan.csv bak-$(date +%H%M%S)-`. +> +> **⏭ ОСТАЛОСЬ (НЕ разобрано, требует прохода):** +> - `album = '2007'` (год вместо альбома) — 10 строк, блок Pushnoy `2007/`. +> - `album` = имя папки-плейсхолдера: `NEW (Remix Album)` (16, Eminem/50cent), +> `Unreleased Tracks` (20+, Infected Mushroom), `The Rest, The Unreleased! THE`, +> `Disko Partizani` — всего ~109 строк. +> - title у ~5000 строк прочих исполнителей глазами НЕ проходился. +> - 138 строк `album == artist` — БОЛЬШИНСТВО ЛЕГИТИМНЫ (одноимённые альбомы: `Земфира` (1999), +> `Би-2` (дебютник), `НТЛ`, `Рубль`, `Краденое Солнце`) — НЕ трогать без проверки дискографии. +> - Ложные срабатывания детектора «артист в title» (21) — там нет разделителя-тире, +> это части названий: `Тест Сабвуфера №2`, `Жуки Комарики`, `Глюк'oza nostra`, +> `Savant Fuck Nexus`, `Земфира (DJ Shek Remix)`, `Ленинград (Bonus Track)`. +> - `allcaps` title в `!Детские/Детский хит` (156 строк) — **ЛОЖНОЕ срабатывание**: капс +> в исходных файлах, artist пустой НАМЕРЕННО (решение зафиксировано в +> `music-tags-review.md`, «Класс 3»), album `Детский Хит Сезона` остаётся. +> +> **📎 Детекторы (read-only, гонять перед заявлением «готово»):** +> ```bash +> cd ~/Downloads/music-tags +> # дубли артистов (нормализация латиница→кириллица-омоглифы) +> python3 - <<'EOF' +> import csv, collections +> rows=list(csv.DictReader(open('tag-fix-plan.csv',encoding='utf-8-sig',newline=''))) +> def norm(s): +> s=s.lower().replace('ё','е') +> for x,y in [('fl','фл'),('f','ф'),('e','е'),('a','а'),('o','о'),('p','р'), +> ('c','с'),('x','х'),('y','у'),('k','к'),('m','м'),('t','т'),('h','н'),('b','в')]: +> s=s.replace(x,y) +> return ''.join(ch for ch in s if ch.isalnum()) +> g=collections.defaultdict(list) +> for k,v in collections.Counter((r['new_artist'] or '').strip() +> for r in rows if (r['new_artist'] or '').strip()).items(): g[norm(k)].append(k) +> print(sum(1 for v in g.values() if len(v)>1), "групп дублей") +> EOF +> ``` +> Плюс: `title` начинается с `artist` + разделитель `[-–—/]` → префикс; +> `album` в `ALBUM_MAP`-подобных вариантах; незакавыченные запятые/пробелы в полях. +> +> **Бэкапы 22-й волны:** `bak-194352-dedup`, `bak-194449-prefix`, `bak--prefix2`, +> `bak--albumfix` в `~/Downloads/music-tags/`. +> +> --- +> > ✅ **НОВОЕ (21-я волна) — блок `Pushnoy.ru`/`Александр Пушной` + `Infected Mushroom`:** > - `Pushnoy-ru_` в title — это НЕ сайт-вода, а **префикс-пространство имён файлов** (транслит > имени файла); артист везде `Александр Пушной`. `Pushnoy.ru`/`Pushnoy-ru` в `cur_artist`