mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-22 20:18:30 +03:00
Add CPU fallback
This commit is contained in:
parent
c2d09f4029
commit
100c7165ee
@ -42,3 +42,8 @@ class ExportRecordingsCustomBody(BaseModel):
|
|||||||
title="FFmpeg output arguments",
|
title="FFmpeg output arguments",
|
||||||
description="Custom FFmpeg output arguments. If not provided, defaults to timelapse output args.",
|
description="Custom FFmpeg output arguments. If not provided, defaults to timelapse output args.",
|
||||||
)
|
)
|
||||||
|
cpu_fallback: bool = Field(
|
||||||
|
default=False,
|
||||||
|
title="CPU Fallback",
|
||||||
|
description="If true, retry export without hardware acceleration if the initial export fails.",
|
||||||
|
)
|
||||||
|
|||||||
@ -484,6 +484,7 @@ def export_recording_custom(
|
|||||||
existing_image = sanitize_filepath(body.image_path) if body.image_path else None
|
existing_image = sanitize_filepath(body.image_path) if body.image_path else None
|
||||||
ffmpeg_input_args = body.ffmpeg_input_args
|
ffmpeg_input_args = body.ffmpeg_input_args
|
||||||
ffmpeg_output_args = body.ffmpeg_output_args
|
ffmpeg_output_args = body.ffmpeg_output_args
|
||||||
|
cpu_fallback = body.cpu_fallback
|
||||||
|
|
||||||
export_case_id = body.export_case_id
|
export_case_id = body.export_case_id
|
||||||
if export_case_id is not None:
|
if export_case_id is not None:
|
||||||
@ -569,6 +570,7 @@ def export_recording_custom(
|
|||||||
export_case_id,
|
export_case_id,
|
||||||
ffmpeg_input_args,
|
ffmpeg_input_args,
|
||||||
ffmpeg_output_args,
|
ffmpeg_output_args,
|
||||||
|
cpu_fallback,
|
||||||
)
|
)
|
||||||
exporter.start()
|
exporter.start()
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
|
|||||||
@ -62,6 +62,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
export_case_id: Optional[str] = None,
|
export_case_id: Optional[str] = None,
|
||||||
ffmpeg_input_args: Optional[str] = None,
|
ffmpeg_input_args: Optional[str] = None,
|
||||||
ffmpeg_output_args: Optional[str] = None,
|
ffmpeg_output_args: Optional[str] = None,
|
||||||
|
cpu_fallback: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.config = config
|
self.config = config
|
||||||
@ -75,6 +76,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
self.export_case_id = export_case_id
|
self.export_case_id = export_case_id
|
||||||
self.ffmpeg_input_args = ffmpeg_input_args
|
self.ffmpeg_input_args = ffmpeg_input_args
|
||||||
self.ffmpeg_output_args = ffmpeg_output_args
|
self.ffmpeg_output_args = ffmpeg_output_args
|
||||||
|
self.cpu_fallback = cpu_fallback
|
||||||
|
|
||||||
# ensure export thumb dir
|
# ensure export thumb dir
|
||||||
Path(os.path.join(CLIPS_DIR, "export")).mkdir(exist_ok=True)
|
Path(os.path.join(CLIPS_DIR, "export")).mkdir(exist_ok=True)
|
||||||
@ -179,7 +181,9 @@ class RecordingExporter(threading.Thread):
|
|||||||
|
|
||||||
return thumb_path
|
return thumb_path
|
||||||
|
|
||||||
def get_record_export_command(self, video_path: str) -> list[str]:
|
def get_record_export_command(
|
||||||
|
self, video_path: str, use_hwaccel: bool = True
|
||||||
|
) -> list[str]:
|
||||||
if (self.end_time - self.start_time) <= MAX_PLAYLIST_SECONDS:
|
if (self.end_time - self.start_time) <= MAX_PLAYLIST_SECONDS:
|
||||||
playlist_lines = f"http://127.0.0.1:5000/vod/{self.camera}/start/{self.start_time}/end/{self.end_time}/index.m3u8"
|
playlist_lines = f"http://127.0.0.1:5000/vod/{self.camera}/start/{self.start_time}/end/{self.end_time}/index.m3u8"
|
||||||
ffmpeg_input = (
|
ffmpeg_input = (
|
||||||
@ -219,10 +223,15 @@ class RecordingExporter(threading.Thread):
|
|||||||
ffmpeg_input = "-y -protocol_whitelist pipe,file,http,tcp -f concat -safe 0 -i /dev/stdin"
|
ffmpeg_input = "-y -protocol_whitelist pipe,file,http,tcp -f concat -safe 0 -i /dev/stdin"
|
||||||
|
|
||||||
if self.ffmpeg_input_args is not None and self.ffmpeg_output_args is not None:
|
if self.ffmpeg_input_args is not None and self.ffmpeg_output_args is not None:
|
||||||
|
hwaccel_args = (
|
||||||
|
self.config.cameras[self.camera].record.export.hwaccel_args
|
||||||
|
if use_hwaccel
|
||||||
|
else None
|
||||||
|
)
|
||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
parse_preset_hardware_acceleration_encode(
|
parse_preset_hardware_acceleration_encode(
|
||||||
self.config.ffmpeg.ffmpeg_path,
|
self.config.ffmpeg.ffmpeg_path,
|
||||||
self.config.cameras[self.camera].record.export.hwaccel_args,
|
hwaccel_args,
|
||||||
f"{self.ffmpeg_input_args} -an {ffmpeg_input}".strip(),
|
f"{self.ffmpeg_input_args} -an {ffmpeg_input}".strip(),
|
||||||
f"{self.ffmpeg_output_args} -movflags +faststart".strip(),
|
f"{self.ffmpeg_output_args} -movflags +faststart".strip(),
|
||||||
EncodeTypeEnum.timelapse,
|
EncodeTypeEnum.timelapse,
|
||||||
@ -241,7 +250,9 @@ class RecordingExporter(threading.Thread):
|
|||||||
|
|
||||||
return ffmpeg_cmd, playlist_lines
|
return ffmpeg_cmd, playlist_lines
|
||||||
|
|
||||||
def get_preview_export_command(self, video_path: str) -> list[str]:
|
def get_preview_export_command(
|
||||||
|
self, video_path: str, use_hwaccel: bool = True
|
||||||
|
) -> list[str]:
|
||||||
playlist_lines = []
|
playlist_lines = []
|
||||||
codec = "-c copy"
|
codec = "-c copy"
|
||||||
|
|
||||||
@ -310,10 +321,15 @@ class RecordingExporter(threading.Thread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.ffmpeg_input_args is not None and self.ffmpeg_output_args is not None:
|
if self.ffmpeg_input_args is not None and self.ffmpeg_output_args is not None:
|
||||||
|
hwaccel_args = (
|
||||||
|
self.config.cameras[self.camera].record.export.hwaccel_args
|
||||||
|
if use_hwaccel
|
||||||
|
else None
|
||||||
|
)
|
||||||
ffmpeg_cmd = (
|
ffmpeg_cmd = (
|
||||||
parse_preset_hardware_acceleration_encode(
|
parse_preset_hardware_acceleration_encode(
|
||||||
self.config.ffmpeg.ffmpeg_path,
|
self.config.ffmpeg.ffmpeg_path,
|
||||||
self.config.cameras[self.camera].record.export.hwaccel_args,
|
hwaccel_args,
|
||||||
f"{self.ffmpeg_input_args} {TIMELAPSE_DATA_INPUT_ARGS} {ffmpeg_input}".strip(),
|
f"{self.ffmpeg_input_args} {TIMELAPSE_DATA_INPUT_ARGS} {ffmpeg_input}".strip(),
|
||||||
f"{self.ffmpeg_output_args} -movflags +faststart {video_path}".strip(),
|
f"{self.ffmpeg_output_args} -movflags +faststart {video_path}".strip(),
|
||||||
EncodeTypeEnum.timelapse,
|
EncodeTypeEnum.timelapse,
|
||||||
@ -379,6 +395,34 @@ class RecordingExporter(threading.Thread):
|
|||||||
capture_output=True,
|
capture_output=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# If export failed and cpu_fallback is enabled, retry without hwaccel
|
||||||
|
if (
|
||||||
|
p.returncode != 0
|
||||||
|
and self.cpu_fallback
|
||||||
|
and self.ffmpeg_input_args is not None
|
||||||
|
and self.ffmpeg_output_args is not None
|
||||||
|
):
|
||||||
|
logger.warning(
|
||||||
|
f"Export with hardware acceleration failed, retrying without hwaccel for {self.export_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.playback_source == PlaybackSourceEnum.recordings:
|
||||||
|
ffmpeg_cmd, playlist_lines = self.get_record_export_command(
|
||||||
|
video_path, use_hwaccel=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ffmpeg_cmd, playlist_lines = self.get_preview_export_command(
|
||||||
|
video_path, use_hwaccel=False
|
||||||
|
)
|
||||||
|
|
||||||
|
p = sp.run(
|
||||||
|
ffmpeg_cmd,
|
||||||
|
input="\n".join(playlist_lines),
|
||||||
|
encoding="ascii",
|
||||||
|
preexec_fn=lower_priority,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Failed to export {self.playback_source.value} for command {' '.join(ffmpeg_cmd)}"
|
f"Failed to export {self.playback_source.value} for command {' '.join(ffmpeg_cmd)}"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user