mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-14 15:15:22 +03:00
Install multiple ffmpeg versions and add config to make it configurable
This commit is contained in:
parent
641f1244dd
commit
95800ad1e3
@ -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 \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 / /
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,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"
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user