Files
jellybit/docs/specs/recognition.md
T
avandClaude Opus 4.8 7a774ad53d Беклог: перенос из Tududi в docs/backlog (файл на задачу + индекс)
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>
2026-07-08 11:43:37 +03:00

11 KiB
Raw Blame History

Распознавание контента

Источник истины переехал в OpenSpec. Актуальные требования — openspec/specs/recognition/ (разбор сигналов LLM) и openspec/specs/metadata-match/ (сверка с внешними базами). Этот файл остаётся справочным нарративом; при расхождении верна спека OpenSpec.

Задача

По доступным сигналам определить: фильм или сериал; каноническое название и год; для сериала — сезон(ы) и соответствие файлов сериям; при включённых базах — провайдер и его id. На выходе — план раскладки, оценка уверенности и решение «авто или review» (как оно встраивается в машину состояний — workflow.md, состояния recognizing/linking/review).

Сигналы

  • Имя торрента и структура каталогов.
  • Список файлов с размерами и расширениями. Абсолютный путь источника восстанавливаем как save_path из qBit (= хост-путь; path_map обычно тождественен) + относительное имя файла из /torrents/files. Имя уже включает корневую папку для многофайловых торрентов, поэтому префикс — именно save_path, а не content_path (последний удвоил бы корневую папку и сломал бы однофайловые раздачи).
  • Текстовый контекст человека (+ накопленные подсказки из review).
  • Распарсенное сообщение торрент-бота (если через Telegram): название с годом, качество, переводы — см. пример в BRIEF.md.

Все сигналы недоверенные — имя торрента, сообщение бота и контекст управляются извне и могут содержать инъекции. Выход LLM не отвечает за безопасность: целевой путь всё равно санитизируется и проверяется на выход за пределы библиотеки (см. architecture.md → «Раскладка файлов»).

Конвейер

  1. Пред-парс имени релиза (go-ptn): черновые название/год/сезон/ серия и качество. Грубо, но бесплатно.
  2. LLM (через провайдер-абстракцию, см. ниже): получает сигналы и пред-парс, возвращает структурированный план в нашей схеме. Хорошо берёт русские релиз-имена. Длинный список файлов усекаем/семплируем под контекст модели.
  3. Сверка с базой (если включена TMDB/TVDB/TVMaze): ищем по названию+году, берём официальный id и каноническое имя, собираем кандидатов. TVMaze — без ключа, только сериалы; внешний id (TVDB/IMDb) из externals идёт в имя папки.
    • Поиск по нескольким названиям в порядке убывания силы ключа: original_title → локализованное titleprovider_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. На приёме срезаем
    при ошибке разбора ретраим, передавая модели саму ошибку и схему в
    промпте, до `llm.max_retries`. Если так и не распарсилось — уходим в
    **review** (не в `failed`) с причиной «ответ LLM не разобран».
    
  • Новые типы (напр. нативный anthropic) добавляются, не трогая recognize.

Модель уверенности

Почему авто только при матче в базе, а не по самооценке LLM — ADR-2026-06-13-auto-link-requires-db-match.

Авто-раскладка — только если выполнено всё:

  1. Подтверждённый матч в базе — единственный сильный результат TMDB/TVDB/TVMaze по названию+году, давший provider_id. Нет матча (или база выключена) → всегда review. Это и закрывает основной кейс (рус/аниме часто отсутствуют в базах), и снимает риск «LLM придумал».
  2. Структурная валидация без предупреждений:
    • фильм: ровно один основной видеофайл (семплы/экстра/ignore отброшены);
    • сериал: число серий бьётся с базой, нумерация S·E консистентна, без пропусков, дублей и неоднозначных спецвыпусков.
  3. Согласованность сигналов — пред-парс (go-ptn) и LLM не противоречат по типу/названию/году.

Самооценку LLM (confidence) учитываем как вспомогательный сигнал, но не как единственный гейт: она плохо откалибрована и поддаётся инъекции. Решают матч в базе и валидация.

Иначе — review (review-ux.md) с явной причиной.

Что делаем с краёв

  • Семплы/«экстра»/мусор → роль ignore (эвристики размер/имя + LLM).
  • Внешние субтитры (.srt, .ass, пары VobSub .idx+.sub) привязываем к видео и именуем по Jellyfin (*.ru.srt).
  • Сезон-паки разбираем по сериям; смешанные паки, спецвыпуски (Season 00), двойные серии (SxxEyy-Eyy) — через per-file season/episode; любая неоднозначность → review.
  • Аниме с абсолютной нумерацией — отдельный крайний случай, задача в беклоге.

На будущее

go-ptn слабее питоновского guessit. Если точности пред-парса не хватит — завернуть guessit лёгким сервисом-спутником (один файл рядом с бинарём). Задача «guessit как сервис-спутник» в беклоге.