257 lines
21 KiB
Markdown
257 lines
21 KiB
Markdown
---
|
||
created: '2026-05-13'
|
||
updated: '2026-05-14'
|
||
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.", год, технические теги из запроса перед поиском).
|
||
|
||
#### 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`.
|
||
|
||
### Баг 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 | `ssh kraken-ssh` → 10.99.1.2:22 через WireGuard (обновлено 2026-05-14) |
|
||
| docker deploy | `docker save img -o /tmp/img.tar && scp /tmp/img.tar kraken-ssh:/tmp/ && ssh kraken-ssh "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`). Файла нет нигде — **никогда не скачивался**.
|
||
|
||
## Журнал
|
||
|
||
- **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).
|