そこで、視聴するTV番組を一個に限定し、チャンネルを切り替わる時は、面倒でもコマンドで切り替える様に変更し、CPU負荷を低減する。
■スクリプトファイルの作成
前回使用していた hls_start.sh hls_proxy.py をリネーム保存
cd /mnt/data/backup/scripts/ sudo mv hls_start.sh hls_start8.sh sudo mv hls_proxy.py hls_proxy8.py
#!/bin/bash
# hls_start.sh - 1チャンネル起動スクリプト(systemdから呼ばれる)
# 使用例: bash hls_start.sh 400101
set -uo pipefail
if [ $# -eq 0 ]; then
echo "[ERROR] SIDを指定してください"
echo "使用例: bash hls_start.sh 400101"
exit 1
fi
SID="$1"
HLS_DIR="/tmp/hls"
TAILSCALE_IP="100.89.99.23"
PROXY_PORT="8890"
# 使用チャンネルのみ定義
declare -A CH_NAME
CH_NAME[400101]="BS-NHK"
CH_NAME[400102]="BS-NHKサブ"
CH_NAME[3273601024]="NHK総合"
CH_NAME[3273601025]="NHKサブ"
NAME="${CH_NAME[$SID]:-SID_$SID}"
# ===== 既存の同SIDプロセスを停止 =====
pkill -f "ffmpeg.*services/${SID}/stream" 2>/dev/null || true
sleep 1
# ===== HLSディレクトリ作成 =====
mkdir -p "${HLS_DIR}/${SID}"
# ===== ffmpeg起動 =====
echo "[INFO] 起動: ${NAME} (SID: ${SID})"
nohup ffmpeg \
-hwaccel vaapi \
-hwaccel_output_format vaapi \
-vaapi_device /dev/dri/renderD128 \
-analyzeduration 10000000 \
-probesize 10000000 \
-fflags +discardcorrupt+genpts \
-i "http://localhost:40772/api/services/${SID}/stream" \
-map 0:v:0 -map 0:a:0 \
-vf 'scale_vaapi=854:480' \
-c:v h264_vaapi \
-b:v 400k \
-c:a aac -ar 44100 -b:a 96k -ac 2 \
-sn \
-f hls -hls_time 3 -hls_list_size 5 \
-hls_flags delete_segments+append_list \
-hls_segment_filename "${HLS_DIR}/${SID}/seg%03d.ts" \
"${HLS_DIR}/${SID}/stream.m3u8" \
> "/tmp/ffmpeg_${SID}.log" 2>&1 &
FFMPEG_PID=$!
echo "[INFO] ffmpeg PID: ${FFMPEG_PID}"
echo "[INFO] 視聴URL: http://${TAILSCALE_IP}:${PROXY_PORT}/hls/${SID}/stream.m3u8"
echo "[INFO] 完了: 約90秒後に視聴可能"
#!/usr/bin/env python3
# hls_proxy.py - HLSプロキシ(ポート8890)
# エンドポイント:
# /hls/{SID}/stream.m3u8 マニフェスト(URL書き換え)
# /hls/{SID}/seg*.ts セグメント配信
import http.server
import os
import re
import sys
TAILSCALE_IP = "100.89.99.23"
PORT = 8890
HLS_DIR = "/tmp/hls"
# 使用チャンネルのみ定義
CH_NAMES = {
"400101": "BS-NHK",
"400102": "BS-NHKサブ",
"3273601024": "NHK総合",
"3273601025": "NHKサブ",
}
class HLSProxyHandler(http.server.BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
print(f"[{self.address_string()}] {fmt % args}")
def do_GET(self):
path = self.path
# /hls/{SID}/stream.m3u8 - マニフェスト(URL書き換え)
m = re.match(r"^/hls/(\d+)/(.+\.m3u8)$", path)
if m:
sid, fname = m.group(1), m.group(2)
local_path = os.path.join(HLS_DIR, sid, fname)
self._serve_m3u8_rewritten(local_path, sid)
return
# /hls/{SID}/seg*.ts - セグメント
m = re.match(r"^/hls/(\d+)/(.+\.ts)$", path)
if m:
sid, fname = m.group(1), m.group(2)
local_path = os.path.join(HLS_DIR, sid, fname)
self._serve_file(local_path, "video/MP2T")
return
self._send_404()
def _serve_m3u8_rewritten(self, filepath, sid):
"""m3u8を読み込み、相対URLを絶対URLに書き換えて返す"""
if not os.path.exists(filepath):
self._send_404()
return
try:
with open(filepath, "r") as f:
content = f.read()
base_url = f"http://{TAILSCALE_IP}:{PORT}/hls/{sid}"
def rewrite(line):
line = line.rstrip()
if line and not line.startswith("#") and not line.startswith("http"):
return f"{base_url}/{line}"
return line
body = ("\n".join(rewrite(l) for l in content.splitlines()) + "\n").encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/vnd.apple.mpegurl")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-cache")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(body)
except Exception as e:
print(f"[ERROR] m3u8: {e}")
self._send_500()
def _serve_file(self, filepath, content_type):
"""ファイルをそのまま返す"""
if not os.path.exists(filepath):
self._send_404()
return
try:
with open(filepath, "rb") as f:
body = f.read()
self.send_response(200)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-cache")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(body)
except Exception as e:
print(f"[ERROR] file: {e}")
self._send_500()
def _send_404(self):
self.send_response(404)
self.end_headers()
self.wfile.write(b"Not Found")
def _send_500(self):
self.send_response(500)
self.end_headers()
self.wfile.write(b"Internal Server Error")
if __name__ == "__main__":
server = http.server.ThreadingHTTPServer(("0.0.0.0", PORT), HLSProxyHandler)
print(f"[INFO] HLS Proxy起動: port {PORT}")
print(f"[INFO] 使用チャンネル:")
for sid, name in CH_NAMES.items():
print(f"[INFO] {name} → http://{TAILSCALE_IP}:{PORT}/hls/{sid}/stream.m3u8")
try:
server.serve_forever()
except KeyboardInterrupt:
sys.exit(0)
sudo chmod +x /mnt/data/backup/scripts/hls_start.sh sudo chmod +x /mnt/data/backup/scripts/hls_proxy.py
■サービスファイルの作成
hls-proxy.service(常時起動・自動起動・enabled)
[Unit] Description=HLS Proxy (port 8890) After=network.target [Service] Type=simple ExecStart=/usr/bin/python3 /mnt/data/backup/scripts/hls_proxy.py Restart=always RestartSec=5 User=root [Install] WantedBy=multi-user.target
[Unit] Description=HLS Streaming - BS-NHK (400101) After=network.target hls-proxy.service Requires=hls-proxy.service [Service] Type=forking ExecStartPre=/bin/mkdir -p /tmp/hls/400101 ExecStart=/mnt/data/backup/scripts/hls_start.sh 400101 RuntimeMaxSec=3600 Restart=on-failure RestartSec=10 User=root [Install] WantedBy=multi-user.target
[Unit] Description=HLS Streaming - BS-NHKサブ (400102) After=network.target hls-proxy.service Requires=hls-proxy.service [Service] Type=forking ExecStartPre=/bin/mkdir -p /tmp/hls/400102 ExecStart=/mnt/data/backup/scripts/hls_start.sh 400102 RuntimeMaxSec=3600 Restart=on-failure RestartSec=10 User=root [Install] WantedBy=multi-user.target
[Unit] Description=HLS Streaming - NHK総合 (3273601024) After=network.target hls-proxy.service Requires=hls-proxy.service [Service] Type=forking ExecStartPre=/bin/mkdir -p /tmp/hls/3273601024 ExecStart=/mnt/data/backup/scripts/hls_start.sh 3273601024 RuntimeMaxSec=3600 Restart=on-failure RestartSec=10 User=root [Install] WantedBy=multi-user.target
[Unit] Description=HLS Streaming - NHKサブ (3273601025) After=network.target hls-proxy.service Requires=hls-proxy.service [Service] Type=forking ExecStartPre=/bin/mkdir -p /tmp/hls/3273601025 ExecStart=/mnt/data/backup/scripts/hls_start.sh 3273601025 RuntimeMaxSec=3600 Restart=on-failure RestartSec=10 User=root [Install] WantedBy=multi-user.target
sudo cp *.service /etc/systemd/system/
■端末の設定
Termius スニペットに登録
### BS-NHKを見る
sudo systemctl start hls-bs1
### BS-NHKサブ に切替
sudo systemctl stop hls-bs1; sudo systemctl start hls-bs2
### BS-NHK に切替
sudo systemctl stop hls-bs2; sudo systemctl start hls-bs1
### BS-NHKサブを見る
sudo systemctl start hls-bs2
sudo systemctl start hls-bs1
### BS-NHKサブ に切替
sudo systemctl stop hls-bs1; sudo systemctl start hls-bs2
### BS-NHK に切替
sudo systemctl stop hls-bs2; sudo systemctl start hls-bs1
### BS-NHKサブを見る
sudo systemctl start hls-bs2
VLC 左上のメニュー(≡)→ 「ストリーム」URLを入力:
http://100.89.99.23:8890/m3u/active.m3u