diff --git a/servers/rpi.md b/servers/rpi.md index 44a4052..4034114 100644 --- a/servers/rpi.md +++ b/servers/rpi.md @@ -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//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=…`. diff --git a/stacks/rpi-media/provision.py b/stacks/rpi-media/provision.py index da6a6f5..ea7d859 100644 --- a/stacks/rpi-media/provision.py +++ b/stacks/rpi-media/provision.py @@ -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):