mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
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:
parent
641f1244dd
commit
5ff476c6f9
@ -17,7 +17,7 @@ sudo chown -R "$(id -u):$(id -g)" /media/frigate
|
||||
# When started as a service, LIBAVFORMAT_VERSION_MAJOR is defined in the
|
||||
# s6 service file. For dev, where frigate is started from an interactive
|
||||
# shell, we define it in .bashrc instead.
|
||||
echo 'export LIBAVFORMAT_VERSION_MAJOR=$(ffmpeg -version | grep -Po "libavformat\W+\K\d+")' >> $HOME/.bashrc
|
||||
echo 'export LIBAVFORMAT_VERSION_MAJOR=$(/usr/lib/ffmpeg/7.0/bin/ffmpeg -version | grep -Po "libavformat\W+\K\d+")' >> $HOME/.bashrc
|
||||
|
||||
make version
|
||||
|
||||
|
@ -201,7 +201,8 @@ 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 PATH="/usr/local/go2rtc/bin:/usr/local/tempio/bin:/usr/local/nginx/sbin:${PATH}"
|
||||
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"
|
||||
|
@ -43,8 +43,6 @@ function get_ip_and_port_from_supervisor() {
|
||||
export FRIGATE_GO2RTC_WEBRTC_CANDIDATE_INTERNAL="${ip_address}:${webrtc_port}"
|
||||
}
|
||||
|
||||
export LIBAVFORMAT_VERSION_MAJOR=$(ffmpeg -version | grep -Po 'libavformat\W+\K\d+')
|
||||
|
||||
if [[ -f "/dev/shm/go2rtc.yaml" ]]; then
|
||||
echo "[INFO] Removing stale config from last run..."
|
||||
rm /dev/shm/go2rtc.yaml
|
||||
|
@ -105,16 +105,34 @@ else:
|
||||
**FRIGATE_ENV_VARS
|
||||
)
|
||||
|
||||
# ensure ffmpeg path is set correctly
|
||||
path = config.get("ffmpeg", {}).get("path", "default")
|
||||
if path == "default":
|
||||
if int(os.getenv("", "59") or "59") >= 59:
|
||||
ffmpeg_path = "/usr/lib/ffmpeg/7.0/bin/ffmpeg"
|
||||
else:
|
||||
ffmpeg_path = "ffmpeg"
|
||||
elif path == "7.0":
|
||||
ffmpeg_path = "/usr/lib/ffmpeg/7.0/bin/ffmpeg"
|
||||
elif path == "5.0":
|
||||
ffmpeg_path = "/usr/lib/ffmpeg/5.0/bin/ffmpeg"
|
||||
else:
|
||||
ffmpeg_path = f"{path}/bin/ffmpeg"
|
||||
|
||||
if go2rtc_config.get("ffmpeg") is None:
|
||||
go2rtc_config["ffmpeg"] = {"bin": ffmpeg_path}
|
||||
elif go2rtc_config["ffmpeg"].get("bin") is None:
|
||||
go2rtc_config["ffmpeg"]["bin"] = ffmpeg_path
|
||||
|
||||
# need to replace ffmpeg command when using ffmpeg4
|
||||
if int(os.environ["LIBAVFORMAT_VERSION_MAJOR"]) < 59:
|
||||
if go2rtc_config.get("ffmpeg") is None:
|
||||
go2rtc_config["ffmpeg"] = {
|
||||
"rtsp": "-fflags nobuffer -flags low_delay -stimeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_transport tcp -i {input}"
|
||||
}
|
||||
elif go2rtc_config["ffmpeg"].get("rtsp") is None:
|
||||
if int(os.environ.get("LIBAVFORMAT_VERSION_MAJOR", "59") or "59") < 59:
|
||||
if go2rtc_config["ffmpeg"].get("rtsp") is None:
|
||||
go2rtc_config["ffmpeg"]["rtsp"] = (
|
||||
"-fflags nobuffer -flags low_delay -stimeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_transport tcp -i {input}"
|
||||
)
|
||||
else:
|
||||
if go2rtc_config.get("ffmpeg") is None:
|
||||
go2rtc_config["ffmpeg"] = {"path": ""}
|
||||
|
||||
for name in go2rtc_config.get("streams", {}):
|
||||
stream = go2rtc_config["streams"][name]
|
||||
@ -145,7 +163,7 @@ if config.get("birdseye", {}).get("restream", False):
|
||||
birdseye: dict[str, any] = config.get("birdseye")
|
||||
|
||||
input = f"-f rawvideo -pix_fmt yuv420p -video_size {birdseye.get('width', 1280)}x{birdseye.get('height', 720)} -r 10 -i {BIRDSEYE_PIPE}"
|
||||
ffmpeg_cmd = f"exec:{parse_preset_hardware_acceleration_encode(config.get('ffmpeg', {}).get('hwaccel_args'), input, '-rtsp_transport tcp -f rtsp {output}')}"
|
||||
ffmpeg_cmd = f"exec:{parse_preset_hardware_acceleration_encode(ffmpeg_path, config.get('ffmpeg', {}).get('hwaccel_args'), input, '-rtsp_transport tcp -f rtsp {output}')}"
|
||||
|
||||
if go2rtc_config.get("streams"):
|
||||
go2rtc_config["streams"]["birdseye"] = ffmpeg_cmd
|
||||
|
@ -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 / /
|
||||
|
@ -162,15 +162,15 @@ listen [::]:5000 ipv6only=off;
|
||||
|
||||
### Custom ffmpeg build
|
||||
|
||||
Included with Frigate is a build of ffmpeg that works for the vast majority of users. However, there exists some hardware setups which have incompatibilities with the included build. In this case, a docker volume mapping can be used to overwrite the included ffmpeg build with an ffmpeg build that works for your specific hardware setup.
|
||||
Included with Frigate is a build of ffmpeg that works for the vast majority of users. However, there exists some hardware setups which have incompatibilities with the included build. In this case, statically built ffmpeg binary can be downloaded to /config and used.
|
||||
|
||||
To do this:
|
||||
|
||||
1. Download your ffmpeg build and uncompress to a folder on the host (let's use `/home/appdata/frigate/custom-ffmpeg` for this example).
|
||||
1. Download your ffmpeg build and uncompress to the Frigate config folder.
|
||||
2. Update your docker-compose or docker CLI to include `'/home/appdata/frigate/custom-ffmpeg':'/usr/lib/btbn-ffmpeg':'ro'` in the volume mappings.
|
||||
3. Restart Frigate and the custom version will be used if the mapping was done correctly.
|
||||
|
||||
NOTE: The folder that is mapped from the host needs to be the folder that contains `/bin`. So if the full structure is `/home/appdata/frigate/custom-ffmpeg/bin/ffmpeg` then `/home/appdata/frigate/custom-ffmpeg` needs to be mapped to `/usr/lib/btbn-ffmpeg`.
|
||||
NOTE: The folder that is set for the config needs to be the folder that contains `/bin`. So if the full structure is `/home/appdata/frigate/custom-ffmpeg/bin/ffmpeg` then the `ffmpeg -> path` field should be `/config/custom-ffmpeg/bin`.
|
||||
|
||||
### Custom go2rtc version
|
||||
|
||||
|
@ -210,6 +210,10 @@ birdseye:
|
||||
# Optional: ffmpeg configuration
|
||||
# More information about presets at https://docs.frigate.video/configuration/ffmpeg_presets
|
||||
ffmpeg:
|
||||
# Optional: ffmpeg binry path (default: shown below)
|
||||
# can also be set to `7.0` or `5.0` to specify one of the included versions
|
||||
# or can be set to any path that holds `bin/ffmpeg` & `bin/ffprobe`
|
||||
path: "default"
|
||||
# Optional: global ffmpeg args (default: shown below)
|
||||
global_args: -hide_banner -loglevel warning -threads 2
|
||||
# Optional: global hwaccel args (default: auto detect)
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user