Files
jellybit/docs/specs/jellyfin-layout.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

7.0 KiB

Конвенции раскладки Jellyfin

Источник истины переехал в OpenSpecopenspec/specs/file-layout/ (имена, хардлинки, коллизия, copy-fallback). Владение путём (superseded) и безопасный undo (nlink<=1) — в openspec/specs/state-reconciliation/. Этот файл — справочный нарратив; при расхождении верна спека OpenSpec.

Целевые имена и структура, в которые jellybit раскладывает файлы хардлинками. Источники: Movies, Shows.

Фильмы

movies/
  Дюна Часть вторая (2024) [tmdbid-693134]/
    Дюна Часть вторая (2024).mkv
    Дюна Часть вторая (2024).ru.srt
  • Папка и файл — Название (Год).
  • provider-id в имени папки ([tmdbid-...]) добавляется при работе с базой — снимает неоднозначность для русских названий, которые Jellyfin иначе может опознать неверно.
  • Внешние субтитры — Имя.<lang>[.flag].srt (флаги forced/sdh/ default/hi), напр. …ru.forced.srt; база имени совпадает с именем видеофайла. Пары VobSub — .idx + .sub.

Сериалы

series/
  Название (2024) [tvdbid-123456]/
    Season 01/
      Название (2024) S01E01.mkv
      Название (2024) S01E02.mkv
  • provider-id — на папке сериала.
  • Сезоны — Season 01, файлы — ... SxxEyy.

Сопоставление источник → цель

Источник берём по пути из qBittorrent (save_path + относительное имя файла из /torrents/files, которое уже содержит корневую папку многофайловой раздачи; это уже хост-путь, path_map — фолбэк). Для каждого распознанного файла (не каталога) создаётся хардлинк в paths.movies/paths.series; целевые каталоги — mkdir (0755, 1000:1000). Исходный файл остаётся на месте (раздача продолжается), inode общий — диск не дублируется.

Целевое имя строится из распознанных полей и санитизируется (без разделителей пути, .., управляющих символов); финальный путь обязан быть строго под библиотекой. Существующую цель не перезаписываем (тот же inode → готово; другой файл → коллизия → review). Инварианты и undo — в architecture.md → «Раскладка файлов».

Владение целевым путём

Целевой путь принадлежит одной загрузке. Когда новая раскладка успешно ложится на путь, который раньше занимала другая загрузка (путь к этому моменту свободен — иначе была бы коллизия → review, чужой файл не перезаписываем), владение переходит к новой загрузке: прежние записи file_link на этот путь помечаются статусом superseded и перестают считаться целью прежней загрузки. Это нужно сверке с реальностью: иначе повторная закачка того же фильма (например, в другом качестве) по тому же пути ложно «воскрешала» бы удалённую задачу — см. workflow.md → «Сверка с реальностью». superseded-ссылки не считаются целью при сверке и не снимаются в Undo.

Желательно: целевой и исходный каталоги — на одной ФС/одном mount'е (внутри контейнера это обеспечивает единая песочница /srv/media), тогда работает дешёвый хардлинк. Если хардлинк невозможен (разные ФС или ФС без поддержки жёстких ссылок), layout не падает, а копирует файл с предупреждением в лог — см. architecture.md → «Раскладка файлов».

Безопасный undo (не снимать последнюю копию)

Undo снимает лишний хардлинк, а не единственный файл. Перед удалением батча layout проверяет каждую цель: если исходный файл уже не существует или у цели не осталось других жёстких ссылок (nlink <= 1), это — последняя копия данных, и весь Undo отклоняется целиком (ошибка ErrLastCopy), не сняв ни одной ссылки (частичный откат тоже стёр бы часть данных). Так нарушенный инвариант «источник неприкосновенен» (источник удалён вручную) не приводит к потере данных. Отсутствующую цель Undo пропускает как уже снятую (идемпотентность). Связь с состояниями рассинхрона — workflow.md → «Сверка с реальностью».

Крайние случаи

  • Многофайловый фильм (части) — стэкинг по точному токену Jellyfin (… - part1/cd1); точный формат уточнить при реализации.
  • РедакцииИмя (Год) [edition-Director's Cut] либо отдельные версии в папке фильма.
  • Двойная серия в одном файле — … SxxEyy-Eyy.
  • СпецвыпускиSeason 00.
  • Сезон-пак — серии в один Season xx; смешанный пак — по per-file сезонам.
  • Несколько аудиодорожек — обычно внутри mkv, не наша забота.
  • Аниме с абсолютной нумерацией — пересчёт в S·E, отдельная проработка (задача в беклоге).