[2026-05-18] watchlist-sync plan

This commit is contained in:
Alexey Martemyanov
2026-05-18 10:55:57 +06:00
parent 5c3954be25
commit 64c6ca3814
2 changed files with 272 additions and 23 deletions
+252
View File
@@ -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]] — источник данных
+20 -23
View File
@@ -1,33 +1,30 @@
## Inbox Triage — 2026-05-18 (since 2026-05-15T19:37:29)
## Inbox Triage — 2026-05-18 (since 09:34)
### New / Changed — Action Required
**[Ship Review]** — [Ship Review: iOS: Demo tracker blocking after chat in onboarding - 2-5 days](https://app.asana.com/0/0/1214147157456478) *(overdue since May 15)*
- **Costas** (Sun May 17, 13:07): @mentioned you + one other: "If the 3 tasks below are wrapped and QA passed, feel free to mark them complete and merge — even before Design Review is approved." Also asks to **port uncompleted tasks** to follow-up project → [1214592943696234](https://app.asana.com/1/137249556945/project/1208671677432066/task/1214592943696234)
- 3 tasks to check: [1214828145455319](https://app.asana.com/1/137249556945/project/1202406491309510/task/1214828145455319), [1214682678629766](https://app.asana.com/1/137249556945/project/1202406491309510/task/1214682678629766), [1214683268207886](https://app.asana.com/1/137249556945/project/1202926619948002/task/1214683268207886)
- **Alessandro** (Mon May 18, 06:01): Running another round of tests today, will share update.
- **Action**: Verify 3 tasks are complete + QA passed → merge. Port any uncompleted tasks to follow-up project.
**[@mention]** — [DRI <> Project Advisor async for Demo tracker blocking after chat in onboarding](https://app.asana.com/0/0/1213551072611261) — Alessandro at 09:35
---
Context: Alessandro summarised the remaining tasks for the Ship Review and tagged Alex with a specific action list:
1. Mark addressed issues as completed so they're visible to everyone
2. Disable the previous experiment and create a new experiment payload named `onboardingDuckAIQueryTrackersDemoExperiment` (per [this task](https://app.asana.com/1/137249556945/project/1214157224317277/task/1214601039604921))
3. Merge the stacked [PR #4584](https://github.com/duckduckgo/apple-browsers/pull/4584) into the main feature branch for final testing
4. Move any unaddressed nice-to-have tasks to the follow-up project
5. Note: Alex is macOS maintenance DRI this week — delegate investigations where possible and keep focus on closing the Ship Review
**[@mention]** — [DRI <> Project Advisor async for iOS Address onboarding conflicts with unified input and ship AI onboarding winner](https://app.asana.com/0/0/1214592851898717) — Alessandro at Mon May 18, 05:22
Context: "would probably be good to create a task with all the design questions and assign it to [designer] so he can go through them once he's back. Given you are both out, should we have an async kick-off for this one?"
Action: Respond — confirm or propose async kick-off; create a design questions task and assign to designer.
**Completed Must-Haves (per Alessandro):**
- ✅ Address Bar Toggle Selection: Revert Copy — addressed in [PR #4855](https://github.com/duckduckgo/apple-browsers/pull/4855)
- ✅ GJ7/ Fix address bar focus dismissing search suggestion dialog — resolved, cannot reproduce
- ✅ GJ8/ Change "Duck.ai" to "Ask AI" — addressed in PR #4855
- ✅ Related task — addressed in PR #4855
**[@mention]** — [DRI <> Project Advisor async…](https://app.asana.com/0/0/1214592851898717) — Costas at Sun May 17, 12:57
Context: "would you mind focused on items that require less design/UX input this week, and ping me on anything that does — so I can prioritize it once working w/c 25th May."
Action: Acknowledge — plan to focus on non-design items; flag any design-dependent blockers.
**⏭️ Still in progress (Must Have):** [GJ7-related task](https://app.asana.com/1/137249556945/project/1206329551987282/task/1214682678629768)
**[@mention]** — [DRI <> Project Advisor async…](https://app.asana.com/0/0/1214592851898717) — Gary at Sat May 16, 06:02
Context: June 15 deadline to ship experiment #1 treatment b (compatible with O-J unified input). Gary wants this kicked off Tuesday. Alessandro is advising. Asks Alex to coordinate with O-J release train.
Action: Coordinate with O-J team on timeline; plan kick-off this week.
---
**[New Assignment]** — [macOS App Maintenance](https://app.asana.com/0/0/1214837564647979) *(due May 22)* — assigned by Rachel M at Fri May 15, 20:47
Context: Rachel M handing maintenance over to you. Ongoing: rebranding fix in progress ([task](https://app.asana.com/1/137249556945/project/1199230911884351/task/1214816707188281)). Also FYI: reports of [visible issue](https://app.asana.com/1/137249556945/project/1204912272578138/task/1214650828403479) being handled.
Action: Acknowledge handover; monitor rebranding fix; be aware of visible issue reports.
**Nice to Haves** (move to follow-up if unaddressed):
- Try a Search or AI chat: Remove pointer
- You've Got This!: Remove pointer
- Try Visiting a Site: Remove Browser Toolbar
- Try Visiting a Site: Background
### New / Changed — Monitoring
- **[Tech input] Permissions on iOS** (task [1214732727456025](https://app.asana.com/0/0/1214732727456025)) — Sveta asked Alex on May 15; Brindy subsequently redirected to Tom Strba as the owner. No action needed from Alex — Tom is handling. Due today (May 18).
_(nothing new)_