import subprocess import os from config import settings class TranscodeManager: _processes: dict[str, list[subprocess.Popen]] = {} def start_transcode(self, stream_key: str, input_url: str, qualities: list[dict]): if stream_key in self._processes: return procs = [] segment_seconds = max(1, settings.hls_segment_seconds) for quality in qualities: label = quality["label"] width = quality["width"] height = quality["height"] fps = quality.get("fps", 30) codec = quality.get("codec", "libx264") bitrate = quality["bitrate"] crf = quality.get("crf", 23) preset = quality.get("preset", "ultrafast") audio_bitrate = quality.get("audio_bitrate", "128k") keyframe_seconds = quality.get("keyframe_seconds", segment_seconds) keyframe_frames = keyframe_seconds * fps hls_dir = os.path.join(settings.hls_runtime_dir, stream_key, label) os.makedirs(hls_dir, exist_ok=True) cmd = [ "ffmpeg", "-y", "-i", input_url, "-c:v", codec, "-preset", preset, "-r", str(fps), "-b:v", bitrate, "-crf", str(crf), "-s", f"{width}x{height}", "-sc_threshold", "0", "-g", str(keyframe_frames), "-keyint_min", str(keyframe_frames), "-force_key_frames", f"expr:gte(t,n_forced*{keyframe_seconds})", "-copyts", "-start_at_zero", "-c:a", "aac", "-b:a", audio_bitrate, "-f", "hls", "-hls_time", str(segment_seconds), "-hls_list_size", "6", "-hls_flags", "delete_segments+independent_segments+program_date_time", "-hls_segment_filename", os.path.join(hls_dir, "segment_%03d.ts"), os.path.join(hls_dir, "index.m3u8"), ] try: proc = subprocess.Popen( cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) procs.append(proc) except FileNotFoundError: pass self._processes[stream_key] = procs def stop_transcode(self, stream_key: str): procs = self._processes.pop(stream_key, []) for proc in procs: try: proc.terminate() proc.wait(timeout=5) except Exception: try: proc.kill() except Exception: pass def stop_all(self): for key in list(self._processes.keys()): self.stop_transcode(key) transcode_mgr = TranscodeManager()