From 5edaaceaf285ede2c7cf62fa73d01860be2e6b4c Mon Sep 17 00:00:00 2001 From: Andrew Reiter Date: Tue, 27 Feb 2024 22:41:36 -0500 Subject: [PATCH] Fix iOS playback of H.265 clips (#10105) * Fix iOS playback of H.265 clips * CI --- frigate/config.py | 63 ++++++++++++++++++++++++--------------- frigate/ffmpeg_presets.py | 13 ++++++-- frigate/util/services.py | 10 +++++++ 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/frigate/config.py b/frigate/config.py index e90adba7e..1f71fd7af 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -614,6 +614,7 @@ class FfmpegOutputArgsConfig(FrigateBaseModel): default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT, title="Record role FFmpeg output arguments.", ) + _force_record_hvc1: bool = PrivateAttr(default=False) class FfmpegConfig(FrigateBaseModel): @@ -878,7 +879,10 @@ class CameraConfig(FrigateBaseModel): if "record" in ffmpeg_input.roles and self.record.enabled: record_args = get_ffmpeg_arg_list( - parse_preset_output_record(self.ffmpeg.output_args.record) + parse_preset_output_record( + self.ffmpeg.output_args.record, + self.ffmpeg.output_args._force_record_hvc1, + ) or self.ffmpeg.output_args.record ) @@ -1161,31 +1165,42 @@ class FrigateConfig(FrigateBaseModel): if camera_config.ffmpeg.hwaccel_args == "auto": camera_config.ffmpeg.hwaccel_args = config.ffmpeg.hwaccel_args - if ( - camera_config.detect.height is None - or camera_config.detect.width is None - ): - for input in camera_config.ffmpeg.inputs: - if "detect" in input.roles: - stream_info = {"width": 0, "height": 0} - try: - stream_info = asyncio.run(get_video_properties(input.path)) - except Exception: - logger.warn( - f"Error detecting stream resolution automatically for {input.path} Applying default values." - ) - stream_info = {"width": 0, "height": 0} + for input in camera_config.ffmpeg.inputs: + need_record_fourcc = "record" in input.roles + need_detect_dimensions = "detect" in input.roles and ( + camera_config.detect.height is None + or camera_config.detect.width is None + ) - camera_config.detect.width = ( - stream_info["width"] - if stream_info.get("width") - else DEFAULT_DETECT_DIMENSIONS["width"] - ) - camera_config.detect.height = ( - stream_info["height"] - if stream_info.get("height") - else DEFAULT_DETECT_DIMENSIONS["height"] + if need_detect_dimensions or need_record_fourcc: + stream_info = {"width": 0, "height": 0, "fourcc": None} + try: + stream_info = asyncio.run(get_video_properties(input.path)) + except Exception: + logger.warn( + f"Error detecting stream parameters automatically for {input.path} Applying default values." ) + stream_info = {"width": 0, "height": 0, "fourcc": None} + + if need_detect_dimensions: + camera_config.detect.width = ( + stream_info["width"] + if stream_info.get("width") + else DEFAULT_DETECT_DIMENSIONS["width"] + ) + camera_config.detect.height = ( + stream_info["height"] + if stream_info.get("height") + else DEFAULT_DETECT_DIMENSIONS["height"] + ) + + if need_record_fourcc: + # Apple only supports HEVC if it is hvc1 (vs. hev1) + camera_config.ffmpeg.output_args._force_record_hvc1 = ( + stream_info["fourcc"] == "hevc" + if stream_info.get("hevc") + else False + ) # Default min_initialized configuration min_initialized = camera_config.detect.fps / 2 diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 795d83c8b..e86602840 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -461,9 +461,18 @@ PRESETS_RECORD_OUTPUT = { } -def parse_preset_output_record(arg: Any) -> list[str]: +def parse_preset_output_record(arg: Any, force_record_hvc1: bool) -> list[str]: """Return the correct preset if in preset format otherwise return None.""" if not isinstance(arg, str): return None - return PRESETS_RECORD_OUTPUT.get(arg, None) + preset = PRESETS_RECORD_OUTPUT.get(arg, None) + + if not preset: + return None + + if force_record_hvc1: + # Apple only supports HEVC if it is hvc1 (vs. hev1) + preset += ["-tag:v", "hvc1"] + + return preset diff --git a/frigate/util/services.py b/frigate/util/services.py index b9b8ceace..ae38b18ac 100644 --- a/frigate/util/services.py +++ b/frigate/util/services.py @@ -505,10 +505,20 @@ async def get_video_properties(url, get_duration=False) -> dict[str, any]: # Get the height of frames in the video stream height = video.get(cv2.CAP_PROP_FRAME_HEIGHT) + # Get the stream encoding + fourcc_int = int(video.get(cv2.CAP_PROP_FOURCC)) + fourcc = ( + chr((fourcc_int >> 0) & 255) + + chr((fourcc_int >> 8) & 255) + + chr((fourcc_int >> 16) & 255) + + chr((fourcc_int >> 24) & 255) + ) + # Release the video stream video.release() result["width"] = round(width) result["height"] = round(height) + result["fourcc"] = fourcc return result