From a833caffa1b82f4e0924bf87802af13c68ff806c Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 17 May 2026 18:36:38 +0600 Subject: [PATCH] =?UTF-8?q?2026-05-17=20media-pipeline=20roadmap:=20=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D1=83=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D1=88=D0=B0=D0=B3?= =?UTF-8?q?=D0=BE=D0=B2=20A-C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../media-pipeline-tracker-parsers.md | 125 ++++++++---------- 1 file changed, 56 insertions(+), 69 deletions(-) diff --git a/personal/projects/media-pipeline-tracker-parsers.md b/personal/projects/media-pipeline-tracker-parsers.md index 8771bc76..f1d0dacd 100644 --- a/personal/projects/media-pipeline-tracker-parsers.md +++ b/personal/projects/media-pipeline-tracker-parsers.md @@ -1,5 +1,5 @@ --- -title: Media Pipeline — Multi-Tracker Support +title: Media Pipeline — Roadmap created: '2026-05-17' updated: '2026-05-17' status: active @@ -9,90 +9,77 @@ tags: - rutor - nnm-club --- -# Media Pipeline — Multi-Tracker Support +# Media Pipeline — Roadmap -## Текущее состояние +## ✅ Сделано -✅ Реализовано и задеплоено на Kraken: - -- `TrackerPageParserRegistry` — dispatch по `url.host`, парсеры: Rutracker, Rutor, NNMClub, PirateBay, Nyaa -- `RutrackerPageParser` — CP1251 decode через `/usr/bin/iconv`, SwiftSoup парсинг -- Жанры извлекаются из `[...]` в заголовке топика, страны фильтруются -- KP/IMDb ID extraction работает -- `TorrentSourceStrategy` использует registry вместо прямого вызова `RutrackerAPI` +### Шаг A — Transmission flow + Rutracker парсинг +- `TrackerPageParserRegistry` — dispatch по `url.host` +- `RutrackerPageParser` — CP1251/iconv, SwiftSoup, жанры из заголовка топика, KP/IMDb IDs +- `TrackerMediaKind` — enum: movie/series/cartoon/documentary/music/audiobook/game/book/unknown +- `extractMediaKind` — детект по `BB.catId` / `FORUM_ID` на странице Rutracker (100+ forum IDs) +- `TorrentPageMetadataFact.mediaKind` + `TorrentLocator.torrentId` — пробрасывается через весь стек +- `QueryBuilderStrategy` — `torrentTitle` как предпочитаемый source для поиска - Fallback NFO при unresolved + `torrentPageMetadata.hasRichData` -## Архитектура +### Шаг B — Структура парсеров других трекеров +- `RutorPageParser`, `NNMClubPageParser`, `PirateBayPageParser`, `NyaaPageParser` — структура реализована +- Требуют верификации на живых HTML (нет тестовых fixture) -### TrackerPageParser protocol +### Шаг C — DirectoryPolicy + transmissionMove +- `DirectoryPolicy` / `MatchPolicy` / `MoveStrategy` в конфиге — backward-compatible +- `FileAction.transmissionMove(torrentId:, from:, to:)` — Codable +- `ApplyPhase` — async execute, RPC → fallback filesystem +- `buildMovieActions` — читает policy, `needsSymlink`, `needsNFO`, `effectiveOutputDir` +- `kindKey` из реального `TrackerMediaKind` → `destination_by_kind` +- `transmissionMove` когда есть `torrentId`, иначе `move` +- `config.json` на Кракене — `directory_policies` для всех директорий +- Canonical маркер `.media-pipeline-canonical` для music/books/games пишется в `output` директорию рядом с файлом -```swift -public protocol TrackerPageParser: Sendable { - var supportedHosts: [String] { get } - func parse(data: Data, url: URL) throws -> TorrentPageMetadata -} -``` +## ⚠️ Частично работает -### TrackerPageParserRegistry +### Фильмы из downloads +- `.mkv/.mp4` → scanner видит → регион → `transmissionMove` → `/media/movies/` → canonical marker → повторный run скипает ✅ +- `TrackerMediaKind` детектится по forum_id → правильная `destination_by_kind` ✅ -```swift -public struct TrackerPageParserRegistry: Sendable { - public static let defaults: [TrackerPageParser] = [ - RutrackerPageParser(), - RutorPageParser(), - NNMClubPageParser(), - PirateBayPageParser(), - NyaaPageParser(), - ] - public func parser(for url: URL) -> TrackerPageParser? -} -``` +### Музыка/книги/игры из downloads +- Scanner не видит `.mp3/.flac/.epub/.zip` — нет `videoExtensions` для аудио ❌ +- Регион не создаётся → `buildMovieActions` не вызывается → файл остаётся в downloads -## Парсеры по трекерам +## 📋 Следующие шаги -### RutrackerPageParser ✅ -- Encoding: Windows-1251 (через `/usr/bin/iconv` temp files — swift-corelibs-foundation не поддерживает CP1251 на Linux) -- Title selector: `#topic-title`, `h1.maintitle`, `a.topictitle` -- **Genres: из заголовка топика `[YEAR, жанр1, жанр2, ..., TECH-TAG]`** — не из поста -- Страны фильтруются через `countryNames: Set` (30+ стран RU+EN) -- Poster: первый img в `div.post_body` не из rutracker UI -- IDs: `imdb.com/title/tt\d+`, `kinopoisk.ru/film/\d+` +### Шаг D — Audio/non-video scanner (приоритет) +Чтобы музыка/книги/игры из downloads реально перекладывались: -### RutorPageParser ✅ (структура реализована) -- Encoding: UTF-8 -- Title: `h1.topic-header` / `div#topic-title` -- Genres: паттерн `Жанр:` в тексте +1. **`AudioClassifier`** — `isAudioFile()` для `.mp3/.flac/.m4a/.ogg/.aac/.wav/.opus` +2. **`NonVideoRegionBuilder`** — создаёт регионы для директорий без video но с audio/doc файлами +3. **`FileScanner`** — при наличии `TrackerMediaKind != .movie/.series` (из Transmission метаданных) создавать регион даже без video +4. **`buildAudioActions`** в `MediaFinalizer` — move + NFO рядом, без symlink, canonical marker рядом +5. **Альтернатива (проще)**: unsorted fallback — отдельный pass по `unsorted_dir` через Transmission API, для торрентов без региона вызывать `torrent-set-location` на основе `mediaKind` из страницы трекера -### NNMClubPageParser ✅ (структура реализована) -- Encoding: Windows-1251 (тот же iconv путь) -- Title: `h1.maintitle` / `a.maintitle` +### Шаг E — Верификация парсеров +- HTML fixture тесты для rutor, nnm-club на живых страницах +- Проверить `extractMediaKind` на реальных forum_id -### PirateBayPageParser ✅ (структура реализована) -- Encoding: UTF-8 -- Title: `

