Files
git_admin c876a46e61 transcode: add CBR/VBR/QP rate control + more x264 presets
Player/transcoding settings gain encoder controls:
- Rate-control mode VBR / CBR / QP (constant quantizer). x264: VBR uses
  -b:v/-maxrate/-bufsize, CBR adds -x264-params nal-hrd=cbr (min=max=b:v),
  QP uses -qp N (no bitrate). VP9 maps QP -> -crf N -b:v 0, CBR -> min=max=b:v.
- Preset list expanded to ultrafast/superfast/veryfast/faster/fast/medium
  (was only ultrafast/superfast); for VP9 the preset maps to cpu-used.
- New QP field in the UI, shown when rate control = QP, otherwise the
  bitrate field is shown.

Both /play.mp4 (archive) and /live.mp4 accept rc + qp query params.
All three modes verified producing valid output on the live restream.
VERSION -> 0.0.119.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:38:04 +05:00

130 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Общие параметры перекодирования 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)