Tududi оказался неудобен для ведения беклога проекта — переходим на файлы в репозитории. Каждая задача — отдельный markdown в docs/backlog/ (48 файлов), плюс индекс README.md со списком по приоритетам и хуками. Тело файла хранит исходное описание (контекст, решения, ссылки на спеки/ADR/черновики). CLAUDE.md: источник истины по беклогу теперь docs/backlog/; Tududi понижен до инбокса сырых идей. Живые ссылки в спеках (recognition, architecture, review-ux, jellyfin-layout) «в беклоге (Tududi)» переписаны на прямые ссылки на файлы беклога. Перенесённые задачи удалены из Tududi; завершённые оставлены как история. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
145 lines
11 KiB
Markdown
145 lines
11 KiB
Markdown
# Распознавание контента
|
||
|
||
> **Источник истины переехал в OpenSpec.** Актуальные требования —
|
||
> `openspec/specs/recognition/` (разбор сигналов LLM) и
|
||
> `openspec/specs/metadata-match/` (сверка с внешними базами). Этот файл остаётся
|
||
> справочным нарративом; при расхождении верна спека OpenSpec.
|
||
|
||
## Задача
|
||
|
||
По доступным сигналам определить: фильм или сериал; каноническое название
|
||
и год; для сериала — сезон(ы) и соответствие файлов сериям; при включённых
|
||
базах — провайдер и его id. На выходе — план раскладки, оценка уверенности
|
||
и решение «авто или review» (как оно встраивается в машину состояний —
|
||
[workflow.md](workflow.md), состояния `recognizing`/`linking`/`review`).
|
||
|
||
## Сигналы
|
||
|
||
- Имя торрента и структура каталогов.
|
||
- Список файлов с размерами и расширениями. Абсолютный путь источника
|
||
восстанавливаем как `save_path` из qBit (= хост-путь; `path_map` обычно
|
||
тождественен) + относительное имя файла из `/torrents/files`. Имя уже
|
||
включает корневую папку для многофайловых торрентов, поэтому префикс —
|
||
именно `save_path`, а не `content_path` (последний удвоил бы корневую
|
||
папку и сломал бы однофайловые раздачи).
|
||
- Текстовый контекст человека (+ накопленные подсказки из review).
|
||
- Распарсенное сообщение торрент-бота (если через Telegram): название с
|
||
годом, качество, переводы — см. пример в [BRIEF.md](../../BRIEF.md).
|
||
|
||
**Все сигналы недоверенные** — имя торрента, сообщение бота и контекст
|
||
управляются извне и могут содержать инъекции. Выход LLM не отвечает за
|
||
безопасность: целевой путь всё равно санитизируется и проверяется на
|
||
выход за пределы библиотеки (см. architecture.md → «Раскладка файлов»).
|
||
|
||
## Конвейер
|
||
|
||
1. **Пред-парс** имени релиза (`go-ptn`): черновые название/год/сезон/
|
||
серия и качество. Грубо, но бесплатно.
|
||
2. **LLM** (через провайдер-абстракцию, см. ниже): получает сигналы и
|
||
пред-парс, возвращает структурированный план в нашей схеме. Хорошо
|
||
берёт русские релиз-имена. Длинный список файлов усекаем/семплируем под
|
||
контекст модели.
|
||
3. **Сверка с базой** (если включена TMDB/TVDB/TVMaze): ищем по
|
||
названию+году, берём официальный id и каноническое имя, собираем
|
||
кандидатов. TVMaze — без ключа, только сериалы; внешний id
|
||
(TVDB/IMDb) из `externals` идёт в имя папки.
|
||
- **Поиск по нескольким названиям** в порядке убывания силы ключа:
|
||
`original_title` → локализованное `title` → `provider_hint`. Базы
|
||
индексированы прежде всего по оригинальным названиям, поэтому
|
||
оригинал — первым; останавливаемся, как только очередной ключ дал
|
||
единичный сильный матч. Пустые и нормализованно-дублирующие ключи
|
||
пропускаем (русский фильм, где оригинал = локализованное, дёргает
|
||
базу один раз). Кандидатов для review копим из всех заходов.
|
||
- **Локаль TMDB:** запрос передаёт `language` (по умолчанию `ru-RU`,
|
||
настраивается `[metadata.tmdb].language`). Влияет только на
|
||
локализованный `Title`/`Name`; `original_title`/`original_name`
|
||
остаётся на языке оригинала, поэтому оригинальная сторона сравнения
|
||
не страдает, а русская — сходится.
|
||
- **Нормализация названий** при сравнении сводит `ё`→`е` («Тёмный» и
|
||
«Темный» — одно название).
|
||
4. **Оценка уверенности** и решение: авто или review.
|
||
|
||
## Структура ответа LLM (предварительная)
|
||
|
||
```
|
||
type movie | series
|
||
title каноническое название
|
||
original_title оригинальное (обычно англ.) название — заполняется всегда:
|
||
нет отдельного / российский контент → дублирует title;
|
||
при неуверенности дублируем, а не выдумываем
|
||
year год
|
||
provider_hint строка для поиска в базе (НЕ итоговый id)
|
||
files[] { src, role: main|episode|subtitle|extra|sample|ignore,
|
||
season?, episode? } # season/episode — на файл
|
||
confidence 0..1 — самооценка модели (вспомогательный сигнал)
|
||
notes пояснения, неоднозначности
|
||
```
|
||
|
||
Сезон/серия — **на файле**: так выражаются мультисезонные паки,
|
||
спецвыпуски и смешанные раскладки; отдельного скалярного `season` нет.
|
||
`provider_hint` — только подсказка для поиска; итоговые `provider`
|
||
(`tmdb|tvdb|tvmaze|none`) и `provider_id` появляются после сверки с базой
|
||
и хранятся отдельно.
|
||
|
||
## Провайдер LLM
|
||
|
||
Доступ к LLM — за интерфейсом; реализация выбирается полем `[llm].type`
|
||
(дискриминатор). Это позволяет подключать локальные модели и сторонние
|
||
(в т.ч. китайские) эндпоинты — ради экономии и независимости от вендора.
|
||
|
||
- Первый и пока единственный тип — **`openai-compat`**: OpenAI-совместимый
|
||
Chat Completions API (`base_url` + `api_key` + `model`). Подходят
|
||
локальные серверы (LM Studio, llama.cpp, Ollama) и облачные совместимые
|
||
провайдеры (DeepSeek, Qwen и др.).
|
||
- **Структурированный вывод надёжно:** просим JSON-режим
|
||
(`response_format: {"type":"json_object"}`) — это поддерживают и мелкие
|
||
локальные модели, в отличие от строгих JSON Schema. На приёме срезаем
|
||
```-ограждения и извлекаем JSON, **валидируем в Go** против нашей схемы;
|
||
при ошибке разбора ретраим, передавая модели саму ошибку и схему в
|
||
промпте, до `llm.max_retries`. Если так и не распарсилось — уходим в
|
||
**review** (не в `failed`) с причиной «ответ LLM не разобран».
|
||
- Новые типы (напр. нативный `anthropic`) добавляются, не трогая
|
||
`recognize`.
|
||
|
||
## Модель уверенности
|
||
|
||
Почему авто только при матче в базе, а не по самооценке LLM —
|
||
[ADR-2026-06-13-auto-link-requires-db-match](../adr/ADR-2026-06-13-auto-link-requires-db-match.md).
|
||
|
||
Авто-раскладка — только если выполнено **всё**:
|
||
|
||
1. **Подтверждённый матч в базе** — единственный сильный результат
|
||
TMDB/TVDB/TVMaze по названию+году, давший `provider_id`. **Нет матча (или
|
||
база выключена) → всегда review.** Это и закрывает основной кейс
|
||
(рус/аниме часто отсутствуют в базах), и снимает риск «LLM придумал».
|
||
2. **Структурная валидация** без предупреждений:
|
||
- фильм: ровно один основной видеофайл (семплы/экстра/ignore отброшены);
|
||
- сериал: число серий бьётся с базой, нумерация S·E консистентна, без
|
||
пропусков, дублей и неоднозначных спецвыпусков.
|
||
3. **Согласованность сигналов** — пред-парс (`go-ptn`) и LLM не
|
||
противоречат по типу/названию/году.
|
||
|
||
Самооценку LLM (`confidence`) учитываем как вспомогательный сигнал, но
|
||
**не как единственный гейт**: она плохо откалибрована и поддаётся
|
||
инъекции. Решают матч в базе и валидация.
|
||
|
||
Иначе — **review** ([review-ux.md](review-ux.md)) с явной причиной.
|
||
|
||
## Что делаем с краёв
|
||
|
||
- Семплы/«экстра»/мусор → роль `ignore` (эвристики размер/имя + LLM).
|
||
- Внешние субтитры (`.srt`, `.ass`, пары VobSub `.idx`+`.sub`) привязываем
|
||
к видео и именуем по Jellyfin (`*.ru.srt`).
|
||
- Сезон-паки разбираем по сериям; смешанные паки, спецвыпуски (`Season
|
||
00`), двойные серии (`SxxEyy-Eyy`) — через per-file season/episode;
|
||
любая неоднозначность → review.
|
||
- Аниме с абсолютной нумерацией — отдельный крайний случай,
|
||
[задача в беклоге](../backlog/anime-absolyutnaya-numeraciya.md).
|
||
|
||
## На будущее
|
||
|
||
`go-ptn` слабее питоновского `guessit`. Если точности пред-парса не
|
||
хватит — завернуть `guessit` лёгким сервисом-спутником (один файл рядом с
|
||
бинарём). Задача [«guessit как сервис-спутник»](../backlog/guessit-sputnik.md)
|
||
в беклоге.
|