From 95800ad1e3af734b8ae9a71e45fd84621098bcb2 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 13 Sep 2024 10:31:29 -0600 Subject: [PATCH] Install multiple ffmpeg versions and add config to make it configurable --- docker/main/Dockerfile | 2 +- docker/main/install_deps.sh | 20 +++++++++++----- .../rootfs/etc/s6-overlay/s6-rc.d/frigate/run | 2 -- docker/rpi/Dockerfile | 2 ++ frigate/api/export.py | 2 ++ frigate/api/media.py | 23 +++++++++++++------ frigate/app.py | 2 +- frigate/config.py | 16 +++++++++++++ frigate/events/audio.py | 2 +- frigate/output/birdseye.py | 12 +++++----- frigate/output/camera.py | 6 +++-- frigate/record/export.py | 8 +++---- frigate/record/maintainer.py | 2 +- frigate/util/image.py | 8 +++++-- 14 files changed, 74 insertions(+), 33 deletions(-) diff --git a/docker/main/Dockerfile b/docker/main/Dockerfile index 5ae041845..9ad818785 100644 --- a/docker/main/Dockerfile +++ b/docker/main/Dockerfile @@ -201,7 +201,7 @@ ENV ALLOW_RESET=True # Disable tokenizer parallelism warning ENV TOKENIZERS_PARALLELISM=true -ENV PATH="/usr/lib/btbn-ffmpeg/bin:/usr/local/go2rtc/bin:/usr/local/tempio/bin:/usr/local/nginx/sbin:${PATH}" +ENV ENV LIBAVFORMAT_VERSION_MAJOR=60 # Install dependencies RUN --mount=type=bind,source=docker/main/install_deps.sh,target=/deps/install_deps.sh \ diff --git a/docker/main/install_deps.sh b/docker/main/install_deps.sh index 95689b411..eb7aeb1b5 100755 --- a/docker/main/install_deps.sh +++ b/docker/main/install_deps.sh @@ -39,18 +39,26 @@ apt-get -qq install --no-install-recommends --no-install-suggests -y \ # btbn-ffmpeg -> amd64 if [[ "${TARGETARCH}" == "amd64" ]]; then - mkdir -p /usr/lib/btbn-ffmpeg + mkdir -p /usr/lib/ffmpeg/5.0 + mkdir -p /usr/lib/ffmpeg/7.0 + wget -qO btbn-ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linux64-gpl-5.1.tar.xz" + tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1 + rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/5.0/doc /usr/lib/ffmpeg/5.0/bin/ffplay wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-13-12-57/ffmpeg-n7.0.2-17-gf705bc5b73-linux64-gpl-7.0.tar.xz" - tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/btbn-ffmpeg --strip-components 1 - rm -rf btbn-ffmpeg.tar.xz /usr/lib/btbn-ffmpeg/doc /usr/lib/btbn-ffmpeg/bin/ffplay + tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1 + rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/7.0/doc /usr/lib/ffmpeg/7.0/bin/ffplay fi # ffmpeg -> arm64 if [[ "${TARGETARCH}" == "arm64" ]]; then - mkdir -p /usr/lib/btbn-ffmpeg + mkdir -p /usr/lib/ffmpeg/5.0 + mkdir -p /usr/lib/ffmpeg/7.0 + wget -qO btbn-ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linuxarm64-gpl-5.1.tar.xz" + tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1 + rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/5.0/doc /usr/lib/ffmpeg/5.0/bin/ffplay wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-13-12-57/ffmpeg-n7.0.2-17-gf705bc5b73-linuxarm64-gpl-7.0.tar.xz" - tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/btbn-ffmpeg --strip-components 1 - rm -rf btbn-ffmpeg.tar.xz /usr/lib/btbn-ffmpeg/doc /usr/lib/btbn-ffmpeg/bin/ffplay + tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1 + rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/7.0/doc /usr/lib/ffmpeg/7.0/bin/ffplay fi # arch specific packages diff --git a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/frigate/run b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/frigate/run index 50da2aef9..eacce294f 100755 --- a/docker/main/rootfs/etc/s6-overlay/s6-rc.d/frigate/run +++ b/docker/main/rootfs/etc/s6-overlay/s6-rc.d/frigate/run @@ -44,8 +44,6 @@ function migrate_db_path() { echo "[INFO] Preparing Frigate..." migrate_db_path -export LIBAVFORMAT_VERSION_MAJOR=$(ffmpeg -version | grep -Po 'libavformat\W+\K\d+') - echo "[INFO] Starting Frigate..." cd /opt/frigate || echo "[ERROR] Failed to change working directory to /opt/frigate" diff --git a/docker/rpi/Dockerfile b/docker/rpi/Dockerfile index 581ca7ff8..9860e65ec 100644 --- a/docker/rpi/Dockerfile +++ b/docker/rpi/Dockerfile @@ -12,5 +12,7 @@ RUN rm -rf /usr/lib/btbn-ffmpeg/ RUN --mount=type=bind,source=docker/rpi/install_deps.sh,target=/deps/install_deps.sh \ /deps/install_deps.sh +ENV LIBAVFORMAT_VERSION_MAJOR=58 + WORKDIR /opt/frigate/ COPY --from=rootfs / / diff --git a/frigate/api/export.py b/frigate/api/export.py index 0993a3a87..2c881a81d 100644 --- a/frigate/api/export.py +++ b/frigate/api/export.py @@ -14,6 +14,7 @@ from flask import ( ) from peewee import DoesNotExist +from frigate.config import FrigateConfig from frigate.const import EXPORT_DIR from frigate.models import Export, Recordings from frigate.record.export import PlaybackFactorEnum, RecordingExporter @@ -144,6 +145,7 @@ def export_delete(id: str): 404, ) + config: FrigateConfig = current_app.frigate_config files_in_use = [] for process in psutil.process_iter(): try: diff --git a/frigate/api/media.py b/frigate/api/media.py index 8604b557b..db9ef5ab2 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -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,10 @@ 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 +477,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.executable_path, "-hide_banner", "-y", "-protocol_whitelist", @@ -1141,8 +1146,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.executable_path, "-hide_banner", "-loglevel", "warning", @@ -1206,9 +1212,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.executable_path, "-hide_banner", "-loglevel", "warning", @@ -1301,8 +1308,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.executable_path, "-hide_banner", "-loglevel", "warning", @@ -1364,9 +1372,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.executable_path, "-hide_banner", "-loglevel", "warning", diff --git a/frigate/app.py b/frigate/app.py index bad2d9281..3401ff6dc 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -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) diff --git a/frigate/config.py b/frigate/config.py index 972562328..fd619077f 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -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,21 @@ class FfmpegConfig(FrigateBaseModel): title="Time in seconds to wait before FFmpeg retries connecting to the camera.", ) + @property + def executable_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 self.path + + class CameraRoleEnum(str, Enum): audio = "audio" diff --git a/frigate/events/audio.py b/frigate/events/audio.py index 4302abb68..d9b7f6d3f 100644 --- a/frigate/events/audio.py +++ b/frigate/events/audio.py @@ -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.executable_path, "-vn", "-threads", "1"] + input_args + ["-i"] + [ffmpeg_input.path] diff --git a/frigate/output/birdseye.py b/frigate/output/birdseye.py index 6c42f450b..022ddda96 100644 --- a/frigate/output/birdseye.py +++ b/frigate/output/birdseye.py @@ -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.executable_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, diff --git a/frigate/output/camera.py b/frigate/output/camera.py index b9c607375..96a630fcf 100644 --- a/frigate/output/camera.py +++ b/frigate/output/camera.py @@ -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.executable_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], diff --git a/frigate/record/export.py b/frigate/record/export.py index feb96f01f..8000bf6cb 100644 --- a/frigate/record/export.py +++ b/frigate/record/export.py @@ -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.executable_path, "-hide_banner", "-loglevel", "warning", @@ -267,7 +267,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 +286,7 @@ def migrate_exports(camera_names: list[str]): ) # use jpg because webp encoder can't get quality low enough ffmpeg_cmd = [ - "ffmpeg", + ffmpeg.executable_path, "-hide_banner", "-loglevel", "warning", diff --git a/frigate/record/maintainer.py b/frigate/record/maintainer.py index e06660223..e8f2c8593 100644 --- a/frigate/record/maintainer.py +++ b/frigate/record/maintainer.py @@ -387,7 +387,7 @@ class RecordingMaintainer(threading.Thread): # add faststart to kept segments to improve metadata reading p = await asyncio.create_subprocess_exec( - "ffmpeg", + self.config.ffmpeg.executable_path, "-hide_banner", "-y", "-i", diff --git a/frigate/util/image.py b/frigate/util/image.py index f3186fe6a..dfd3a3061 100644 --- a/frigate/util/image.py +++ b/frigate/util/image.py @@ -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",