7.5 KiB
title, aliases, created, updated, status, tags, related
| title | aliases | created | updated | status | tags | related | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Media Pipeline — File Routing & Directory Policies |
|
2026-05-17 | 2026-05-24 | planning |
|
|
Media Pipeline — File Routing & Directory Policies
Текущее состояние (до реализации)
Текущий конфиг уже содержит:
directories— список папок для сканирования (downloads, movies, cartoons, series, documentaries)transmission.sorting_rules— genre-based routing для downloads → destinationtransmission.default_movies_destination/default_series_destinationoutput— куда идут 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"— файл остаётся на месте
library_symlink
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)
Порядок реализации
- C1 —
DirectoryPolicyвConfig.swift+ парсинг из JSON - C2 —
FileAction.transmissionMove+ выполнение вApplyExecutor - C3 —
MediaFinalizer— routing по политике (move + symlink + nfo) - C4 —
MediaKinddetection из resolved metadata - C5 — Обновить конфиг на Кракене
- C6 — Тесты: downloads → movies (Transmission), movies (no move, symlink), music (no move, no symlink)
Связанные файлы
Sources/MediaPipeline/Config/Config.swift— добавитьDirectoryPolicySources/MediaPipeline/Apply/FileAction.swift—transmissionMovecaseSources/MediaPipeline/Apply/ApplyExecutor.swift(или аналог) — выполнение RPCSources/MediaPipeline/Apply/MediaFinalizer.swift— routing по политикеSources/MediaSources/APIs/TransmissionRPC.swift— уже естьmoveTorrent
Связанные планы
- media-pipeline-tracker-parsers — multi-tracker (реализован)
- Шаг B (новые типы медиа) — блокирует
destination_by_kindдля music/games/books