115 lines
8.0 KiB
Markdown
115 lines
8.0 KiB
Markdown
# Распознавание контента
|
||
|
||
## Задача
|
||
|
||
По доступным сигналам определить: фильм или сериал; каноническое название
|
||
и год; для сериала — сезон(ы) и соответствие файлов сериям; при включённых
|
||
базах — провайдер и его id. На выходе — план раскладки, оценка уверенности
|
||
и решение «авто или review».
|
||
|
||
## Сигналы
|
||
|
||
- Имя торрента и структура каталогов.
|
||
- Список файлов с размерами и расширениями. Абсолютный путь источника
|
||
восстанавливаем как `save_path`/`content_path` из qBit (после трансляции
|
||
`path_map`) + относительное имя файла; учитываем одно- и многофайловые
|
||
торренты.
|
||
- Текстовый контекст человека (+ накопленные подсказки из review).
|
||
- Распарсенное сообщение торрент-бота (если через Telegram): название с
|
||
годом, качество, переводы — см. пример в [BRIEF.md](../../BRIEF.md).
|
||
|
||
**Все сигналы недоверенные** — имя торрента, сообщение бота и контекст
|
||
управляются извне и могут содержать инъекции. Выход LLM не отвечает за
|
||
безопасность: целевой путь всё равно санитизируется и проверяется на
|
||
выход за пределы библиотеки (см. architecture.md → «Раскладка файлов»).
|
||
|
||
## Конвейер
|
||
|
||
1. **Пред-парс** имени релиза (`go-ptn`): черновые название/год/сезон/
|
||
серия и качество. Грубо, но бесплатно.
|
||
2. **LLM** (через провайдер-абстракцию, см. ниже): получает сигналы и
|
||
пред-парс, возвращает структурированный план в нашей схеме. Хорошо
|
||
берёт русские релиз-имена. Длинный список файлов усекаем/семплируем под
|
||
контекст модели.
|
||
3. **Сверка с базой** (если включена TMDB/TVDB): ищем по названию+году,
|
||
берём официальный id и каноническое имя, собираем кандидатов.
|
||
4. **Оценка уверенности** и решение: авто или review.
|
||
|
||
## Структура ответа LLM (предварительная)
|
||
|
||
```
|
||
type movie | series
|
||
title каноническое название
|
||
original_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|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` со схемой где поддерживается; иначе json-режим или
|
||
tool-call); на приёме срезаем ```-ограждения и извлекаем JSON,
|
||
**валидируем в Go**, ретраим со схемой-в-промпте до `llm.max_retries`;
|
||
если так и не распарсилось — уходим в **review** (не в `failed`) с
|
||
причиной «ответ LLM не разобран». Серверы заметно различаются по
|
||
поддержке строгих схем, особенно мелкие локальные модели.
|
||
- Новые типы (напр. нативный `anthropic`) добавляются, не трогая
|
||
`recognize`.
|
||
|
||
## Модель уверенности
|
||
|
||
Авто-раскладка — только если выполнено **всё**:
|
||
|
||
1. **Подтверждённый матч в базе** — единственный сильный результат
|
||
TMDB/TVDB по названию+году, давший `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.
|
||
- Аниме с абсолютной нумерацией — отдельный крайний случай, см.
|
||
[drafts/ideas.md](../drafts/ideas.md).
|
||
|
||
## На будущее
|
||
|
||
`go-ptn` слабее питоновского `guessit`. Если точности пред-парса не
|
||
хватит — завернуть `guessit` лёгким сервисом-спутником (один файл рядом с
|
||
бинарём). См. [drafts/ideas.md](../drafts/ideas.md).
|