diff --git a/servers/rpi.md b/servers/rpi.md index 374cc71..99f9984 100644 --- a/servers/rpi.md +++ b/servers/rpi.md @@ -84,6 +84,8 @@ OneDrive на внешний HDD; с того же дня ещё и медиац - Для перевёрстки извлечённого текста в PDF установлен `python3-reportlab` 4.3.1 (2026-10-11; ранее средств создания PDF с кириллицей не было), с зависимостями Pillow/Cairo. Шрифт DejaVu Sans внедряется в PDF; PNG-предпросмотр строится из итогового серого PDF. Проверено на комплекте Лермонтова: 3 страницы A4, текст со скриншотов и исходный портрет. ## Медиацентр (с 2026-10-11, код — `stacks/rpi-media/`) + +- 2026-10-18 по просьбе владельца добавлен «Невероятный Бёрт Уандерстоун» (2013, TMDB 124459, Radarr id=3), вручную выбрана RuTracker-раздача BDRip 1080p MVO + Original Eng RUS, 11,4 ГБ. Установлен `notify-movie-ready.timer` (раз в 2 мин, ранее уведомления для этого фильма не было): после импорта с русской аудиодорожкой и появления в Jellyfin отправляет владельцу 607015 ссылку на фильм. Скрипт и юниты — `stacks/rpi-media/`; отметка доставки `/var/lib/media-ready/burt-wonderstone.sent`, после неё повторно не шлёт. Секреты читает из существующих env, в логах только тип ошибки, чтобы не раскрыть Telegram URL с токеном. ⚠️ Ссылка доступна в домашней сети; таймер не означает, что загрузка уже завершилась. Docker CE 29.9 + Compose v5.6 из репо download.docker.com (в Debian старее). `/etc/docker/daemon.json`: json-file `10m×3` — чтобы логи контейнеров не точили SD. `rus` в группе `docker` (= root-эквивалент; у него и так sudo NOPASSWD). Стек — `/opt/media/docker-compose.yml`, конфиги — `/opt/media/config/<сервис>` (SD, ~20 МБ), образы ~7.8 ГБ на SD. | Сервис | Порт | Зачем | Версия на 2026-10-11 | diff --git a/stacks/rpi-media/notify-movie-ready.service b/stacks/rpi-media/notify-movie-ready.service new file mode 100644 index 0000000..825a75c --- /dev/null +++ b/stacks/rpi-media/notify-movie-ready.service @@ -0,0 +1,14 @@ +[Unit] +Description=Notify owner when Burt Wonderstone is ready in Jellyfin +After=network-online.target docker.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/python3 /usr/local/lib/media-ready/notify-movie-ready.py +StateDirectory=media-ready +UMask=0077 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +TimeoutStartSec=120 diff --git a/stacks/rpi-media/notify-movie-ready.timer b/stacks/rpi-media/notify-movie-ready.timer new file mode 100644 index 0000000..224b804 --- /dev/null +++ b/stacks/rpi-media/notify-movie-ready.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Check requested movie readiness every two minutes + +[Timer] +OnBootSec=2min +OnUnitInactiveSec=2min +Unit=notify-movie-ready.service + +[Install] +WantedBy=timers.target diff --git a/stacks/rpi-media/scripts/notify-movie-ready.py b/stacks/rpi-media/scripts/notify-movie-ready.py new file mode 100644 index 0000000..548f807 --- /dev/null +++ b/stacks/rpi-media/scripts/notify-movie-ready.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Send one owner notification after Radarr import and Jellyfin indexing.""" +import json +from pathlib import Path +import urllib.request + +TMDB = 124459 +STATE = Path('/var/lib/media-ready/burt-wonderstone.sent') + +def env(path): + return dict(line.strip().split('=', 1) for line in open(path) + if '=' in line and not line.startswith('#')) + +def api(base, path, headers=None, data=None): + request = urllib.request.Request(base + path, headers=headers or {}, + data=None if data is None else json.dumps(data).encode()) + with urllib.request.urlopen(request, timeout=30) as response: + body = response.read() + return json.loads(body) if body else None + +def main(): + if STATE.exists(): + return + keys = env('/etc/media.env') + movies = api('http://127.0.0.1:7878', '/api/v3/movie', + {'X-Api-Key': keys['RADARR_KEY']}) + movie = next((m for m in movies if m['tmdbId'] == TMDB), None) + if not movie or not movie.get('hasFile'): + print('Waiting for Radarr import') + return + audio = str(movie.get('movieFile', {}).get('mediaInfo', {}).get('audioLanguages', '')).lower() + if not any(language in audio.replace('/', ' ').replace(',', ' ').split() + for language in ('russian', 'rus', 'ru')): + print('Waiting for an imported file with Russian audio') + return + base = 'http://127.0.0.1:8096' + headers = {'Authorization': 'MediaBrowser Token="' + keys['JELLYFIN_KEY'] + '"'} + items = api(base, '/Items?Recursive=true&IncludeItemTypes=Movie&Fields=ProviderIds', headers) + item = next((i for i in items['Items'] + if i.get('ProviderIds', {}).get('Tmdb') == str(TMDB)), None) + if not item: + api(base, '/Library/Refresh', headers, {}) + print('Waiting for Jellyfin index; refresh requested') + return + server = api(base, '/System/Info/Public')['Id'] + link = ('http://192.168.10.25:8096/web/#/details?id=' + + item['Id'] + '&serverId=' + server) + token = env('/etc/astra-bot.env')['TG_TOKEN'] + result = api('https://api.telegram.org', '/bot' + token + '/sendMessage', + {'Content-Type': 'application/json'}, + {'chat_id': 607015, 'text': '«Невероятный Бёрт Уандерстоун» скачался. ' + 'Русская озвучка, 1080p. Смотреть дома:\n' + link, + 'disable_web_page_preview': True}) + if not result.get('ok'): + raise RuntimeError('Telegram did not confirm delivery') + STATE.parent.mkdir(parents=True, exist_ok=True) + STATE.write_text(str(result['result']['message_id']) + '\n') + print('Ready notification delivered') + +if __name__ == '__main__': + # urllib exceptions can contain the Telegram token in their URL. + try: + main() + except Exception as error: + print('Movie notification failed: ' + type(error).__name__) + raise SystemExit(1)