Add config option for handling HEVC playback on Apple devices

This commit is contained in:
Nicolas Mowen 2025-01-03 06:57:07 -07:00
parent 05f9ae50b5
commit 7362771d0c
6 changed files with 17 additions and 15 deletions

View File

@ -167,7 +167,7 @@ class CameraConfig(FrigateBaseModel):
record_args = get_ffmpeg_arg_list( record_args = get_ffmpeg_arg_list(
parse_preset_output_record( parse_preset_output_record(
self.ffmpeg.output_args.record, self.ffmpeg.output_args.record,
self.ffmpeg.output_args._force_record_hvc1, self.ffmpeg.apple_compatibility,
) )
or self.ffmpeg.output_args.record or self.ffmpeg.output_args.record
) )

View File

@ -42,7 +42,6 @@ class FfmpegOutputArgsConfig(FrigateBaseModel):
default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT, default=RECORD_FFMPEG_OUTPUT_ARGS_DEFAULT,
title="Record role FFmpeg output arguments.", title="Record role FFmpeg output arguments.",
) )
_force_record_hvc1: bool = PrivateAttr(default=False)
class FfmpegConfig(FrigateBaseModel): class FfmpegConfig(FrigateBaseModel):
@ -64,6 +63,10 @@ class FfmpegConfig(FrigateBaseModel):
default=10.0, default=10.0,
title="Time in seconds to wait before FFmpeg retries connecting to the camera.", title="Time in seconds to wait before FFmpeg retries connecting to the camera.",
) )
apple_compatibility: bool = Field(
default=False,
title="Set tag on HEVC (H.265) recording stream to improve compatibility with Apple players.",
)
@property @property
def ffmpeg_path(self) -> str: def ffmpeg_path(self) -> str:

View File

@ -458,13 +458,12 @@ class FrigateConfig(FrigateBaseModel):
camera_config.ffmpeg.hwaccel_args = self.ffmpeg.hwaccel_args camera_config.ffmpeg.hwaccel_args = self.ffmpeg.hwaccel_args
for input in camera_config.ffmpeg.inputs: for input in camera_config.ffmpeg.inputs:
need_record_fourcc = False and "record" in input.roles
need_detect_dimensions = "detect" in input.roles and ( need_detect_dimensions = "detect" in input.roles and (
camera_config.detect.height is None camera_config.detect.height is None
or camera_config.detect.width is None or camera_config.detect.width is None
) )
if need_detect_dimensions or need_record_fourcc: if need_detect_dimensions:
stream_info = {"width": 0, "height": 0, "fourcc": None} stream_info = {"width": 0, "height": 0, "fourcc": None}
try: try:
stream_info = stream_info_retriever.get_stream_info( stream_info = stream_info_retriever.get_stream_info(
@ -488,14 +487,6 @@ class FrigateConfig(FrigateBaseModel):
else DEFAULT_DETECT_DIMENSIONS["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
)
# Warn if detect fps > 10 # Warn if detect fps > 10
if camera_config.detect.fps > 10: if camera_config.detect.fps > 10:
logger.warning( logger.warning(

View File

@ -65,6 +65,7 @@ INCLUDED_FFMPEG_VERSIONS = ["7.0", "5.0"]
FFMPEG_HWACCEL_NVIDIA = "preset-nvidia" FFMPEG_HWACCEL_NVIDIA = "preset-nvidia"
FFMPEG_HWACCEL_VAAPI = "preset-vaapi" FFMPEG_HWACCEL_VAAPI = "preset-vaapi"
FFMPEG_HWACCEL_VULKAN = "preset-vulkan" FFMPEG_HWACCEL_VULKAN = "preset-vulkan"
FFMPEG_HVC1_ARGS = ["-tag:v", "hvc1"]
# Regex constants # Regex constants

View File

@ -6,6 +6,7 @@ from enum import Enum
from typing import Any from typing import Any
from frigate.const import ( from frigate.const import (
FFMPEG_HVC1_ARGS,
FFMPEG_HWACCEL_NVIDIA, FFMPEG_HWACCEL_NVIDIA,
FFMPEG_HWACCEL_VAAPI, FFMPEG_HWACCEL_VAAPI,
FFMPEG_HWACCEL_VULKAN, FFMPEG_HWACCEL_VULKAN,
@ -497,6 +498,6 @@ def parse_preset_output_record(arg: Any, force_record_hvc1: bool) -> list[str]:
if force_record_hvc1: if force_record_hvc1:
# Apple only supports HEVC if it is hvc1 (vs. hev1) # Apple only supports HEVC if it is hvc1 (vs. hev1)
preset += ["-tag:v", "hvc1"] preset += FFMPEG_HVC1_ARGS
return preset return preset

View File

@ -19,6 +19,7 @@ from frigate.const import (
CACHE_DIR, CACHE_DIR,
CLIPS_DIR, CLIPS_DIR,
EXPORT_DIR, EXPORT_DIR,
FFMPEG_HVC1_ARGS,
MAX_PLAYLIST_SECONDS, MAX_PLAYLIST_SECONDS,
PREVIEW_FRAME_TYPE, PREVIEW_FRAME_TYPE,
) )
@ -219,7 +220,7 @@ class RecordingExporter(threading.Thread):
if self.playback_factor == PlaybackFactorEnum.realtime: if self.playback_factor == PlaybackFactorEnum.realtime:
ffmpeg_cmd = ( ffmpeg_cmd = (
f"{self.config.ffmpeg.ffmpeg_path} -hide_banner {ffmpeg_input} -c copy -movflags +faststart {video_path}" f"{self.config.ffmpeg.ffmpeg_path} -hide_banner {ffmpeg_input} -c copy -movflags +faststart"
).split(" ") ).split(" ")
elif self.playback_factor == PlaybackFactorEnum.timelapse_25x: elif self.playback_factor == PlaybackFactorEnum.timelapse_25x:
ffmpeg_cmd = ( ffmpeg_cmd = (
@ -227,11 +228,16 @@ class RecordingExporter(threading.Thread):
self.config.ffmpeg.ffmpeg_path, self.config.ffmpeg.ffmpeg_path,
self.config.ffmpeg.hwaccel_args, self.config.ffmpeg.hwaccel_args,
f"-an {ffmpeg_input}", f"-an {ffmpeg_input}",
f"{self.config.cameras[self.camera].record.export.timelapse_args} -movflags +faststart {video_path}", f"{self.config.cameras[self.camera].record.export.timelapse_args} -movflags +faststart",
EncodeTypeEnum.timelapse, EncodeTypeEnum.timelapse,
) )
).split(" ") ).split(" ")
if self.config.ffmpeg.apple_compatibility:
ffmpeg_cmd += FFMPEG_HVC1_ARGS
ffmpeg_cmd.append(video_path)
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) -> list[str]: