Files
obsidian-vault/family/projects/media-toolbox-kraken.md
T
2026-05-30 10:03:30 +00:00

885 lines
61 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
created: '2026-05-13'
updated: '2026-05-17'
status: in-progress
tags:
- kraken
- media
- jellyfin
- transmission
- swift
- project
---
# Media Toolbox на Кракене
> Цель: полноценный медиасервер на Кракене (RPi 5) — перенос библиотеки с HTPC, автосортировка через media-pipeline (Swift), Jellyfin, торренты.
---
## Архитектура (целевая)
```
HTPC (Bazzite 192.168.1.86)
└── rsync → Kraken HDD /srv/.../media/
Kraken (RPi 5, 192.168.1.14)
├── /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/media/
│ ├── movies/ ← фильмы (127 папок)
│ ├── cartoons/ ← мультфильмы (126 папок)
│ ├── series/ ← сериалы (45 папок)
│ ├── documentaries/ ← документальные (35 папок)
│ └── downloads/complete ← Transmission
├── Docker: Transmission (128 торрентов, порт 9091)
├── Jellyfin native (порт 8096)
└── Docker: media-pipeline:kraken (Swift CLI, arm64)
media-pipeline (~/Developer/media_files_db, ветка kraken-agent)
├── scan → resolve → apply (sync)
├── NFO-файлы для Jellyfin/Kodi
├── Transmission RPC для обновления путей
└── unresolved.json — реестр нераспознанных
```
---
## Текущий статус (2026-05-14, сессия 5)
### Sync результаты (последний запуск — dry_run=false)
| Категория | Resolved | Unresolved | Статус |
|-----------|----------|------------|--------|
| movies | ✅ | 2 (generation.p, Black Mirror dup) | NFO пишутся |
| series | ✅ | 1 (Black Mirror — homonym) | NFO пишутся |
| cartoons | ✅ | 6 | в работе |
| documentaries | ✅ | 6 | Tier 4 запускается |
| **Итого** | **401** | **15** | sync запущен |
> `dry_run` выключен (2026-05-14), запущен `sync` — NFO пишутся на диск.
### Что работает
- ✅ Docker образ `media-pipeline:kraken` задеплоен (ветка `kraken-agent`)
- ✅ Фильмы: 127/127 NFO
- ✅ Сериалы: все resolved (Black Mirror, Ну погоди!, и др. — через resolve-manual)
- ✅ Кеш API-ответов: `/config/cache`, 30-дневный TTL
- ✅ Transmission download-complete hook установлен
-`resolve-manual` работает для movie/series/single-file
### Открытые задачи
#### 1. 🔴 [ОБЯЗАТЕЛЬНО К ИСПОЛНЕНИЮ] Довести media-pipeline до 0 unresolved без resolve-manual
**Принцип:** `resolve-manual` — не решение. Тул должен резолвить всё автоматически.
**Текущее состояние:** 32 unresolved после sync (dry_run=false). Разбивка:
**Группа A — старые source-папки (scan подхватывает оригиналы после apply):**
`apply` создаёт canonical папки с симлинками, но оригинальные папки (`flow.aka.straume...`, `bezumnyj_maks_3...` и т.д.) остаются и при следующем scan снова попадают в unresolved. Это архитектурный баг.
- **Нужно:** scanner должен пропускать папки, чьи файлы уже используются как цели симлинков в canonical структуре (или: хранить список resolved source-paths в unresolved.json и пропускать их при scan).
**Группа B — мультики (tie/ambiguity):**
`козленок`, `тигренок`, `нехочуха.xvid`, `чебурашка.m4v`, `котенок по имени гав.m4v`, `25е - первый день`, `рики тики тави`, `приключения фунтика`, `незнайка`, `миграция`
- Причины: KP возвращает несколько кандидатов с одинаковым весом (tie), или IMDB/web search не находит по транслитерации.
- **Нужно:** улучшить tie-breaking (год из папки/файла, popularity score из KP), расширить кириллические варианты запроса.
**Группа C — документалки:**
`bbc. the real jungle book bear 2012`, `bbc. секреты собак`, `penguin meet the family (2020)`, `бегемоты - жизнь в воде`, `гиганты моря 2011`, `таинственная жизнь собак (hdtvrip 720p, 2013)`, `тайная жизнь собак (2016г)`, `prehistoric.planet.s02.1080p`, `жизнь птиц 1998`, `пингвины. шпион в толпе`, `супер белки (2018)`, `шпион в снегах 2018`
- Tier 4 (IMDb scrape) запускается, но candidates = 0 — title matching слишком строгий для BBC-документалок.
- **Нужно:** для documentaries/ добавить relaxed matching (убирать "bbc.", год, технические теги из запроса перед поиском).
#### 7. 🟡 ROM-инфраструктура: qBittorrent + Rutracker + Эмуляторы
**Цель:** Eagle/Кракен могут самостоятельно искать и качать игры/ROM-ы.
**qBittorrent на HTPC:**
- [ ] Установить qBittorrent (Flatpak: `com.qbittorrent.qBittorrent`)
- [ ] Настроить Web UI (порт 8080, доступ с Mac/Кракена)
- [ ] Добавить Rutracker в список трекеров
**Switch эмуляция:**
- [ ] Установить EmuDeck на HTPC
- [ ] Достать `prod.keys` + `title.keys` (Switch firmware keys) — путь: `~/.config/Ryujinx/system/`
- Источник: rutracker (поиск "nintendo switch keys prod.keys") или community
- [ ] Скачать DKC: Tropical Freeze (.nsp или .xci) через qBittorrent
**Rutracker доступ для агентов:**
- [ ] Настроить парсинг/API-доступ к Rutracker (учётка + cookie или jackett)
- [ ] Научить Eagle/Кракена инициировать скачку через qBittorrent Web UI API
- [ ] Документировать workflow в этом файле
**Wii альтернатива (проще чем Switch):**
- [ ] Dolphin ставится через EmuDeck без BIOS
- [ ] DKC Returns (Wii) скачать как .wbfs
- [ ] Маппинг геймпада (нет Wiimote)
#### 5. 🔴 Торренты: Transmission не видит 271/293 файлов
**Проблема:** Transmission ищет файл по `downloadDir/torrent_name`. После pipeline `apply` файлы переименованы в canonical форму (`Furiosa - A Mad Max Saga (2024)/Furiosa - A Mad Max Saga (2024).mkv`), а торрент ожидает оригинальное имя (`Furiosa.A.Mad.Max.Saga.2024.1080p....mkv`) прямо в `/media/movies/`. Совпадений нет → `haveValid=0` у 271 торрентов.
**22 торрента с файлами** — только те что в `/downloads/complete` (свежескачанные, pipeline ещё не обработал).
**Нужно:** реализовать `torrent-fix` subcommand: для каждого торрента искать файл по имени (case-insensitive fuzzy) в source директориях `/media/movies/`, `/media/cartoons/` и т.д., и вызывать `torrent-set-location` RPC.
#### 6. 🔴 Мультфильмы в library/movies (классификационный баг)
**Проблема:** `library/cartoons = 0 items`, `library/movies = 202 items` из которых **106 — мультфильмы**. `MediaFinalizer.OutputConfig` содержал только `moviesDirectories` + `seriesDirectories``buildMovieActions` всегда писал в movies.
**Фикс (задеплоен):**
1. `Config.OutputConfig` + `MediaFinalizer.OutputConfig` расширены: добавлены `cartoonsDirectories` + `documentariesDirectories`.
2. `movieOutputDir(forGenres:)` — роутинг по resolved жанрам (Animation→cartoons, Documentary→documentaries). Source path не используется.
3. `ProviderSearchStrategy.facts()` теперь эмитит `NFOMetadataFact` с жанрами провайдера (раньше genres терялись, в граф не писались).
**После деплоя:** нужно переместить существующие 106 неправильных entries из `library/movies` в `library/cartoons` (отдельный скрипт). Новые sync'и будут класть правильно.
- Настроить библиотеки в Jellyfin (http://192.168.1.14:8096)
- NFO файлы готовы (716 шт.), Jellyfin должен их подхватить
#### 4. 🟢 Hermes cron на Кракене
- Периодический запуск sync (новые загрузки через Transmission)
---
### Архитектурные принципы (важно)
| Принцип | Описание |
|---------|----------|
| **NFO как evidence** | LocalEvidenceStrategy читает NFO только если они пришли с контентом (торрент). Pipeline-generated NFO не должны читаться как входные данные — это circular dependency. Сейчас защита через `.media-pipeline-canonical` маркер (FileScanner пропускает canonical dirs). Долгосрочно: добавить `<generator>media-pipeline</generator>` тег в generated NFO и фильтровать в LocalEvidenceStrategy. |
| **Классификация по жанрам провайдера** | Роутинг в cartoons/documentaries — **только по жанрам из API** (TMDb/KP), через `MediaSearchCandidate.genres → NFOMetadataFact → movieOutputDir(forGenres:)`. Source path **никогда не используется** для категоризации. |
| **resolve-manual — не решение** | `unresolved.json` — очередь для автоматического резолва. resolve-manual только для экстренных случаев. |
| Решение | Описание |
|---------|----------|
| **Unresolved = last resort** | `unresolved.json` — НЕ мусорка. Это очередь для AI-агента (Tier 5), который по крону подбирает нераспознанные и разрешает их через LLM + web search. Для данной библиотеки цель: Tier 0-4 (TMDb → KP → IMDb scrape → web search) должны закрывать всё — в `unresolved.json` не должно попадать ни одного item'а из текущей библиотеки. |
| **Web search enabled** | `providers.webSearch.enabled=true` в config.json. IMDb scrape как Tier 4 для всего, что не нашли TMDb/KP. Tier 4 должен срабатывать до того, как item уходит в unresolved.json |
| **Документалки приоритет KP** | BBC/Discovery документалки плохо индексированы в TMDb. KP часто знает их под русскими названиями. Стратегия: сначала KP с русским названием из папки, потом IMDb |
| **resolve-manual** | Только для экстренных случаев, не как регулярный процесс |
| **Single-file NFO** | Для `.mkv`/`.m4v` файлов NFO создаётся рядом (`film.nfo`), не внутри |
---
## Исправленные баги
### Баг 1: Двойной вызов binary (КРИТИЧЕСКИЙ)
`docker run ... media-pipeline:kraken media-pipeline sync` → бинарь получал `media-pipeline` как аргумент.
**Фикс:** `ENTRYPOINT ["media-pipeline"]`
### Баг 2: TitleSimilarity — симметричный Jaccard
`encanto_2021_bdrip_by_dalemake` vs `Encanto`: lengthRatio=0.2 → rejected.
**Фикс:** Asymmetric scoring — candidate-coverage Jaccard когда query длиннее. Коммит `2704ce3`
### Баг 3: maxCallsPerRun = 8
8 API-вызовов → 192/200 items никогда не искались.
**Фикс:** 8 → 500. Коммит `2704ce3`
### Баг 4: Series-гипотезы для одиночных файлов
Одиночные `.avi`/`.mkv` без S/E маркеров получали series-гипотезу → TMDb искал по `/search/tv`.
**Фикс:** `videos.count < 2 && !hasAnyEpisodeMarkers → return nil`. Коммит `17c2ddc`
### Баг 5: HTTP кеш не персистился
`cacheDirectory = /usr/local/bin/cache` — удалялся при `docker run --rm`.
**Фикс:** Autodetect `/config` mount → `/config/cache`. Коммит `5a1053c`
### Баг 7: Tier 4 (web search) не запускался для контента не в TMDb/KP
**Проблема:** `MediaPlanner` гейтил Tier 4 на `hasLiveUnresolved()` — но когда TMDb/KP возвращают 0 кандидатов, арбитр фаерит `provider-not-found`, который прунит все гипотезы. После этого `hasLiveUnresolved=false` → Tier 4 никогда не запускался.
**Фикс:**
- `MediaPlanner`: Tier 4/5 теперь гейтятся на `!hasWinner` (нет `.winning` гипотезы), а не `hasLiveUnresolved`
- `ProviderSearchStrategy`: добавлен флаг `includePrunedHypotheses` — когда `true`, поиск идёт и по `.pruned` ветками
- `WebSearchStrategy.webSearchDefaults`: `includePrunedHypotheses=true`
**Коммит:** `115f757`
### Баг 8: KP API URL смена домена (301 redirect)
`api.kinopoisk.dev` переехал на `api.poiskkino.dev`. Swift `URLSession` при редиректе теряет `X-API-KEY` заголовок → KP возвращает `{"docs":[]}` → кешируется как валидный пустой ответ → советские мультики и BBC не резолвились неделями.
**Фикс:** (1) обновлён URL в `KinopoiskAPI.swift`; (2) `HTTPClient.fetch` получил `shouldCache: ((Data) -> Bool)?` — KP не кеширует ответы с пустым `docs`.
### Баг 9: Leet-speak в именах файлов
`ni4ego_horoshego`, `o_4em_govoryat_muzh4iny` — цифра `4` = `ч` в русском warez-транслите. Стандартная транслитерация не покрывает.
**Фикс:** `String.decodeLeetSpeak()` в `StringExtensions.swift` + дополнительный variant в `NameTokenization.variants`.
### Баг 10: Год `2023г` не парсился как год
`Миграция. 2023г.mkv` — кириллическая `г` (год) является word char, поэтому `\b` после `2023` не срабатывает → `qYear=nil` → TMDb ищет без фильтра года → 0 результатов.
**Фикс:** В `TitleParser.cleanupTitleAndYear` добавлен паттерн `(19|20)\d\d)г\b` до стандартного `\b` regex. Коммит `770e51e`.
### Баг 11: Cyr→Lat query variant — мусорные запросы
`NameTokenization` генерировал `transliteratedToLatin` variant для кириллических имён → `Миграция``Migratsiya` → TMDb/KP не знают транслит, 0 результатов, только занимают cache-слоты. Оригинальный Go-sync делал только Latin→Cyrillic.
**Фикс:** Убран `transliteratedToLatin` из query variants в `NameTokenization.swift`. Коммит `10d64da`.
### Баг 12: TitleParser `_year_` — `\b` не срабатывает на underscore-границах
**Проблема:** `bezumnyj_maks_3_pod_kupolom_groma_1985_[torrents.ru]` — год `1985` окружён `_`, а `_` является `\w` в Swift regex. Поэтому `\b1985\b` не срабатывает → `year=nil`, `1985` остаётся в title → TMDb запрос содержит `1985` в строке вместо `year=` параметра → 0 результатов.
**Следствие:** заголовок `безумный макс 3 под куполом грома 1985` → TokenParser видит `1985` как токен → вариант обрезается по digit-граничному паттерну → появляется мусорный вариант `безумный мак` (truncated).
**Фикс:** в `TitleParser.cleanupTitleAndYear` заменён `\b(\d{4})\b` на `(?<![0-9])((?:19|20)\d\d)(?![0-9])` — игнорирует `_` как boundary, проверяет только цифровые границы.
**Тест:** `NameTokenizationTests.yearInUnderscores` + `titleParserYearInUnderscores` — оба GREEN.
**Коммит:** `61be066` (заодно с NFO sidecar fix, фактически в коде с TitleParser.swift line 39).
### Баг 13: `OutputConfig` — `cartoons`/`documentaries` не optional в JSON
**Проблема:** Старые config-файлы и тест-JSON без новых полей бросали `keyNotFound` при декодировании.
**Фикс:** Добавлен `init(from: Decoder)` с `decodeIfPresent` + дефолт `[]`. Backward compatible. Коммит `2db0f81`.
### Баг 14: `xvid`/`divx` не в списке quality markers
**Проблема:** `Nehochuha.XVid.DVDRip``xvid` не стрипался → попадал в title tokens → мусорный запрос.
**Фикс:** Добавлены `xvid|divx` в регекс quality markers в `TitleParser.cleanupTitleAndYear`. Коммит `2db0f81`.
### Баг 15: TitleParser — год как название фильма (1984, 2001)
**Проблема:** `TitleParser` жадно вырезал первый `(19|20)\d\d` — для фильмов `1984`, `2001 Odyssey` оставлял пустой title и ломал TMDb запрос.
**Фикс:** Safety guard: стрип года только если перед ним ≥2 буквенных символа. Применён в обоих year-extraction блоках.
**Тесты:** `TitleParserExhaustiveTests.nineteenEightyFour`, `nineteenEightyFourBDRip`, `spaceOdyssey` — GREEN. Коммит `2db0f81`.
---
### Инструмент: QuerySnapshotGenerator
При изменениях в `TitleParser`/`NameTokenization`/`QueryBuilder` снапшоты в `QueryBuilderFixtureParameterizedTests.swift` устаревают.
**Регенерация:** `swift run QuerySnapshotGenerator` из корня репо.
НЕ использовать `SNAPSHOT_TESTING_RECORD=all` — InlineSnapshotTesting 1.18.7 крашит при 364 concurrent writes (SIGSEGV).
---
### Баг 6: resolve-manual NFO path для single-файлов
`resolve-manual` писал NFO внутрь файла вместо рядом.
**Фикс:** Detect `isDirectory`, для файлов → `deletingPathExtension().appendingPathExtension("nfo")`. Коммит `c0fdc59`
---
## Конфиг на Кракене
```
/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/media-pipeline/
├── config.json ← TMDb/KP ключи, Transmission RPC, directories
├── unresolved.json ← реестр нераспознанных (создаётся автоматически)
├── cache/ ← HTTP кеш 30 дней
└── on-download-complete.sh
```
**Запуск sync:**
```bash
docker run --rm \
-v /srv/.../media:/media \
-v /srv/.../docker/media-pipeline:/config \
media-pipeline:kraken \
sync --config /config/config.json
```
**Force-resolve одного item:**
```bash
docker run --rm ... media-pipeline:kraken \
resolve-manual --config /config/config.json \
--media-path '/media/cartoons/bremenskie.muzykanty' \
--tmdb 14327
```
---
## Технические детали
| | |
|--|--|
| HTPC | `bazzite@192.168.1.86`, Data: `/run/media/bazzite/Data/`, WoL: `B0:6E:BF:60:6D:64` |
| Кракен | `kraken@192.168.1.14`, HDD: `/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/` |
| Jellyfin | http://192.168.1.14:8096 |
| Transmission | http://192.168.1.14:9091 |
| Исходники | `~/Developer/media_files_db/`, ветка `kraken-agent` |
| Docker образ | `media-pipeline:kraken` (arm64, Colima 24GB) |
| kraken | `ssh kraken` → LAN (`192.168.1.14`) или VPN (`10.99.1.2`) через DNS-резолв |
| docker deploy | `docker save img -o /tmp/img.tar && scp /tmp/img.tar kraken:/tmp/ && ssh kraken "docker load -i /tmp/img.tar && rm /tmp/img.tar"``docker save \| ssh` зависает через WireGuard tunnel |
| Transmission RPC | Токен: `curl -D /tmp/hdrs ... > /dev/null`, затем `grep X-Transmission-Session-Id /tmp/hdrs \| awk '{print $2}' \| tr -d '\r\n'`. Payload с base64 торрента — писать в файл (`-d @file`), не аргументом (иначе "Argument list too long") |
---
## Источник торрентов (важно)
Торренты на Кракене (128 шт.) импортированы из **Windows Transmission-daemon** на HTPC:
```
/run/media/bazzite/Windows/windows/serviceprofiles/localservice/appdata/local/transmission-daemon/
├── torrents/ ← 210 .torrent файлов
└── resume/ ← 214 resume (пути, статус)
```
Валидация resume (2026-05-14): **185/214 файлов есть на HTPC**. 29 missing:
- ~15 медиафайлов с утраченного диска `X:/Movies/` — потеряны безвозвратно
- ~14 игр/архивов из `Downloads/Unsorted/` — не медиа, не важно
Отдельно: **uTorrent** (`/run/media/bazzite/Windows/program files/utorrent/`) — 131 .torrent файл, resume.dat с 18 записями. Из них только 6 хешей совпадают с Кракеном.
`Karnavalnaya.noch.1956.BDRip.AVC.mkv` — есть в uTorrent и в Transmission на Кракене (`downloaded=0`). Файла нет нигде — **никогда не скачивался**.
## Router script: Sonarr/Radarr → правильная библиотека Jellyfin
**Задача:** после импорта Sonarr/Radarr создать symlink в нужную папку Jellyfin на основе жанра/рейтинга. Sonarr/Radarr кладут файлы в `movies-radarr/` и `series-sonarr/` — Jellyfin видит только эти папки. Роутер создаёт symlinks в тематические библиотеки чтобы Jellyfin показывал правильную структуру.
### Логика маршрутизации
```
Входные папки:
/media/movies-radarr/ → фильмы
/media/series-sonarr/ → сериалы
Для каждого тайтла читаем NFO (или TMDb API):
- genres[]
- content_rating (R / 18+ / TV-MA / PG / etc.)
- type (movie / series)
Алгоритм (приоритет сверху вниз):
IF documentary IN genres:
movie → /media/documentaries/
series → /media/documentaries-series/
ELIF animation IN genres AND rating NOT IN [R, 18+, TV-MA]:
movie → /media/cartoons/
series → /media/cartoons-series/
ELSE (default):
movie → /media/movies/
series → /media/series/
```
Movies и Series — финальный fallback, если не попало в другие категории.
### Что делает скрипт
1. Сканирует `movies-radarr/` и `series-sonarr/`
2. Для каждого тайтла читает `movie.nfo` / `tvshow.nfo` → жанры + рейтинг
3. Если NFO нет → запрос TMDb API по имени папки
4. Определяет целевую папку по алгоритму выше
5. Создаёт symlink: `ln -s /media/movies-radarr/X /media/movies/X`
6. Копирует NFO + постер рядом с symlink (Jellyfin не видит метадату через symlink)
7. Тригерит Jellyfin rescan нужной библиотеки
### Запуск
- **При добавлении**: вызывается из Sonarr/Radarr → Settings → Connect → Custom Script
- **Полный пересчёт**: `router.py --rescan-all`
### Что НЕ делает
- Не трогает файлы в `movies-radarr/` и `series-sonarr/`
- Не удаляет старые symlinks без `--cleanup`
- Не переименовывает торренты
### Jellyfin библиотеки (целевые)
| Библиотека | Тип | Пути |
|---|---|---|
| Movies | movies | `/media/movies` + `/media/movies-radarr` |
| Series | tvshows | `/media/series` + `/media/series-sonarr` |
| Cartoons | movies | `/media/cartoons` |
| Cartoon Series | tvshows | `/media/cartoons-series` |
| Documentaries | movies | `/media/documentaries` |
| Documentary Series | tvshows | `/media/documentaries-series` |
Порядок в Jellyfin UI: Movies → Series → Cartoons → Cartoon Series → Documentaries → Documentary Series → Music
---
## *arr стек (Prowlarr / Radarr / Sonarr) — настройка 2026-05-17
### Архитектура
```
Prowlarr (9696) → Radarr (7878) + Sonarr (8989) → Transmission (9091)
↓ файлы на диск
Jellyfin (8096)
```
### Статус контейнеров
| Контейнер | Порт | Compose | Сети |
|-----------|------|---------|------|
| transmission | 9091 | docker/transmission/ | media_default + media_net |
| radarr | 7878 | docker/radarr/ | docker_default + media_net |
| sonarr | 8989 | docker/sonarr/ | docker_default + media_net |
| prowlarr | 9696 | docker/prowlarr/ | docker_default + media_net |
| jellyfin | 8096 | docker/jellyfin/ | media_default (не в media_net — OK, читает диск) |
Все compose-файлы обновлены: добавлена `media_net` (external).
### API ключи
| Сервис | Ключ |
|--------|------|
| Radarr | `cbcb8ec320104359920c0f611d503c7b` |
| Sonarr | `15fbec32f1cb4b05b1c2b229ce27359b` |
| Prowlarr | `134ac38a02b94d5fb3e3a6e67228b18e` |
| Jellyfin | `87af49b6ff62ea68da6abdab0d7a4fbc` (eagle-script) |
### Конфигурация
- **Radarr root folder:** `/media/movies-radarr`
- **Sonarr root folder:** `/media/series-sonarr`
- **Radarr ← Transmission:** настроен (host=transmission, port=9091, no auth)
- **Sonarr ← Transmission:** настроен
- **Prowlarr → Radarr + Sonarr:** fullSync
- **Prowlarr индексер:** RuTracker (user: mallexxx), baseUrl `https://rutracker.org/`
- **Radarr/Sonarr NFO:** включен (Kodi/Emby metadata writer)
### Jellyfin библиотеки
| Библиотека | Пути |
|------------|------|
| Movies | `/media/library/movies` + `/media/movies-radarr` |
| Cartoons | `/media/library/cartoons` |
| Series | `/media/library/series` + `/media/series-sonarr` |
`/media/library/` — существующая библиотека с NFO/постерами от media-pipeline.
`/media/movies-radarr`, `/media/series-sonarr` — новые скачки через *arr.
### Полная документация
Скилл на Кракене: `~/.hermes/skills/kraken-media-stack/SKILL.md`
Покрывает: все операции, API примеры, pitfalls.
---
## Текущий статус (2026-05-18, сессия 16)
### Jellyfin библиотеки (актуально после 2026-05-18)
| Библиотека | Тип | Пути |
|---|---|---|
| Movies | movies | `/media/movies` |
| Series | tvshows | `/media/series` |
| Cartoons | movies | `/media/cartoons` |
| Cartoon Series | tvshows | `/media/cartoons-series` |
| Documentaries | movies | `/media/documentaries` |
| Documentary Series | tvshows | `/media/documentaries-series` |
| Music | music | *(пусто)* |
`movies-radarr` и `series-sonarr` убраны из библиотек Jellyfin. Router script создаёт symlinks в тематические папки при каждом новом импорте.
**Router script:** `/srv/.../docker/media-pipeline/router.py`
```bash
# Dry-run:
python3 /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/media-pipeline/router.py
# Apply:
python3 /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/media-pipeline/router.py --apply
```
### Сделано сегодня
- ✅ Love, Death & Robots → `/media/series` (был в cartoons)
- ✅ Пингвины. Шпион в толпе → `/media/documentaries-series` (был в cartoons-series)
- ✅ Удалены 79 дублей standalone-файлов в cartoons (папка+файл → оставлена папка)
- ✅ Удалены standalone-дубли в documentaries: Шпион в снегах, Невероятные хищники, Таинственная жизнь собак 2013
- ✅ Удалён LDR s01e18 lowercase-дубль
- ✅ Исправлен NFO Чебурашки → советский 1971 (tmdb 34859)
- ✅ Созданы NFO+постеры: В синем море в белой пене, Котёнок по имени Гав, Смешарики Новый сезон, Трям! Здравствуйте!, Тигрёнок в чайнике
- ✅ Исправлен NFO Знакомимся с кабанами → русский заголовок + постер
- ✅ Стрим (Flow 2024) → `/media/movies/Flow (2024)/`
- ✅ Обновлены Transmission locations: ~15 торрентов (cartoons-series, documentaries-series, series, правильные подпапки)
- ✅ Исправлена Тайна третьей планеты — удалена lowercase NFD-дублирующая папка
- ✅ Удалена пустая папка-призрак Бременских музыкантов
- ✅ Перемещены 5 арменфильм мультиков в отдельные папки
- ✅ rsync "в синем море в белой пене.avi" с HTPC
- ✅ Вокзал для двоих — `movie.nfo` рядом с part1/part2 (part1/part2 формат поддерживается Jellyfin нативно)
- ✅ Написан план router script: Sonarr/Radarr → тематические библиотеки по жанру/рейтингу
### Открытые задачи
-**Router script**`router.py` реализован и запущен. Symlinks созданы, `movies-radarr`/`series-sonarr` убраны из Jellyfin библиотек.
- 🟡 **Передать Transmission locations** через router script при каждом новом импорте (вместо ручного обновления)
- 🔴 **rsync невыполненных с HTPC** — список в `Фильмы не перенесенные с htpc на кракен.md` (пункты `[ ]`)
- 🟡 **Watchlist** — добавить перенесённые фильмы в нужные разделы (кроме Знакомимся с кабанами и Нехочухи)
---
## Текущий статус (2026-05-17, сессия 15)
### rsync финальный прогон (28 файлов)
Запущен rsync 28 медиафайлов отсутствующих на Кракене (скрипт `/tmp/rsync_missing28.sh`, tmux сессия rsync3):
**Фильмы (22):** Брат 2 (4K UpScale), Вивариум, Ворон, Голодные игры Баллада, Голый пистолет, Злая, Инвалид, Как взломать экзамен, Компаньон, Кошачьи миры Луиса Уэйна, Леон, Начало, Операция С Новым Годом, Особенности охоты (1995), Особенности охоты зимний период, Особенности рыбалки, Первый день моей жизни, Пираты, Планета обезьян, Самолетом поездом машиной, Тетрис, Знакомимся с кабанами
**Мультфильмы (5):** Despicable Me 4, Encanto, Inside Out, Le Grand Mechant Renard, Nehochuha
**Сериалы (1):** Fleabag
После завершения — прогнать `fix_torrent_case.py` ещё раз для привязки новых файлов к торрентам.
## Текущий статус (2026-05-16, сессия 14)
### Регрессия 278→194 — диагноз и фикс
**Причина:** два бага в `RegionPartitioner.shadowedFilePaths`:
1. **Case-sensitive сравнение имён**`The.Iron.Giant.1999.mkv` не матчился с sibling-dir `the.iron.giant.1999.../` (Linux case-sensitive FS).
2. **Canonical-dir не видны партиционеру**`FileScanner.discover()` пропускал canonical-папки (`.media-pipeline-canonical` маркер) но не сообщал о них. Поэтому `Cloud.Atlas.BDRip-AVC.mkv` не знал что рядом уже есть `Cloud Atlas (2012)/`.
**Фиксы:**
- `ScanModels.ScanResult`: новое поле `canonicalDirectories: [URL]`
- `FileScanner.discover()`: заполняет `canonicalDirectories` при скипе
- `RegionPartitioner`: (1) case-insensitive baseName match, (2) shadow standalone если в том же каталоге есть любая canonical-dir
- **3 новых теста** в `ScanPhaseRegionSelectionTests`
- **Коммит:** `855b806`
**402/402 тестов GREEN. Деплой на Кракен запущен.**
## Текущий статус (2026-05-16, сессия 13)
### Итог прогона (новый образ, после всех фиксов сессий 10–13)
| Метрика | Значение |
|---------|----------|
| applied | ~269 |
| skipped (unresolved) | **13** |
| errors | **0** ✅ |
### ✅ Resolved в этой сессии
- **Generation П (2011)** — standalone `.mkv`, applied
- **Mad Max Beyond Thunderdome (1985)** — standalone `.mkv` слинкован в существующую canonical папку
- **Nehochuha** — resolved (папка)
### 13 остатков — финальный список
| # | Путь | Группа | Причина |
|---|------|---------|---------|
| 1 | `/media/cartoons/budil'nik 1984` | Мультик | Нет в TMDb/KP под этим названием |
| 2 | `/media/cartoons/novogodn'aya_pesenka_deda_moroza` | Мультик | Нет в TMDb/KP |
| 3 | `/media/documentaries/bbc. the real jungle book bear 2012` | BBC (genuinely absent) | Нет в TMDb/KP |
| 4 | `/media/documentaries/bbc. секреты собак` | BBC (genuinely absent) | Нет в TMDb/KP |
| 5 | `/media/documentaries/penguin meet the family (2020)` | BBC (genuinely absent) | Нет в TMDb/KP |
| 6 | `/media/documentaries/Таинственная жизнь собак (HDTVRip 720p, 2013).mkv` | Дубль | Дубль строки 7 (разный регистр) |
| 7 | `/media/documentaries/таинственная жизнь собак (hdtvrip 720p, 2013)` | Дока | Нет в TMDb/KP |
| 8 | `/media/documentaries/бегемоты - жизнь в воде` | BBC (genuinely absent) | Нет в TMDb/KP |
| 9 | `/media/documentaries/супер белки (2018)` | Дока | Нет в TMDb/KP |
| 10 | `/media/documentaries/шпион в снегах 2018` | Дока | Нет в TMDb/KP |
| 11 | `/media/movies/Dolina.Ekho.2025.ATVP.WEB-DLRip.AVC.mkv` | Дубль | Дубль строки 12 |
| 12 | `/media/movies/dolina.ekho.2025.atvp.web-dlrip.avc` | Папка | Уже resolved как папка — дубль |
| 13 | `/media/movies/Stiratel.1996.x264.BDRip.(AVC).0ptimus.mkv` | Фильм | Транслит `Stiratel``Стёртый` (KP не находит) |
**По группам:**
- **Дубли** (3): строки 6/7 — один и тот же контент; строки 11/12 — папка уже resolved
- **Genuinely absent в TMDb/KP** (8): BBC документалки + советские мультфильмы
- **Транслит mismatch** (1): `Stiratel` → нужен русский title `Стёртый` для KP поиска
### Sync результат (pipeline-run3, образ `738b4b7426e9`)
| Метрика | Значение |
|---------|----------|
| applied | 260 |
| skipped (unresolved) | 23 |
| errors | **0** ✅ |
### Фиксы этой сессии
-**Code=516 idempotent symlink** — повторный прогон больше не падает на уже существующих симлинках
-**TitleParser `\b` episode regex**`Movie 43` больше не обрезается до `Movi`; `Movie 43` → resolved
-**BBC prefix strip**`bbc. the real jungle book bear``the real jungle book bear` в запросах
-**Yoficator**`котёнок по имени гав` и `тигрёнок в чайнике` resolved через е→ё
-**rsync с HTPC завершён** — 303 фильма, 206 мультиков, 72 серии, 46 документалок (2.2TB)
-**unresolved.json маунтится**`-v $(pwd)/unresolved.json:/config/unresolved.json`
⚠️ **Обязательный pipeline.providers в config.json** — если `tmdb`/`kinopoisk` отсутствуют в блоке → disabled → 213 skipped вместо 278+ applied!
```json
"pipeline": {
"providers": {
"tmdb": { "enabled": true, "language": "ru-RU" },
"kinopoisk": { "enabled": true },
"torrent": { "enabled": true },
"webSearch": { "enabled": true },
"aiAgent": { "enabled": false }
}
}
```
### Запуск pipeline (правильная команда)
```bash
cd /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/media-pipeline
docker run --rm \
-v "$(pwd)/config.json:/config/config.json:ro" \
-v "/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/media:/media" \
-v "$(pwd)/cache:/config/cache" \
-v "$(pwd)/unresolved.json:/config/unresolved.json" \
media-pipeline:kraken
```
### 23 оставшихся unresolved — анализ
**Группа A — дубли (папка + файл, 4 пары):**
Папка resolv-ится (separateItems hypothesis), standalone файл нет.
- `Nehochuha.XVid.DVDRip.avi` (папка `nehochuha.xvid.dvdrip` — resolved ✅)
- `Bezumnyj_Maks_3_Pod_Kupolom_Groma_1985_[torrents.ru].mkv` (папка resolved)
- `Dolina.Ekho.2025.ATVP.WEB-DLRip.AVC.mkv` (папка resolved)
- `dolina.ekho.2025.atvp.web-dlrip.avc` (дубль папки выше)
**Группа B — не индексированы в KP/TMDb:**
- `bbc. the real jungle book bear 2012`
- `bbc. секреты собак`
- `penguin meet the family (2020)`
- `бегемоты - жизнь в воде`
- `супер белки (2018)`
- `шпион в снегах 2018`
- `Таинственная жизнь собак (HDTVRip 720p, 2013).mkv`
- `таинственная жизнь собак (hdtvrip 720p, 2013)` (дубль выше)
**Группа C — транслитные фильмы (KP находит, но support=0 из-за wrong-hypothesis):**
- `Ni4ego_horoshego_v_otele_El_Royal_2018` — KP `446021|2018` находит
- `O_4em_govoryat_muzh4iny_2010``О чем говорят мужчины` (year filter: 2010 vs 2011)
- `Serebryanye_konki_2020`
- `Generation.P.BDRip-AVC`
- `Stiratel.1996` — KP знает как `Стёртый`, транслит неверный
- `The.Frozen.Kingdom.of.the.Snow.Leopard.(2020)`
**Группа D — год mismatch:**
- `рики тики тави 1965` — в KP год 1976 (советский м/ф)
**Группа E — 2024-2025 новинки (возможно не проиндексированы):**
- `Odin.den.v.Stambule.2024`
- `Prorok.Istoriya.Aleksandra.Pushkina.2025`
**Группа F — неопределённое:**
- `25е - первый день`
### Следующие шаги
1. **Группа A:** удалить дубли-standalone-файлы (папки с тем же контентом есть)
2. **Группа C:** разобраться с wrong-hypothesis багом в арбитраже (KP result привязывается не к той hypothesis)
3. **Группа B:** rename папок на правильные названия (BBC docs не индексированы)
4. **Группа D/E:** relaxed year matching или override через resolve-manual
## Unresolved: Investigation List
> Эти 15 позиций прошли все автоматические тиры (TMDb→KP→IMDb→WebSearch) и остались нераспознанными.
> Статус на 2026-05-16. Для каждого — план ручного расследования.
### Группа: Дубли (удалить с диска или дедуплицировать scanner)
| Путь | Действие |
|------|----------|
| `/media/cartoons/Nehochuha.XVid.DVDRip.avi` | Папка `nehochuha.xvid.dvdrip` уже resolved. Удалить standalone файл. |
| `/media/movies/Bezumnyj_Maks_3_Pod_Kupolom_Groma_1985_[torrents.ru].mkv` | Resolved в прогоне 2026-05-16 (слинкован). Удалить из unresolved.json вручную. |
| `/media/movies/Dolina.Ekho.2025.ATVP.WEB-DLRip.AVC.mkv` | Папка `dolina.ekho.2025...` уже resolved. Это standalone дубль. |
| `/media/movies/dolina.ekho.2025.atvp.web-dlrip.avc` | Та же папка что выше — в scanner попадает дважды. |
| `/media/documentaries/Таинственная жизнь собак (HDTVRip 720p, 2013).mkv` | Дубль строки ниже (разный регистр). |
| `/media/documentaries/таинственная жизнь собак (hdtvrip 720p, 2013)` | Основная версия — нужно искать. |
### Группа: Советские мультфильмы (нет под транслитом в TMDb/KP)
| Путь | Query для ручного поиска | Примечание |
|------|--------------------------|------------|
| `/media/cartoons/budil'nik 1984` | КП: «Будильник», год 1978 или 1984 | Советский м/ф Союзмультфильм |
| `/media/cartoons/novogodn'aya_pesenka_deda_moroza` | КП: «Новогодняя песенка Деда Мороза», год 1983 | |
**Расследование:** `curl "https://api.poiskkino.dev/v1.4/movie/search?query=Будильник&year=1984&type=cartoon"`
### Группа: Документальные (BBC/Discovery, нет в TMDb)
| Путь | Варианты поиска | Примечание |
|------|-----------------|------------|
| `/media/documentaries/bbc. the real jungle book bear 2012` | IMDb: «The Real Jungle Book» (2012, BBC) | Возможно серия BBC Natural World |
| `/media/documentaries/bbc. секреты собак` | IMDb/КП: «Secrets of Your Dog», «BBC Секреты собак» | |
| `/media/documentaries/penguin meet the family (2020)` | IMDb: «Penguin: Meet the Family» (2020) | BBC Studios |
| `/media/documentaries/бегемоты - жизнь в воде` | КП/IMDb: «Hippos: Life in the Water», «BBC Бегемоты» | |
| `/media/documentaries/супер белки (2018)` | КП: «Супер белки», IMDb: «Super Squirrels» (2018) | |
| `/media/documentaries/шпион в снегах 2018` | КП: «Шпион в снегах», IMDb: «Snow Spy», «Spy in the Snow» | |
| `/media/documentaries/таинственная жизнь собак (hdtvrip 720p, 2013)` | КП: «Тайная жизнь собак», IMDb: «Secret Life of Dogs» (2013, BBC) | |
**Расследование:**
1. Поиск на IMDb: `https://www.imdb.com/find?q=<title>&s=tt`
2. Если нашли — взять TMDb ID через: `https://api.themoviedb.org/3/find/<imdb_id>?external_source=imdb_id`
### Группа: Транслит-mismatch
| Путь | Реальное название | KP ID | Действие |
|------|-------------------|-------|---------|
| `/media/movies/Stiratel.1996.x264.BDRip.(AVC).0ptimus.mkv` | «Стёртый» (1996) | KP 9268 | `resolve-manual --tmdb <id>` или добавить alias в транслит-словарь |
**Расследование:** KP ID 9268 уже в кеше. Проблема — `Stiratel``Стирател` не совпадает с `Стёртый`.
Нужно проверить есть ли у KP 9268 TMDb external ID: `curl "https://api.poiskkino.dev/v1.4/movie/9268"`.
---
## Архитектура: Agentic Flow (agent subcommand)
> Записано 2026-05-16. Это главная спека `media-pipeline agent` subcommand.
### Входные данные агента (для каждого unresolved item)
Агент получает **всё что можно извлечь без баз**:
1. **Файловые метаданные** — имя папки/файла, набор файлов в папке (для TV show), длительность, формат, битрейт, разрешение (через `ffprobe` или `AVFoundation`)
2. **Транскрипция речи**`ffmpeg` вырезает первые 60–120 сек аудио → **Whisper API** → текст (речь дикторов, диалоги, announcer voice)
3. **OCR титров**`ffmpeg` захватывает 5–10 кадров из начала → **tesseract** или Vision API → текст с title card, студия, год
4. **Имена файлов** как уже есть (query names из UnresolvedEntry)
### Инструменты агента (function calling / tool use)
LLM получает инструменты и итеративно их вызывает до 10 шагов:
| Tool | Параметры | Описание |
|------|-----------|----------|
| `search_tmdb` | `title, year?, language?` | Поиск в TMDb API |
| `search_kinopoisk` | `title, year?` | Поиск в KP API |
| `search_imdb` | `title, year?` | IMDb full-text search |
| `fetch_page` | `url` | Фетч и возврат текста страницы (IMDb, Wikipedia, BBC, Rutracker, etc.) |
| `search_web` | `query` | Веб-поиск (DuckDuckGo / SerpAPI) |
### Выходные данные агента — одно из трёх
**Вариант A — DB-bound resolve** (нашёл в базе):
```json
{
"tmdb_id": 12345,
"kp_id": 678,
"imdb_id": "tt1234567"
}
```
→ Pipeline пишет NFO стандартно через TMDb/KP fetch
**Вариант B — full metadata без DB binding** (BBC/редкие доки):
```json
{
"title": "The Frozen Kingdom of the Snow Leopard",
"original_title": "...",
"year": 2020,
"description": "...",
"genres": ["Documentary"],
"poster_url": "https://...",
"source_url": "https://bbc.com/programmes/...",
"duration_min": 59
}
```
→ Pipeline пишет custom NFO с этими данными (без tmdb/kp ID). Jellyfin подхватит.
**Вариант C — manual resolve**:
```json
{
"category": "unrecognized",
"reason": "Не удалось идентифицировать после всех источников"
}
```
→ Остаётся в unresolved.json с agent_hint для ручного разбора
### Выход для TV show / папки с несколькими фильмами
Агент возвращает **список результатов**, по одному на файл/эпизод. Структура аналогична тому что делает media sync — те же поля что и NFO.
### Приоритет обработки
1. Сначала `The.Frozen.Kingdom.of.the.Snow.Leopard.(2020)` — самый сложный кейс, нет в TMDb/KP
2. Остальные BBC/Soviet docs из unresolved.json
3. Все остальные unresolved
### Реализация
#### Шаг 1: `MediaFileInspector` (новый модуль)
- `ffprobe` / AVFoundation → duration, format, resolution, audio tracks
- `ffmpeg` → extract frames → Vision/tesseract OCR → title card text
- `ffmpeg` → extract 60s audio → Whisper API → speech transcript
- Все результаты упакованы в `MediaFileContext` struct
#### Шаг 2: `ChatGPTAPI.resolveMedia(context: MediaFileContext)` (новый метод)
- OpenAI function calling loop (max 10 iterations)
- Системный промпт включает все данные из `MediaFileContext`
- LLM итеративно вызывает tools пока не придёт к выводу
- Возвращает `AgentResolutionResult` (A/B/C)
#### Шаг 3: `Agent.swift` subcommand расширен
- Запускает `MediaFileInspector` для каждого unresolved item
- Передаёт `MediaFileContext` в `resolveMedia`
- По результату: пишет NFO (A/B) или обновляет hint (C)
#### Шаг 4: Tier 6 в sync pipeline (опционально)
- После Tier 5 (AIAgentStrategy) — запускает VideoAnalysis для застрявших
- Добавляет найденные variants как новые QueryNameFact → передаёт через Tier 0–4
### Зависимости (Linux/arm64 на Кракене)
- `ffprobe` / `ffmpeg` — уже установлен на Кракене
- `whisper-cpp` или OpenAI Whisper API (через ChatGPTAPI)
- `tesseract` — нужно установить (`apt install tesseract-ocr tesseract-ocr-rus`)
- Vision API (macOS only) — для Linux использовать tesseract
## План: CV/Whisper для Tier 6 (для оценки)
> Идея: когда все тиры исчерпаны — использовать сам медиафайл как источник сигнала для идентификации.
### Подход A: Whisper — транскрипция аудиодорожки начала фильма
- `ffmpeg -i film.mkv -ss 0 -t 120 -vn -acodec pcm_s16le -ar 16000 /tmp/opening.wav`
- `whisper /tmp/opening.wav --language ru --model small` → текст
- Из текста: название, студия, год, авторы → уточнённый TMDb/KP запрос
- **Применимо:** советские мультфильмы, документалки с русским закадровым голосом
### Подход B: CV — захват кадров с титрами
- `ffmpeg -i film.mkv -ss 5 -vframes 10 /tmp/frames/frame%03d.jpg` (начало)
- `ffmpeg -i film.mkv -sseof -120 -vframes 10 /tmp/frames/end%03d.jpg` (конец — credits)
- OCR через `tesseract` или Vision API → текст с кадров
- Из текста: оригинальное название, год, студия
- **Применимо:** советские мультфильмы всегда имеют title card в первые секунды
### Подход C: Chromaprint/AcoustID — audio fingerprint
- `fpcalc film.mkv` → fingerprint → `https://api.acoustid.org/v2/lookup`
- **Применимо:** редко
### Реализация (если решим делать)
1. Новый `Tier6VideoAnalysisStrategy` в `MediaDomain`
2. Запускается только для `attempt_count >= 10`
3. На Linux: `ffmpeg`, `whisper-cpp` или OpenAI Whisper API, `tesseract`
4. Вывод: дополнительные `NameVariant` для QueryBuilder
5. **Стоимость Whisper API:** ~$0.006/мин, 120 сек = $0.01 за фильм. 13 items = $0.13
6. **Вердикт:** Хорошая идея для stuck документалок. Реализовать после agentic flow.
---
- **2026-05-15 сессия 10:** rsync с HTPC завершён (2.2TB). Фиксы: Code=516 idempotent symlink, TitleParser `\b` episode regex, BBC prefix strip, Yoficator (е→ё). Результат: **260 applied, 0 errors, 23 unresolved**. Movie 43 ✅, Котёнок ✅, Тигрёнок ✅, Нехочуха (папка) ✅.
- **2026-05-13 сессия 1:** HTPC разбужен, rsync запущен (~1.3TB), торрент-база импортирована (128 торрентов из Windows Transmission-daemon)
- **2026-05-13 сессия 2:** Docker build arm64, deploy на Кракен, first sync
- **2026-05-13 сессия 3:** `kraken-agent` ветка: UnresolvedStore, resolve-manual, agent, torrent-fix subcommands
- **2026-05-14 сессия 4:** 5 системных багов исправлены. Sync: 222/317 resolved (70%).
- **2026-05-14 сессия 5:** resolve-manual для 13 items. unresolved: 24 → 8. NFO path bug fix. kraken-ssh → WireGuard.
- **2026-05-14 сессия 6:** dry_run=false, sync запущен → 401 resolved, 32 unresolved. `apply` создаёт canonical папки + симлинки (оригиналы не трогает). 716 NFO написано. Валидация торрентов HTPC.
- **2026-05-15 сессия 7:** Торренты: 467 скопированы с HTPC, 323 добавлены в Transmission (294 итого после удаления 50 игр/нон-медиа). rsync: movies + мультфильмы + BBC документалки. Фиксы в коде: (1) best-effort collapse при исчерпании tierов, (2) дедупликация dir+file регионов в RegionPartitioner, (3) strip .aka./full.screen из TitleParser, (4) scanner пропускает canonical dirs по маркеру `.media-pipeline-canonical`, (5) Roman numerals в транслитерации (Ампир V). Финал: 19 unresolved.
- **2026-05-16 сессия 8:** Обнаружен корневой баг: KP API переехал `api.kinopoisk.dev``api.poiskkino.dev` (301 redirect, Swift теряет X-API-KEY). Исправлен URL. Удалены 359 невалидных KP-кеш записей. Добавлен leet-speak decode (`4``ch`: ni4ego→nichego, muzh4iny→muzhchiny). Починен кеш: KP пустые `docs:[]` больше не кешируются (были причиной повторных промахов после rate-limit/redirect). **Текущий unresolved: 22. Завтра KP лимит сбросится → ожидаем ≤5.**
- **2026-05-16 сессия 9:** Добавлен жанровый роутинг (cartoons/documentaries — только по genres из провайдера, не по source path). Фикс `nfoMeta priority` (provider genres > local empty NFO). Фикс парсинга года `2023г` (русский суффикс). TMDb genres передаются из config. **Убран Cyr→Lat query variant** (не соответствует оригинальному Go-sync, TMDb не ищет по транслиту). Порядок провайдеров подтверждён: TMDb → KP → IMDb (short-circuit после каждого). KP — настоящий fallback (40 запросов vs 8428 TMDb). Текущий unresolved: 30 (ждём сброса KP rate limit).
- **2026-05-16 сессия 1011 (продолжение):** Серия кодовых фиксов. **278 applied, 3 skipped (98.9%).** Созданы ручные NFO для 11 тайтлов (нарушение принципа — удалены в сессии 12).
- **2026-05-16 сессия 13:** Integration тест `BezumyjMaksIntegrationTests` (8 кейсов, все шаги pipeline). Generation П ✅ resolved. Mad Max Beyond Thunderdome standalone ✅ resolved. Nehochuha ✅. **13 unresolved** — финальный список: 3 дубля, 8 genuinely absent (BBC/советские доки), 1 транслит-mismatch (Stiratel→Стёртый). Удалены 15 ручных NFO. Подтверждён и задокументирован Баг 12 (`_1985_` year extraction). TitleParser регекс исправлен. Exhaustive тесты `TitleParserExhaustiveTests` (24 кейса) — GREEN. Баги 13 (OutputConfig backward compat), 14 (xvid/divx markers), 15 (год-как-название: 1984, 2001). `QuerySnapshotGenerator` executable — регенерация 364 снапшотов без racing. **391/391 тестов GREEN.** Текущий unresolved на Кракене: **6**. Следующий шаг: deploy → clean run → agentic flow для 3 BBC/доков.
### Фиксы сессий 10–11 (код)
-**double-counting fix**`HypothesisArbiter`: `penalizedZeroProviders` в `HypothesisFact`, только newly-zero провайдеры пенализируются за проход. Серебряные коньки resolved.
-**originalHypo scope bug**`collectEvidence` использовал `originalHypo` вне scope → compile error. Заменён на `hypothesis`.
-**TMDb en-US locale merge** — для Latin-script запросов: dual search (ru-RU + en-US), merge (ru-RU first), лимит 40. Generation П resolved.
-**NFO reverse-prefix match**`HypothesisGenerator`: sidecar `Generation.P.nfo` теперь матчится к `Generation.P.BDRip-AVC.mkv` через `stemPrefix` проверку.
-**FileScanner sibling sidecar** — для file-region (не директория) сканируются сиблинги в parent dir → NFO рядом с mkv подхватывается. Stiratel resolved.
### 6 оставшихся unresolved (на Кракене, актуально)
**Группа A — standalone-дубли (2 шт.):**
Папки с тем же контентом уже resolved. Standalone-файлы pipeline не резолвит потому что дубль.
- `/media/movies/Bezumnyj_Maks_3_Pod_Kupolom_Groma_1985_[torrents.ru].mkv`
- `/media/cartoons/Nehochuha.XVid.DVDRip.avi`
**Группа B — genuinely absent в TMDb и KP (3 шт.):**
- `/media/documentaries/bbc. the real jungle book bear 2012`
- `/media/documentaries/bbc. секреты собак`
- `/media/documentaries/бегемоты - жизнь в воде`
**Группа C — папка (дубль resolved-папки):**
- `/media/movies/bezumnyj_maks_3_pod_kupolom_groma_1985_[torrents.ru]`
Группы A и C — устранить через дедупликацию scanner'а (пропускать standalone если есть resolved canonical папка с тем же контентом).
Группа B — Tier 5 (agentic flow) или manual resolve как последний resort.
### Следующий шаг: exhaustive tests → deploy → agentic flow
**Задача:** написать exhaustive тесты валидирующие каждый шаг парсинга для **всех** проблемных тайтлов — как незарезолвленных, так и тех, где создавались ручные NFO. Никаких ручных NFO не создавать — тул должен всё находить автоматически.
**Тайтлы для exhaustive тестов:**
| Filename | Ожидаемый title | Ожидаемый year | Проблема/баг |
|----------|----------------|----------------|-------------|
| `bezumnyj_maks_3_pod_kupolom_groma_1985_[torrents.ru]` | `bezumnyj maks 3 pod kupolom groma` | 1985 | Баг 12: `_year_` |
| `Nehochuha.XVid.DVDRip.avi` | `nehochuha` | nil | — |
| `budil'nik 1984` | `budilnik` | 1984 | апостроф soft-sign |
| `novogodn'aya_pesenka_deda_moroza_1982` | `novogodnyaya pesenka deda moroza` | 1982 | апостроф + год в underscore |
| `таинственная жизнь собак (HDTVRip 720p, 2013)` | `таинственная жизнь собак` | 2013 | год в скобках со стороссей |
| `шпион в снегах 2018` | `шпион в снегах` | 2018 | год в конце |
| `супер белки (2018)` | `супер белки` | 2018 | год в скобках |
| `penguin meet the family (2020)` | `penguin meet the family` | 2020 | год в скобках |
| `dolina.ekho.2025.atvp.web-dlrip.avc` | `dolina ekho` | 2025 | качество-маркеры |
| `Stiratel.1996` | `stiratel` | 1996 | год через точку |
| `The.Frozen.Kingdom.of.the.Snow.Leopard.(2020)` | `the frozen kingdom of the snow leopard` | 2020 | точки как разделители |
| `Generation.P.BDRip-AVC` | `generation p` | nil | буква как часть названия |
| `Ni4ego_horoshego_v_otele_El_Royal_2018` | `nichego horoshego v otele el royal` | 2018 | leet-speak + год |
| `O_4em_govoryat_muzh4iny_2010` | `o chem govoryat muzhchiny` | 2010 | leet-speak |
| `Serebryanye_konki_2020` | `serebryanye konki` | 2020 | год в underscore |
| `bbc. the real jungle book bear 2012` | `the real jungle book bear` | 2012 | BBC prefix + год |
| `bbc. секреты собак` | `секреты собак` | nil | BBC prefix кириллица |
| `Миграция. 2023г.mkv` | `миграция` | 2023 | Баг 10: кирилл. суффикс `г` |
| `рики тики тави 1965` | `рики тики тави` | 1965 | — |
| `25е - первый день` | `первый день` | nil | числовой префикс |