From c0a76a56c9a06eab4130b34f0fa1e0e1553dd4b6 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 14:09:06 +0600 Subject: [PATCH] [2026-09-27] eagle: personal/plans/music-artist-albumartist-repair.md personal/tech/music-tags-repair-truenas.md personal/tech/scripts/find_aa_hijack.py --- .../plans/music-artist-albumartist-repair.md | 276 +++++++++++++++++- personal/tech/music-tags-repair-truenas.md | 15 +- personal/tech/scripts/find_aa_hijack.py | 56 ++++ 3 files changed, 341 insertions(+), 6 deletions(-) create mode 100644 personal/tech/scripts/find_aa_hijack.py diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index fd018a8a..627753f1 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -4,7 +4,7 @@ created: '2026-09-27' updated: '2026-09-27' type: plan namespace: personal -status: session4-done-diff-rebuilt-4864-rows-0-losses +status: session6-open-7-albumartist-hijack-folders tags: - music - metadata @@ -23,6 +23,30 @@ related: # Music `artist` + `albumartist` Repair — правила Alex и ход работ +> ## 🔴 АКТУАЛЬНО НА КОНЕЦ СЕССИИ 6 (2026-09-27) +> +> ``` +> APPLY-PLAN.csv 7861 строка md5 c8d43cc56d82cd35a67a0efe8173cd4b +> DIFF-new-vs-38.csv 4864 строки md5 3ebc67c9d6abe5fce2d927fcc7726808 +> tag-fix-plan.csv не тронут (база 38-го, источник истины для диффа) +> ``` +> +> **Пустых полей в плане — 0** (`new_album` / `new_albumartist` / `new_artist` / `new_title` / `new_genre`). +> Дифф проверен: `ПОТЕРЯНО 0 · ЛИШНИХ 0 · ИТОГ: ПОТЕРЬ НЕТ, дифф ПОЛНЫЙ`. +> +> 🔴 **ОТКРЫТО, ЖДЁТ «go» (главное сессии 6):** класс дефекта **«`albumartist` размножен от чужого +> файла в корне папки»** — найдено **7 папок** (`The Prodigy`→`DJ Feel`, `Банд Эрос`→`VIP77`, +> `Дискотека Авария`, `Жуки`, `Потап & Каменских`, `Король и Шут`, `Ленинград`). Детали и +> детектор-скрипт — в разделе **«🔷 СЕССИЯ 6»** в конце документа. +> +> **Формат `album` для папок `!Radio/*` ИЗМЕНЁН (сессия 5):** `Radio - <имя>` +> (`Radio - OOF`, `Radio - Старое`, `Radio - Df1`) — **вытесняет** прежние `OOF Radio` / +> `Старое Radio` / `RADIO 1 DRIVE`. Упоминания старого формата ниже по тексту — устарели. +> +> **Всё остальное о формате `album` в старых разделах ниже — историческое.** Хронология +> сессий 1-5 и все разобранные питфоллы — в разделах «🔷 СЕССИЯ N» в конце документа. +> **Свежие правила всегда в последнем разделе.** +> > ## 🔴 ПУТИ ПЕРЕЕХАЛИ (2026-09-27, вечер) — `~/milestones` БОЛЬШЕ НЕТ > > Alex: «какой блядь ~/milestones?! мы блядь работаем в `/Users/admin/Downloads/music-tags/`! @@ -2317,5 +2341,255 @@ python3 verify_diff_completeness.py # exit 0 = чисто не нужна, а в диффе строка есть. Пользователь обозначил принцип: **в диффе только то, что реально поменяется.** +--- + +## 🔷 СЕССИЯ 5 (2026-09-27): ЗАКРЫТИЕ ПУСТЫХ ПОЛЕЙ + РУЧНЫЕ ПРАВКИ `album`/`albumartist` + +### Задача Alex + +> «блядь поправь пустые!» +> «OOF Radio -> Radio - OOF / Старое Radio -> Radio - Старое / Df1 -> Radio - Df1 / +> Infected Mushroom - Other Track's -> Other Tracks» +> «!Rock&Folk/Rammstein — треки в корне тоже кстати Rammstein - misc сделай» +> «Benassi Bros/2003 Hypnotica Bonus — albumartist Various Artists исправь» → «ну ставь benassi bros» +> «!Pop, Club, Любэ/Dj Tiesto — там не Tiesto должен быть разве albumartist?» + +### 1️⃣ ЗАКРЫТЫ ВСЕ ПУСТЫЕ ПОЛЯ (0 пустых) + +| # | Файл | Поле | Было | Стало | +|---|---|---|---|---| +| 1 | `z Другое, Enigma, Ленинград/Красная плесень - На дискотеку сельскую.mp3` | `album` | пусто | `Сосни попса` | +| 2 | `z Другое, Enigma, Ленинград/Красная Плесень - Заебал мой кот.mp3` | `album` | пусто | `Сосни попса` | +| 3 | `!Pop, Club, Любэ/!Radio/t 2009-07 c6Or_NICK/10 Boom (Bimbo Jones Mix).mp3` | `artist` | мусор `ÿþd` (UTF-16-обрывок) | `Bimbo Jones` | + +Файлы №1-2 лежали **в корне папки-КАТЕГОРИИ** `z Другое, Enigma, Ленинград` (внутри — +подпапки `Красная Плесень`, `Ленинград`, `Сектор Газа`, `Enigma`, `Саундтреки` и др.). +Имя папки-категории в `album` — мусор; оба файла принадлежат альбому `Сосни попса` +(в дереве есть `Красная Плесень/Сосни попса/`, 16 файлов). + +**Итог: пустых `new_album` / `new_albumartist` / `new_artist` / `new_title` / `new_genre` = 0.** + +### 🔴 ПИТФОЛЛ: поиск мусора по точному значению промахивается + +Я искал битый `artist` по литералу `'ÿþd'` — **не нашёл**, файл проскочил. Значение реально +было **пустой строкой** после нормализации. Правильный фильтр — не по литералу, а **по +свойствам**: `if not s.strip() or not s.isprintable() or any(ord(c) < 32 for c in s)`. + +**Мораль:** искать мусор по списку известных значений = гарантированный пропуск. +Искать по предикату свойства. + +### 🔴 ЧЕСТНОСТЬ В ОТЧЁТЕ: я сказал «пустого `new_artist` нет» — это была неправда + +Первый ответ был «empty=1, сейчас назову» → затем «моя ошибка, значение есть, просто мусорное». +Alex отреагировал резко («шо??»). **Нельзя объявлять «0 пустых», не перепроверив все поля +по предикату.** Потеря доверия к отчёту дороже, чем задержка на проверку. + +### 2️⃣ РУЧНЫЕ ПРАВКИ `album` — 61 файл (команда Alex) + +| Папка | Файлов | Было | Стало | +|---|---:|---|---| +| `!Radio/y OOF` | 29 | `OOF Radio` | **`Radio - OOF`** | +| `!Radio/z Старое` (корень) | 16 | `Старое Radio` | **`Radio - Старое`** | +| `Df1` | 12 | `Df1` | **`Radio - Df1`** | +| `Infected Mushroom - Other Track's` | 2 | `Infected Mushroom - Other Track's` | **`Other Tracks`** | +| `!Rock&Folk/Rammstein` (корень) | 2 | `Rammstein` | **`Rammstein - misc`** | + +**Принцип формат-имени: `Radio - <имя>`** — вытесняет прежний суффиксный `OOF Radio` / +прежний префиксный `RADIO 1 DRIVE`. 🔴 Актуальный формат папок `!Radio/*` снизу — +`Radio - OOF`, `Radio - Старое`, `Radio - Df1` (не `OOF Radio`). + +**Не тронуто (осознанно — это НЕ имя папки, а реальные альбомы):** +- `Df1/My CD` (`Vino Vino Vinyl`, `Nachtschicht Vol 24 CD1`, `FR0NT0700510`…) — 4 файла +- `Rammstein/Rammstein - Kokain.mp3` — уже `Das Modell` +- 21 файл в `Other Track's` со своими альбомами (`Unreleased Tracks` 14, `Magnet`, `Voom…`) +- реальные альбомы внутри `y OOF` / `z Старое` (`Вспышка Сверхновой 2007 Club`, `Borat`, `Bucovina`…) + +> 🔴 **Питфолл точности:** «Infected Mushroom — Other Track's» переименовывалось **только там, +> где `new_album` буквально равен имени папки**. Слепой заменой по папке было бы испорчено +> 21 файл с настоящими альбомами. Правило: править по совпадению `new_album == имя_папки`, +> а не по предикату «файл лежит в папке X». + +### 3️⃣ `albumartist` Benassi Bros — 20 файлов + +| Папка | Файлов | Было | Стало | +|---|---:|---|---| +| `Benassi Bros/2003 Hypnotica Bonus` | 5 | `Various Artists` | **`Benassi Bros`** | +| `Benassi Bros/Re Sfaction` | 15 | `Various Artists` | **`Benassi Bros`** | + +Alex выбрал `Benassi Bros` (не `Benny Benassi`). Оба — **АЛЬБОМЫ с гостевыми фитами**, не сборники +(`Re Sfaction`: In-Grid, Sonique, Electric Six, Ann Lee, KMC Feat. Sandy, Gambafreaks). + +> ⏳ **Обнаружена несогласованность внутри группы `Benassi Bros/`:** часть папок +> `Benny Benassi` (`2003 Hypnotica`, `2004 Puphonia`, `2008 Rock N Rave`, `2003 MegaMix`, +> `2003 Satisfaction*`, `2003 Pres_the_Biz…`), часть `Benassi Bros` (`2005 Phobia` + эти две). +> Alex не подтвердил приведение к одному имени — **не трогать без указания**. + +### 4️⃣ `Dj Tiesto` — ВОПРОС ОТКРЫТ (ждёт ответа Alex) + +`!Pop, Club, Любэ/Dj Tiesto` — 14 файлов, все `new_albumartist = 'Various Artists'`. + +| Файлов | `new_album` | Состав | +|---:|---|---| +| 13 | `In Search of Sunrise 3` | компиляция-микс Tiesto: Nalin & Kane, Andain, Way Out West, Solar Stone, Choir… | +| 1 | `PoW - Team PoW - 2oo4` | `artist = Tiesto`, чужой сборник, в дереве альбома нет | + +**Предложено (НЕ применено):** `In Search of Sunrise 3` → `albumartist = Tiesto` +(DJ-микс серии, автор компиляции — диджей; `artist` каждого трека остаётся свой), +`PoW` — оставить `Various Artists`. **Ждём «go».** + +--- + +### 📊 АКТУАЛЬНЫЕ АРТЕФАКТЫ (конец сессии 5) + +``` +APPLY-PLAN.csv 7861 строка md5 87ab8406509fb99651762886645e35e8 +DIFF-new-vs-38.csv 4864 строки md5 6852b25fd36fe14e97eb7788cdfa844a +tag-fix-plan.csv не тронут (база 38-го, источник истины для диффа) +``` + +**Цепочка MD5 `APPLY-PLAN.csv` (сессия 5):** +``` +bb724716… начало сессии 5 (0 пустых, до ручных правок) +88e1059c… + 61 файл Radio-формат / Other Tracks / Rammstein - misc +87ab8406… ⬅ ТЕКУЩИЙ, + Benassi Bros albumartist (20 файлов) +``` + +**Цепочка MD5 `DIFF-new-vs-38.csv` (сессия 5):** +``` +0d7a31ca… начало сессии 5 +a0a52be9… + 61 файл Radio-формат / Other Tracks / Rammstein - misc +6852b25f… ⬅ ТЕКУЩИЙ, + Benassi Bros albumartist +``` + +**Проверка после каждой правки (оба скрипта, выход):** +``` +diff_new_vs_38.py: ВСЕГО правок 18558, assert OK: потерь нет +verify_diff_completeness.py: ПОТЕРЯНО 0 · ЛИШНИХ 0 + НЕТ-В-38 41=41 · ТОЛЬКО-В-38 85=85 + ИТОГ: ПОТЕРЬ НЕТ, дифф ПОЛНЫЙ +``` + +> 🔴 **Рабочий цикл правки CSV (установлен, повторять):** +> 1. `shutil.copy2(p, p + '.bak-before-<тема>-' + ts)` — бэкап **до** правки; +> 2. правка скриптом из **файла** (`write_file` в `/tmp`), не инлайном; +> 3. `python3 diff_new_vs_38.py > /tmp/log 2>&1` (**не через пайп**); +> 4. `python3 verify_diff_completeness.py` → `exit 0`; +> 5. отчёт Alex: «Готово, проверяй» + что изменено + **MD5 до/после**. + +Бэкапы сессии 5: `.bak-before-finalempty-20260927-134959`, `.bak-before-artistempty-20260927-135034`, +`.bak-before-radiofix-20260927-140315`, `.bak-before-benassi-20260927-140435`. + +--- + +### ⏳ НЕ ЗАКРЫТО (актуализировано, конец сессии 6) + +| # | Вопрос | Статус | +|---|---|---| +| 1 | 🔴 **`albumartist`-перехват чужим файлом — 7 папок** (`The Prodigy`→`The Prodigy`, `Банд Эрос`→`Банд'Эрос`, `Дискотека Авария`→`Дискотека Авария`, `Жуки`→`Жуки`, `Потап & Каменских`→`Потап и Настя Каменских`, `Король и Шут`→`Король и Шут`, `Ленинград` корень → `Ленинград` ×3 + `Сергей Шнуров` ×1) | 🔴 план предложен, **ждёт «go» Alex** | +| 2 | `Benassi Bros/` — свести `Benny Benassi` и `Benassi Bros` к одному имени? | ⏳ Alex не подтвердил | +| 3 | `track = '238'` у 56 файлов (`Красная Плесень/Золушка`) | ⏳ верное значение неизвестно | +| 4 | `track` пуст у 1438, из них **9 с непустым `cur_track`** → тег `TRCK` будет УДАЛЁН | ⏳ решение Alex | +| 5 | `changed = 'no'` у 213 строк (50 файлов / 61 строка в диффе) | ⏳ уточнить трактовку | +| 6 | Боевой прогон `--apply --verify` | 🔴 не запущен | +| 7 | Контейнер `snapcur` на NAS (только чтение, `:ro`) | ⏳ не убран — Alex не подтвердил | +| 8 | Глазами пройти `field=album` (305 строк) в диффе | ⏳ следующий шаг по указанию Alex | +| 9 | `ДДТ/2008 L'Echoppe` — `ДДТ` (2 файла `ДДТ`) vs 10 файлов `Юрий Шевчук & Константин Казански` | ✅ вердикт: **НЕ порча**, дуэт-альбом, не трогать | + +--- + +# 🔷 СЕССИЯ 6 (2026-09-27, вечер) — `albumartist`-ПЕРЕХВАТ ЧУЖИМ ФАЙЛОМ ПАПКИ + +Триггер: Alex по очереди указывал корневые папки — «`Dj Tiesto` — там не Tiesto должен быть +разве albumartist?», «`Банд Эрос` — че за VIP77?», «`The Prodigy` — почему dj feel?». + +## 🔴 ГЛАВНАЯ НАХОДКА: класс дефекта «`albumartist` размножен от чужого файла в корне» + +**Механизм.** Правило «у корневых файлов папки `albumartist` = имя артиста папки» брало значение +**от первого попавшегося файла в корне**. Если в корень папки артиста затесался ОДИН файл чужого +исполнителя (из сборника), его `albumartist` **расходился на все файлы** папки. + +**Проявления (подтверждено фактом, найдено Alex):** +``` +The Prodigy DJ Feel ← из трека DJ Feel (Trancemission Vol.9) — 3 файла Prodigy испорчены +Банд Эрос VIP77 ← из трека VIP77 (Russian R'n'B vol.1) — 6 файлов испорчены +``` + +> ⚠️ **Это не гипотеза — порча видна в `cur_albumartist`**: у `Prodigy/Hotride [EP].mp3` +> и `Prodigy/Record Super Chart (15.08.09).mp3` было `cur_albumartist = 'The Prodigy'`, +> а план ставил `DJ Feel` — **прямое затирание верного значения**. + +## 🔧 ДЕТЕКТОР КЛАССА (обязателен после каждой пакетной правки `albumartist`) + +📄 **Скрипт сохранён в vault: `personal/tech/scripts/find_aa_hijack.py`** +(копия также в `/tmp/find_aa_hijack.py` — `/tmp` очищается!). + +Алгоритм: для каждой папки, где `new_albumartist` **один на всю +папку**, сравнить его с `new_artist` файлов. Признак порчи: + +``` +new_albumartist не совпадает с top-артистом папки + И не совпадает с именем папки + И совпадает с МИНОРИТАРНЫМ артистом папки + → ПОДОЗРЕВАЕМЫЙ +``` + +**Результат прогона: 11 подозреваемых папок.** Разбор: + +| Папка | `albumartist` (у скольких) | Должен быть (у скольких) | Вердикт | +|---|---|---|---| +| `The Prodigy` | `DJ Feel` (1) | `The Prodigy` (3) | 🔴 порча | +| `Банд Эрос` | `VIP77` (1) | `Банд'Эрос` (6) | 🔴 порча | +| `Дискотека Авария` | `ДА и Меньшиков` (1) | `Дискотека Авария` (6) | 🔴 порча | +| `Жуки` | `Виктор Жуков` (1) | `Жуки` (10) | 🔴 порча | +| `Потап & Каменских` | `Мика Ньютон` (1) | `Потап и Настя Каменских` (7) | 🔴 порча | +| `Король и Шут` | `КняZz` (1) | `Король и Шут` (3) | 🔴 порча | +| `Ленинград` (корень) | `Бумер` (1) | `Ленинград` (3) + `Сергей Шнуров` (1) | 🔴 порча | +| `ДДТ/2008 L'Echoppe` | `ДДТ` (2) | — | ✅ НЕ порча: `ДДТ` — верный albumartist, 10 файлов = дуэт Шевчук+Казански | +| `Benassi Bros/Re Sfaction` | `Benassi Bros` (3) | — | ✅ решение Alex (сессия 5) | +| `IM/S Is Here (Yahel Mix)` | `Infected Mushroom` (1) | — | ✅ решение Alex (сессия 6) | +| `Детские/Сборка/L` | `Детские песни` (4) | — | ✅ сборник, верно | + +**СЕССИЯ 6 ЗАКОНЧИЛАСЬ НА ЭТОМ СПИСКЕ — 7 папок ожидают «go» Alex.** + +## ✅ ПРИМЕНЕНО В СЕССИИ 6 (19 файлов) + +| Папка | Файлов | Было | Стало | Основание | +|---|---:|---|---|---| +| `Dj Tiesto` | 14 | `Various Artists` | **`Tiesto`** | Alex: «тоже» (13 `In Search of Sunrise 3` + 1 `PoW`) | +| `IM/Life System … Electro Panic … -2016` | 2 | `Various Artists` | **`Infected Mushroom`** | Alex — папка внутри IM | +| `IM/Unreleased/Remixes, Mixes/(2001) … S Is Here (Yahel Mix)` | 3 | `Various Artists` | **`Infected Mushroom`** | Alex: «то же» | + +``` +MD5 до: 6852b25fd36fe14e97eb7788cdfa844a (DIFF) / 87ab8406509fb99651762886645e35e8 (PLAN) +MD5 после: 3ebc67c9d6abe5fce2d927fcc7726808 (DIFF) / c8d43cc56d82cd35a67a0efe8173cd4b (PLAN) +``` +Бэкап: `.bak-before-aa-tiesto-im-20260927-140556`. +Проверка: `ВСЕГО правок 18558 · assert OK · ПОТЕРЯНО 0 · ЛИШНИХ 0 · дифф ПОЛНЫЙ`. + +## 📋 КОНТРОЛЬНАЯ ПРОВЕРКА «ЗАТИРАНИЙ» `cur_albumartist` (765 строк) + +Скрипт `/tmp/check_aa_overwrite.py`: `cur_albumartist` непуст **и** `new_albumartist != cur_albumartist` +→ **765 строк**. ⚠️ **Почти все — ЗАКОННЫЕ**, это нормализация: + +| Класс | Пример | Объём | +|---|---|---| +| CP1251-мойибаке → кириллица | `Èëüÿ ×åðò` → `Илья Чёрт` | ~основная масса | +| Транслит → кириллица | `Vladimir Vysockij` → `Владимир Высоцкий`, `Nautilus Pompilius` → `Наутилус Помпилиус` | — | +| Регистр/пунктуация | `ДИСКОТЕКА АВАРИЯ` → `Дискотека Авария`, `Ногу свело!` → `Ногу Свело!` | — | +| Смена артиста по решению | `OverClocked ReMix` → `Various Artists` (Final Fantasy VI) | 64 | + +> 🔴 **Урок: счётчик «765 затираний» — ШУМ, а не список дефектов.** Реальные порчи — +> только те, где значение взято **от чужого файла той же папки** (детектор из раздела выше, +> нашёл 11 подозреваемых из 765). Не рапортовать число 765 как «проблему». + +## 🔴 ПРАВИЛО, ВЫВЕДЕННОЕ В СЕССИИ 6 + +`albumartist` трека = **его собственный артист**, если альбом не сборник. +Значение `albumartist` **НИКОГДА** не размножается на файлы других исполнителей внутри папки. +Если в корне папки артиста лежат треки разных исполнителей — **у каждого свой `albumartist`** +(например, `КняZz` у 1 файла, `Король и Шут` у 3; `Бумер` у 1, `Ленинград` у 3, +`Сергей Шнуров` у 1). + + diff --git a/personal/tech/music-tags-repair-truenas.md b/personal/tech/music-tags-repair-truenas.md index fa4dd478..3195ed5b 100755 --- a/personal/tech/music-tags-repair-truenas.md +++ b/personal/tech/music-tags-repair-truenas.md @@ -23,19 +23,24 @@ related: # Music Tags Repair — TrueNAS (7906 → 7905 файлов) -> ### 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27) — ЧИТАЙ ЭТО ПЕРВЫМ +> ## 🔴 АКТУАЛЬНОЕ СОСТОЯНИЕ (2026-09-27, конец сессии 6) — ЧИТАЙ ЭТО ПЕРВЫМ > > **Этот файл — исторический лог волны 38 (завершена). Актуальная работа идёт здесь:** > > | Документ | О чём | > |---|---| > | **[[personal/plans/tagwriter-container]]** | ⭐ скрипт-писатель `apply_tags_v2.py` (566 стр): логика полей, ID3v1-снос, тесты, 18 питфоллов, запуск | -> | **[[personal/plans/music-artist-albumartist-repair]]** | правила формирования плана, сборка `APPLY-PLAN-FINAL2.csv` | +> | **[[personal/plans/music-artist-albumartist-repair]]** | ⭐ **правила формирования плана, `APPLY-PLAN.csv`, дифф `DIFF-new-vs-38.csv`, сессии 1-6** | > | **[[personal/tech/music-broken-toload]]** | 29 битых/DRM файлов на перезагрузку | > -> **Состояние:** скрипт готов и протестирован, **боевой прогон НЕ запущен — ждёт «go» от Alex.** -> Боевой вход: `~/Downloads/music-tags/APPLY-PLAN-FINAL2.csv` (7861 строка). -> Скрипты: `~/Automation/music-tags/`. +> **Состояние:** план `APPLY-PLAN.csv` доведён до **0 пустых полей** и проверенного диффа +> (`ПОТЕРЯНО 0 · ЛИШНИХ 0`). **Боевой прогон НЕ запущен — ждёт «go» от Alex.** +> 🔴 Открыт класс дефекта **«`albumartist` размножен от чужого файла в корне папки»** (7 папок) — +> см. сессию 6 в [[personal/plans/music-artist-albumartist-repair]]. +> +> Боевой вход: `/Users/admin/Downloads/music-tags/APPLY-PLAN.csv` (**7861 строка**, +> md5 `c8d43cc56d82cd35a67a0efe8173cd4b`). Рабочая папка — **`~/Downloads/music-tags/`** +> (`~/milestones/` больше НЕ существует). Скрипты: `~/Automation/music-tags/`. > > ⚠️ Волна 38 (TIT2/TPE1/TALB/TRCK, 7041 файл) — **завершена**, описывается ниже как история. > Её дефекты (`apply_tags.py` испортил 16 не-MP3, `fix40.py` убил 40 MP3) разобраны в diff --git a/personal/tech/scripts/find_aa_hijack.py b/personal/tech/scripts/find_aa_hijack.py new file mode 100644 index 00000000..4750863a --- /dev/null +++ b/personal/tech/scripts/find_aa_hijack.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Детектор класса «albumartist размножен от чужого файла в корне папки». + +Сессия 6, 2026-09-27. Нашёл 7 испорченных папок из 11 подозреваемых +(The Prodigy->DJ Feel, Банд Эрос->VIP77, Дискотека Авария, Жуки, +Потап & Каменских, Король и Шут, Ленинград). + +ПРИЗНАК ПОРЧИ: + для папки, где new_albumartist ОДИН на всю папку: + он не совпадает с top-артистом папки + И не совпадает с именем папки + И совпадает с МИНОРИТАРНЫМ артистом папки + -> ПОДОЗРЕВАЕМЫЙ + +ЗАПУСК: python3 find_aa_hijack.py +Не меняет файлы. Только печатает. +""" +import csv +import collections +import os + +P = '/Users/admin/Downloads/music-tags/APPLY-PLAN.csv' +rows = list(csv.DictReader(open(P, encoding='utf-8-sig', newline=''))) + + +def s(v): + return (v or '').strip() + + +by_folder = collections.defaultdict(list) +for r in rows: + by_folder[r['folder']].append(r) + +suspects = [] +for folder, rs in sorted(by_folder.items()): + aa_vals = collections.Counter(s(r['new_albumartist']) for r in rs if s(r['new_albumartist'])) + if len(aa_vals) != 1: + continue # смешанные папки — отдельный случай + aa = next(iter(aa_vals)) + art = collections.Counter(s(r['new_artist']) for r in rs if s(r['new_artist'])) + if not art: + continue + top_artist, top_n = art.most_common(1)[0] + folder_name = os.path.basename(folder) + if aa != top_artist and aa != folder_name and aa in art: + minority = art[aa] + if minority < top_n: + suspects.append((folder, aa, top_artist, top_n, minority, len(rs))) + +print(f"ПОДОЗРЕВАЕМЫХ ПАПОК: {len(suspects)}") +print() +for folder, aa, top, tn, mn, tot in suspects: + print(f" albumartist={aa!r} (у {mn} из {tot})") + print(f" должен быть {top!r} (у {tn})") + print(f" {folder}")