[2026-05-18] watchlist-sync plan
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
---
|
||||
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 AI Curation Job (Hermes Kraken)
|
||||
|
||||
**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]] — источник данных
|
||||
Reference in New Issue
Block a user