forked from Github/frigate
Configurable ffmpeg (#13722)
* Install multiple ffmpeg versions and add config to make it configurable * Update docs * Run ffprobe too * Cleanup * Apply config to go2rtc as well * Fix ffmpeg bin * Docs * Restore path * Cleanup env var * Fix ffmpeg path for encoding * Fix export * Formatting
This commit is contained in:
@@ -414,7 +414,7 @@ def ffprobe():
|
||||
output = []
|
||||
|
||||
for path in paths:
|
||||
ffprobe = ffprobe_stream(path.strip())
|
||||
ffprobe = ffprobe_stream(current_app.frigate_config.ffmpeg, path.strip())
|
||||
output.append(
|
||||
{
|
||||
"return_code": ffprobe.returncode,
|
||||
|
||||
@@ -17,6 +17,7 @@ from peewee import DoesNotExist, fn
|
||||
from tzlocal import get_localzone_name
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import (
|
||||
CACHE_DIR,
|
||||
CLIPS_DIR,
|
||||
@@ -216,9 +217,10 @@ def get_snapshot_from_recording(camera_name: str, frame_time: str, format: str):
|
||||
|
||||
height = request.args.get("height", type=int)
|
||||
codec = "png" if format == "png" else "mjpeg"
|
||||
config: FrigateConfig = current_app.frigate_config
|
||||
|
||||
image_data = get_image_from_recording(
|
||||
recording.path, time_in_segment, codec, height
|
||||
config.ffmpeg, recording.path, time_in_segment, codec, height
|
||||
)
|
||||
|
||||
if not image_data:
|
||||
@@ -273,9 +275,12 @@ def submit_recording_snapshot_to_plus(camera_name: str, frame_time: str):
|
||||
)
|
||||
|
||||
try:
|
||||
config: FrigateConfig = current_app.frigate_config
|
||||
recording: Recordings = recording_query.get()
|
||||
time_in_segment = frame_time - recording.start_time
|
||||
image_data = get_image_from_recording(recording.path, time_in_segment, "png")
|
||||
image_data = get_image_from_recording(
|
||||
config.ffmpeg, recording.path, time_in_segment, "png"
|
||||
)
|
||||
|
||||
if not image_data:
|
||||
return make_response(
|
||||
@@ -474,9 +479,11 @@ def recording_clip(camera_name, start_ts, end_ts):
|
||||
file_name = secure_filename(file_name)
|
||||
path = os.path.join(CLIPS_DIR, f"cache/{file_name}")
|
||||
|
||||
config: FrigateConfig = current_app.frigate_config
|
||||
|
||||
if not os.path.exists(path):
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
config.ffmpeg.ffmpeg_path,
|
||||
"-hide_banner",
|
||||
"-y",
|
||||
"-protocol_whitelist",
|
||||
@@ -1141,8 +1148,9 @@ def preview_gif(camera_name: str, start_ts, end_ts, max_cache_age=2592000):
|
||||
diff = start_ts - preview.start_time
|
||||
minutes = int(diff / 60)
|
||||
seconds = int(diff % 60)
|
||||
config: FrigateConfig = current_app.frigate_config
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
config.ffmpeg.ffmpeg_path,
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
@@ -1206,9 +1214,10 @@ def preview_gif(camera_name: str, start_ts, end_ts, max_cache_age=2592000):
|
||||
|
||||
last_file = selected_previews[-2]
|
||||
selected_previews.append(last_file)
|
||||
config: FrigateConfig = current_app.frigate_config
|
||||
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
config.ffmpeg.ffmpeg_path,
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
@@ -1301,8 +1310,9 @@ def preview_mp4(camera_name: str, start_ts, end_ts, max_cache_age=604800):
|
||||
diff = start_ts - preview.start_time
|
||||
minutes = int(diff / 60)
|
||||
seconds = int(diff % 60)
|
||||
config: FrigateConfig = current_app.frigate_config
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
config.ffmpeg.ffmpeg_path,
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
@@ -1364,9 +1374,10 @@ def preview_mp4(camera_name: str, start_ts, end_ts, max_cache_age=604800):
|
||||
|
||||
last_file = selected_previews[-2]
|
||||
selected_previews.append(last_file)
|
||||
config: FrigateConfig = current_app.frigate_config
|
||||
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
config.ffmpeg.ffmpeg_path,
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
|
||||
@@ -374,7 +374,7 @@ class FrigateApp:
|
||||
except PermissionError:
|
||||
logger.error("Unable to write to /config to save export state")
|
||||
|
||||
migrate_exports(self.config.cameras.keys())
|
||||
migrate_exports(self.config.ffmpeg, self.config.cameras.keys())
|
||||
|
||||
def init_external_event_processor(self) -> None:
|
||||
self.external_event_processor = ExternalEventProcessor(self.config)
|
||||
|
||||
@@ -866,6 +866,7 @@ class FfmpegOutputArgsConfig(FrigateBaseModel):
|
||||
|
||||
|
||||
class FfmpegConfig(FrigateBaseModel):
|
||||
path: str = Field(default="default", title="FFmpeg path")
|
||||
global_args: Union[str, List[str]] = Field(
|
||||
default=FFMPEG_GLOBAL_ARGS_DEFAULT, title="Global FFmpeg arguments."
|
||||
)
|
||||
@@ -884,6 +885,34 @@ class FfmpegConfig(FrigateBaseModel):
|
||||
title="Time in seconds to wait before FFmpeg retries connecting to the camera.",
|
||||
)
|
||||
|
||||
@property
|
||||
def ffmpeg_path(self) -> str:
|
||||
if self.path == "default":
|
||||
if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59")) >= 59:
|
||||
return "/usr/lib/ffmpeg/7.0/bin/ffmpeg"
|
||||
else:
|
||||
return "ffmpeg"
|
||||
elif self.path == "7.0":
|
||||
return "/usr/lib/ffmpeg/7.0/bin/ffmpeg"
|
||||
elif self.path == "5.0":
|
||||
return "/usr/lib/ffmpeg/5.0/bin/ffmpeg"
|
||||
else:
|
||||
return f"{self.path}/bin/ffmpeg"
|
||||
|
||||
@property
|
||||
def ffprobe_path(self) -> str:
|
||||
if self.path == "default":
|
||||
if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59")) >= 59:
|
||||
return "/usr/lib/ffmpeg/7.0/bin/ffprobe"
|
||||
else:
|
||||
return "ffprobe"
|
||||
elif self.path == "7.0":
|
||||
return "/usr/lib/ffmpeg/7.0/bin/ffprobe"
|
||||
elif self.path == "5.0":
|
||||
return "/usr/lib/ffmpeg/5.0/bin/ffprobe"
|
||||
else:
|
||||
return f"{self.path}/bin/ffprobe"
|
||||
|
||||
|
||||
class CameraRoleEnum(str, Enum):
|
||||
audio = "audio"
|
||||
@@ -1194,7 +1223,7 @@ class CameraConfig(FrigateBaseModel):
|
||||
)
|
||||
|
||||
cmd = (
|
||||
["ffmpeg"]
|
||||
[self.ffmpeg.ffmpeg_path]
|
||||
+ global_args
|
||||
+ hwaccel_args
|
||||
+ input_args
|
||||
@@ -1520,7 +1549,9 @@ class FrigateConfig(FrigateBaseModel):
|
||||
if need_detect_dimensions or need_record_fourcc:
|
||||
stream_info = {"width": 0, "height": 0, "fourcc": None}
|
||||
try:
|
||||
stream_info = stream_info_retriever.get_stream_info(input.path)
|
||||
stream_info = stream_info_retriever.get_stream_info(
|
||||
config.ffmpeg, input.path
|
||||
)
|
||||
except Exception:
|
||||
logger.warn(
|
||||
f"Error detecting stream parameters automatically for {input.path} Applying default values."
|
||||
|
||||
@@ -50,7 +50,7 @@ def get_ffmpeg_command(ffmpeg: FfmpegConfig) -> list[str]:
|
||||
or get_ffmpeg_arg_list(ffmpeg.input_args)
|
||||
)
|
||||
return (
|
||||
["ffmpeg", "-vn", "-threads", "1"]
|
||||
[ffmpeg.ffmpeg_path, "-vn", "-threads", "1"]
|
||||
+ input_args
|
||||
+ ["-i"]
|
||||
+ [ffmpeg_input.path]
|
||||
|
||||
@@ -49,12 +49,12 @@ class LibvaGpuSelector:
|
||||
|
||||
FPS_VFR_PARAM = (
|
||||
"-fps_mode vfr"
|
||||
if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59")) >= 59
|
||||
if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59") or "59") >= 59
|
||||
else "-vsync 2"
|
||||
)
|
||||
TIMEOUT_PARAM = (
|
||||
"-timeout"
|
||||
if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59")) >= 59
|
||||
if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59") or "59") >= 59
|
||||
else "-stimeout"
|
||||
)
|
||||
|
||||
@@ -111,17 +111,17 @@ PRESETS_HW_ACCEL_SCALE["preset-nvidia-h265"] = PRESETS_HW_ACCEL_SCALE[
|
||||
]
|
||||
|
||||
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
|
||||
"preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m {1}",
|
||||
"preset-rpi-64-h265": "ffmpeg -hide_banner {0} -c:v hevc_v4l2m2m {1}",
|
||||
FFMPEG_HWACCEL_VAAPI: "ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {2} {0} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {1}",
|
||||
"preset-intel-qsv-h264": "ffmpeg -hide_banner {0} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
|
||||
"preset-intel-qsv-h265": "ffmpeg -hide_banner {0} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
|
||||
FFMPEG_HWACCEL_NVIDIA: "ffmpeg -hide_banner {0} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {1}",
|
||||
"preset-jetson-h264": "ffmpeg -hide_banner {0} -c:v h264_nvmpi -profile high {1}",
|
||||
"preset-jetson-h265": "ffmpeg -hide_banner {0} -c:v h264_nvmpi -profile high {1}",
|
||||
"preset-rk-h264": "ffmpeg -hide_banner {0} -c:v h264_rkmpp -profile:v high {1}",
|
||||
"preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp -profile:v high {1}",
|
||||
"default": "ffmpeg -hide_banner {0} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {1}",
|
||||
"preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m {2}",
|
||||
"preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m {2}",
|
||||
FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}",
|
||||
"preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}",
|
||||
"preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}",
|
||||
FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}",
|
||||
"preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}",
|
||||
"preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}",
|
||||
"preset-rk-h264": "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}",
|
||||
"preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v high {2}",
|
||||
"default": "{0} -hide_banner {1} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {2}",
|
||||
}
|
||||
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-nvidia-h264"] = (
|
||||
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_NVIDIA]
|
||||
@@ -131,18 +131,18 @@ PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-nvidia-h265"] = (
|
||||
)
|
||||
|
||||
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
|
||||
"preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m -pix_fmt yuv420p {1}",
|
||||
"preset-rpi-64-h265": "ffmpeg -hide_banner {0} -c:v hevc_v4l2m2m -pix_fmt yuv420p {1}",
|
||||
FFMPEG_HWACCEL_VAAPI: "ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {2} {0} -c:v h264_vaapi {1}",
|
||||
"preset-intel-qsv-h264": "ffmpeg -hide_banner {0} -c:v h264_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
|
||||
"preset-intel-qsv-h265": "ffmpeg -hide_banner {0} -c:v hevc_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
|
||||
FFMPEG_HWACCEL_NVIDIA: "ffmpeg -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {0} -c:v h264_nvenc {1}",
|
||||
"preset-nvidia-h265": "ffmpeg -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {0} -c:v hevc_nvenc {1}",
|
||||
"preset-jetson-h264": "ffmpeg -hide_banner {0} -c:v h264_nvmpi -profile high {1}",
|
||||
"preset-jetson-h265": "ffmpeg -hide_banner {0} -c:v hevc_nvmpi -profile high {1}",
|
||||
"preset-rk-h264": "ffmpeg -hide_banner {0} -c:v h264_rkmpp -profile:v high {1}",
|
||||
"preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp -profile:v high {1}",
|
||||
"default": "ffmpeg -hide_banner {0} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {1}",
|
||||
"preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m -pix_fmt yuv420p {2}",
|
||||
"preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m -pix_fmt yuv420p {2}",
|
||||
FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi {2}",
|
||||
"preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {2}",
|
||||
"preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v hevc_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {2}",
|
||||
FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {1} -c:v h264_nvenc {2}",
|
||||
"preset-nvidia-h265": "{0} -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {1} -c:v hevc_nvenc {2}",
|
||||
"preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}",
|
||||
"preset-jetson-h265": "{0} -hide_banner {1} -c:v hevc_nvmpi -profile high {2}",
|
||||
"preset-rk-h264": "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}",
|
||||
"preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v high {2}",
|
||||
"default": "{0} -hide_banner {1} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {2}",
|
||||
}
|
||||
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE["preset-nvidia-h264"] = (
|
||||
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[FFMPEG_HWACCEL_NVIDIA]
|
||||
@@ -150,7 +150,7 @@ PRESETS_HW_ACCEL_ENCODE_TIMELAPSE["preset-nvidia-h264"] = (
|
||||
|
||||
# encoding of previews is only done on CPU due to comparable encode times and better quality from libx264
|
||||
PRESETS_HW_ACCEL_ENCODE_PREVIEW = {
|
||||
"default": "ffmpeg -hide_banner {0} -c:v libx264 -profile:v baseline -preset:v ultrafast {1}",
|
||||
"default": "{0} -hide_banner {1} -c:v libx264 -profile:v baseline -preset:v ultrafast {2}",
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +197,11 @@ class EncodeTypeEnum(str, Enum):
|
||||
|
||||
|
||||
def parse_preset_hardware_acceleration_encode(
|
||||
arg: Any, input: str, output: str, type: EncodeTypeEnum = EncodeTypeEnum.birdseye
|
||||
ffmpeg_path: str,
|
||||
arg: Any,
|
||||
input: str,
|
||||
output: str,
|
||||
type: EncodeTypeEnum = EncodeTypeEnum.birdseye,
|
||||
) -> str:
|
||||
"""Return the correct scaling preset or default preset if none is set."""
|
||||
if type == EncodeTypeEnum.birdseye:
|
||||
@@ -215,6 +219,7 @@ def parse_preset_hardware_acceleration_encode(
|
||||
arg = "default"
|
||||
|
||||
return arg_map.get(arg, arg_map["default"]).format(
|
||||
ffmpeg_path,
|
||||
input,
|
||||
output,
|
||||
_gpu_selector.get_selected_gpu(),
|
||||
|
||||
@@ -15,7 +15,7 @@ import cv2
|
||||
import numpy as np
|
||||
|
||||
from frigate.comms.config_updater import ConfigSubscriber
|
||||
from frigate.config import BirdseyeModeEnum, FrigateConfig
|
||||
from frigate.config import BirdseyeModeEnum, FfmpegConfig, FrigateConfig
|
||||
from frigate.const import BASE_DIR, BIRDSEYE_PIPE
|
||||
from frigate.util.image import (
|
||||
SharedMemoryFrameManager,
|
||||
@@ -112,7 +112,7 @@ class Canvas:
|
||||
class FFMpegConverter(threading.Thread):
|
||||
def __init__(
|
||||
self,
|
||||
camera: str,
|
||||
ffmpeg: FfmpegConfig,
|
||||
input_queue: queue.Queue,
|
||||
stop_event: mp.Event,
|
||||
in_width: int,
|
||||
@@ -123,8 +123,8 @@ class FFMpegConverter(threading.Thread):
|
||||
birdseye_rtsp: bool = False,
|
||||
):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = f"{camera}_output_converter"
|
||||
self.camera = camera
|
||||
self.name = "birdseye_output_converter"
|
||||
self.camera = "birdseye"
|
||||
self.input_queue = input_queue
|
||||
self.stop_event = stop_event
|
||||
self.bd_pipe = None
|
||||
@@ -133,7 +133,7 @@ class FFMpegConverter(threading.Thread):
|
||||
self.recreate_birdseye_pipe()
|
||||
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
ffmpeg.ffmpeg_path,
|
||||
"-threads",
|
||||
"1",
|
||||
"-f",
|
||||
@@ -725,7 +725,7 @@ class Birdseye:
|
||||
self.config = config
|
||||
self.input = queue.Queue(maxsize=10)
|
||||
self.converter = FFMpegConverter(
|
||||
"birdseye",
|
||||
config.ffmpeg,
|
||||
self.input,
|
||||
stop_event,
|
||||
config.birdseye.width,
|
||||
|
||||
@@ -6,7 +6,7 @@ import queue
|
||||
import subprocess as sp
|
||||
import threading
|
||||
|
||||
from frigate.config import CameraConfig
|
||||
from frigate.config import CameraConfig, FfmpegConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -15,6 +15,7 @@ class FFMpegConverter(threading.Thread):
|
||||
def __init__(
|
||||
self,
|
||||
camera: str,
|
||||
ffmpeg: FfmpegConfig,
|
||||
input_queue: queue.Queue,
|
||||
stop_event: mp.Event,
|
||||
in_width: int,
|
||||
@@ -30,7 +31,7 @@ class FFMpegConverter(threading.Thread):
|
||||
self.stop_event = stop_event
|
||||
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
ffmpeg.ffmpeg_path,
|
||||
"-threads",
|
||||
"1",
|
||||
"-f",
|
||||
@@ -142,6 +143,7 @@ class JsmpegCamera:
|
||||
)
|
||||
self.converter = FFMpegConverter(
|
||||
config.name,
|
||||
config.ffmpeg,
|
||||
self.input,
|
||||
stop_event,
|
||||
config.frame_shape[1],
|
||||
|
||||
@@ -78,6 +78,7 @@ class FFMpegConverter(threading.Thread):
|
||||
|
||||
# write a PREVIEW at fps and 1 key frame per clip
|
||||
self.ffmpeg_cmd = parse_preset_hardware_acceleration_encode(
|
||||
config.ffmpeg.ffmpeg_path,
|
||||
config.ffmpeg.hwaccel_args,
|
||||
input="-f concat -y -protocol_whitelist pipe,file -safe 0 -threads 1 -i /dev/stdin",
|
||||
output=f"-threads 1 -g {PREVIEW_KEYFRAME_INTERVAL} -bf 0 -b:v {PREVIEW_QUALITY_BIT_RATES[self.config.record.preview.quality]} {FPS_VFR_PARAM} -movflags +faststart -pix_fmt yuv420p {self.path}",
|
||||
|
||||
@@ -14,7 +14,7 @@ from typing import Optional
|
||||
|
||||
from peewee import DoesNotExist
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.config import FfmpegConfig, FrigateConfig
|
||||
from frigate.const import (
|
||||
CACHE_DIR,
|
||||
CLIPS_DIR,
|
||||
@@ -116,7 +116,7 @@ class RecordingExporter(threading.Thread):
|
||||
minutes = int(diff / 60)
|
||||
seconds = int(diff % 60)
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
self.config.ffmpeg.ffmpeg_path,
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
@@ -230,11 +230,12 @@ class RecordingExporter(threading.Thread):
|
||||
|
||||
if self.playback_factor == PlaybackFactorEnum.realtime:
|
||||
ffmpeg_cmd = (
|
||||
f"ffmpeg -hide_banner {ffmpeg_input} -c copy -movflags +faststart {video_path}"
|
||||
f"{self.config.ffmpeg.ffmpeg_path} -hide_banner {ffmpeg_input} -c copy -movflags +faststart {video_path}"
|
||||
).split(" ")
|
||||
elif self.playback_factor == PlaybackFactorEnum.timelapse_25x:
|
||||
ffmpeg_cmd = (
|
||||
parse_preset_hardware_acceleration_encode(
|
||||
self.config.ffmpeg.ffmpeg_path,
|
||||
self.config.ffmpeg.hwaccel_args,
|
||||
f"{TIMELAPSE_DATA_INPUT_ARGS} {ffmpeg_input}",
|
||||
f"{self.config.cameras[self.camera].record.export.timelapse_args} -movflags +faststart {video_path}",
|
||||
@@ -267,7 +268,7 @@ class RecordingExporter(threading.Thread):
|
||||
logger.debug(f"Finished exporting {video_path}")
|
||||
|
||||
|
||||
def migrate_exports(camera_names: list[str]):
|
||||
def migrate_exports(ffmpeg: FfmpegConfig, camera_names: list[str]):
|
||||
Path(os.path.join(CLIPS_DIR, "export")).mkdir(exist_ok=True)
|
||||
|
||||
exports = []
|
||||
@@ -286,7 +287,7 @@ def migrate_exports(camera_names: list[str]):
|
||||
) # use jpg because webp encoder can't get quality low enough
|
||||
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
ffmpeg.ffmpeg_path,
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
|
||||
@@ -209,7 +209,9 @@ class RecordingMaintainer(threading.Thread):
|
||||
if cache_path in self.end_time_cache:
|
||||
end_time, duration = self.end_time_cache[cache_path]
|
||||
else:
|
||||
segment_info = await get_video_properties(cache_path, get_duration=True)
|
||||
segment_info = await get_video_properties(
|
||||
self.config.ffmpeg, cache_path, get_duration=True
|
||||
)
|
||||
|
||||
if segment_info["duration"]:
|
||||
duration = float(segment_info["duration"])
|
||||
@@ -387,7 +389,7 @@ class RecordingMaintainer(threading.Thread):
|
||||
|
||||
# add faststart to kept segments to improve metadata reading
|
||||
p = await asyncio.create_subprocess_exec(
|
||||
"ffmpeg",
|
||||
self.config.ffmpeg.ffmpeg_path,
|
||||
"-hide_banner",
|
||||
"-y",
|
||||
"-i",
|
||||
|
||||
@@ -314,10 +314,10 @@ class StreamInfoRetriever:
|
||||
def __init__(self) -> None:
|
||||
self.stream_cache: dict[str, tuple[int, int]] = {}
|
||||
|
||||
def get_stream_info(self, path: str) -> str:
|
||||
def get_stream_info(self, ffmpeg, path: str) -> str:
|
||||
if path in self.stream_cache:
|
||||
return self.stream_cache[path]
|
||||
|
||||
info = asyncio.run(get_video_properties(path))
|
||||
info = asyncio.run(get_video_properties(ffmpeg, path))
|
||||
self.stream_cache[path] = info
|
||||
return info
|
||||
|
||||
@@ -765,12 +765,16 @@ def add_mask(mask: str, mask_img: np.ndarray):
|
||||
|
||||
|
||||
def get_image_from_recording(
|
||||
file_path: str, relative_frame_time: float, codec: str, height: Optional[int] = None
|
||||
ffmpeg, # Ffmpeg Config
|
||||
file_path: str,
|
||||
relative_frame_time: float,
|
||||
codec: str,
|
||||
height: Optional[int] = None,
|
||||
) -> Optional[any]:
|
||||
"""retrieve a frame from given time in recording file."""
|
||||
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
ffmpeg.executable_path,
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
|
||||
@@ -378,11 +378,11 @@ def get_jetson_stats() -> dict[int, dict]:
|
||||
return results
|
||||
|
||||
|
||||
def ffprobe_stream(path: str) -> sp.CompletedProcess:
|
||||
def ffprobe_stream(ffmpeg, path: str) -> sp.CompletedProcess:
|
||||
"""Run ffprobe on stream."""
|
||||
clean_path = escape_special_characters(path)
|
||||
ffprobe_cmd = [
|
||||
"ffprobe",
|
||||
ffmpeg.ffprobe_path,
|
||||
"-timeout",
|
||||
"1000000",
|
||||
"-print_format",
|
||||
@@ -438,7 +438,9 @@ def auto_detect_hwaccel() -> str:
|
||||
return ""
|
||||
|
||||
|
||||
async def get_video_properties(url, get_duration=False) -> dict[str, any]:
|
||||
async def get_video_properties(
|
||||
ffmpeg, url: str, get_duration: bool = False
|
||||
) -> dict[str, any]:
|
||||
async def calculate_duration(video: Optional[any]) -> float:
|
||||
duration = None
|
||||
|
||||
@@ -453,7 +455,7 @@ async def get_video_properties(url, get_duration=False) -> dict[str, any]:
|
||||
# if cv2 failed need to use ffprobe
|
||||
if duration is None:
|
||||
p = await asyncio.create_subprocess_exec(
|
||||
"ffprobe",
|
||||
ffmpeg.ffprobe_path,
|
||||
"-v",
|
||||
"error",
|
||||
"-show_entries",
|
||||
|
||||
Reference in New Issue
Block a user