` -- Нет IMDb/KP ссылок обычно → только title/year из названия торрента - -### NyaaPageParser ✅ (структура реализована) -- Encoding: UTF-8 -- Аниме: title из `h3.panel-title`, AniDB ID если есть +### Шаг F — Series из downloads +- `buildSeriesActions` сейчас не читает policy → нет move для сериалов из downloads +- Нужно прокинуть `torrentURLFacts`/`torrentPageFacts` аналогично movie ## Ключевые питфоллы -- `extractGenres` должен парсить из **заголовка топика** (`#topic-title`), а не из тела поста — Rutracker кодирует жанры в `[YEAR, жанр, ...]` в title -- CP1251 decode: использовать `/usr/bin/iconv` через temp files — единственный способ на Linux -- Кеш хранит сырые байты (binary) — `grep` по кешу не работает для CP1251 файлов -- `TrackerParsers.swift` — боевой файл (используется в продакшне); `RutrackerAPI.swift` — legacy, параллельно существует +- CP1251: `/usr/bin/iconv` через temp files — единственный способ на Linux +- `extractGenres` — из заголовка топика `[YEAR, жанр, ...]`, не из тела поста +- `transmissionMove` генерируется только если scanner создал регион (нужен video файл) +- Canonical marker для music/books: `output == source dir` → повторный run скипает через `agentResolvedInLibrary` +- `allOutputDirs` в PipelineRunner не включает `directoryPolicies[].output` → нужно обновить ## Файлы -- `Sources/MediaSources/APIs/TrackerParsers.swift` — все парсеры + registry (боевой) -- `Sources/MediaSources/APIs/RutrackerAPI.swift` — legacy парсер (используется в части flow) -- `Sources/MediaSources/Providers/TorrentSourceStrategy.swift` — dispatch через registry -- `Sources/MediaDomain/Facts/TorrentPageMetadataFact.swift` — модель: title, year, genres, posterURL, description, externalLinks - -## Roadmap - -- [ ] Шаг B — новые типы медиа: игры, музыка, книги (следующий) -- [ ] Реальные HTML fixture тесты для каждого трекера (rutor, nnm-club) -- [ ] Верификация RutorPageParser/NNMClubPageParser на живых торрентах -- [ ] `MediaKind` detection по категории торрента (напр. "Игры PC" → `.game`) +- `Sources/MediaSources/APIs/TrackerParsers.swift` — парсеры + registry + `extractMediaKind` +- `Sources/MediaDomain/Facts/TorrentPageMetadataFact.swift` — `TrackerMediaKind`, поля +- `Sources/MediaDomain/Facts/TorrentInfoURLFact.swift` — `torrentId: Int?` +- `Sources/MediaSources/Protocol/TorrentInfoProvider.swift` — `TorrentLocator.torrentId` +- `Sources/MediaPipeline/Config/Config.swift` — `DirectoryPolicy`, `MatchPolicy`, `MoveStrategy` +- `Sources/MediaPipeline/Apply/FileAction.swift` — `transmissionMove` case +- `Sources/MediaPipeline/Apply/ApplyPhase.swift` — async execute, RPC fallback +- `Sources/MediaPipeline/Apply/MediaFinalizer.swift` — policy-aware routing, torrent extractors