39 KiB
created, updated, status, tags
| created | updated | status | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|
| 2026-05-13 | 2026-05-13 | planning |
|
Media Toolbox на Кракене
Создано: 2026-05-13. Цель: полноценный медиасервер на Кракене (RPi 5) — перенос библиотеки с HTPC, автосортировка через kodi-media-sync, Jellyfin, торренты.
Архитектура (целевая)
HTPC (Bazzite 192.168.1.86)
└── /run/media/bazzite/Data/{Мультфильмы,Сериалы,movies,...}
└─── rsync ──→ Kraken HDD /srv/.../media/
Kraken (RPi 5, 192.168.1.14)
├── /srv/dev-disk-by-uuid-.../media/
│ ├── movies/ ← фильмы
│ ├── cartoons/ ← мультфильмы
│ ├── series/ ← сериалы
│ ├── documentaries/ ← документальные
│ └── downloads/ ← папка Transmission (входящие)
├── Docker: Transmission (скачка торрентов)
├── Docker: Jellyfin (стриминг на TV/телефоны)
└── Docker: kodi-media-sync (сортировщик + метаданные)
kodi-media-sync (Swift CLI, cross-compile Mac → Linux ARM64)
├── Читает downloads/ + необработанные папки
├── Идентифицирует через Transmission RPC (если торрент) или поиск по имени
├── Создаёт NFO-файлы (Kodi/Jellyfin-совместимые)
├── Раскладывает по категориям (movies/cartoons/series/docs)
├── Обновляет пути скачки в Transmission через RPC API
└── Запускается как download-complete скрипт Transmission
Hermes cron (Кракен, 1-2x/day)
└── kodi-media-sync --dry-run → лог → manual confirm / auto-apply
TrueNAS (Taiga)
└── Transmission → миграция на Крaken (или оставить для seed, скопировать БД торрентов)
Статус по шагам
| Шаг | Статус | Заметки |
|---|---|---|
| 1 — Исследование (Kodi lib на HTPC) | ✅ | Data: мультфильмы(229G) сериалы(116G) movies(673G) bbc(284G) |
| 2 — Wake HTPC + prevent sleep | ✅ | sleep masked, linger=yes, rsync через systemd-run --user |
| 3 — Копирование медиа HTPC → Kraken | 🔄 | ~58GB/категорию скопировано, ~1.3TB всего, rsync активен |
| 4 — Transmission на Кракене | ✅ | 128 торрентов (7 старых + 121 новый), paused, категоризированы |
| 5 — Перенос torrent-базы | ✅ | 121 медиа-торрент импортирован из Windows Downloads |
| 6 — MediaFilesScraper: изучить + настроить | ✅ | Swift 6 SPM, пропатчен для Linux, OpenRouter вместо OpenAI |
| 7 — Docker build ARM64 | ✅ | Colima 24GB, --memory=20g -j 1, образ 78MB задеплоен |
| 8 — Конфиг на Кракене | ✅ | config.json, on-download-complete.sh на месте |
| 9 — Dry run | 🔄 | 1/6 resolved (Black Mirror S06E01 ✅), остальное ждёт rsync |
| 10 — OpenRouter ключ | ⚠️ | Нужно вставить sk-or-v1-... в config.json на Кракене |
| 11 — Агент для unresolved | 🔲 | см. шаг 16 ниже |
| 12 — Jellyfin: настройка библиотек | 🔲 | после rsync |
| 13 — Transmission download-complete hook | ✅ | установлен и прописан |
| 14 — Hermes cron (unresolved notify) | 🔲 | см. шаг 16 |
| 15 — Rutracker-скилл | 🔲 | |
| 16 — Музыка: sync с TrueNAS | 🔲 |
Важные замечания по архитектуре
Кеширование API-ответов ✅
HTTPClient кеширует все ответы в файловую систему (30-дневный TTL), включая при dry run. Кеш: /srv/.../docker/media-pipeline/cache/. Повторные resolve-прогоны не тратят API-лимиты.
AI-агент: OpenRouter вместо OpenAI
ChatGPTAPI переключён на https://openrouter.ai/api/v1/chat/completions, модель по умолчанию openai/gpt-4o-mini:free. Ключ: openai_api_key в config.json — нужен реальный sk-or-v1-... ключ с openrouter.ai (бесплатный tier достаточен для Tier 5 last-resort).
Встроенный агент вместо Hermes cron ⭐
Идея (под вопросом / для реализации): вместо внешнего Hermes cron-джоба — встроить в media-pipeline интерактивный режим resolve --interactive (или отдельная команда agent).
Концепция:
- Для файлов которые не нашлись через TMDB/KP/webSearch — не просто записать "unresolved", а запустить mini-агентскую сессию
- Агент имеет промпты + тулы (веб-поиск, TMDB по ID, Kinopoisk по ID, пользовательский ввод)
- Может задавать уточняющие вопросы если нашёл несколько кандидатов
- Сохраняет результат в кеш чтобы последующие прогоны не тратили API
- Это делает media-pipeline самодостаточным — не зависит от внешнего оркестратора
Преимущества vs Hermes cron:
- Нет latency между обнаружением проблемы и её решением
- Контекст resolve-сессии доступен агенту напрямую (graph facts, провайдеры, история)
- Работает как download-complete хук: скачал → resolve → если не нашёл → агент разбирается тут же
Реализация (примерно):
// media-pipeline agent --config /config/config.json [--path ...]
// Запускает resolve, для unresolved — интерактивная LLM-сессия с тулами
struct AgentCommand {
// Инструменты:
// - tmdb_search(query, year) → candidates[]
// - kp_search(query) → candidates[]
// - web_search(query) → snippets[]
// - confirm(candidate) → принять результат
// - skip() → пометить как manual-review-needed
}
Статус: под вопросом — оценить объём работ перед реализацией. Возможно проще оставить Hermes cron как оркестратор но дать ему доступ к graph facts через --format json.
Цель: найти реальную структуру медиабиблиотеки, где симлинки, куда ведут.
ssh -i ~/.ssh/id_rsa bazzite@192.168.1.86
# Kodi userdata (может быть в двух местах)
ls ~/.kodi/userdata/
ls ~/.kodi/userdata/sources.xml # список медиаисточников
# Если Kodi с Windows раздела мигрировал
ls /run/media/bazzite/Data/AppData/Roaming/Kodi/ 2>/dev/null
# Найти симлинки в home
find ~ -type l 2>/dev/null | head -30
# Медиа на Data разделе
ls /run/media/bazzite/Data/ | grep -iE "film|movie|кино|мульт|serial|сери|doc"
Ожидаемые пути на Data:
/run/media/bazzite/Data/Мультфильмы//run/media/bazzite/Data/Сериалы//run/media/bazzite/Data/movies//run/media/bazzite/Data/документальные/(или аналог)
Шаг 2 — Wake HTPC + предотвращение сна
Wake on LAN
# С Mac или Кракена:
wakeonlan B0:6E:BF:60:6D:64
# или
python3 ~/Automation/wake-on-lan.py
Предотвратить сон во время SSH / копирования
Вариант A — systemd-inhibit (рекомендуется):
# Запустить на HTPC в отдельной SSH-сессии, оставить висеть пока идёт копирование:
systemd-inhibit --what=sleep:idle --who="media-copy" \
--why="Copying media to Kraken" --mode=block \
sleep infinity
Закрыть эту сессию = сон разрешён снова.
Вариант B — через systemd сервис-lockfile (долгосрочно):
Если SSH-сессия падает, нужен более надёжный способ. Создать временный inhibit.service который поднимается на время rsync.
Вариант C — xdg-screensaver (только экран):
# Только блокирует screensaver, не suspend
while true; do xdg-screensaver reset; sleep 60; done &
Рекомендация: вариант A (systemd-inhibit). Запускать в tmux, чтобы сессия жила независимо от SSH:
ssh bazzite@192.168.1.86 "tmux new-session -d -s inhibit 'systemd-inhibit --what=sleep:idle --who=media-copy --why=Copying --mode=block sleep 86400'"
Шаг 3 — Копирование медиа HTPC → Kraken
Целевая структура на Кракене
/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/media/
├── movies/
├── cartoons/
├── series/
├── documentaries/
└── downloads/ ← Transmission download dir
└── complete/ ← сюда Transmission кладёт готовые
Создать директории
ssh kraken@192.168.1.14 "mkdir -p /srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/media/{movies,cartoons,series,documentaries,downloads/complete,downloads/incomplete}"
rsync HTPC → Kraken (прямо с Кракена)
# Запустить на Кракене (pull с HTPC):
HDD=/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade
# Мультфильмы
rsync -avh --progress --no-links \
bazzite@192.168.1.86:"/run/media/bazzite/Data/Мультфильмы/" \
"$HDD/media/cartoons/"
# Сериалы
rsync -avh --progress --no-links \
bazzite@192.168.1.86:"/run/media/bazzite/Data/Сериалы/" \
"$HDD/media/series/"
# Фильмы (уточнить точное имя папки)
rsync -avh --progress --no-links \
bazzite@192.168.1.86:"/run/media/bazzite/Data/movies/" \
"$HDD/media/movies/"
# Документальные (если есть)
rsync -avh --progress --no-links \
bazzite@192.168.1.86:"/run/media/bazzite/Data/документальные/" \
"$HDD/media/documentaries/" 2>/dev/null || true
⚠️
--no-links— не копировать симлинки как симлинки, следовать им. ⚠️ Для SSH без пароля с Кракена нужен ключ:ssh-copy-id -i ~/.ssh/id_rsa bazzite@192.168.1.86
Альтернатива — пушить с HTPC через SSH:
# На HTPC:
rsync -avh --progress /run/media/bazzite/Data/Мультфильмы/ \
kraken@192.168.1.14:/srv/.../media/cartoons/
Шаг 4 — Transmission на Кракене
Transmission уже задеплоен вместе с Jellyfin в /home/kraken/media/docker-compose.yml.
Проверить конфиг:
cat /home/kraken/media/docker-compose.yml
Download dir должен быть смонтирован как HDD/media/downloads.
Порты: 9091 (RPC), 51413 (peers)
Если Transmission ещё не настроен — проверить и обновить docker-compose, добавить volume:
volumes:
- /srv/dev-disk-by-uuid-.../media/downloads:/downloads
- /srv/dev-disk-by-uuid-.../docker/transmission:/config
Шаг 5 — Перенос torrent-базы с HTPC
Найти торрент-клиент на HTPC
ssh bazzite@192.168.1.86
# Что стоит?
which transmission-daemon qbittorrent-nox rtorrent 2>/dev/null
systemctl list-units | grep -iE "torrent|transmission|qbit"
# Transmission (если есть):
ls ~/.config/transmission/torrents/ | head -10
ls ~/.config/transmission/resume/ | head -10
# qBittorrent:
ls ~/.local/share/data/qBittorrent/BT_backup/ | head -10
Если Transmission на Bazzite:
# Скопировать на Кракен:
rsync -avh bazzite@192.168.1.86:~/.config/transmission/ \
kraken@192.168.1.14:/tmp/htpc-transmission/
Если торрент-клиент был на Windows (TrueNAS Transmission):
Торрентная БД TrueNAS уже работает. Нужно решить:
- Вариант A: Перенести TrueNAS Transmission на Крaken (один клиент)
- Вариант B: Оставить TrueNAS для seed, Крaken для новых загрузок
Миграция .torrent + .resume файлов:
# Скопировать с TrueNAS (через duckdns):
rsync -avh -e "ssh -i ~/.ssh/id_rsa" \
truenas_admin@mallexxx.duckdns.org:/mnt/RED_2TB/docker/transmission/ \
/tmp/taiga-transmission/
# Поправить пути в .resume файлах:
# Старый путь TrueNAS: /mnt/storage/Downloads/...
# Новый путь Кракена: /downloads/complete/...
for f in /tmp/taiga-transmission/resume/*.resume; do
# .resume это bencoded файл — нужен python с bencode или transmission-edit
echo "$f"
done
⚠️ Файлы
.resume— бинарный bencode-формат. Для правки путей использовать:pip install bencoder.pyxилиtransmission-remote --torrent N --find /new/pathПроще: после импорта торрентов запустить media-sync с --fix-paths флагом через RPC API.
Исправление путей через Transmission RPC:
# Transmission RPC: изменить путь для конкретного торрента
curl -s -u user:pass http://kraken:9091/transmission/rpc \
-H "X-Transmission-Session-Id: TOKEN" \
-d '{"method":"torrent-set-location","arguments":{"ids":[N],"location":"/downloads/complete/FolderName","move":false}}'
kodi-media-sync должен уметь это автоматически (см. шаг 6).
Шаг 6 — kodi-media-sync: функциональность
Текущее состояние проекта
Проект ~/Developer/kodi-media-sync (Swift):
Config.swift— читает config.json:transmissionURL,directories[],output.{movies,series}MediaSync.swift— scaffold: сканирует директории, вызываетupdateMetadata(пустая)Path.swift— утилиты для файловой системы
Нужно реализовать:
6.1 Transmission RPC клиент
// Sources/TransmissionClient.swift
struct TransmissionClient {
let url: URL
// Получить все торренты
func getTorrents() async throws -> [Torrent]
// Найти торрент по имени файла / папке
func findTorrent(forPath path: Path) async throws -> Torrent?
// Обновить путь торрента
func setLocation(torrentId: Int, location: Path) async throws
}
6.2 Идентификация медиа
// Sources/MediaIdentifier.swift
enum MediaType {
case movie(title: String, year: Int?)
case series(title: String, season: Int?, episode: Int?)
case cartoon(title: String, year: Int?)
case documentary(title: String, year: Int?)
case unknown
}
struct MediaIdentifier {
// Из имени папки/файла → тип медиа + нормализованное название
func identify(path: Path, torrent: Torrent?) -> MediaType
// Поиск метаданных (TMDB/OMDB/локальный поиск)
func fetchMetadata(for media: MediaType) async throws -> MediaMetadata
}
Алгоритм идентификации:
- Если есть торрент в Transmission — взять имя торрента, категорию
- Парсить имя файла/папки: регексы для
S01E01, год(2024), качество1080p - Для документалок типа "знакомимся с кабанами" — поиск по названию (TMDB API)
- Маппинг исходной папки → тип:
Мультфильмы/→ cartoon,Сериалы/→ series
6.3 NFO-файлы (Kodi/Jellyfin)
// Sources/NFOWriter.swift
// Формат Kodi NFO: https://kodi.wiki/view/NFO_files/Movies
struct NFOWriter {
func writeMovieNFO(_ metadata: MediaMetadata, to path: Path) throws
func writeSeriesNFO(_ metadata: MediaMetadata, to path: Path) throws
func writeTVShowNFO(_ metadata: MediaMetadata, to path: Path) throws
}
6.4 Config расширение
{
"transmission": "http://user:pass@192.168.1.14:9091/transmission/rpc",
"tmdb_api_key": "...",
"directories": [
"/media/downloads/complete",
"/media/movies",
"/media/cartoons"
],
"output": {
"movies": "/media/movies",
"series": "/media/series",
"cartoons": "/media/cartoons",
"documentaries": "/media/documentaries"
},
"dry_run": false,
"category_hints": {
"/media/cartoons": "cartoon",
"/media/series": "series",
"/media/documentaries": "documentary"
}
}
6.5 CLI флаги
kodi-media-sync [--config path] [--dry-run] [--path <specific_path>] [--fix-paths]
--dry-run Показать что будет сделано, не двигать файлы
--path PATH Обработать только конкретную папку/файл
--fix-paths Обновить пути в Transmission для уже существующих файлов
Шаг 7 — Cross-compilation Mac → Linux ARM64
Swift на Mac имеет встроенный cross-compiler для Linux.
Установить Swift toolchain для Linux (если не стоит):
# На Mac:
swiftly install latest
# или скачать Linux ARM toolchain:
# https://www.swift.org/install/linux/
Build для aarch64-linux:
cd ~/Developer/kodi-media-sync
# Вариант A — через Docker buildx (рекомендуется):
docker buildx build --platform linux/arm64 \
-f Dockerfile.linux \
-t kodi-media-sync:arm64 \
--output type=local,dest=./build-arm64 .
# Вариант B — swift cross-compile (нужен SDK):
swift build --triple aarch64-unknown-linux-gnu \
--sdk /path/to/linux-arm64-sdk
Dockerfile.linux для Крaken:
FROM swift:5.9-jammy AS builder
WORKDIR /build
COPY . .
RUN swift build -c release
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y libstdc++6 libgcc-s1 && rm -rf /var/lib/apt/lists/*
COPY --from=builder /build/.build/release/kodi-media-sync /usr/local/bin/
COPY config.json /etc/kodi-media-sync/config.json
ENTRYPOINT ["kodi-media-sync"]
CMD ["--config", "/etc/kodi-media-sync/config.json"]
⚠️ Собирать образ на Mac через buildx с эмуляцией QEMU — не на RPi5 (undervoltage под нагрузкой)!
Deploy на Кракен:
# Сохранить образ и скопировать:
docker save kodi-media-sync:arm64 | gzip | ssh kraken@192.168.1.14 "docker load"
Шаг 8 — Docker-образ на Кракене
/home/kraken/media-sync/docker-compose.yml:
version: '3.8'
services:
kodi-media-sync:
image: kodi-media-sync:arm64
container_name: kodi-media-sync
restart: "no" # запускается по требованию, не автостарт
volumes:
- /srv/dev-disk-by-uuid-.../media:/media
- /srv/dev-disk-by-uuid-.../docker/media-sync:/config
environment:
- CONFIG=/config/config.json
network_mode: host # доступ к Transmission на localhost
Запуск (разовый):
cd /home/kraken/media-sync
sudo docker compose run --rm kodi-media-sync --dry-run
sudo docker compose run --rm kodi-media-sync
Шаг 9 — Config для Кракена
{
"transmission": "http://admin:pass@192.168.1.14:9091/transmission/rpc",
"tmdb_api_key": "КЛЮЧ_ИЗ_TMDB",
"directories": [
"/media/downloads/complete"
],
"category_hints": {
"/media/cartoons": "cartoon",
"/media/series": "series",
"/media/documentaries": "documentary",
"/media/movies": "movie"
},
"output": {
"movies": "/media/movies",
"series": "/media/series",
"cartoons": "/media/cartoons",
"documentaries": "/media/documentaries"
},
"dry_run": false
}
TMDB API key: получить на https://www.themoviedb.org/settings/api (бесплатно)
Шаг 10 — Dry run и ручная проверка
# Dry run — показывает что будет сделано
sudo docker run --rm \
-v /srv/.../media:/media \
-v /srv/.../docker/media-sync:/config \
kodi-media-sync:arm64 --dry-run
# Проверить проблемные документалки
sudo docker run --rm ... kodi-media-sync:arm64 --dry-run \
--path "/media/downloads/complete/знакомимся с кабанами"
# Проверить NFO файлы
cat /srv/.../media/movies/SomeMovie/SomeMovie.nfo
Что проверить:
- Фильмы попали в movies/, мультфильмы в cartoons/ и т.д.
- NFO-файлы созданы и содержат правильные title/year/plot
- Документалки с нестандартными названиями найдены через TMDB
- Торренты в Transmission показывают правильные пути
Шаг 11 (новый) — Встроенный агент для unresolved файлов
Концепция
Когда стандартный resolve-пайплайн (TMDB → KP → IMDb → WebSearch) не смог идентифицировать файл, вместо записи "unresolved" — запускается LLM-агент с инструментами. Агент работает как последний уровень: пробует поиск по-другому, подтверждает кандидата, либо честно записывает "не могу".
Активация: media-pipeline agent --config /config/config.json [--path ...]
Или автоматически после resolve если остались unresolved регионы.
Архитектура агента
Промпт (системный)
You are a media file identifier. You help identify what a file is so it can be
categorized correctly in a media library.
You are given:
- File path and name
- If the file is NOT in a root media folder: full listing of its parent directory
(helps distinguish: single movie, series season pack, documentary collection, etc.)
- Any context already gathered (partial TMDB/KP results, failed queries)
Your job: identify the content and return a structured JSON result.
Tools available:
- search(query) → DuckDuckGo HTML results (titles, URLs, snippets)
- fetch(url) → raw page content (use for TMDB/IMDb/KP specific pages)
Files can be: movie, series, cartoon, documentary, music album, game, book,
software, or other. Support "uncategorizable" as last resort.
Always prefer TMDB ID when available. Think step by step. Use tools to verify.
Инструменты
// Поверх существующего DuckDuckGo HTML scraper (уже есть в IMDbAPI/WebSearchStrategy)
struct AgentSearchTool {
func search(_ query: String) async -> [SearchResult] // DDG HTML, top 5
func fetch(_ url: URL) async -> String // curl-like, обрезан до 8k символов
}
Выходной JSON (с валидацией)
{
"status": "resolved" | "uncategorizable" | "needs_manual",
// Если resolved:
"media_type": "movie" | "series" | "cartoon" | "documentary" |
"music" | "game" | "book" | "software" | "other",
"title": "Название на языке оригинала",
"title_ru": "Русское название (если известно)",
"year": 2024,
// IDs (хотя бы один обязателен для resolved):
"tmdb_id": "12345",
"imdb_id": "tt1234567",
"kp_id": "12345",
// Для сериалов:
"season": 1,
"episode": 5, // если отдельный эпизод
"is_season_pack": true,
"episodes_count": 10,
// Метаданные:
"genres": ["драма", "триллер"],
"confidence": 0.9, // 0.0–1.0, агент сам оценивает
"notes": "...", // почему uncategorizable или что смущает
// Для группировки:
"is_sideband": false, // субтитры, NFO, постер — не самостоятельный контент
"parent_title": "...", // если sideband — к чему относится
}
Валидация: после каждого ответа LLM — JSON schema check. Если невалидный — повторный запрос с описанием ошибки (до 2 попыток исправить).
Хранилище unresolved файлов
/srv/.../docker/media-pipeline/unresolved.json
[
{
"path": "/media/downloads/complete/Знакомимся с кабанами",
"added_at": "2026-05-13T04:00:00Z",
"last_attempt": "2026-05-13T04:00:00Z",
"attempts": 1,
"agent_notes": "Не нашлось в TMDB/KP, агент тоже не смог — слишком общее название",
"status": "needs_manual"
}
]
Force-resolve через CLI
# Разрезолвить конкретный файл вручную (из файла)
media-pipeline resolve-manual \
--config /config/config.json \
--path "/media/downloads/complete/Знакомимся с кабанами" \
--result '{"status":"resolved","media_type":"documentary","title":"Meet the Boars","tmdb_id":"12345",...}'
# Или из файла (удобно для скилла Кракена):
media-pipeline resolve-manual \
--config /config/config.json \
--from-file /tmp/manual-resolve.json
Что делает resolve-manual:
- Берёт готовый JSON результат
- Применяет (создаёт NFO, перемещает если нужно, обновляет Transmission пути)
- Удаляет запись из
unresolved.json - Пишет в лог
Cron: почасовой обход + Hermes notify
Скрипт-чекер /srv/.../docker/media-pipeline/check-unresolved.sh:
#!/bin/bash
# Запускает agent только если есть новые unresolved файлы
UNRESOLVED=/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/media-pipeline/unresolved.json
# Нет файла — нечего делать
[ -f "$UNRESOLVED" ] || exit 0
# Считаем needs_manual записи
COUNT=$(jq '[.[] | select(.status == "needs_manual")] | length' "$UNRESOLVED" 2>/dev/null || echo 0)
# Нет needs_manual — тихо выходим (Hermes не получит пустое сообщение)
[ "$COUNT" -gt 0 ] || exit 0
echo "UNRESOLVED_COUNT=$COUNT"
# Вывод идёт в Hermes cron prompt как контекст
jq '[.[] | select(.status == "needs_manual") | {path, added_at, agent_notes}]' "$UNRESOLVED"
Hermes cron (каждый час, Кракен):
Schedule: 0 * * * *
Script: /srv/.../docker/media-pipeline/check-unresolved.sh
Prompt:
Скрипт вернул список нераспознанных медиафайлов (вывод выше).
Для каждого файла:
1. Посмотри на путь и agent_notes
2. Если можешь идентифицировать (поиск, здравый смысл) — вызови:
docker run --rm ... media-pipeline resolve-manual --path "..." --result '{...}'
3. Если не можешь — отправь уведомление в Telegram с описанием файла и попроси
меня разрезолвить вручную.
Не присылай сообщение если всё уже разрезолвлено или список пустой.
Скрипт запускается до обращения к модели — если пустой вывод, Hermes не получает запрос. Нет пустых сообщений.
Реализация в media_files_db
Новые файлы:
Sources/MediaPipeline/Agent/
AgentCommand.swift ← CLICommand: media-pipeline agent
AgentSession.swift ← LLM loop: prompt → tools → validate → save
AgentTools.swift ← search() + fetch() wrappers
AgentResultSchema.swift ← AgentResult struct + JSON validation
UnresolvedStore.swift ← чтение/запись unresolved.json
Sources/MediaPipeline/ResolveManual/
ResolveManualCommand.swift ← media-pipeline resolve-manual
Зависимости уже есть:
ChatGPTAPI— LLM клиент (переключён на OpenRouter)IMDbAPI/ DDG HTML scraper — search toolHTTPClient— fetch tool- Кеш — все LLM вызовы кешируются через
ChatGPTAPI.sendPrompt(cacheKey:)
Оценка объёма: ~400-500 строк Swift, 2-3 дня работы.
Jellyfin уже работает на Кракене: http://192.168.1.14:8096
Категории библиотек:
| Библиотека | Тип | Путь | Язык |
|---|---|---|---|
| Детские мультфильмы | Animated Movies | /media/cartoons | ru |
| Детские сериалы | Series | /media/cartoons-series | ru |
| Документальные | Movies | /media/documentaries | ru |
| Сериалы | TV Shows | /media/series | ru |
| Фильмы | Movies | /media/movies | ru |
Настройки библиотеки:
- Metadata Language: Russian (или auto-detect)
- NFO reading: включить (Kodi-compatible NFO)
- Sort by: Date Added (поле из NFO), Rating, Title
Сортировка и фильтрация в UI (Jellyfin):
- Детские: отдельная коллекция "Детям" → мультфильмы + детские сериалы
- Можно использовать Collections для кастомных подборок
- Recently Added — показывает по дате скачки (metadata: dateadded в NFO)
- Рейтинг: IMDB/TMDB — парсится из NFO
<rating>тег
Шаг 12 — Transmission download-complete hook
Transmission вызывает скрипт при завершении скачки:
# settings.json Transmission (в /config/):
# "script-torrent-done-enabled": true,
# "script-torrent-done-filename": "/usr/local/bin/on-download-complete.sh"
/usr/local/bin/on-download-complete.sh (внутри контейнера):
#!/bin/bash
# Transmission передаёт переменные:
# TR_TORRENT_NAME, TR_TORRENT_DIR, TR_TORRENT_ID, TR_APP_VERSION
logger "Transmission: download complete: $TR_TORRENT_NAME"
# Запустить media-sync для этой конкретной папки
docker run --rm \
-v /srv/.../media:/media \
-v /srv/.../docker/media-sync:/config \
kodi-media-sync:arm64 \
--path "$TR_TORRENT_DIR/$TR_TORRENT_NAME"
⚠️ Скрипт должен быть доступен изнутри контейнера Transmission, или монтируется туда. Альтернатива: Transmission вызывает скрипт на хосте → хост запускает docker run.
Шаг 13 — Hermes cron на Кракене
Добавить через Hermes на Кракене:
Крон: каждые 12 часов
Задача: запустить kodi-media-sync --dry-run на папке downloads, прислать отчёт в Telegram
Cron job (добавить в Hermes Кракена):
Schedule: 0 */12 * * *
Prompt: Запусти kodi-media-sync на Кракене:
sudo docker run --rm \
-v /srv/.../media:/media \
-v /srv/.../docker/media-sync:/config \
kodi-media-sync:arm64 --dry-run
Пришли отчёт: что было распознано, что нет.
Шаг 14 — Rutracker / торрент-поиск скилл
Добавить скилл Кракену:
- Поиск по Rutracker (может требовать авторизацию)
- Альтернативы: Jackett (агрегатор) + Prowlarr
- Добавление торрента в Transmission через RPC API
Jackett (рекомендуется как агрегатор):
# docker-compose для Jackett:
services:
jackett:
image: linuxserver/jackett
ports:
- "9117:9117"
volumes:
- /srv/.../docker/jackett:/config
Jackett поддерживает Rutracker + сотни других tracker-ов, отдаёт unified API.
Шаг 15 — Музыка: скачка + sync с TrueNAS
Структура:
Kraken: /media/music/
├── [Artist]/
│ └── [Album]/
│ └── *.mp3
TrueNAS: /mnt/RED_2TB/storage/Music/ (master copy)
kodi-media-sync — расширение для музыки:
- Идентификация: MusicBrainz / Beets
- Тегирование через
beets(python tool) - Sync Kraken → TrueNAS через rsync
Jellyfin музыкальная библиотека:
- Тип: Music
- Путь: /media/music
- Плеер: Jellyfin + Finamp на Android
Технические детали
HTPC
- IP: 192.168.1.86
- SSH:
ssh -i ~/.ssh/id_rsa bazzite@192.168.1.86 - Data mount:
/run/media/bazzite/Data/ - WoL MAC:
B0:6E:BF:60:6D:64
Крaken
- IP: 192.168.1.14
- SSH:
ssh -i ~/.ssh/id_rsa 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
kodi-media-sync
- Исходники:
~/Developer/kodi-media-sync/(Mac) - Язык: Swift 5.9+
- Зависимости: swift-argument-parser
- Target: Linux ARM64 (aarch64)
TrueNAS (Taiga)
- SSH:
truenas_admin@mallexxx.duckdns.org - Transmission config:
/mnt/RED_2TB/docker/transmission/ - Downloads:
/mnt/RED_2TB/storage/Downloads/
Журнал выполнения
2026-05-13 — Активная работа
- Разбужен HTPC, сон заблокирован:
systemctl masksleep/suspend +loginctl enable-linger bazzite - 4x rsync запущены через
systemd-run --user(выживают без SSH): ~1.3TB - Torrent-база Windows найдена (7 торрентов,
D:/Movies), скопирована в/tmp/transmission-backup/ - Изучен MediaFilesScraper: полный Swift 6.0 SPM, бинарь
media-pipeline, команды scan/resolve/apply/sync - Пропатчен AVFoundationMediaMetadataReader для Linux (
#if canImport(AVFoundation)+ stub) Package.swift: платформа понижена до macOS(.v14) для совместимостиDockerfile.kraken+docker-compose.kraken.ymlнаписаныconfig.kraken.jsonготов: Transmission RPC, TMDB/KP, все провайдеры,dry_run=true- macOS build запущен в фоне для проверки компиляции
2026-05-13 — Сессия 2 (Docker build + resolve)
Docker build media-pipeline:kraken
.dockerignoreдобавлен — исключает.build/(6.7GB), OOM устранён- Colima: 24GB RAM, 8 CPU;
--memory=20g -j 1 - Linux-фиксы:
#if os(Linux)для CF-encoding (RutrackerAPI),FoundationNetworkingв 3 файлах - Образ
media-pipeline:kraken(78MB) собран и задеплоен на Кракен ✅
Resolve dry run (первый)
- 6 region'ов; 1 resolved: Black Mirror S06E01 →
seriesEpisodesNumbered, support=0.90 ✅ - Проблема:
ProviderSearchStrategy.aggregateCandidates—originalVariantвыбирался по non-ASCII (кириллица), которая давала 0 результатов от TMDB - Фикс:
originalVariant→ первый вариант с реальными результатами; high-confidence (≥0.85) single-variant принимается без unique-result требования - Все 6 ProviderSearchStrategy тестов прошли, образ пересобран и задеплоен
on-download-complete.shустановлен, прописан в Transmission settings ✅
Текущее состояние
- rsync идёт (~19-20GB из сотен GB по категориям)
- Следующий dry run — когда rsync скопирует больше файлов с правильной структурой подпапок
Сессия 3 — 2026-05-13 (agent + deploy scripts)
Реализованы новые компоненты (kraken-agent ветка, коммиты 22abd5a, 4c91ddd):
UnresolvedEntry / UnresolvedStore — персистентный JSON-реестр нераспознанных регионов:
- Автоматически обновляется после каждого
resolve/sync - Поддерживает merge (upsert + remove resolved), atomic write
- Хранит: path, reason, queryNames, attemptCount, agentHint
- Файл:
/config/unresolved.json(рядом с config.json)
media-pipeline resolve-manual — force-resolve по ID:
media-pipeline resolve-manual --media-path /media/movies/Сталкер \
--tmdb 11 --type movie
- TMDb / IMDb / Kinopoisk lookup → NFO write → удаление из unresolved.json
media-pipeline agent — LLM-assisted batch:
media-pipeline agent --apply --limit 30
- Pre-check: TMDb text search (score ≥ 0.85) → resolve без LLM
- Fallback: OpenRouter/GPT-4o-mini → corrected title → TMDb lookup → NFO
- Без
--apply: сохраняет hints в unresolved.json для ревью
Deploy scripts:
scripts/kraken-agent-cron.sh— запуск агента 2x/день, pre-check на пустой unresolved.jsonscripts/on-download-complete.sh— download-complete hook: sync → torrent-set-locationscripts/transmission-location-update.sh— RPC helperdeploy/docker-compose.jellyfin.yml— Jellyfin + 5 библиотек + RPi5 V4L2 transcode
Что осталось
- Дождаться SSH на Кракен + вставить OpenRouter ключ в config.json
- Docker build нового образа (собирается через Colima, proc_5bfba754244a)
- Задеплоить новый образ на Кракен (
docker save | scp | docker load) - Дождаться rsync (~1.3TB), прогнать полный dry run
- torrent-set-location для 7 перенесённых торрентов (после rsync)
- Настроить Jellyfin (
deploy/docker-compose.jellyfin.yml→docker compose up -d) - Hermes cron на Кракене:
scripts/kraken-agent-cron.sh2x/day - Rutracker-скилл (шаг 14)