Fix browser recording playback and web management consistency
This commit is contained in:
+70
@@ -0,0 +1,70 @@
|
||||
"""Bounded, ephemeral H.264 playback; original recordings remain untouched."""
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
SLOTS = threading.BoundedSemaphore(2)
|
||||
|
||||
|
||||
class PlaybackError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def command(source, authorization, duration):
|
||||
executable = shutil.which('ffmpeg')
|
||||
if not executable:
|
||||
raise PlaybackError('回放转换需要服务器安装 FFmpeg')
|
||||
device = os.environ.get('VISION_VAAPI_DEVICE', '/dev/dri/renderD128')
|
||||
acceleration = []
|
||||
if os.access(device, os.R_OK | os.W_OK):
|
||||
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', 'CQP', '-qp', '25', '-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', '-pix_fmt', 'yuv420p',
|
||||
'-crf', '26', '-g', '20', '-bf', '0']
|
||||
return [executable, '-hide_banner', '-loglevel', 'error', '-nostdin',
|
||||
'-threads', '1', '-filter_threads', '1'] + acceleration + [
|
||||
'-rw_timeout', '15000000', '-headers', 'Authorization: ' + authorization + '\r\n',
|
||||
'-readrate', '1', '-i', source, '-t', str(duration),
|
||||
'-map', '0:v:0', '-map', '0:a:0?', '-sn', '-dn'] + video + [
|
||||
'-c:a', 'aac', '-ar', '48000', '-ac', '1', '-b:a', '64k',
|
||||
'-movflags', '+frag_keyframe+empty_moov+default_base_moof',
|
||||
'-frag_duration', '1000000', '-f', 'mp4', 'pipe:1']
|
||||
|
||||
|
||||
@contextmanager
|
||||
def stream(source, authorization, duration):
|
||||
if not SLOTS.acquire(blocking=False):
|
||||
raise PlaybackError('已有两个回放正在进行,请关闭其他回放后重试')
|
||||
process = timer = None
|
||||
try:
|
||||
# No shell and no diagnostics containing authentication headers in logs.
|
||||
process = subprocess.Popen(command(source, authorization, duration),
|
||||
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL)
|
||||
timer = threading.Timer(duration + 45, process.kill)
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
yield process.stdout
|
||||
except OSError as exc:
|
||||
raise PlaybackError('录像转换服务暂不可用') from exc
|
||||
finally:
|
||||
if timer:
|
||||
timer.cancel()
|
||||
if process:
|
||||
if process.poll() is None:
|
||||
process.terminate()
|
||||
try:
|
||||
process.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.wait()
|
||||
process.stdout.close()
|
||||
SLOTS.release()
|
||||
Reference in New Issue
Block a user