rpi-media: приоритет русской дорожки и автозамена, ручные субтитры из Sub-раздачи

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
gilfanovr
2026-10-11 16:39:35 +03:00
co-authored by Claude Opus 5.5
parent 716539def3
commit b8a31c7e54
2 changed files with 46 additions and 3 deletions
+2 -1
View File
@@ -101,7 +101,8 @@ Docker CE 29.9 + Compose v5.6 из репо download.docker.com (в Debian ст
- Метаданные и интерфейс — ru, предпочтительные аудио/субтитры у пользователя — `rus`, режим субтитров Smart. Библиотеки «Фильмы» (`/data/library/movies`) и «Сериалы» (`/data/library/tv`), в Jellyfin только чтение.
- Индексаторы (публичные, без аккаунтов): NoNaMe Club, The Pirate Bay, YTS — синхронизированы в Radarr/Sonarr; Rutor и EZTV есть в Prowlarr, но в *arr не доехали (Rutor не проходит тест по категориям, EZTV на момент настройки лежал); 1337x не добавился (таймаут даже через FlareSolverr). **RuTracker** (добавлен 2026-10-11, аккаунт пользователя; логин/пароль — только в БД Prowlarr): через FlareSolverr, опции «убрать кириллицу», «добавить RUS», «первые теги в конец» — чтобы *arr сопоставляли английские названия и видели русскую дорожку; приоритет 10 (выше публичных, 25). Синхронизирован в Radarr и Sonarr. Kinozal и прочие приватные — руками.
- ⚠️ RuTracker закрывает `login.php`/`tracker.php` челленджем Cloudflare (по IPv4; IPv6 у малины нет маршрута). FlareSolverr на Pi решает его **80–180 с** — у прокси в Prowlarr `requestTimeout=180` (было 60, с ним добавление падало по таймауту). После решения Prowlarr держит cookie и поиск идёт ~4 с; когда cookie протухнет, первый поиск снова будет минутами, и *arr могут на время пометить индексатор недоступным.
- Bazarr: провайдеры без аккаунтов (`embeddedsubtitles`, `podnapisi`, `yifysubtitles`); для русских субтитров лучший — OpenSubtitles.com, нужен аккаунт.
- **Языки дорожек** (2026-10-11, `provision.py langs`): custom formats «Русская дорожка» (язык Russian или `RUS|MVO|DVO|Dub|Дубляж` в названии) = 100 и «Оригинальная»/«Английская дорожка» = 50 во всех профилях Radarr/Sonarr; `minFormatScore=0` (ничего не отклоняется), `cutoffFormatScore=150` + `upgradeAllowed` — *arr сами перекачают, когда появится раздача с русской дорожкой. Язык профилей Radarr — Any (было Original: раздачи только с дубляжом отклонялись). Повод — первый фильм (The Fix, 2026) скачался с YTS только на английском; русского дубляжа у свежих релизов ещё нет неделями, есть только `Sub`-раздачи.
- Bazarr: провайдеры без аккаунтов (`embeddedsubtitles`, `podnapisi`, `yifysubtitles`); для русских субтитров лучший — OpenSubtitles.com, нужен аккаунт. ⚠️ Бесплатные провайдеры для свежих фильмов русских субтитров не находят (The Fix — пусто); тогда субтитры вытаскивали из `Sub`-раздачи RuTor: mkv в `torrents/other`, `ffmpeg -map 0:s:m:language:rus -c copy` → `<имя>.ru.srt` рядом с фильмом (сверить длительность/fps — иначе рассинхрон), лишнюю раздачу удалить.
- **Авторизация:** везде пользователь `rus`, один пароль — `/opt/media/.admin-password` (rus 600). *arr — формы, пароль **всегда** (режим «без пароля из LAN» в новых *arr требует `AllowedHosts`). Между собой сервисы ходят по API-ключам из `config/<app>/config.xml`.
- **Точка входа для пользователя — http://192.168.10.25:3000** (Homepage, конфиги `stacks/rpi-media/homepage/` → `/opt/media/homepage/`). Ключи виджетов — `/opt/media/homepage.env` (rus 600, `HOMEPAGE_VAR_*`, генерит `provision.py homepage`). ⚠️ Там же пароль qBittorrent: после смены общего пароля перегенерировать env и `docker compose up -d homepage`, иначе виджет qBittorrent отвалится. `HOMEPAGE_ALLOWED_HOSTS` обязателен — без него 400 на любой Host, кроме localhost.
- ⚠️ Встроенный виджет `jellyfin` Homepage 2.5 с Jellyfin 12 не работает: ходит на `/emby/…?api_key=` — пути нет (404), ключ в query отключён (`EnableLegacyAuthorization=false`, включать не стали). Вместо него `customapi` на `/Items/Counts` с заголовком `Authorization: MediaBrowser Token=…`.
+44 -2
View File
@@ -3,7 +3,7 @@
ничего не ломает, уже сделанное пропускает.
Запуск на малине: python3 /opt/media/provision.py [шаг ...]
Шаги: qbit jellyfin arr prowlarr bazarr seerr connect homepage (без аргументов — все).
Шаги: qbit jellyfin arr langs prowlarr bazarr seerr connect homepage (без аргументов — все).
Пароль админа берётся из /opt/media/.admin-password, API-ключи — из config.xml.
"""
from http.cookiejar import CookieJar
@@ -187,6 +187,48 @@ def arr():
log(name, "ok")
# ---------------------------------------------------------------- языки в *arr
def langs():
"""Приоритет дорожек: русская+оригинал (150) > русская (100) > оригинал (50). Ничего не отклоняется,
а cutoffFormatScore=150 + upgradeAllowed — *arr сами перекачают, когда появится раздача с русской дорожкой
(у свежих фильмов дубляж выходит через недели после оригинала)."""
for name, a in ARR.items():
h = {"X-Api-Key": apikey(name)}
u = f"{a['url']}/api/{a['api']}"
st, ll = http("GET", u + "/language", headers=h)
lid = {l["name"]: l["id"] for l in ll}
spec = lambda n, impl, v: {"name": n, "implementation": impl, "negate": False, "required": False,
"fields": [{"name": "value", "value": v}]}
orig = ("Оригинальная дорожка", "Original") if name == "radarr" else ("Английская дорожка", "English")
defs = {
# RuTracker с опцией «добавить RUS»: язык может не распознаться, а тег в названии есть
"Русская дорожка": [spec("Russian", "LanguageSpecification", lid["Russian"]),
spec("RUS в названии", "ReleaseTitleSpecification", r"\b(RUS|Rus|MVO|DVO|Dub|Дубляж)\b")],
orig[0]: [spec(orig[1], "LanguageSpecification", lid[orig[1]])],
}
scores = {"Русская дорожка": 100, orig[0]: 50}
st, cfs = http("GET", u + "/customformat", headers=h)
ids = {c["name"]: c["id"] for c in cfs}
for cname, specs in defs.items():
if cname not in ids:
st, r = http("POST", u + "/customformat",
{"name": cname, "includeCustomFormatWhenRenaming": False, "specifications": specs}, h)
ids[cname] = r["id"]
st, profs = http("GET", u + "/qualityprofile", headers=h)
for p in profs:
items = {fi["format"]: fi for fi in p["formatItems"]}
for cname, sc in scores.items():
if ids[cname] in items:
items[ids[cname]]["score"] = sc
else:
p["formatItems"].append({"format": ids[cname], "name": cname, "score": sc})
p.update({"minFormatScore": 0, "cutoffFormatScore": 150, "upgradeAllowed": True})
if name == "radarr": # было Original — отклоняло раздачи только с дубляжом
p["language"] = {"id": lid["Any"], "name": "Any"}
http("PUT", u + "/qualityprofile/%d" % p["id"], p, h)
log(name, "langs ok")
# ---------------------------------------------------------------- Prowlarr
def prowlarr():
k = apikey("prowlarr")
@@ -385,7 +427,7 @@ def jellyseerr():
log("seerr ok")
STEPS = {"qbit": qbit, "jellyfin": jellyfin, "arr": arr, "prowlarr": prowlarr, "bazarr": bazarr, "connect": connect, "homepage": homepage, "seerr": jellyseerr}
STEPS = {"qbit": qbit, "jellyfin": jellyfin, "arr": arr, "langs": langs, "prowlarr": prowlarr, "bazarr": bazarr, "connect": connect, "homepage": homepage, "seerr": jellyseerr}
if __name__ == "__main__":
for s in sys.argv[1:] or list(STEPS):