Files
obsidian-vault/personal/projects/media-pipeline-file-routing.md
2026-05-30 10:03:30 +00:00

7.5 KiB

title, aliases, created, updated, status, tags, related
title aliases created updated status tags related
Media Pipeline — File Routing & Directory Policies
Медиа пайплайн роутинг
Media Pipeline file routing
file routing policies
2026-05-17 2026-05-24 planning
media-pipeline
file-routing
transmission
config
personal/projects/media-pipeline-tracker-parsers
family/projects/media-toolbox-kraken

Media Pipeline — File Routing & Directory Policies

Текущее состояние (до реализации)

Текущий конфиг уже содержит:

  • directories — список папок для сканирования (downloads, movies, cartoons, series, documentaries)
  • transmission.sorting_rules — genre-based routing для downloads → destination
  • transmission.default_movies_destination / default_series_destination
  • output — куда идут symlink'и library (movies, series, cartoons, documentaries)

Проблема: политика действий с файлами захардкодена в MediaFinalizer:

  • для всех директорий → всегда symlink в library
  • нет способа сказать "для music — не делать canonical symlink"
  • нет способа сказать "для downloads — переместить через Transmission; для movies — оставить на месте"
  • FileAction.move и TransmissionRPC.moveTorrent существуют, но нигде не вызываются из apply-фазы

Цель

Политика обработки файлов задаётся в конфиге per-directory, не в коде. MediaFinalizer читает политику и генерирует правильный набор FileAction.

Дизайн конфига

"directory_policies": [
  {
    "path": "/media/downloads",
    "unsorted": true,
    "on_match": {
      "move": "transmission",
      "destination_by_kind": {
        "movie":       "/media/movies",
        "series":      "/media/series",
        "cartoon":     "/media/cartoons",
        "documentary": "/media/documentaries",
        "music":       "/media/music",
        "audiobook":   "/media/audiobooks",
        "book":        "/media/books",
        "game":        "/media/games"
      },
      "library_symlink": true,
      "nfo": true
    },
    "on_unmatched": {
      "move": "none"
    }
  },
  {
    "path": "/media/movies",
    "on_match": {
      "move": "none",
      "library_symlink": true,
      "nfo": true
    }
  },
  {
    "path": "/media/cartoons",
    "on_match": {
      "move": "none",
      "library_symlink": true,
      "nfo": true
    }
  },
  {
    "path": "/media/series",
    "on_match": {
      "move": "none",
      "library_symlink": true,
      "nfo": true
    }
  },
  {
    "path": "/media/documentaries",
    "on_match": {
      "move": "none",
      "library_symlink": true,
      "nfo": true
    }
  },
  {
    "path": "/media/music",
    "on_match": {
      "move": "none",
      "library_symlink": false,
      "nfo": true
    }
  }
]

move values

  • "transmission" — вызвать TransmissionRPC.moveTorrent; если торрент не найден → fallback на "filesystem"
  • "filesystem"FileAction.move (прямое перемещение)
  • "none" — файл остаётся на месте
  • true — создать каноническое имя в output.* папке (как сейчас)
  • false — не создавать symlink (файл сам по себе — конечная точка, напр. music)

nfo

  • true — писать NFO рядом с файлом (если нет library_symlink) или в library (если есть)
  • false — не писать NFO (игры, книги — нет Kodi-совместимой схемы)

destination_by_kind

Только для unsorted: true директорий (downloads). Для остальных не нужно — файл уже в правильном месте. MediaKind определяется из resolved metadata (TMDb genre → kind mapping) или из категории торрента.

on_unmatched

Что делать если resolve не удался. По умолчанию move: "none" — файл остаётся в downloads.

Совместимость с текущим конфигом

Старые поля (transmission.sorting_rules, transmission.default_movies_destination, output) сохраняются как fallback. directory_policies приоритетнее если задан.

Компоненты реализации

1. DirectoryPolicy struct (Config.swift)

public struct DirectoryPolicy: Codable {
    public let path: String
    public let unsorted: Bool              // defaults false
    public let onMatch: MatchPolicy
    public let onUnmatched: UnmatchedPolicy
}

public struct MatchPolicy: Codable {
    public let move: MoveStrategy          // .transmission / .filesystem / .none
    public let destinationByKind: [String: String]?  // only for unsorted
    public let librarySymlink: Bool        // defaults true
    public let nfo: Bool                   // defaults true
}

public enum MoveStrategy: String, Codable {
    case transmission, filesystem, none = "none"
}

2. FileAction.transmissionMove (FileAction.swift)

case transmissionMove(torrentId: Int, from: URL, to: URL)

3. ApplyExecutor — выполнение transmissionMove

Нужен TransmissionRPC в контексте apply-фазы. При ошибке RPC — fallback на FileAction.move.

4. MediaFinalizer — читает политику из конфига

func policy(for sourceURL: URL, config: Config) -> DirectoryPolicy?

Вместо hardcoded symlink генерирует actions по политике.

5. MediaKind detection

  • Из TMDb genres через mapping → movie, series, cartoon, documentary
  • Из Rutracker категории торрента → music, game, audiobook, book
  • Fallback: по исходной директории (/media/music/music)

Порядок реализации

  • C1DirectoryPolicy в Config.swift + парсинг из JSON
  • C2FileAction.transmissionMove + выполнение в ApplyExecutor
  • C3MediaFinalizer — routing по политике (move + symlink + nfo)
  • C4MediaKind detection из resolved metadata
  • C5 — Обновить конфиг на Кракене
  • C6 — Тесты: downloads → movies (Transmission), movies (no move, symlink), music (no move, no symlink)

Связанные файлы

  • Sources/MediaPipeline/Config/Config.swift — добавить DirectoryPolicy
  • Sources/MediaPipeline/Apply/FileAction.swifttransmissionMove case
  • Sources/MediaPipeline/Apply/ApplyExecutor.swift (или аналог) — выполнение RPC
  • Sources/MediaPipeline/Apply/MediaFinalizer.swift — routing по политике
  • Sources/MediaSources/APIs/TransmissionRPC.swift — уже есть moveTorrent

Связанные планы

  • media-pipeline-tracker-parsers — multi-tracker (реализован)
  • Шаг B (новые типы медиа) — блокирует destination_by_kind для music/games/books

Связанные заметки