diff --git a/family/plans/music-jellyfin-lidarr-taiga.md b/family/plans/music-jellyfin-lidarr-taiga.md index a05380a7..9ccb9bcb 100755 --- a/family/plans/music-jellyfin-lidarr-taiga.md +++ b/family/plans/music-jellyfin-lidarr-taiga.md @@ -1,6 +1,106 @@ -# Music → Jellyfin + Lidarr на Тайге — план (v8, 2026-09-27) +# Music → Jellyfin + Lidarr на Тайге — план (v9, 2026-09-27) -> ## 🆕 v8: НОВАЯ ЗАДАЧА — ПОЛНЫЙ РЕМОНТ `artist` + `albumartist` (7861 файл) +> ## 🆕 v9: ЕДИНЫЙ ПЛАН НА ПРИМЕНЕНИЕ ГОТОВ — `APPLY-PLAN-FINAL.csv` (7861 строка) +> +> ✅ **Все этапы подготовки закрыты. Файлы музыки НЕ тронуты — ждёт вычитки Alex и «go».** +> +> | Этап | Артефакт | Итог | +> |---|---|---| +> | Сбор тегов | `raw_tags_full.tsv` | 7861 строка, md5 `0c3647fadb6556b4100c555ed59bfb44` | +> | Инвентарь | `tag-inventory3.csv` | 7861 строка, 13 колонок (+`albumartist`) | +> | План artist/albumartist/album | `fix-plan-FINAL.csv` | 7861 строка, **0 пустых** | +> | План жанров | `genre-plan-FINAL.csv` | 7861 строка, **65 уникальных, 0 пустых** | +> | Мусор в тегах | — | 10 классов вычищено (спам-URL 51, мойибаке 21, заглушки 325…) | +> | ⭐ **ЕДИНЫЙ ПЛАН** | **`APPLY-PLAN-FINAL.csv`** | **7861 строка, 7628 изменений (97%)** | +> +> ### ⭐ `APPLY-PLAN-FINAL.csv` — 14 колонок +> +> ``` +> path, folder, class, +> cur_title, cur_artist, cur_albumartist, cur_album, cur_genre, +> new_title, new_artist, new_albumartist, new_album, new_genre, changed +> ``` +> +> Сведён `build_unified.py` из двух планов по ключу `path`. В одном файле и `cur_*` (диск), +> и `new_*` (что писать) — Alex видит дельту, скрипт сравнивает сам. +> +> **Топ `new_albumartist`:** +> +> ``` +> 1518 Various Artists ← сборники (!Radio, !Retro, НТЛ, Sbornik_Novij_Rok) +> 322 Детские песни ← !Детские/* +> 284 Владимир Высоцкий ← НЕ сборник: albumartist = artist +> 248 Сказки ← !Детские/Сказки +> 244 Savant · 237 Пилот · 234 Infected Mushroom · 233 Король и Шут · 215 ДДТ +> ``` +> +> **Топ жанров:** +> +> ``` +> 3094 Rock · 888 Pop · 385 Punk · 344 Детские · 276 Бардовская песня · +> 248 Сказки · 235 Hip-Hop · 227 Chanson · 211 Heavy Metal · 189 Rap +> ``` +> +> ### ✅ ВАЛИДАЦИЯ — ВСЁ ПО НУЛЯМ (`validate_final.py`) +> +> ``` +> ✓ дублей путей 0 · спама 0 · мойибаке 0 +> ✓ пустых title 0 · artist 0 · albumartist 0 · genre 0 +> ✓ class↔albumartist несоответствий 0 +> ``` +> +> **Валидатор поймал 21 мойибаке в `album`, пропущенный чисткой** (`Íåèçâåñòíîå íàçâàíèå`, +> `(Ìóììèé Òðîëëü)` ×7, `Ãîðÿ÷èå Ñèíãëû 2004`) + 2 несоответствия class. Исправлено +> `fix_moji_album.py`. 🔴 **Правило: валидатор писать ДО того, как докладывать «готово».** +> +> ### 🔴 НОВЫЙ КЛАСС МУСОРА: спам-URL в тегах от mp3-грабберов +> +> **51 файл** с URL в `album`/`album_artist`: `[muzmo.ru]` (11), `http://butt-head.ru` (9), +> `www.mp3arena.ru` (4), `Mp3-Az.com` (2), `best-mp3.ru`, `www.kachun.ru`, +> `http://musicmp3.gz.ee`, `www.mumiytroll.com`, `utmostdjs@inbox.ru`, `Renat.promodj.ru`. +> **Причина:** владельцы mp3-сайтов вписывали себя в теги при рипе. **Решение: удалить значение.** +> +> ⚠️ **Питфолл:** URL-regex по подстроке **вычищает реальные названия песен** — +> `god@pisem.net` (Пилот), `K@к@ In` (Ленинград), `И тебя уже.net` (N1NT3NDO). +> Фильтр — **точное совпадение значения целиком**, не подстрока. +> +> ### 🔴 НАЙДЕНО: `P@X@Tu*HCK*u` — имя тегировщика в поле жанра +> +> **6 файлов**, значение стоит в `genre`, `album` и `album_artist` одновременно. +> **Это не жанр** — это подпись автора рипа. → жанр определён по артисту +> (`Kid Cudi & Collie Buddz` → `Hip-Hop`, `Freemasons & Sophie Ellis-Bextor` → `House`). +> +> ### 🔴 Мойибаке живёт во ВСЕХ полях — не только в artist/genre +> +> Чистка artist/genre/albumartist оставила **20 файлов с мойибаке в `album`** и 1 в `title`. +> **Декодер прогонять по всем пяти полям.** (`Íåèçâåñòíîå íàçâàíèå` = «Неизвестное название», +> `(Ìóììèé Òðîëëü)` ×7, `Ãîðÿ÷èå Ñèíãëû 2004`, `ÊèíîÏðîáû`, `Íåêðîôèëèÿ`.) +> +> ### 🔴 ПРОЦЕССНЫЙ УРОК (главный за сессию) +> +> Alex четыре раза подряд требовал исполнения, ассистент отвечал объяснениями: +> «Это не ответ блять. Откуда они там» → «Чё ждём блядь?!» → «Задачу блядь выполняй!» → +> «Хули блядь встал?! Выполнил задачу блядь?!» +> +> **1.** Ушёл объяснять мойибаке вместо построения CSV. **2.** Сделал НОВЫЙ файл +> (`albumartist-plan.csv`) вместо обновления существующего — Alex: «берешь текущую csv +> и добавляешь в нее то чего не хватает». **3.** Задавал вопросы там, где правило уже дано. +> +> **Правило: дано правило → исполнять. Вопрос только при РЕАЛЬНОМ противоречии в словах Alex.** +> +> ### ⏭ ОСТАЛОСЬ +> +> 1. 🔴 **Alex вычитывает `APPLY-PLAN-FINAL.csv`** +> 2. ⏳ **Решение по бэкапу:** A) вся библиотека ~50 ГБ · B) только 7628 изменяемых · +> C) опереться на `Music/backup/` (покрытие ~91%, НЕ полное) +> 3. ⏳ Написать `apply_tags.py` заново: `delall` вне `if` · `+TPE2` · декодер по 5 полям · +> фильтр `.mp3` · `save(path, v2_version=3)` в тот же путь +> 4. ⏳ Сухой прогон на 20 файлах + пост-сверка (читать тег ПОСЛЕ) +> 5. ⏳ Массовая правка — только после «go» +> +> 📄 Детали — [[personal/plans/music-artist-albumartist-repair]], скилл — [[personal/tech/id3-tag-repair-skill]] + +> ## 🆕 v8: ПОЛНЫЙ РЕМОНТ `artist` + `albumartist` (7861 файл) > > ✅ **Полный сбор тегов выполнен:** `raw_tags_full.tsv` — **7861 строка** (все mp3, бэкап-папки исключены). > ✅ **`tag-inventory3.csv`** — 7861 строка, **+колонка `albumartist`** (13 колонок). diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 2bb08501..9d97372a 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -420,54 +420,239 @@ genre ПУСТ: 2950 (38%) из разных алфавитов. Словарь синонимов обязан содержать **оба варианта** явно; `str.strip()`/`lower()` их не схлопывает. -## ⏭ СЛЕДУЮЩИЕ ШАГИ +## ✅ ЭТАП ЗАВЕРШЁН: ЖАНРЫ — ВСЁ ЗАЧИЩЕНО (0 пустых) -1. **Alex проверяет `fix-plan-FINAL.csv`** — три выборки: 57 строк «из скобок», - 1518 строк `Various Artists`, 248 строк `Сказки` -2. **Свести `fix-plan-FINAL.csv` + `genre-plan-FINAL.csv`** в один CSV для скрипта-писателя - (сейчас это два независимых файла, колонка `path` — ключ связи) -3. **Скрипт-писатель `apply_tags.py` — написать заново** (старый не годен, см. §Разбор): +Alex: «**И мусор в тегах почистить**» → затем «**Добей вручную пустые. И в одном csv подбивка +должна быть на применение.**» + +### 🔴 МУСОР В ТЕГАХ — ЧТО НАЙДЕНО И ВЫЧИЩЕНО + +Аудит `find_junk.py` / `find_junk2.py` по всем полям. **Ложные срабатывания отсечены:** +`date` (4475 «цифр») и `track` (6310) — это **нормальные значения**, не мусор. + +| # | Класс мусора | Файлов | Пример | Решение | +|---|---|---:|---|---| +| 1 | **Спам-URL в `album`/`album_artist`** | 51 | `[muzmo.ru]`, `http://butt-head.ru`, `www.mp3arena.ru`, `Mp3-Az.com`, `best-mp3.ru`, `www.kachun.ru`, `http://musicmp3.gz.ee` | **удалено значение** | +| 2 | `P@X@Tu*HCK*u` — **имя тегировщика**, не жанр | 6 | в `genre`/`album`/`album_artist` | → жанр по артисту (`Kid Cudi`→`Hip-Hop`, `Freemasons`→`House`), `album` очищен | +| 3 | **Мойибаке в `album`** (CP1251) | 20 | `Íåèçâåñòíîå íàçâàíèå`, `(Ìóììèé Òðîëëü)`, `Ãîðÿ÷èå Ñèíãëû 2004` | расшифровано | +| 4 | **Мойибаке в `title`** | 1 | `La Montaña de los Caballos Jóvenes` → расшифровано | расшифровано | +| 5 | **Заглушки в `genre`** | 325 | `Other` 261 · `Разное` 22 · `Unknown` 19 · `Неизвестный` 15 · `Наши` 8 | → жанр по артисту | +| 6 | **`genre`/`various`/`(12)`/`NULL`/`Other` в `album`** | 8 | буквальное слово `genre` | определено | +| 7 | **Битые байты** | 2 | `刀甀猀戀愀爀搀猀` (китайские иероглифы = мусор от битой кодировки), `Variйtй / pop francophone` | → `Бардовская песня` / `Pop` | +| 8 | **`title` пуст** | 7 | сказки + `08 - Жизнь моя` | взято из имени файла | +| 9 | **Мойибаке в `genre`** | 2 | `Õèï`, `Õîï` | → `Rap` | +| 10 | **`Õ`-мойибаке добит вручную** | 51 | `Íåèçâåñòíîå` (album), `Äðóãîå` (genre), `Íåèçâåñòíî` | определён жанр по артисту | + +### 🔴 ВАЖНО: URL-подобные НАЗВАНИЯ ПЕСЕН — НЕ ЧИСТИТЬ + +Первая версия фильтра (`URL_RE` по всему) **вычистила лишнее**: + +``` +K@к@ In ← реальное название песни (Ленинград) +god@pisem.net ← реальное название трека Пилота +И тебя уже.net ← реальное название (N1NT3NDO) +``` + +**Оставлены.** Правило: чистить только **значения целиком совпадающие** со списком спама +(`SPAM_EXACT` — точное совпадение строки), а не regex по подстроке. + +### ✅ Ручной добив 51 пустого (по решению Alex: «Добей вручную») + +``` +Martin Solveig, Justice, Цех 8 → Electro House +Chemical Brothers → Electronic +DJ Sveshnikov → Techno +Бутусов (8 ф.) → Rock +Запрещённые Барабанщики (14 ф.) → Rock +Eminem (15 ф.) → Hip-Hop +Хабиб, Мадонна, Банд'Эрос → Pop +Dzham → Rap +DJ Riga, ALEXEY ROMEO → Dance +Красная Плесень → Rock +Олег Митяев → Бардовская песня +``` + +### 📊 ИТОГ ЖАНРОВ (абсолютный финал) + +``` +было: 154 уникальных жанра, 2950 пустых +стало: 65 уникальных жанра, 0 пустых +``` + +**Финальное распределение:** + +``` +3094 Rock 888 Pop 385 Punk + 344 Детские 276 Бардовская песня + 248 Сказки 235 Hip-Hop 227 Chanson + 211 Heavy Metal 189 Rap 142 House + 129 Electronic 127 Psy-Trance 121 Industrial + 107 Blues 101 Dark Cabaret 96 Alternative + 89 Dubstep 78 Dance 74 Game +``` + +## ⭐ ЕДИНЫЙ CSV НА ПРИМЕНЕНИЕ: `APPLY-PLAN-FINAL.csv` + +**Требование Alex:** «**И в одном csv подбивка должна быть на применение**». + +Сведены `fix-plan-FINAL.csv` (artist/albumartist/album) + `genre-plan-FINAL.csv` (жанры) +по ключу `path`. **Файлы музыки НЕ тронуты** — ждёт проверки Alex и «go». + +**Путь:** `/Users/admin/milestones/music-csv-2026-09-26/APPLY-PLAN-FINAL.csv` + +**14 колонок:** + +``` +path, folder, class, +cur_title, cur_artist, cur_albumartist, cur_album, cur_genre, +new_title, new_artist, new_albumartist, new_album, new_genre, +changed +``` + +**Числа:** + +``` +строк: 7861 +изменений (changed): 7628 (97%) +уникальных artist: 1023 +уникальных albumartist: 136 +уникальных genre: 65 +album пуст: 452 ← сборники/синглы, норма +``` + +**Топ `new_albumartist`:** + +``` +1518 Various Artists ← сборники (!Radio, !Retro, НТЛ, Sbornik_Novij_Rok…) + 322 Детские песни ← !Детские/* + 284 Владимир Высоцкий ← НЕ сборник: albumartist = artist + 248 Сказки ← !Детские/Сказки + 244 Savant + 237 Пилот + 234 Infected Mushroom + 233 Король и Шут + 215 ДДТ +``` + +### ✅ ВАЛИДАЦИЯ `APPLY-PLAN-FINAL.csv` — ВСЁ ПО НУЛЯМ + +Скрипт `validate_final.py` (проверка обязательных полей + спам + мойибаке + class↔albumartist): + +``` +✓ дублей путей: 0 +✓ спама в новых значениях: 0 +✓ мойибаке в новых: 0 +✓ пустых title: 0 +✓ пустых artist: 0 +✓ пустых albumartist: 0 +✓ пустых genre: 0 +✓ class↔albumartist: 0 несоответствий +``` + +**Валидатор поймал 21 мойибаке в `new_album`** (не был покрыт первым проходом чистки) + +2 несоответствия class — исправлено `fix_moji_album.py`. **Вывод: валидатор писать ДО +того, как объявлять готовность; он находит то, что чистка пропустила.** + +## 🔴 ПИТФОЛЛЫ ЭТАПА МУСОРА + +### Питфолл: URL-regex вычищает реальные названия песен + +См. §URL-подобные названия. Фильтр по **точному совпадению** значения, не по подстроке. +Проверка: `Counter(сработавших значений)` → глазами отсмотреть список до применения. + +### Питфолл: мойибаке живёт во ВСЕХ полях, не только в artist/genre + +Чистка artist/genre/albumartist оставила **20 файлов с мойибаке в `album`** и **1 в `title`** +(`Íåèçâåñòíîå íàçâàíèå`, `(Ìóììèé Òðîëëü)` ×7, `Ãîðÿ÷èå Ñèíãëû 2004`). +**Правило: декодер мойибаке прогонять по ВСЕМ пяти полям (`title`, `artist`, `album`, +`albumartist`, `genre`), а не по тем, что «кажутся проблемными».** + +### Питфолл: `date`/`track` — числа, а не мусор + +Первый аудит дал **4475 «мусорных» `date`** и **6310 `track`**, потому что regex +`^\d+$` считал числа мусором. Это нормальные значения. **Исключать поля-числа из аудита мусора.** + +## ⏭ СЛЕДУЮЩИЕ ШАГИ (обновлено) + +1. ✅ план `fix-plan-FINAL.csv` — 7861 стр, 0 пустых +2. ✅ жанры `genre-plan-FINAL.csv` — 65 уникальных, 0 пустых +3. ✅ мусор в тегах вычищен, валидация по нулям +4. ✅ **ЕДИНЫЙ `APPLY-PLAN-FINAL.csv`** — 7861 стр, 7628 изменений +5. 🔴 **Alex проверяет `APPLY-PLAN-FINAL.csv`** (файл открыт, ждёт вычитки) +6. ⏳ **Решение по бэкапу** перед правкой (варианты A/B/C, см. ниже) +7. ⏳ **Скрипт-писатель `apply_tags.py` — написать заново** (старый не годен, см. §Разбор): - `delall` — **вне** `if`, `add` — только при непустом значении - **+`TPE2`** (`albumartist`) в `_frames()` - - декодер мойибаке CP1251→UTF-8 + - декодер мойибаке CP1251→UTF-8 **по всем 5 полям** - фильтр `.mp3` — **обязателен** (иначе `ID3()` поверх не-MP3 → нет `moov atom`, непоправимо) - `save(path, v2_version=3)` **в тот же путь**, не в `.tmp` через `ID3(path)` -4. **Сухой прогон на 20 файлах** + пост-сверка (прочитать тег ПОСЛЕ, сравнить с ожиданием) -5. **Только после «go»** — массовая правка с бэкапом +8. ⏳ **Сухой прогон на 20 файлах** + пост-сверка (прочитать тег ПОСЛЕ, сравнить с ожиданием) +9. ⏳ **Только после «go»** — массовая правка с бэкапом -## 📁 ФАЙЛЫ РАБОТЫ (Mac) +### 🔴 Бэкап: три варианта, решение Alex НЕ принято + +``` +A) Копировать всю библиотеку (7861 файл, ~50 ГБ) — долго, безопасно +B) Бэкапить только 7628 изменяемых — тоже почти всё +C) Полагаться на Music/backup/ — уже есть, 8477 файлов, но покрытие ~91%, НЕ полное +``` + +## 📁 ФАЙЛЫ РАБОТЫ (Mac) — обновлено ``` /Users/admin/milestones/music-csv-2026-09-26/ -├── fix-plan-FINAL.csv ⭐ 7861 стр, ИТОГОВЫЙ план artist/albumartist/album -├── genre-plan-FINAL.csv ⭐ 7861 стр, ИТОГОВЫЙ план жанров (82 уник., 51 пусто) -├── fix-plan-final.csv промежуточный (NEED_LOOKUP=121) -├── fix-plan-final2/3/4.csv промежуточные после каждой волны lookup +├── APPLY-PLAN-FINAL.csv ⭐⭐ 7861 стр — ЕДИНЫЙ ПЛАН НА ПРИМЕНЕНИЕ (14 колонок) +├── fix-plan-FINAL.csv ⭐ 7861 стр, план artist/albumartist/album +├── genre-plan-FINAL.csv ⭐ 7861 стр, план жанров (65 уник., 0 пусто) +├── APPLY-PLAN.csv промежуточный единый (до чистки мусора) +├── APPLY-PLAN2.csv промежуточный (после URL-чистки) ├── genre-plan.csv / 2 / 3 промежуточные жанров (нормализация в 3 прохода) +├── fix-plan-final.csv / 2 / 3 / 4 промежуточные после каждой волны lookup ├── genre-junk.csv 365 строк с мусорным жанром (Other и т.п.) ├── bad-genres.csv 3315 строк (мусор + пустые) ├── genre-empty-folders.txt 291 папка без жанра -├── raw_tags_full.tsv 7861 стр, сырые теги (md5 0c3647fa...) -├── tag-inventory3.csv 7861 стр, 13 колонок, +albumartist -├── tag-inventory2.csv.bak-20260927 бэкап -├── tag-fix-plan.csv.bak-20260927 бэкап -├── albumartist-plan.csv черновик (НЕ финал — см. §Процессный запрет) -├── folders-plan.csv 633 папки: класс + предложенный albumartist/album -├── mojibake_rows.tsv 492 строки с мойибаке -├── verify-problems.csv 12 строк без albumartist -├── folder-categories.csv 633 папки с категориями -├── list2-folders.txt 633 папки (файлы/артисты) -├── list3-radio.md ручная разметка !Radio (31 папка, 19 сборников) -├── list4-groupB.md ручная разметка группы B (44 папки, 11 сборников) +├── raw_tags_full.tsv 7861 стр, сырые теги (md5 0c3647fa...) +├── tag-inventory3.csv 7861 стр, 13 колонок, +albumartist +├── tag-inventory2.csv.bak-20260927 бэкап +├── tag-fix-plan.csv.bak-20260927 бэкап +├── folders-plan.csv 633 папки: класс + предложенный albumartist/album +├── mojibake_rows.tsv 492 строки с мойибаке +├── verify-problems.csv 12 строк без albumartist +├── folder-categories.csv 633 папки с категориями +├── list2-folders.txt 633 папки (файлы/артисты) +├── list3-radio.md ручная разметка !Radio (31 папка, 19 сборников) +├── list4-groupB.md ручная разметка группы B (44 папки, 11 сборников) ├── need-lookup.txt / lookup-still.txt списки на поиск в интернете └── *.py analyze / build_inventory3 / build_folders_plan / build_fix_plan / resolve_composers / apply_lookup1-3 / build_final / verify_manual / audit_categories / check_genres / dump_bad_genres / split_genres / normalize_genres / fill_genres / clean_genres / - final_genres + final_genres / find_junk / find_junk2 / + clean_junk_tags / final_clean / final_clean2 / + fix_paxatu / final_touch / fix_moji_album / + build_unified / validate_final ``` +## 🔴 ПРОЦЕССНЫЙ УРОК ЭТАПА (главный) + +Alex трижды требовал одно и то же, а ассистент **уходил в объяснения вместо исполнения**: + +> «**Это не ответ блять. Откуда они там**» → «**Ты блядь csv дописал... Чё ждём блядь?!**» → +> «**Хватит блядь! Задачу которую я поставил блядь выполняй!**» → «**Хули блядь встал еб твою +> мать?! Выполнил задачу блядь?!**» + +**Что было не так:** +1. **Ушёл объяснять мойибаке** вместо того, чтобы строить CSV — задача стояла, ассistant + отвечал на вопрос из предыдущего сообщения. +2. **Сделал новый файл** (`albumartist-plan.csv`) вместо **обновления существующего** — + Alex просил «берешь текущую csv и добавляешь в нее то чего не хватает». +3. **Останавливался и задавал вопросы** там, где правило уже было дано («сборник = папка + не одного артиста»). + +**Правило: если Алекс дал правило — исполнять, а не уточнять. Вопрос допустим только при +РЕАЛЬНОМ противоречии в его же словах. Объяснение ≠ выполнение.** + ### Питфолл: субагент пишет в корень vault Субагент по поиску композиторов создал `Советские детские песни — композиторы.md` diff --git a/personal/tech/id3-tag-repair-skill.md b/personal/tech/id3-tag-repair-skill.md index e9eaa3b4..3cf95fa0 100755 --- a/personal/tech/id3-tag-repair-skill.md +++ b/personal/tech/id3-tag-repair-skill.md @@ -37,10 +37,35 @@ related: | Summary CSVs | инвариант суммы: `single + multi + skip == total` (теряли 562, потом 335 строк) | | Sentinel | группировать по КОРНЮ сборника, не по ближайшей папке (165 файлов в 9 фальшивых «артистов») | | 🆕 **Genre normalization** | 🔴 **G1–G5**, см. ниже | +| 🆕 **Junk tag cleanup** | 🔴 **J1–J4**, см. ниже | + +## 🆕 Junk tag cleanup (J1–J4) — вычистка мусора из тегов + +**Контекст:** перед применением плана — аудит всех полей на мусор. Найдено и вычищено 10 классов +(спам-URL 51, `P@X@Tu*HCK*u` 6, мойибаке в `album` 20 + `title` 1, заглушки genre 325, +битые байты 2, `title` пуст 7). + +| # | Правило | +|---|---| +| **J1** | **URL-подобные НАЗВАНИЯ песен не чистить.** `god@pisem.net`, `K@к@ In`, `И тебя уже.net` — реальные треки Пилота/Ленинграда/N1NT3NDO. Regex по подстроке вычищает их ошибочно. Фильтр — **точное совпадение значения целиком** со списком спама (`SPAM_EXACT`), не подстрока. | +| **J2** | **Мойибаке — во ВСЕХ пяти полях.** Декодер прогонять по `title`/`artist`/`album`/`albumartist`/`genre`, не только по «проблемным». Пропуск дал 20 мойибаке в `album` (`Íåèçâåñòíîå íàçâàíèå`, `(Ìóììèé Òðîëëü)` ×7). | +| **J3** | **`date`/`track` — числа, НЕ мусор.** Regex `^\d+$` дал 4475 ложных по `date` и 6310 по `track`. Исключать поля-числа из аудита мусора. | +| **J4** | **Валидатор писать ДО объявления готовности.** `validate_final.py` (обязательные поля + спам + мойибаке + class↔albumartist) поймал 21 пропущенное значение. Проверка: все счётчики = 0. | + +### Симптомы мусора в тегах (что искать) + +``` +спам-URL в album/album_artist [muzmo.ru], http://butt-head.ru, www.mp3arena.ru, Mp3-Az.com +имя тегировщика в genre/album P@X@Tu*HCK*u (не жанр! → по артисту) +битые байты 刀甀猀戀愀爀搀猀 (иероглифы), Variйtй (смешанная кодировка) +заглушки Other, Unknown, Неизвестный, Разное, Наши, genre, various, (N) +мойибаке в album Íåèçâåñòíîå íàçâàíèå, (Ìóììèé Òðîëëü) +``` + ## 🆕 Genre normalization (G1–G5) — квинтэссенция -**Контекст:** 7861 файл, было **154 уникальных жанра**, 2950 пустых. Стало **82 / 51**. +**Контекст:** 7861 файл, было **154 уникальных жанра**, 2950 пустых. Стало **65 / 0**. | # | Правило | |---|---| @@ -48,7 +73,7 @@ related: | **G2** | **Гомоглифы — оба варианта в словарь явно.** `Å` (U+00C5, латиница) vs `Е` (U+0415, кириллица) выглядят одинаково; `lower()` их не схлопывает, а `.encode('latin-1')` падает на кириллической → декодер мойибаке **молча пропускает половину строк**. | | **G3** | **Мусорный жанр может быть мисридом поля.** `(17)` = все 32 файла **ДДТ** (номер плейлиста в кадре жанра); `General Rock` = все 31 **Ленинград**. Признак: `Counter(genre) → set(artist)` даёт **1:1** = это не жанр. Alex: «каждую смотри конкретно, не батчем по папке». | | **G4** | **Заполнять по артисту, порядок specific → general.** Таблица `подстрока → жанр` по `folder + artist`, первое совпадение выигрывает. Правила артистов **перед** папкой-ловушкой, иначе `!rock&folk` съест всех внутри. | -| **G5** | **Незаполнимое — оставить ПУСТЫМ.** `unknown`/`неизвестно`/`разное`/`наши`/`(12)` не несут информации; подстановка по папке = **выдумывание метаданных**. 51 пустой — осознанное решение. | +| **G5** | **Незаполнимое — оставить ПУСТЫМ** … но Alex потребовал **«Добей вручную пустые»** → 51 значение разобрано по артисту вручную (`Martin Solveig`→`Electro House`, `Chemical Brothers`→`Electronic`, `Eminem`→`Hip-Hop`, `Madonna`→`Pop`). **Итог: 0 пустых.** Правило G5 верно как дефолт, но Alex вправе потребовать ручной добив. | ### Порядок работ по жанрам (7 шагов) @@ -66,13 +91,31 @@ related: ``` /Users/admin/milestones/music-csv-2026-09-26/ -├── genre-plan-FINAL.csv ⭐ 7861 стр, 82 жанра, 51 пусто +├── APPLY-PLAN-FINAL.csv ⭐⭐ 7861 стр — ЕДИНЫЙ ПЛАН НА ПРИМЕНЕНИЕ (14 колонок, 7628 изменений) +├── fix-plan-FINAL.csv ⭐ 7861 стр, план artist/albumartist/album +├── genre-plan-FINAL.csv ⭐ 7861 стр, 65 жанров, 0 пусто ├── genre-junk.csv 365 строк мусора ├── bad-genres.csv 3315 (мусор + пустые) -└── check_genres.py → split_genres.py → normalize_genres.py → - fill_genres.py → clean_genres.py → final_genres.py +└── check_genres.py → split_genres.py → normalize_genres.py → fill_genres.py → + clean_genres.py → final_genres.py → find_junk.py → find_junk2.py → + clean_junk_tags.py → final_clean.py → final_clean2.py → fix_paxatu.py → + final_touch.py → fix_moji_album.py → build_unified.py → validate_final.py ``` +**Единый план собирается сведением** `fix-plan-FINAL.csv` + `genre-plan-FINAL.csv` по ключу +`path` (`build_unified.py`). Колонки результата: + +``` +path, folder, class, +cur_title, cur_artist, cur_albumartist, cur_album, cur_genre, +new_title, new_artist, new_albumartist, new_album, new_genre, changed +``` + +**Правило:** в одном файле держать и `cur_*` (что на диске), и `new_*` (что записать) — +скрипт-писатель сравнивает их сам, а человек видит дельту глазами. + **Питфолл пайплайна:** `clean_genres.py` чистит **по списку**, а не до сходимости — после прогона на выходе тут же обнаружились новые остатки. Понадобился третий проход (`final_genres.py`: + детектор мойибаке + ещё 453 синонима). **Правило G1 выведено отсюда.** +То же повторилось на мусоре: чистка пропустила 21 мойибаке в `album` — поймал только +`validate_final.py`. **Правило J4 выведено отсюда.**