[2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/tech/id3-tag-repair-skill.md personal/tech/music-tags-repair-truenas.md
This commit is contained in:
1 parent
d5fd802113
commit
7d71a558d9
3 files changed
+192
-5
No files matched your search
@@ -324,26 +324,129 @@ NEED_LOOKUP: 0
|
||||
|
||||
`АЛЬБОМ-мульти(?)` (30 папок) разобраны вручную — почти все НЕ сборники, а альбомы с фитами.
|
||||
|
||||
## ✅ ЭТАП ЗАВЕРШЁН: ЖАНРЫ — `genre-plan-FINAL.csv` (7861 строка, 51 пустой)
|
||||
|
||||
Alex: «Ещё проверь» → разбор жанров по всей библиотеке.
|
||||
|
||||
### Исходное состояние (до чистки)
|
||||
|
||||
```
|
||||
genre ЗАПОЛНЕН: 4911 (62%)
|
||||
genre ПУСТ: 2950 (38%)
|
||||
уникальных: 154
|
||||
```
|
||||
|
||||
### 🔴 Дефекты жанров, найденные аудитом
|
||||
|
||||
| # | Дефект | Файлов | Пример |
|
||||
|---|---|---:|---|
|
||||
| 1 | **Мойибаке CP1251 в жанрах** | 29 | `Psy Årance` / `Psy Еrance` (Е — **кириллическая**) → `Psy-Trance` |
|
||||
| 2 | **Разнобой регистра одного жанра** | ~1800 | `Rock` | `ROCK` | `Рок` | `General Rock` — четыре варианта |
|
||||
| 3 | **Синонимы-дубли** | 453 | `Metal`→`Heavy Metal`, `Punk Rock`→`Punk`, `Russia Rap`→`Rap`, `Поп`→`Pop`, `Рэп и хип-хоп`→`Hip-Hop` |
|
||||
| 4 | **Мусорные значения** | 365 | `Other` 261 · `(17)` 32 · `General Rock` 31 · `Разное` 22 · `Unknown` 19 |
|
||||
| 5 | **Детские/сказки без жанра вообще** | 570 | `ДЕТСКИЕ` 0 файлов с жанром, `СКАЗКИ` 11 (10 `Other`, 1 `Blues` у сказки — мусор) |
|
||||
| 6 | **Спам в тегах** | 4 | `www.mp3-ogg.ru`, `.:::artMkiss 2009:::.`, `ÿþElectronic` |
|
||||
|
||||
### ✅ Ручной разбор мусора (по решению Alex — НЕ батчем по папке)
|
||||
|
||||
> Alex: «По other/unknown — не уверен про что речь, **каждую смотри конкретно и ставь правильный
|
||||
> жанр, не батчем по папке**.»
|
||||
|
||||
`(17)` и `General Rock` расшифрованы фактом — это **не жанры вообще, а артисты**:
|
||||
|
||||
| Значение | Факт | Решение |
|
||||
|---|---|---|
|
||||
| `(17)` | **все 32 файла — ДДТ**, попал номер трека/плейлиста | → `Rock` |
|
||||
| `General Rock` | **все 31 файл — Ленинград** (Дачники, Бабаробот) | → `Rock` |
|
||||
| `Other` (261) | DJ-сборники `!Radio`, Пушной, Vanessa Mae, Каста, Трофим | разобраны **по исполнителю** |
|
||||
|
||||
### Ручные правила заполнения 2715 файлов (по Alex: «Вроде все так»)
|
||||
|
||||
| Папка/артист | Ф. | Жанр |
|
||||
|---|---:|---|
|
||||
| `!Барды/Владимир Высоцкий/*` | 223 | `Бардовская песня` |
|
||||
| `!A Not sorted/Sbornik_Novij_Rok` | 200 | `Rock` |
|
||||
| `!Retro` | 94 | `Pop` |
|
||||
| `!Radio/*` (DJ-сборники) | ~200 | `Dance` |
|
||||
| `Захар Май` | ~60 | `Rock` |
|
||||
| `Александр Пушной` | 23 | `Rock` |
|
||||
| `Vanessa Mae` | 26 | `Classical Crossover` |
|
||||
| `The Tiger Lillies` | 24 | `Dark Cabaret` |
|
||||
| `Каста` / `Eminem` | 46 | `Hip-Hop` |
|
||||
| `Трофим` | 50 | `Chanson` |
|
||||
| `Король и Шут` | 21 | `Punk` |
|
||||
| `Rammstein` | — | `Industrial` |
|
||||
|
||||
**Полные правила — 130 key→genre в `fill_genres.py` (RULES), первое совпадение выигрывает.**
|
||||
|
||||
### 📊 Итог
|
||||
|
||||
```
|
||||
было: 154 уникальных жанра, 2950 пустых
|
||||
стало: 82 уникальных жанра, 51 пустой
|
||||
|
||||
мойибаке исправлено: 29
|
||||
синонимов схлопнуто: 453
|
||||
заполнено правилами: 2715 → 0 пустых, затем дочистка 173 + 453
|
||||
```
|
||||
|
||||
**Финальное распределение:**
|
||||
|
||||
```
|
||||
3071 Rock 882 Pop 385 Punk
|
||||
344 Детские 276 Бардовская песня
|
||||
248 Сказки 226 Chanson 218 Hip-Hop
|
||||
211 Heavy Metal 188 Rap 139 House
|
||||
127 Electronic 127 Psy-Trance 121 Industrial
|
||||
107 Blues 101 Dark Cabaret 95 Alternative
|
||||
```
|
||||
|
||||
**51 пустой — осознанно.** Это `unknown`/`неизвестно`/`other`/`другое`/`разное`/`наши`,
|
||||
`(12)`, `www.mp3-ogg.ru` — заполнять нечем. Navidrome покажет файлы без жанра,
|
||||
они просто не попадут в жанровые фильтры.
|
||||
|
||||
### 🔴 ПИТФОЛЛ: `clean_genres.py` чистит по списку, а не итеративно до сходимости
|
||||
|
||||
Первый прогон схлопнул 173 значения, но **на выходе тут же обнаружились новые остатки**
|
||||
(`Russia Rap`, `Рэп и хип-хоп`, `Поп`, `Pop/Rock`) — потому что нормализация дала
|
||||
значения, которых не было в исходном `FIX`-словаре. Понадобился **третий проход**
|
||||
(`final_genres.py`: +мойибаке-декодер + ещё 453 синонима).
|
||||
**Правило: нормализацию гонять до нуля изменений, а не один раз.** Проверка —
|
||||
`Counter(new_genre)` на выходе: если появились неизвестные значения, словарь неполон.
|
||||
|
||||
### 🔴 ПИТФОЛЛ: `Å`/`Е` в `Psy Årance` — латиница vs кириллица
|
||||
|
||||
`Psy Årance` и `Psy Еrance` выглядят идентично, но `Å` (U+00C5) и `Е` (U+0415) — разные символы
|
||||
из разных алфавитов. Словарь синонимов обязан содержать **оба варианта** явно;
|
||||
`str.strip()`/`lower()` их не схлопывает.
|
||||
|
||||
## ⏭ СЛЕДУЮЩИЕ ШАГИ
|
||||
|
||||
1. **Alex проверяет `fix-plan-FINAL.csv`** — три выборки: 57 строк «из скобок»,
|
||||
1518 строк `Various Artists`, 248 строк `Сказки`
|
||||
2. **Скрипт-писатель `apply_tags.py` — написать заново** (старый не годен, см. §Разбор):
|
||||
2. **Свести `fix-plan-FINAL.csv` + `genre-plan-FINAL.csv`** в один CSV для скрипта-писателя
|
||||
(сейчас это два независимых файла, колонка `path` — ключ связи)
|
||||
3. **Скрипт-писатель `apply_tags.py` — написать заново** (старый не годен, см. §Разбор):
|
||||
- `delall` — **вне** `if`, `add` — только при непустом значении
|
||||
- **+`TPE2`** (`albumartist`) в `_frames()`
|
||||
- декодер мойибаке CP1251→UTF-8
|
||||
- фильтр `.mp3` — **обязателен** (иначе `ID3()` поверх не-MP3 → нет `moov atom`, непоправимо)
|
||||
- `save(path, v2_version=3)` **в тот же путь**, не в `.tmp` через `ID3(path)`
|
||||
3. **Сухой прогон на 20 файлах** + пост-сверка (прочитать тег ПОСЛЕ, сравнить с ожиданием)
|
||||
4. **Только после «go»** — массовая правка с бэкапом
|
||||
4. **Сухой прогон на 20 файлах** + пост-сверка (прочитать тег ПОСЛЕ, сравнить с ожиданием)
|
||||
5. **Только после «go»** — массовая правка с бэкапом
|
||||
|
||||
## 📁 ФАЙЛЫ РАБОТЫ (Mac)
|
||||
|
||||
```
|
||||
/Users/admin/milestones/music-csv-2026-09-26/
|
||||
├── fix-plan-FINAL.csv ⭐ 7861 стр, ИТОГОВЫЙ план правок
|
||||
├── 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
|
||||
├── genre-plan.csv / 2 / 3 промежуточные жанров (нормализация в 3 прохода)
|
||||
├── 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 бэкап
|
||||
@@ -359,7 +462,10 @@ NEED_LOOKUP: 0
|
||||
├── 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
|
||||
build_final / verify_manual / audit_categories /
|
||||
check_genres / dump_bad_genres / split_genres /
|
||||
normalize_genres / fill_genres / clean_genres /
|
||||
final_genres
|
||||
```
|
||||
|
||||
### Питфолл: субагент пишет в корень vault
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: ID3 Tag Repair — скилл (жанры, мойибаке, безопасная пакетная правка)
|
||||
created: '2026-09-27'
|
||||
updated: '2026-09-27'
|
||||
type: tech
|
||||
namespace: personal
|
||||
tags:
|
||||
- music
|
||||
- id3
|
||||
- metadata
|
||||
- genre
|
||||
- mojibake
|
||||
- skill
|
||||
related:
|
||||
- '[[personal/plans/music-artist-albumartist-repair]]'
|
||||
- '[[personal/tech/music-tags-repair-truenas]]'
|
||||
---
|
||||
|
||||
# Скилл `id3-tag-repair` — зеркало знаний
|
||||
|
||||
**Путь скилла:** `~/.hermes/hermes-whale/skills/media/id3-tag-repair/SKILL.md`
|
||||
**Обновлён:** 2026-09-27 — добавлен раздел **Genre normalization** (5 правил G1–G5).
|
||||
|
||||
## Что в скилле (карта)
|
||||
|
||||
| Раздел | О чём |
|
||||
|---|---|
|
||||
| Core rules | дампить БАЙТЫ до гипотезы; `ffprobe` врёт (пусто при валидном ID3v2.3) |
|
||||
| Step 2 — три случая `enc_byte` | 🔴 BOM-ловушка: `enc=0` + `FF FE` → **снять BOM, читать как 8-bit**, НЕ `decode('utf-16')` |
|
||||
| Step 3 — guard мойибаке | 🔴 конвертировать **по РУНАМ**, не по строке (иначе `Ïîëç¸ò(Birthday mix)` не чинится) |
|
||||
| Step 4 — трек-номера | `01 ` без пунктуации не матчился → **2125 файлов**; `N-MM` → disc/track; `159_Css` не трогать |
|
||||
| CSV worklist | 🔴 НЕ писать в источник: `w`-режим обрезает файл (7906→4640). Схема `.tmp` → revalidate → `os.replace` |
|
||||
| Rule 4/4b | идемпотентность: цель считать от ИСХОДНОГО (кейс Disc4 10→60→110); `was == now` не идемпотентна |
|
||||
| Rule 6 | `action=no_change` — **инструкция**, а не статус; валидировать по ДИСКУ (353 ложных) |
|
||||
| Rule 7/8 | не-MP3 в MP3-плане = MISSING, не потеря; `backup/` из плана покрывает только его форматы |
|
||||
| Unicode | NFC vs NFD (`Принц и нищий (Диск 2)`, combining breve U+0306) |
|
||||
| Summary CSVs | инвариант суммы: `single + multi + skip == total` (теряли 562, потом 335 строк) |
|
||||
| Sentinel | группировать по КОРНЮ сборника, не по ближайшей папке (165 файлов в 9 фальшивых «артистов») |
|
||||
| 🆕 **Genre normalization** | 🔴 **G1–G5**, см. ниже |
|
||||
|
||||
## 🆕 Genre normalization (G1–G5) — квинтэссенция
|
||||
|
||||
**Контекст:** 7861 файл, было **154 уникальных жанра**, 2950 пустых. Стало **82 / 51**.
|
||||
|
||||
| # | Правило |
|
||||
|---|---|
|
||||
| **G1** | **Чистить итеративно до фиксированной точки.** Нормализация РОЖДАЕТ новые значения (`Metal`→`Heavy Metal`, но всплывает `Russia Rap`). Один проход дал 173, понадобилось ещё 453. Тест: прогон с 0 изменений. |
|
||||
| **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 пустой — осознанное решение. |
|
||||
|
||||
### Порядок работ по жанрам (7 шагов)
|
||||
|
||||
```
|
||||
1. check — перепись: заполнено/пусто, Counter(genre), дыры по папкам
|
||||
2. split — три корзины: норма / МУСОР / пусто; мусор — отдельным CSV
|
||||
3. группировка мусора по АРТИСТУ (G3) → 1:1 разбирать руками
|
||||
4. normalize — регистр + синонимы + декодер мойибаке (G1, G2)
|
||||
5. fill — таблица правил по артисту, specific→general (G4)
|
||||
6. перепись → добавить правила → повтор до 0 изменений
|
||||
7. реально-неизвестное оставить пустым (G5)
|
||||
```
|
||||
|
||||
## Артефакты сессии
|
||||
|
||||
```
|
||||
/Users/admin/milestones/music-csv-2026-09-26/
|
||||
├── genre-plan-FINAL.csv ⭐ 7861 стр, 82 жанра, 51 пусто
|
||||
├── 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
|
||||
```
|
||||
|
||||
**Питфолл пайплайна:** `clean_genres.py` чистит **по списку**, а не до сходимости — после
|
||||
прогона на выходе тут же обнаружились новые остатки. Понадобился третий проход
|
||||
(`final_genres.py`: + детектор мойибаке + ещё 453 синонима). **Правило G1 выведено отсюда.**
|
||||
@@ -38,6 +38,9 @@ related:
|
||||
> - мойибаке — **исходное состояние**, НЕ следствие волны 38 (доказано `diff` v1 vs v2 инвентаря)
|
||||
> - **сборник ≠ «>1 артиста»** — это ловит альбомы с фитами; Alex дал прямые примеры
|
||||
> - процессный запрет Alex: **сначала ручные списки, скриптом — только обновление CSV**
|
||||
> - ✅ `fix-plan-FINAL.csv` — 7861 стр, **0 пустых `artist`**, 7233 изменения
|
||||
> - ✅ `genre-plan-FINAL.csv` — 7861 стр, **82 уникальных жанра** (было 154), 51 пустой,
|
||||
> мойибаке `Psy Årance` → `Psy-Trance` (29 файлов, `Å`/`Е` — латиница vs кириллица)
|
||||
|
||||
> ## ✅ ТРИДЦАТЬ ВОСЬМАЯ ВОЛНА (2026-09-26) — ФИНАЛЬНЫЙ АУДИТ + 4 ПРАВКИ + ПРИМЕНЕНИЕ НА NAS
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user