import argparse import json import socket import time from pathlib import Path parser = argparse.ArgumentParser(description="Replay a Project CARS UDP recording.") parser.add_argument("recording", type=Path, help="Recording directory containing manifest.jsonl.") parser.add_argument("--host", default="127.0.0.1", help="Destination host. Defaults to 127.0.0.1.") parser.add_argument("--port", type=int, default=5606, help="Destination UDP port. Defaults to 5606.") parser.add_argument("--speed", type=float, default=1.0, help="Playback speed multiplier.") parser.add_argument("--loop", action="store_true", help="Loop playback until interrupted.") parser.add_argument( "--types", default=None, help="Comma-separated V2 packet types to replay, for example 1,2,3,4,7,8. V1 packets are skipped when this is set." ) args = parser.parse_args() recording_dir = args.recording manifest_path = recording_dir / "manifest.jsonl" if not manifest_path.exists(): raise SystemExit(f"Missing manifest: {manifest_path}") if args.speed <= 0: raise SystemExit("--speed must be greater than 0") type_filter = None if args.types: type_filter = {int(part.strip()) for part in args.types.split(",") if part.strip()} with manifest_path.open("r", encoding="utf8") as manifest: records = [json.loads(line) for line in manifest if line.strip()] if type_filter is not None: records = [ record for record in records if record.get("format") == "v2" and record.get("packetType") in type_filter ] if not records: raise SystemExit("No packets to replay after applying filters.") sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) def play_once() -> None: playback_started = time.monotonic() first_offset = records[0]["timeOffset"] for record in records: target_offset = (record["timeOffset"] - first_offset) / args.speed delay = target_offset - (time.monotonic() - playback_started) if delay > 0: time.sleep(delay) data = (recording_dir / record["file"]).read_bytes() sock.sendto(data, (args.host, args.port)) print( f"Replaying {len(records)} packets to {args.host}:{args.port} " f"at {args.speed:g}x from {recording_dir}" ) try: while True: play_once() if not args.loop: break except KeyboardInterrupt: print("\nStopped replay.")