"""Общие параметры перекодирования H.264/VP9: профиль, битрейт, пресет, GOP, режим RC.""" from __future__ import annotations H264_PROFILES = ("baseline", "main", "high") BITRATES = ("500k", "1000k", "2000k", "4000k", "8000k", "10000k", "16000k", "20000k", "24000k") PRESETS = ("ultrafast", "superfast", "veryfast", "faster", "fast", "medium") KEYINTS = (1, 2, 5) # интервал ключевых кадров, секунды CODECS = ("h264", "vp9") RC_MODES = ("vbr", "cbr", "qp") # контроль битрейта: переменный / постоянный / постоянный квантизатор DEFAULT_PROFILE = "main" DEFAULT_BITRATE = "2000k" DEFAULT_PRESET = "superfast" DEFAULT_KEYINT = 2 DEFAULT_CODEC = "h264" DEFAULT_RC = "vbr" DEFAULT_QP = 23 QP_MIN, QP_MAX = 0, 51 # пресет x264 → cpu-used VP9 (больше число = быстрее кодирование, ниже качество) _VP9_CPU = {"ultrafast": "8", "superfast": "8", "veryfast": "7", "faster": "6", "fast": "5", "medium": "4"} def norm_profile(p: str | None) -> str: return p if p in H264_PROFILES else DEFAULT_PROFILE def norm_codec(c: str | None) -> str: return c if c in CODECS else DEFAULT_CODEC def norm_bitrate(b: str | None) -> str: return b if b in BITRATES else DEFAULT_BITRATE def norm_preset(p: str | None) -> str: return p if p in PRESETS else DEFAULT_PRESET def norm_rc(m: str | None) -> str: return m if m in RC_MODES else DEFAULT_RC def norm_keyint(k) -> int: try: k = int(k) except (TypeError, ValueError): return DEFAULT_KEYINT return k if k in KEYINTS else DEFAULT_KEYINT def norm_qp(q) -> int: try: q = int(q) except (TypeError, ValueError): return DEFAULT_QP return max(QP_MIN, min(QP_MAX, q)) def _kbps(bitrate: str) -> int: return int(bitrate[:-1]) def x264_args(profile: str, bitrate: str, low_latency: bool = False, preset: str | None = None, keyint_sec=None, rc: str = DEFAULT_RC, qp=DEFAULT_QP) -> list[str]: """Аргументы libx264: профиль, пресет, GOP и режим контроля битрейта. rc: vbr — целевой битрейт с потолком (-b:v/-maxrate); cbr — постоянный битрейт (nal-hrd=cbr, min=max=b:v); qp — постоянный квантизатор (-qp, без битрейта). Ключевые кадры расставляются по времени (-force_key_frames).""" preset = norm_preset(preset) sec = norm_keyint(keyint_sec) rc = norm_rc(rc) args = [ "-c:v", "libx264", "-preset", preset, "-profile:v", profile, "-force_key_frames", f"expr:gte(t,n_forced*{sec})", "-pix_fmt", "yuv420p", ] if rc == "qp": args += ["-qp", str(norm_qp(qp))] elif rc == "cbr": kb = _kbps(bitrate) args += ["-b:v", bitrate, "-minrate", bitrate, "-maxrate", bitrate, "-bufsize", f"{kb}k", "-x264-params", "nal-hrd=cbr"] else: # vbr kb = _kbps(bitrate) args += ["-b:v", bitrate, "-maxrate", bitrate, "-bufsize", f"{kb * 2}k"] if low_latency: args += ["-tune", "zerolatency"] return args def vp9_args(bitrate: str, low_latency: bool = False, preset: str | None = None, keyint_sec=None, rc: str = DEFAULT_RC, qp=DEFAULT_QP) -> list[str]: """Аргументы libvpx-VP9 (профиль H.264 неприменим). rc: vbr/cbr — по битрейту; qp — постоянное качество (-crf N -b:v 0, у VP9 нет прямого -qp). cpu-used маппится из пресета. realtime для live.""" sec = norm_keyint(keyint_sec) rc = norm_rc(rc) args = [ "-c:v", "libvpx-vp9", "-deadline", "realtime", "-cpu-used", _VP9_CPU.get(norm_preset(preset), "6"), "-row-mt", "1", "-force_key_frames", f"expr:gte(t,n_forced*{sec})", "-pix_fmt", "yuv420p", ] if rc == "qp": args += ["-crf", str(norm_qp(qp)), "-b:v", "0"] elif rc == "cbr": args += ["-b:v", bitrate, "-minrate", bitrate, "-maxrate", bitrate] else: # vbr args += ["-b:v", bitrate, "-maxrate", bitrate] return args def encode_args(codec: str, profile: str, bitrate: str, low_latency: bool = False, preset: str | None = None, keyint_sec=None, rc: str = DEFAULT_RC, qp=DEFAULT_QP) -> list[str]: """Аргументы видеокодировщика под выбранный кодек (h264 | vp9).""" if norm_codec(codec) == "vp9": return vp9_args(bitrate, low_latency, preset, keyint_sec, rc, qp) return x264_args(profile, bitrate, low_latency, preset, keyint_sec, rc, qp)