176 lines
7.2 KiB
Markdown
176 lines
7.2 KiB
Markdown
# Makov webkamera → Jellyfin (Live TV)
|
||
|
||
Restreamuje YouTube live přenos ("Krmítko", https://www.youtube.com/watch?v=F0fsV1GGZ3M)
|
||
do lokálního HLS, na který se Jellyfin dívá přes stabilní adresu. Když YouTube
|
||
odkaz vyprší nebo spadne, systemd proces sám restartuje a natáhne novou URL —
|
||
divák/Jellyfin si ničeho nevšimne.
|
||
|
||
## 0) Kam skript patří
|
||
|
||
Toto **musí běžet na stejném stroji (resp. za stejnou veřejnou IP), jako
|
||
Jellyfin** — YouTube vrací streamovací URL svázanou s IP adresou, ze které
|
||
byla vyžádána (`yt-dlp -g`). Pokud poběží jinde než Jellyfin, `ffmpeg` dostane
|
||
403 a nikdy se nerozjede.
|
||
|
||
## 1) Závislosti
|
||
|
||
```bash
|
||
sudo apt install -y ffmpeg python3
|
||
sudo pip install -U yt-dlp # nebo pipx install yt-dlp — apt verze bývá stará
|
||
# a YouTube ji rád rozbije
|
||
```
|
||
|
||
Ověř, že `yt-dlp` a `ffmpeg` jedou z shellu bez chyby:
|
||
```bash
|
||
yt-dlp --version
|
||
ffmpeg -version
|
||
```
|
||
|
||
## 2) Uživatel a oprávnění
|
||
|
||
Vlastní, neprivilegovaný systémový účet — obě služby pod ním běží:
|
||
|
||
```bash
|
||
sudo useradd --system --no-create-home --shell /usr/sbin/nologin makov-cam
|
||
sudo mkdir -p /var/lib/jellyfin-iptv/hls
|
||
sudo chown -R makov-cam:makov-cam /var/lib/jellyfin-iptv
|
||
```
|
||
|
||
Pokud je `yt-dlp` nainstalované přes **snap** (`which yt-dlp` ukazuje
|
||
`/snap/bin/yt-dlp`), potřebuje při startu zapisovatelný domovský adresář —
|
||
a to i přes `--no-create-home`, protože domov si zjišťuje přímo z
|
||
`/etc/passwd`, ne z proměnné `$HOME` (tu přenastavenou v service souboru
|
||
ignoruje). Bez toho skončí na `Permission denied: mkdir /home/makov-cam`.
|
||
Vytvoř mu ho ručně:
|
||
|
||
```bash
|
||
sudo mkdir -p /home/makov-cam
|
||
sudo chown makov-cam:makov-cam /home/makov-cam
|
||
sudo chmod 750 /home/makov-cam
|
||
```
|
||
|
||
(Pokud `yt-dlp` instaluješ přes `pip`/`pipx` dle kroku 1, tenhle problém
|
||
nenastává a krok lze přeskočit.)
|
||
|
||
## 3) Nasazení souborů
|
||
|
||
```bash
|
||
sudo install -m 755 makov-cam.sh /usr/local/bin/makov-cam.sh
|
||
sudo install -m 644 makov-cam.service /etc/systemd/system/
|
||
sudo install -m 644 makov-cam-http.service /etc/systemd/system/
|
||
sudo systemctl daemon-reload
|
||
```
|
||
|
||
## 4) Spuštění
|
||
|
||
```bash
|
||
sudo systemctl enable --now makov-cam.service
|
||
sudo systemctl enable --now makov-cam-http.service
|
||
```
|
||
|
||
Ověření:
|
||
```bash
|
||
systemctl status makov-cam.service makov-cam-http.service
|
||
journalctl -u makov-cam.service -f # živě sleduj log ffmpeg/yt-dlp
|
||
|
||
# do pár vteřin by se měl objevit playlist a segmenty:
|
||
ls -la /var/lib/jellyfin-iptv/hls/
|
||
|
||
# a mělo by jít stáhnout přes HTTP:
|
||
curl -sI http://127.0.0.1:8888/hls/makov.m3u8
|
||
```
|
||
|
||
`makov-cam-http.service` je svázaná (`BindsTo=`) na `makov-cam.service` — když
|
||
restream spadne/restartuje, HTTP server padne/naskočí s ním, ať nikdy
|
||
neservíruje mrtvý/prázdný playlist. Pozor: `BindsTo=` jen zajišťuje, že HTTP
|
||
server *spadne*, když spadne hlavní služba — nezajišťuje, že se s ní i sám
|
||
*nastartuje*. Po `enable --now` obou služeb to není problém (obě se spustí),
|
||
ale pokud jen restartuješ `makov-cam.service` ručně, zkontroluj/restartuj i
|
||
`makov-cam-http.service`.
|
||
|
||
HTTP server poslouchá jen na jedné IP (`--bind`), ne na `0.0.0.0` — nemá cenu
|
||
ho vystavovat celé síti, stačí odkud se na něj bude dívat Jellyfin:
|
||
|
||
- **Jellyfin běží přímo na hostu** (ne v kontejneru): `--bind 127.0.0.1`
|
||
stačí.
|
||
- **Jellyfin běží v Docker kontejneru na tomtéž hostu** (běžný případ) —
|
||
`127.0.0.1` uvnitř kontejneru je jeho *vlastní* loopback, ne hostitelův,
|
||
takže na hostitelův HTTP server se tak nedostane. Zjisti gateway IP té
|
||
docker sítě, na které kontejner běží, a naviaž se na ni:
|
||
```bash
|
||
docker inspect <jellyfin-container> --format '{{.HostConfig.NetworkMode}}'
|
||
docker inspect <jellyfin-container> --format '{{json .NetworkSettings.Networks}}'
|
||
# z výstupu vezmi "Gateway", např. 172.19.0.1 — tam se z kontejneru dostaneš
|
||
```
|
||
a použij `--bind <ta-gateway-IP>` (viz `makov-cam-http.service`). Pokud by
|
||
Jellyfin běžel v jiné VM, změň `--bind` na IP, na které je pro ni
|
||
dosažitelný, a případně otevři firewall jen pro tenhle port a jen z její
|
||
IP.
|
||
|
||
## 5) Playlist pro Jellyfin
|
||
|
||
`WorkingDirectory` HTTP služby je `/var/lib/jellyfin-iptv` (rodič `hls/`), aby
|
||
šel přes stejný server servírovat i samotný `.m3u` playlist — to se hodí,
|
||
pokud Jellyfin (typicky v kontejneru) nevidí souborový systém hostitele a
|
||
tuner tak musí načíst playlist přes HTTP URL, ne lokální cestou.
|
||
|
||
Vytvoř `/var/lib/jellyfin-iptv/makov.m3u`:
|
||
|
||
```
|
||
#EXTM3U
|
||
#EXTINF:-1 tvg-id="makov-krmitko" tvg-name="Makov - Krmítko",Makov - Krmítko
|
||
http://<BIND_IP>:8888/hls/makov.m3u8
|
||
```
|
||
|
||
kde `<BIND_IP>` je stejná adresa, na kterou je navázaný `makov-cam-http.service`
|
||
(viz krok 4).
|
||
|
||
## 6) Nastavení v Jellyfinu
|
||
|
||
1. **Dashboard → Live TV → Tuner Devices → +** (Add)
|
||
2. Typ: **M3U Tuner**
|
||
3. File or URL:
|
||
- Jellyfin na hostu s přístupem k souborovému systému: lokální cesta
|
||
`/var/lib/jellyfin-iptv/makov.m3u`.
|
||
- Jellyfin v kontejneru (nevidí hostitelův FS): HTTP URL
|
||
`http://<BIND_IP>:8888/makov.m3u` (viz krok 5).
|
||
4. **Save** → Jellyfin načte kanál "Makov - Krmítko" do Live TV
|
||
5. Doporučeno: **Dashboard → Live TV → Guide Providers** — pro tenhle kanál
|
||
žádného není třeba (žádný EPG), Jellyfin ho ukáže jako kanál bez programu,
|
||
což nevadí.
|
||
6. Zkontroluj přehrávání v **Live TV** sekci hlavního rozhraní.
|
||
|
||
Protože kanál pořád ukazuje na stejnou lokální adresu, odpadá starost o
|
||
obnovování guide dat nebo expiraci URL na Jellyfin straně — o to se stará jen
|
||
`makov-cam.service` na pozadí.
|
||
|
||
## 7) Autostart po restartu serveru
|
||
|
||
`enable` v kroku 4 už zajistil start při bootu. Rychlá kontrola:
|
||
```bash
|
||
systemctl is-enabled makov-cam.service makov-cam-http.service
|
||
```
|
||
|
||
## Řešení problémů
|
||
|
||
- **`ffmpeg` pořád padá / v logu 403 od googlevideo.com** — nejčastěji IP
|
||
mismatch (viz bod 0): stroj, co spouští `yt-dlp -g`/`ffmpeg`, musí mít
|
||
stejnou veřejnou IP jako to, odkud by se stream normálně přehrával.
|
||
Zkontroluj `journalctl -u makov-cam.service -n 50`.
|
||
- **`yt-dlp: command not found` v service logu** — systemd nepoužívá tvůj
|
||
interaktivní `$PATH`; ověř `which yt-dlp` a případně uprav `ExecStart` v
|
||
`makov-cam.sh`/skriptu na plnou cestu (`/usr/local/bin/yt-dlp` apod.), nebo
|
||
nainstaluj přes `sudo pip install`, aby šel do `/usr/local/bin`.
|
||
- **Playlist v Jellyfinu jede, ale sekundy/minuty výpadku při refreshi** —
|
||
normální, `hls_list_size 6` × `hls_time 4s` drží ~24s okno; při restartu
|
||
streamu je pár vteřin díra, než naběhnou nové segmenty. Jde zkrátit/prodloužit
|
||
úpravou těch dvou hodnot ve skriptu.
|
||
- **YouTube někdy dočasně rozbije `yt-dlp` novou verzí přehrávače** — drž
|
||
`yt-dlp` aktuální (`sudo pip install -U yt-dlp`, klidně jako týdenní cron).
|
||
- **`ERROR: Requested format is not available`** — stream v tu chvíli nenabízí
|
||
žádný kombinovaný (progresivní) formát, jen oddělené video-only/audio-only
|
||
HLS řady (ověř přes `yt-dlp -F <url>`). Skript proto primárně používá
|
||
`-f 'bestvideo+bestaudio/best'` a při dvou vrácených URL pustí `ffmpeg` se
|
||
dvěma `-i` vstupy (`-map 0:v -map 1:a`); jedna URL = fallback na progresivní
|
||
formát, pokud se zase objeví.
|