"""On-demand H.264/AAC previews; main-stream HD uses Intel VAAPI.""" import json import os from pathlib import Path import re import shutil import signal import subprocess import sys from urllib.parse import quote def main(): name = os.environ.get('MTX_PATH', '') match = re.fullmatch(r'(cam_[a-f0-9]{16})_(compat|hd)', name) if not match: raise SystemExit('Invalid preview path') data = Path(sys.argv[1]).resolve() config = json.loads((data / 'mediamtx.yml').read_text(encoding='utf8')) hd = match[2] == 'hd' original = match[1] + ('_main' if hd else '_sub') if config['paths'].get(original, {}).get('source', 'publisher') == 'publisher': raise SystemExit('Camera is disabled') secret = next(u['pass'] for u in config['authInternalUsers'] if u['user'] == 'vision') base = 'rtsp://vision:' + quote(secret, safe='') + '@127.0.0.1:18554/' executable = shutil.which('ffmpeg') if not executable: raise SystemExit('Install FFmpeg to enable compatible preview') # HD reads the actual main stream. Decode, resize and encode on the GPU; # never silently start six high-resolution software decoders on a small CPU. acceleration = [] if hd: device = os.environ.get('VISION_VAAPI_DEVICE', '/dev/dri/renderD128') if not os.access(device, os.R_OK | os.W_OK): raise SystemExit('HD preview requires an accessible VAAPI render device') acceleration = ['-hwaccel', 'vaapi', '-hwaccel_device', device, '-hwaccel_output_format', 'vaapi', '-extra_hw_frames', '8'] video = ['-vf', 'fps=15,scale_vaapi=w=1920:h=1080:format=nv12:force_original_aspect_ratio=decrease:force_divisible_by=2', '-c:v', 'h264_vaapi', '-profile:v', 'main', '-level:v', '4.1', '-rc_mode', 'VBR', '-b:v', '3500k', '-maxrate', '5000k', '-bufsize', '10000k', '-g', '30', '-bf', '0'] else: video = ['-vf', 'fps=10,scale=768:432:force_original_aspect_ratio=decrease:force_divisible_by=2', '-c:v', 'libx264', '-threads', '1', '-preset', 'veryfast', '-tune', 'zerolatency', '-profile:v', 'baseline', '-level:v', '3.1', '-pix_fmt', 'yuv420p', '-crf', '26', '-maxrate', '1100k', '-bufsize', '2200k', '-g', '20', '-keyint_min', '20', '-sc_threshold', '0'] command = [executable, '-hide_banner', '-loglevel', 'error', '-nostdin', '-threads', '1', '-filter_threads', '1'] + acceleration + [ '-rtsp_transport', 'tcp', '-timeout', '15000000', '-i', base + original, '-map', '0:v:0', '-map', '0:a:0?', '-sn', '-dn'] + video + [ '-c:a', 'aac', '-ar', '48000', '-ac', '1', '-b:a', '48k', '-f', 'rtsp', '-rtsp_transport', 'tcp', base + name] process = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, errors='replace') def stop(_signum, _frame): if process.poll() is None: process.terminate() try: process.wait(timeout=5) except subprocess.TimeoutExpired: process.kill() signal.signal(signal.SIGTERM, stop) signal.signal(signal.SIGINT, stop) try: for line in process.stderr: # Never write connection credentials from FFmpeg diagnostics to logs. clean = re.sub(r'rtsp://[^\s]+', '[local stream]', line.replace(secret, '[redacted]')) print(name + ': ' + clean.rstrip(), file=sys.stderr, flush=True) return process.wait() finally: stop(None, None) if __name__ == '__main__': sys.exit(main())