Install multiple ffmpeg versions and add config to make it configurable

This commit is contained in:
Nicolas Mowen 2024-09-13 10:31:29 -06:00
parent 641f1244dd
commit 95800ad1e3
14 changed files with 74 additions and 33 deletions

View File

@ -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 \

View File

@ -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

View File

@ -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"

View File

@ -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 / /

View File

@ -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:

View File

@ -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",

View File

@ -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)

View File

@ -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"

View File

@ -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]

View File

@ -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,

View File

@ -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],

View File

@ -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",

View File

@ -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",

View File

@ -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",