Files
obsidian-vault/family/projects/watchlist-sync.md
T

262 lines
11 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-18'
updated: '2026-05-18'
status: planning
tags:
- kraken
- jellyfin
- radarr
- sonarr
- watchlist
- project
---
# Watchlist Sync
> Цель: автоматическая загрузка непросмотренных фильмов/сериалов из `family/documents/movies-watchlist.md` через Radarr/Sonarr, синхронизация просмотренных обратно из Jellyfin, и AI-curated рекомендации на основе профиля.
Репо: `~/Developer/watchlist-sync` → GitHub `mallexxx/watchlist-sync`
---
## Архитектура
```
movies-watchlist.md (Obsidian vault)
watchlist-sync (Python CLI)
├── parse — читает .md, классифицирует [ ] / [x] / ❓ / ⬇️
├── resolve — TMDB + KP API → находит ID, тип (movie/series), рейтинг
├── sync-down — непросмотренные → Radarr / Sonarr (батчами)
└── sync-up — Jellyfin watched history → [x] в .md + перемещение наверх
Hermes Kraken cron job
├── каждые 6ч: resolve ❓ → LLM disambiguation → уточнение у Alex
└── каждые 24ч: sync-up из Jellyfin
```
---
## Stage 1: Core Sync
### 1.1 Парсинг watchlist.md
**Формат строк:**
- `- [ ] Название` — непросмотрено, не добавлено
- `- [ ] ⬇️ Название` — добавлено в очередь (скачивается/скачано)
- `- [ ] ❓ Название` — ambiguous, ждёт ручного уточнения
- `- [x] Название` — просмотрено
**Источник файла на Кракене:** `/home/kraken/obsidian/family/documents/movies-watchlist.md`
(vault смонтирован в Hermes-контейнере как `/vault`)
### 1.2 Резолв названий (TMDB + KP)
**Алгоритм:**
1. Для каждого `- [ ]` без `⬇️` и `❓` — запрос к TMDB `/search/multi` (русский язык)
2. Если 0 результатов → fallback KP `/v1.4/movie/search?query=...`
3. **Однозначный результат** (score > 0.8, рейтинг ≥ 6.0, один кандидат) → определить тип (movie/tv), взять ID
4. **Ambiguous** (несколько кандидатов с близким score, или score < 0.8) → добавить `❓` перед названием, пропустить
5. **Не найдено** → добавить `❓`, пропустить
**Пороги:**
- `min_rating: 6.0` — ниже 6 игнорировать (защита от мусора)
- `min_score: 0.75` — сходство названия (Jaccard/fuzzy)
- `max_candidates_for_auto: 1` — при 2+ близких кандидатах → ambiguous
**Тип контента:**
- TMDB `media_type: movie` → Radarr
- TMDB `media_type: tv` → Sonarr
- KP `type: MOVIE/MINI_SERIES/TV_SERIES` → маппинг аналогично
### 1.3 Загрузка (sync-down)
**Батчинг:**
- Максимум **5 новых** в одном прогоне (защита от flood)
- Приоритет: порядок в файле (выше = важнее)
- После добавления в Radarr/Sonarr → заменить `- [ ]` на `- [ ] ⬇️` в .md
**API:**
- Radarr: `POST /api/v3/movie` с `addOptions.searchForMovie: true`
- Sonarr: `POST /api/v3/series` с `addOptions.searchForMissingEpisodes: true`
- Проверка дублей перед добавлением (GET by tmdbId/tvdbId)
**Конфиг** (`.env` / `config.json`, не в репо):
```
RADARR_URL=http://localhost:7878
RADARR_KEY=...
SONARR_URL=http://localhost:8989
SONARR_KEY=...
TMDB_KEY=...
KP_KEY=...
JELLYFIN_URL=http://localhost:8096
JELLYFIN_TOKEN=...
WATCHLIST_PATH=/vault/family/documents/movies-watchlist.md
```
### 1.4 Обратная синхронизация (sync-up)
**Алгоритм:**
1. Jellyfin API: `GET /Users/{userId}/Items?IsPlayed=true&IncludeItemTypes=Movie,Series`
2. Для каждого просмотренного — fuzzy-match по названию с `⬇️` строками в .md
3. При совпадении (score > 0.85):
- Заменить `- [ ] ⬇️ Название``- [x] Название`
- Переместить строку на **первое место** среди `[x]` записей (под последней `[ ]`)
4. Обновить `.md`, git commit
**Jellyfin userId:** получать через `GET /Users` (admin user = alex)
### 1.5 Cron Jobs (Hermes Eagle)
Два детерминированных shell-script крона:
| Job | Расписание | Скрипт |
|-----|-----------|--------|
| `watchlist-nightly` | ежедн. 01:00 | `~/.hermes/scripts/watchlist-nightly.sh` |
| `watchlist-discover` | вс 09:00 | `~/.hermes/scripts/watchlist-discover.sh` |
Используют `~/scripts/sync-vault.sh` для всех git-операций (stash/pull/pop/commit/push). **Не агентные** — скрипты вызывают CLI напрямую, никакого LLM в pipeline.
**Cron: каждые 6 часов**
Промпт агенту на Кракене:
1. Прочитать `.md`, найти все `❓` строки
2. Для каждой — запросить TMDB/KP с расширенными вариантами запроса (LLM генерирует варианты: оригинальное название, транслит, английский перевод)
3. Если нашёл с уверенностью → убрать `❓`, поставить `⬇️`
4. Если нет → собрать список всех нерезолвленных и спросить Alex в Zulip `#personal::Kraken torrents`:
```
❓ Не смог распознать 3 фильма, уточни:
• "Дворец полански" — возможно "The Palace" (Polanski, 2023)? [да/нет/другое]
• "Space merchants" — фильм или книга Пола? Нашёл несколько.
• "Ради нескольких строчек" — не нашёл. Уточни название.
```
---
## Stage 2: Profile & Recommendations
> После накопления истории просмотров (≥ 50 фильмов)
### 2.1 Анализ профиля
- Топ жанры по просмотренным `[x]`
- Любимые режиссёры / актёры (через TMDB credits)
- Средний рейтинг принятых фильмов
- Паттерны: когда смотрит (с женой / один / семья) — вывести из тегов/времени просмотра в Jellyfin
### 2.2 Curated Lists (AI job, еженедельно)
Три списка, **только рейтинг ≥ 6.0**, **не в watchlist уже**:
| Список | Критерии |
|--------|----------|
| 🎬 Вдвоём с Лизой | Драма, комедия, триллер. Без слишком тяжёлого контента. |
| 💀 Один (трэш/боевики) | Хоррор, боевик, sci-fi, cult. Без ограничений. |
| 👨‍👩‍👧 Семейное с ребёнком | Возраст 7+. Мультфильмы, приключения, комедии. |
**Источники кандидатов:**
- TMDB `/movie/recommendations` на основе топ-10 просмотренных
- TMDB `/movie/similar` для любимых жанров
- KP подборки (русское кино / советская классика)
**Формат вывода** — добавить в конец `movies-watchlist.md`:
```markdown
## 🤖 Рекомендации (2026-W21)
### 🎬 Вдвоём
- [ ] Past Lives (2023) ★8.0 — драма о двух корейцах разлучённых в детстве
...
### 💀 Один
- [ ] Nosferatu (2024) ★7.2 — ремейк Эгgers'а
...
### 👨‍👩‍👧 Семья
- [ ] The Wild Robot (2024) ★8.1 — анимация
...
```
После подтверждения Alex — строки перемещаются в основной список.
---
## Структура проекта
```
~/Developer/watchlist-sync/
├── watchlist_sync/
│ ├── __init__.py
│ ├── parser.py — парсинг .md
│ ├── resolver.py — TMDB + KP API
│ ├── radarr.py — Radarr API client
│ ├── sonarr.py — Sonarr API client
│ ├── jellyfin.py — Jellyfin API client
│ ├── sync_down.py — непросмотренные → arr
│ ├── sync_up.py — Jellyfin → .md
│ └── recommend.py — Stage 2 recommendations
├── config.example.json — шаблон конфига (без секретов)
├── .env.example
├── .gitignore — .env, config.json, *.key
├── pyproject.toml
└── README.md
```
**Конфиг секреты:** только в `.env` (не в репо). `config.example.json` — шаблон.
---
## Имплементация: очерёдность задач
### Фаза 1 (MVP)
- [ ] Инициализировать репо `~/Developer/watchlist-sync`, push на GitHub
- [ ] `parser.py` — парсинг .md, классификация строк
- [ ] `resolver.py` — TMDB `/search/multi` + KP fallback
- [ ] `sync_down.py` — add to Radarr/Sonarr (батч 5), пометить `⬇️`
- [ ] CLI: `watchlist-sync resolve --dry-run` и `--apply`
- [ ] CLI: `watchlist-sync sync-down --batch 5`
### Фаза 2
- [ ] `sync_up.py` — Jellyfin → `[x]` + сортировка
- [ ] CLI: `watchlist-sync sync-up`
- [ ] Cron на Кракене: sync-up каждые 24ч
### Фаза 3
- [ ] AI disambiguation job (Hermes Kraken cron)
- [ ] Уточнение у Alex через Zulip для нерезолвленных
### Фаза 4 (Stage 2)
- [ ] `recommend.py` — профиль + TMDB recommendations
- [ ] Три curated списка с подтверждением
- [ ] Еженедельный cron job
---
## Ключи и секреты
| Секрет | Где хранить |
|--------|-------------|
| TMDB API key | `.env` на Кракене (из `config.json` media-pipeline) |
| KP API key | `.env` (тот же что в media-pipeline config.json) |
| Radarr key | `.env` |
| Sonarr key | `.env` |
| Jellyfin token | `.env` |
Ключи TMDB/KP уже есть в `/srv/.../docker/media-pipeline/config.json` — взять оттуда при деплое.
`.gitignore`:
```
.env
config.json
*.key
__pycache__/
.venv/
```
---
## Связанные проекты
- [[family/projects/media-toolbox-kraken]] — *arr стек, Jellyfin, media-pipeline
- [[family/documents/movies-watchlist]] — источник данных