fix: ソースFPSの認識の問題
This commit is contained in:
parent
0fdff5423e
commit
32e4fbceb2
|
|
@ -53,6 +53,40 @@ from server.detector import get_detector
|
|||
|
||||
app = FastAPI(title="Face Mask Inference Server")
|
||||
|
||||
|
||||
def _get_r_frame_rate(video_path: str) -> tuple:
|
||||
"""ffprobe でコンテナ宣言の r_frame_rate を取得する。
|
||||
|
||||
Returns:
|
||||
(fps_float, fps_str): fps_str は "120/1" のような分数文字列。
|
||||
取得失敗時は (0.0, "")。
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
"ffprobe", "-v", "error",
|
||||
"-select_streams", "v:0",
|
||||
"-show_entries", "stream=r_frame_rate",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||
video_path,
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
rate_str = result.stdout.strip()
|
||||
if "/" in rate_str:
|
||||
num, den = rate_str.split("/")
|
||||
fps_float = float(num) / float(den)
|
||||
else:
|
||||
fps_float = float(rate_str)
|
||||
rate_str = str(fps_float)
|
||||
return fps_float, rate_str
|
||||
except Exception:
|
||||
pass
|
||||
return 0.0, ""
|
||||
|
||||
# GPU status cache
|
||||
_gpu_status_cache = None
|
||||
|
||||
|
|
@ -142,8 +176,33 @@ def _build_ffmpeg_vaapi_writer(
|
|||
fps: float,
|
||||
width: int,
|
||||
height: int,
|
||||
out_fps_str: str = "",
|
||||
) -> _FFmpegPipeWriter:
|
||||
"""Create ffmpeg h264_vaapi writer with QP=24 (balanced quality/speed)."""
|
||||
"""Create ffmpeg h264_vaapi writer with QP=24 (balanced quality/speed).
|
||||
|
||||
fps: ソース動画の avg_frame_rate(rawパイプの入力レート)
|
||||
out_fps_str: 出力コンテナに宣言する r_frame_rate("120/1" 等)。
|
||||
ソースと異なる場合は fps フィルタでフレームを補完する。
|
||||
"""
|
||||
# ソースの avg_fps と出力の r_fps が有意に異なる場合のみ fps フィルタを挿入
|
||||
needs_fps_filter = bool(out_fps_str)
|
||||
if needs_fps_filter:
|
||||
try:
|
||||
if "/" in out_fps_str:
|
||||
num, den = out_fps_str.split("/")
|
||||
out_fps_float = float(num) / float(den)
|
||||
else:
|
||||
out_fps_float = float(out_fps_str)
|
||||
needs_fps_filter = abs(out_fps_float - fps) > 0.01
|
||||
except ValueError:
|
||||
needs_fps_filter = False
|
||||
|
||||
if needs_fps_filter:
|
||||
vf = f"format=nv12,fps={out_fps_str},hwupload"
|
||||
print(f"[FaceMask] fps filter: {fps:.3f} -> {out_fps_str}")
|
||||
else:
|
||||
vf = "format=nv12,hwupload"
|
||||
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
|
|
@ -164,7 +223,7 @@ def _build_ffmpeg_vaapi_writer(
|
|||
"-",
|
||||
"-an",
|
||||
"-vf",
|
||||
"format=nv12,hwupload",
|
||||
vf,
|
||||
"-c:v",
|
||||
"h264_vaapi",
|
||||
"-qp",
|
||||
|
|
@ -180,13 +239,14 @@ def _build_video_writer(
|
|||
fps: float,
|
||||
width: int,
|
||||
height: int,
|
||||
out_fps_str: str = "",
|
||||
) -> object:
|
||||
"""Create writer with VAAPI preference and OpenCV fallback."""
|
||||
format_key = fmt.lower()
|
||||
|
||||
if format_key in {"mp4", "mov"}:
|
||||
try:
|
||||
writer = _build_ffmpeg_vaapi_writer(output_path, fps, width, height)
|
||||
writer = _build_ffmpeg_vaapi_writer(output_path, fps, width, height, out_fps_str)
|
||||
print("[FaceMask] Using output encoder: ffmpeg h264_vaapi (-qp 24)")
|
||||
return writer
|
||||
except Exception as e:
|
||||
|
|
@ -417,6 +477,15 @@ def process_bake_task(task_id: str, req: BakeRequest):
|
|||
src_frames = int(temp_cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
temp_cap.release()
|
||||
|
||||
# ffprobe で r_frame_rate を取得し、出力コンテナの宣言 FPS をソースに合わせる。
|
||||
# 例: 120fps タイムベースで記録された 60fps 動画は r_frame_rate=120/1 だが
|
||||
# cv2 は avg_frame_rate=60fps を返すため、Bake 後に Blender がFPSを別値で認識してしまう。
|
||||
r_fps_float, r_fps_str = _get_r_frame_rate(req.video_path)
|
||||
if r_fps_float > 0:
|
||||
print(f"[FaceMask] r_frame_rate={r_fps_str}, avg_fps={src_fps:.3f}")
|
||||
else:
|
||||
r_fps_str = ""
|
||||
|
||||
if src_width <= 0 or src_height <= 0:
|
||||
tasks[task_id].status = TaskStatus.FAILED
|
||||
tasks[task_id].message = "Invalid source video dimensions"
|
||||
|
|
@ -617,7 +686,7 @@ def process_bake_task(task_id: str, req: BakeRequest):
|
|||
frame_count = 0
|
||||
writer = None
|
||||
try:
|
||||
writer = _build_video_writer(req.output_path, req.format, src_fps, src_width, src_height)
|
||||
writer = _build_video_writer(req.output_path, req.format, src_fps, src_width, src_height, r_fps_str)
|
||||
|
||||
while True:
|
||||
if cancel_event and cancel_event.is_set():
|
||||
|
|
@ -859,13 +928,20 @@ def get_video_info(req: VideoInfoRequest):
|
|||
raise HTTPException(status_code=400, detail="Failed to open video")
|
||||
|
||||
try:
|
||||
fps = float(cap.get(cv2.CAP_PROP_FPS) or 0.0)
|
||||
avg_fps = float(cap.get(cv2.CAP_PROP_FPS) or 0.0)
|
||||
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 0)
|
||||
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 0)
|
||||
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
|
||||
finally:
|
||||
cap.release()
|
||||
|
||||
# Blender は r_frame_rate でタイムライン配置を計算するため、
|
||||
# cv2 の avg_frame_rate ではなく r_frame_rate を fps として返す。
|
||||
# 例: 120fps タイムベース記録の 60fps 動画で r_frame_rate=120 を返すことで
|
||||
# compute_strip_frame_range の fps_ratio が Blender の解釈と一致する。
|
||||
r_fps_float, _ = _get_r_frame_rate(req.video_path)
|
||||
fps = r_fps_float if r_fps_float > 0 else avg_fps
|
||||
|
||||
return {
|
||||
"video_path": req.video_path,
|
||||
"fps": fps,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user