You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
3.4 KiB
122 lines
3.4 KiB
from argparse import ArgumentParser
|
|
from msgq.visionipc import VisionIpcClient, VisionStreamType
|
|
from cereal.messaging import SubMaster
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
from subprocess import DEVNULL
|
|
import time
|
|
import atexit
|
|
from random import randint
|
|
|
|
DEFAULT_DISPLAY = ":99"
|
|
RESOLUTION = "2160x1080"
|
|
PIXEL_DEPTH = "24"
|
|
FRAMERATE = 20
|
|
DEFAULT_OUTPUT = "output.mp4"
|
|
DEMO_ROUTE = "a2a0ccea32023010/2023-07-27--13-01-19/0"
|
|
|
|
|
|
def wait_for_video():
|
|
sm = SubMaster(['uiDebug'])
|
|
no_frames_drawn = True
|
|
while no_frames_drawn:
|
|
sm.update()
|
|
no_frames_drawn = sm['uiDebug'].drawTimeMillis == 0.
|
|
|
|
|
|
def ensure_xvfb(display: str):
|
|
xvfb_cmd = ["Xvfb", display, "-screen", "0", f"{RESOLUTION}x{PIXEL_DEPTH}"]
|
|
xvfb_proc = subprocess.Popen(xvfb_cmd, stdout=DEVNULL, stderr=DEVNULL)
|
|
time.sleep(1)
|
|
if xvfb_proc.poll() is not None:
|
|
raise RuntimeError(f"Failed to start Xvfb on display {display}")
|
|
return xvfb_proc
|
|
|
|
|
|
def main(route: str, output_filepath: str, start_seconds: int, end_seconds: int):
|
|
assert end_seconds > start_seconds, 'end must be greater than start'
|
|
display = ':' + str(randint(99, 999))
|
|
|
|
duration = end_seconds - start_seconds
|
|
|
|
env = os.environ.copy()
|
|
env["DISPLAY"] = display
|
|
env["QT_QPA_PLATFORM"] = "xcb"
|
|
|
|
print(f'starting xvfb on display {display}')
|
|
xvfb_proc = ensure_xvfb(display)
|
|
atexit.register(lambda: xvfb_proc.terminate()) # Ensure cleanup on exit
|
|
|
|
ui_args = ["./selfdrive/ui/ui"]
|
|
ui_proc = subprocess.Popen(ui_args, env=env, stdout=DEVNULL, stderr=DEVNULL)
|
|
atexit.register(lambda: ui_proc.terminate())
|
|
|
|
replay_proc = subprocess.Popen(["./tools/replay/replay", "-c", "1", "-s", str(start_seconds), "--no-loop", "--demo"], env=env, stdout=DEVNULL, stderr=DEVNULL)
|
|
atexit.register(lambda: replay_proc.terminate())
|
|
|
|
# Wait for video data
|
|
wait_for_video()
|
|
time.sleep(2)
|
|
|
|
# Start FFmpeg
|
|
ffmpeg_cmd = [
|
|
"ffmpeg",
|
|
"-y",
|
|
"-video_size",
|
|
RESOLUTION,
|
|
"-framerate",
|
|
str(FRAMERATE),
|
|
"-f",
|
|
"x11grab",
|
|
"-draw_mouse",
|
|
"0",
|
|
"-i",
|
|
env.get('DISPLAY'),
|
|
"-c:v",
|
|
"libx264",
|
|
"-preset",
|
|
"ultrafast",
|
|
"-pix_fmt",
|
|
"yuv420p",
|
|
output_filepath,
|
|
]
|
|
ffmpeg_proc = subprocess.Popen(ffmpeg_cmd, env=env, stdout=DEVNULL, stderr=DEVNULL)
|
|
atexit.register(lambda: ffmpeg_proc.terminate())
|
|
|
|
print(f'starting at {start_seconds} seconds and clipping {duration} seconds')
|
|
time.sleep(duration)
|
|
|
|
# Stop FFmpeg gracefully
|
|
ffmpeg_proc.send_signal(signal.SIGINT)
|
|
ffmpeg_proc.wait(timeout=5)
|
|
|
|
# Clean up
|
|
ui_proc.terminate()
|
|
ui_proc.wait(timeout=5)
|
|
xvfb_proc.terminate()
|
|
xvfb_proc.wait(timeout=5)
|
|
|
|
print(f"Recording complete: {output_filepath}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
p = ArgumentParser(
|
|
prog='clip.py',
|
|
description='Clip your openpilot route.',
|
|
epilog='comma.ai'
|
|
)
|
|
p.add_argument('-r', '--route', help='Route', default=DEMO_ROUTE)
|
|
p.add_argument('-o', '--output', help='Output clip to (.mp4)', default=DEFAULT_OUTPUT)
|
|
p.add_argument('-s', '--start', help='Start clipping at <start> seconds', type=int, required=True)
|
|
p.add_argument('-e', '--end', help='Stop clipping at <end> seconds', type=int, required=True)
|
|
args = p.parse_args()
|
|
try:
|
|
main(args.route, args.output, args.start, args.end)
|
|
except KeyboardInterrupt:
|
|
print("Interrupted by user")
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
finally:
|
|
# Ensure all processes are terminated
|
|
atexit._run_exitfuncs()
|
|
|