Skip to content

Commit 934ea4e

Browse files
core: services: recoder_extractor: Add mcap recover
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
1 parent 27207a5 commit 934ea4e

File tree

1 file changed

+59
-0
lines changed
  • core/services/recorder_extractor

1 file changed

+59
-0
lines changed

core/services/recorder_extractor/main.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import asyncio
44
import contextlib
55
import logging
6+
import shutil
67
from functools import wraps
78
from io import BytesIO
89
from pathlib import Path
@@ -101,6 +102,61 @@ def parse_duration_ns(discover_output: str) -> int:
101102
return duration_ns
102103

103104

105+
async def check_and_recover_mcap(mcap_path: Path) -> None:
106+
"""
107+
Check if mcap binary is available, run mcap doctor on the file,
108+
and if it fails, run mcap recover to fix the file.
109+
"""
110+
# Check if mcap binary exists
111+
mcap_binary = shutil.which("mcap")
112+
if not mcap_binary:
113+
logger.warning("mcap binary not found, skipping doctor/recover check")
114+
return
115+
116+
# Ensure path is absolute and exists
117+
if not mcap_path.exists():
118+
logger.warning(f"MCAP file not found: {mcap_path}")
119+
return
120+
121+
logger.info(f"Running mcap doctor on {mcap_path}")
122+
# Run mcap doctor
123+
doctor_cmd = ["mcap", "doctor", str(mcap_path)]
124+
doctor_proc = await asyncio.create_subprocess_exec(
125+
*doctor_cmd,
126+
stdout=asyncio.subprocess.PIPE,
127+
stderr=asyncio.subprocess.PIPE,
128+
text=False,
129+
)
130+
stdout_bytes, stderr_bytes = await doctor_proc.communicate()
131+
stdout = stdout_bytes.decode("utf-8", "ignore")
132+
stderr = stderr_bytes.decode("utf-8", "ignore")
133+
134+
if doctor_proc.returncode == 0:
135+
logger.info(f"mcap doctor passed for {mcap_path}: {stdout.strip()}")
136+
return
137+
138+
logger.error(f"mcap doctor failed for {mcap_path} (code={doctor_proc.returncode}): {stderr}")
139+
logger.info(f"Running mcap recover to fix {mcap_path}")
140+
# Run mcap recover to replace the file
141+
recover_cmd = ["mcap", "recover", str(mcap_path), "-o", str(mcap_path)]
142+
recover_proc = await asyncio.create_subprocess_exec(
143+
*recover_cmd,
144+
stdout=asyncio.subprocess.PIPE,
145+
stderr=asyncio.subprocess.PIPE,
146+
text=False,
147+
)
148+
recover_stdout_bytes, recover_stderr_bytes = await recover_proc.communicate()
149+
recover_stdout = recover_stdout_bytes.decode("utf-8", "ignore")
150+
recover_stderr = recover_stderr_bytes.decode("utf-8", "ignore")
151+
152+
if recover_proc.returncode != 0:
153+
logger.error(
154+
f"mcap recover failed for {mcap_path} (code={recover_proc.returncode}): {recover_stderr}",
155+
)
156+
else:
157+
logger.info(f"mcap recover completed for {mcap_path}: {recover_stdout.strip()}")
158+
159+
104160
@cached()
105161
async def build_thumbnail_bytes(path: Path) -> bytes:
106162
"""
@@ -187,6 +243,9 @@ async def extract_mcap_recordings() -> None:
187243
logger.info(f"Skipping MCAP extract, file in use: {mcap_path}")
188244
continue
189245

246+
# Check and recover MCAP file if mcap binary is available
247+
await check_and_recover_mcap(mcap_path)
248+
190249
command = [
191250
"mcap-foxglove-video-extract",
192251
str(mcap_path),

0 commit comments

Comments
 (0)