84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
"""Run FreeMoCap's stable headless pipeline for one uploaded reference video."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--input", required=True, type=Path)
|
|
parser.add_argument("--recording", required=True, type=Path)
|
|
parser.add_argument("--output", required=True, type=Path)
|
|
parser.add_argument("--metadata", required=True, type=Path)
|
|
parser.add_argument("--fps", required=True, type=float)
|
|
return parser.parse_args()
|
|
|
|
|
|
def prepare_mp4(source: Path, destination: Path) -> None:
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
if source.suffix.lower() == ".mp4":
|
|
shutil.copyfile(source, destination)
|
|
return
|
|
subprocess.run(
|
|
[
|
|
"ffmpeg", "-y", "-i", str(source), "-an",
|
|
"-c:v", "libx264", "-pix_fmt", "yuv420p", str(destination),
|
|
],
|
|
check=True,
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
synchronized = args.recording / "synchronized_videos"
|
|
prepared_video = synchronized / "camera_0.mp4"
|
|
prepare_mp4(args.input, prepared_video)
|
|
|
|
# FreeMoCap 1.8.x exposes this stable headless entry point. Blender and the
|
|
# notebook are deliberately disabled: Tilt only needs its filtered body XYZ.
|
|
from freemocap.core_processes.process_motion_capture_videos.process_recording_headless import (
|
|
process_recording_headless,
|
|
)
|
|
from freemocap.data_layer.recording_models.post_processing_parameter_models import (
|
|
ProcessingParameterModel,
|
|
)
|
|
import cv2
|
|
|
|
capture = cv2.VideoCapture(str(prepared_video))
|
|
detected_fps = float(capture.get(cv2.CAP_PROP_FPS))
|
|
capture.release()
|
|
source_fps = detected_fps if detected_fps > 0 else args.fps
|
|
|
|
parameters = ProcessingParameterModel()
|
|
parameters.post_processing_parameters_model.framerate = source_fps
|
|
parameters.post_processing_parameters_model.butterworth_filter_parameters.sampling_rate = source_fps
|
|
# Preserve MediaPipe's monocular depth estimate; the default flattened mode
|
|
# throws that axis away before FreeMoCap's interpolation and rigid-bone pass.
|
|
parameters.anipose_triangulate_3d_parameters_model.flatten_single_camera_data = False
|
|
process_recording_headless(
|
|
recording_path=args.recording,
|
|
recording_processing_parameter_model=parameters,
|
|
run_blender=False,
|
|
make_jupyter_notebook=False,
|
|
use_tqdm=False,
|
|
)
|
|
|
|
result = args.recording / f"{args.recording.name}_by_frame.csv"
|
|
if not result.is_file():
|
|
matches = sorted(args.recording.glob("*_by_frame.csv"))
|
|
if not matches:
|
|
raise FileNotFoundError("FreeMoCap completed without producing a by-frame CSV")
|
|
result = matches[0]
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(result, args.output)
|
|
args.metadata.write_text(json.dumps({"fps": source_fps}), encoding="utf-8")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|