Merge pull request 'UCI to YML Syncing, Removed UCI Mask Config, General UX Improvements' (#3) from riley/luci-app-frigate:main into main

Reviewed-on: #3
main
jason 1 year ago
commit 8d5b4fb127

@ -34,6 +34,7 @@ o.default = "/dev/crypto"
o = s:option(Value, "storage", "Storage Path") o = s:option(Value, "storage", "Storage Path")
o.default = "./frigate/storage" o.default = "./frigate/storage"
-- MQTT Configuration -- MQTT Configuration
s = m:section(NamedSection, "mqtt", "frigate_config", "MQTT Configuration") s = m:section(NamedSection, "mqtt", "frigate_config", "MQTT Configuration")
s.addremove = false s.addremove = false
@ -68,8 +69,18 @@ s.novaluetext = "There are no cameras configured yet."
o = s:option(Value, "name", "Camera Name") o = s:option(Value, "name", "Camera Name")
o = s:option(Value, "path", "RTSP Path") o = s:option(Value, "path", "RTSP Path")
o = s:option(Value, "roles", "Roles") o = s:option(Value, "roles", "Roles")
o = s:option(Flag, "record", "Enable Recording") o = s:option(Flag, "record", "Enable Recording")
o.default = "0"
o.rmempty = false
o = s:option(Flag, "snapshots", "Enable Snapshots") o = s:option(Flag, "snapshots", "Enable Snapshots")
o = s:option(Value, "mask", "Mask") o.default = "0"
o.rmempty = false
-- Add the new flag for Overwrite Frigate Config
o = s:option(Flag, "overwrite_cfg", "Overwrite Frigate Config")
o.default = "1"
o.rmempty = false
return m return m

@ -14,27 +14,11 @@ config frigate_config 'tpu'
option type 'edgetpu' option type 'edgetpu'
option device 'usb' option device 'usb'
config camera_config 'cam1' config camera_config
option name 'living_room' option name 'living_room'
option path 'rtsp://admin:password@192.168.70.173:554/cam/realmonitor?channel=1&subtype=0&authbasic=64' option path 'rtsp://admin:password@192.168.70.173:554/cam/realmonitor?channel=1&subtype=0&authbasic=64'
option roles 'detect' option roles 'detect'
option record '1' option record '1'
option snapshots '1' option snapshots '1'
option mask '0,461,3,0,1919,0,1919,843,1699,492,1344,458,1346,336,973,317,869,375,866,432' option overwrite_cfg '1'
config camera_config 'cam2'
option name 'upstairs_bedroom'
option path 'rtsp://admin:password@192.168.70.173:554/cam/realmonitor?channel=1&subtype=0&authbasic=64'
option roles 'detect'
option record '1'
option snapshots '1'
option mask '0,461,3,0,1919,0,1919,843,1699,492,1344,458,1346,336,973,317,869,375,866,432'
config camera_config 'cam3'
option name 'kitchen'
option path 'rtsp://admin:password@192.168.70.173:554/cam/realmonitor?channel=1&subtype=0&authbasic=64'
option roles 'detect'
option record '1'
option snapshots '1'
option mask '0,461,3,0,1919,0,1919,843,1699,492,1344,458,1346,336,973,317,869,375,866,432'

@ -4,9 +4,20 @@ APP_NAME="frigate"
ACTION="${1}" ACTION="${1}"
shift 1 shift 1
#set -x
get_image() { fetch_and_validate_uci () {
do_install_detail local config_name=$1
local config_attr=$2
local result=$(uci get frigate.$config_name.$config_attr 2>/dev/null)
if [ -z "$result" ]; then
echo "Error: $config_name not found in configuration." >&2
exit 1
fi
echo "Fetched $config_name from configuration: $result" >&2
echo $result
} }
do_install_detail() { do_install_detail() {
@ -14,44 +25,34 @@ do_install_detail() {
local usbcoral hwaccel storage mqtt host coral type device local usbcoral hwaccel storage mqtt host coral type device
echo "Fetching port from configuration..." echo "Fetching port from configuration..."
port=$(uci get frigate.docker.port 2>/dev/null) port=$(fetch_and_validate_uci docker port)
[ -z "$port" ] && { echo "Error: Port not found in configuration."; exit 1; }
echo "Fetching image name from configuration..." echo "Fetching image name from configuration..."
IMAGE_NAME=$(uci get frigate.docker.image 2>/dev/null) IMAGE_NAME=$(fetch_and_validate_uci docker image)
[ -z "$IMAGE_NAME" ] && { echo "Error: Image name not found in configuration."; exit 1; }
echo "Fetching USB Coral path from configuration..." echo "Fetching USB Coral path from configuration..."
usbcoral=$(uci get frigate.docker.usbcoral 2>/dev/null) usbcoral=$(fetch_and_validate_uci docker usbcoral)
[ -z "$usbcoral" ] && { echo "Error: USB Coral path not found in configuration."; exit 1; }
echo "Fetching hardware acceleration path from configuration..." echo "Fetching hardware acceleration path from configuration..."
hwaccel=$(uci get frigate.docker.hwaccel 2>/dev/null) hwaccel=$(fetch_and_validate_uci docker hwaccel)
[ -z "$hwaccel" ] && { echo "Error: Hardware acceleration path not found in configuration."; exit 1; }
echo "Fetching storage path from configuration..." echo "Fetching storage path from configuration..."
storage=$(uci get frigate.docker.storage 2>/dev/null) storage=$(fetch_and_validate_uci docker storage)
[ -z "$storage" ] && { echo "Error: Storage path not found in configuration."; exit 1; }
echo "Fetching MQTT status from configuration..." echo "Fetching MQTT status from configuration..."
mqtt=$(uci get frigate.mqtt.enabled 2>/dev/null) mqtt=$(fetch_and_validate_uci mqtt enabled)
[ -z "$mqtt" ] && { echo "Error: MQTT status not found in configuration."; exit 1; }
echo "Fetching MQTT host from configuration..." echo "Fetching MQTT host from configuration..."
host=$(uci get frigate.mqtt.host 2>/dev/null) host=$(fetch_and_validate_uci mqtt host)
[ -z "$host" ] && { echo "Error: MQTT host not found in configuration."; exit 1; }
echo "Fetching Coral status from configuration..." echo "Fetching Coral status from configuration..."
coral=$(uci get frigate.tpu.device 2>/dev/null) coral=$(fetch_and_validate_uci tpu device)
[ -z "$coral" ] && { echo "Error: Coral status not found in configuration."; exit 1; }
echo "Fetching Coral type from configuration..." echo "Fetching Coral type from configuration..."
type=$(uci get frigate.tpu.type 2>/dev/null) type=$(fetch_and_validate_uci tpu type)
[ -z "$type" ] && { echo "Error: Coral type not found in configuration."; exit 1; }
echo "Fetching Coral device from configuration..." echo "Fetching Coral device from configuration..."
device=$(uci get frigate.tpu.device 2>/dev/null) device=$(fetch_and_validate_uci tpu device)
[ -z "$device" ] && { echo "Error: Coral device not found in configuration."; exit 1; }
LAN_IP=$(uci get network.lan.ipaddr) LAN_IP=$(uci get network.lan.ipaddr)
LAN_IP="${LAN_IP%/*}" LAN_IP="${LAN_IP%/*}"
@ -102,14 +103,13 @@ EOF
# yq eval ".usbcoral = \"$usbcoral\"" -i /opt/docker2/compose/frigate/config.yml # yq eval ".usbcoral = \"$usbcoral\"" -i /opt/docker2/compose/frigate/config.yml
# yq eval ".hwaccel = \"$hwaccel\"" -i /opt/docker2/compose/frigate/config.yml # yq eval ".hwaccel = \"$hwaccel\"" -i /opt/docker2/compose/frigate/config.yml
# yq eval ".media.storage = \"$storage\"" -i /opt/docker2/compose/frigate/config.yml # yq eval ".media.storage = \"$storage\"" -i /opt/docker2/compose/frigate/config.yml
yq eval '. head_comment="WARNING: Values labeled OVERWRITTEN_BY_ROUTER are auto-managed by Private Router. Uncheck \"Overwrite Frigate Config\" in camera settings to edit manually."' -i /opt/docker2/compose/frigate/config.yml
yq eval ".mqtt.enabled = $mqtt" -i /opt/docker2/compose/frigate/config.yml yq eval ".mqtt.enabled = $mqtt" -i /opt/docker2/compose/frigate/config.yml
yq eval ".mqtt.host = \"$host\"" -i /opt/docker2/compose/frigate/config.yml yq eval ".mqtt.host = \"$host\"" -i /opt/docker2/compose/frigate/config.yml
yq eval ".detectors.coral.type = \"$type\"" -i /opt/docker2/compose/frigate/config.yml yq eval ".detectors.coral.type = \"$type\"" -i /opt/docker2/compose/frigate/config.yml
yq eval ".detectors.coral.device = \"$coral\"" -i /opt/docker2/compose/frigate/config.yml yq eval ".detectors.coral.device = \"$coral\"" -i /opt/docker2/compose/frigate/config.yml
# Initialize cameras structure
yq eval ".cameras = {}" -i /opt/docker2/compose/frigate/config.yml
# Write each camera's configuration to config.yml # Write each camera's configuration to config.yml
local camera_index=0 local camera_index=0
while : ; do while : ; do
@ -117,65 +117,218 @@ EOF
local name=$(uci get frigate.@camera_config[$camera_index].name 2>/dev/null | tr ' ' '_') local name=$(uci get frigate.@camera_config[$camera_index].name 2>/dev/null | tr ' ' '_')
# Exit loop if no more cameras are found # Exit loop if no more cameras are found
[[ -z "$name" ]] && break [[ -z "$name" ]] && break
write_camera_to_yml $name
camera_index=$((camera_index+1))
done
docker-compose -f /opt/docker2/compose/frigate/docker-compose.yml up -d --force-recreate
uci add shortcutmenu lists
uci set shortcutmenu.@lists[-1].webname="$APP_NAME"
uci set shortcutmenu.@lists[-1].weburl="$LAN_IP:$port"
uci set shortcutmenu.@lists[-1].webpath="/"
uci commit shortcutmenu
}
usage() {
echo "usage: $0 sub-command"
echo "where sub-command is one of:"
echo " install Install frigate"
echo " upgrade Upgrade frigate"
echo " rm/start/stop/restart Remove/Start/Stop/Restart frigate"
echo " status frigate status"
echo " port frigate port"
}
# Gets a specific setting for a camera. If no setting is provided, it retrieves the camera name.
get_uci_camera_value() {
local index=$1
local setting=${2:-name}
uci get frigate.@camera_config[$index].$setting 2>/dev/null | tr ' ' '_'
}
# Retrieve the rest of the camera settings here... # Sets a specific setting for a camera
set_uci_camera_value() {
local name=$1
local setting=$2
local value=$3
uci set frigate.@camera_config[@$name].$setting=$value
uci commit frigate
}
write_camera_to_yml() {
local name=$1
local camera_index=""
local lines=$(uci show frigate | grep '@camera_config')
IFS=$'\n'
for line in $lines
do
if [[ $line == *".name='$name'"* ]]
then
camera_index=$(echo $line | grep -E -o "@camera_config\[[0-9]+\]" | grep -E -o "[0-9]+" )
break
fi
done
if [[ -z "$camera_index" ]]
then
echo "Camera $name not found in UCI"
return
fi
# Retrieves the rest of the camera settings from UCI...
local path=$(uci get frigate.@camera_config[$camera_index].path 2>/dev/null) local path=$(uci get frigate.@camera_config[$camera_index].path 2>/dev/null)
local roles=$(uci get frigate.@camera_config[$camera_index].roles 2>/dev/null) local roles=$(uci get frigate.@camera_config[$camera_index].roles 2>/dev/null)
local record=$(uci get frigate.@camera_config[$camera_index].record 2>/dev/null) local record=$(uci get frigate.@camera_config[$camera_index].record 2>/dev/null)
local snapshots=$(uci get frigate.@camera_config[$camera_index].snapshots 2>/dev/null) local snapshots=$(uci get frigate.@camera_config[$camera_index].snapshots 2>/dev/null)
local mask=$(uci get frigate.@camera_config[$camera_index].mask 2>/dev/null)
# Initialize camera structure # Initialize camera structure in YML
yq eval ".cameras.$name = {}" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name = {}" -i /opt/docker2/compose/frigate/config.yml
# Initialize ffmpeg and inputs for each camera # Initialize ffmpeg and inputs for each camera in YML
yq eval ".cameras.$name.ffmpeg = {}" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.ffmpeg = {}" -i /opt/docker2/compose/frigate/config.yml
yq eval ".cameras.$name.ffmpeg.inputs = []" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.ffmpeg.inputs = []" -i /opt/docker2/compose/frigate/config.yml
# Add path and roles to the inputs # Add path and roles to the inputs in YML
yq eval ".cameras.$name.ffmpeg.inputs[0].path = \"$path\"" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.ffmpeg.inputs[0].path = \"$path\"" -i /opt/docker2/compose/frigate/config.yml
yq eval ".cameras.$name.ffmpeg.inputs[0].roles = [\"detect\"]" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.ffmpeg.inputs[0].roles = [\"detect\"]" -i /opt/docker2/compose/frigate/config.yml
# Add other camera settings to YML
# Add other camera settings
yq eval ".cameras.$name.detect = {}" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.detect = {}" -i /opt/docker2/compose/frigate/config.yml
yq eval ".cameras.$name.detect.width = 1280" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.detect.width = 1280" -i /opt/docker2/compose/frigate/config.yml
yq eval ".cameras.$name.detect.height = 720" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.detect.height = 720" -i /opt/docker2/compose/frigate/config.yml
yq eval ".cameras.$name.record = {}" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.record = {}" -i /opt/docker2/compose/frigate/config.yml
yq eval ".cameras.$name.record.enabled = $record" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.record.enabled = $record" -i /opt/docker2/compose/frigate/config.yml
yq eval ".cameras.$name.snapshots = {}" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.snapshots = {}" -i /opt/docker2/compose/frigate/config.yml
yq eval ".cameras.$name.snapshots.enabled = $snapshots" -i /opt/docker2/compose/frigate/config.yml yq eval ".cameras.$name.snapshots.enabled = $snapshots" -i /opt/docker2/compose/frigate/config.yml
# Check if the mask is not empty add_warning_comments $name
if [ -n "$mask" ]; then }
# Run the yq commands to update the YAML file
yq eval ".cameras.$name.motion = {}" -i /opt/docker2/compose/frigate/config.yml
yq eval ".cameras.$name.motion.mask = [\"$mask\"]" -i /opt/docker2/compose/frigate/config.yml
else
echo "Mask is empty, ignoring."
fi
get_camera_index_by_name() {
local name=$1
local camera_index=0
while : ; do
local current_name=$(get_uci_camera_value $camera_index)
[[ "$current_name" == "$name" ]] && echo $camera_index && break
[[ -z "$current_name" ]] && break
camera_index=$((camera_index+1)) camera_index=$((camera_index+1))
done done
}
docker-compose -f /opt/docker2/compose/frigate/docker-compose.yml up -d --force-recreate # Toggles the line comment for a specific camera attribute in the YML
add_warning_comments() {
local name=$1
yq eval '(.cameras.'$name' | key) line_comment="DO NOT REMOVE - Managed by PrivateRouter Script"' -i /opt/docker2/compose/frigate/config.yml
uci add shortcutmenu lists yq eval '(.cameras.'$name'.ffmpeg.inputs[0].path | key) line_comment="OVERWRITTEN_BY_ROUTER"' -i /opt/docker2/compose/frigate/config.yml
uci set shortcutmenu.@lists[-1].webname="$APP_NAME"
uci set shortcutmenu.@lists[-1].weburl="$LAN_IP:$port" yq eval '(.cameras.'$name'.ffmpeg.inputs[0].roles | key) line_comment="OVERWRITTEN_BY_ROUTER"' -i /opt/docker2/compose/frigate/config.yml
uci set shortcutmenu.@lists[-1].webpath="/"
uci commit shortcutmenu yq eval '.cameras.'$name'.record.enabled line_comment="OVERWRITTEN_BY_ROUTER"' -i /opt/docker2/compose/frigate/config.yml
yq eval '.cameras.'$name'.snapshots.enabled line_comment="OVERWRITTEN_BY_ROUTER"' -i /opt/docker2/compose/frigate/config.yml
} }
remove_warning_comments() {
local name=$1
usage() { yq eval '.cameras.'$name'.ffmpeg.inputs[0].path line_comment=""' -i /opt/docker2/compose/frigate/config.yml
echo "usage: $0 sub-command"
echo "where sub-command is one of:" yq eval '(.cameras.'$name'.ffmpeg.inputs[0].roles | key) line_comment=""' -i /opt/docker2/compose/frigate/config.yml
echo " install Install frigate"
echo " upgrade Upgrade frigate" yq eval '.cameras.'$name'.record.enabled line_comment=""' -i /opt/docker2/compose/frigate/config.yml
echo " rm/start/stop/restart Remove/Start/Stop/Restart frigate"
echo " status frigate status" yq eval '.cameras.'$name'.snapshots.enabled line_comment=""' -i /opt/docker2/compose/frigate/config.yml
echo " port frigate port" }
update_camera_in_yml() {
local name=$1
# Get the index of the camera with name "$name"
local camera_index=$(get_camera_index_by_name $name)
# Fetch and Update the specific settings for this camera from UCI -> YAML
# For each attribute you update, apply warning comment
local path=$(get_uci_camera_value $camera_index "path")
yq eval '.cameras.'$name'.ffmpeg.inputs[0].path = "'$path'"' -i /opt/docker2/compose/frigate/config.yml
local roles="[\"detect\"]" # adjust as necessary
yq eval '.cameras.'$name'.ffmpeg.inputs[0].roles = '$roles'' -i /opt/docker2/compose/frigate/config.yml
local record=$(get_uci_camera_value $camera_index "record")
yq eval '.cameras.'$name'.record.enabled = '$record'' -i /opt/docker2/compose/frigate/config.yml
local snapshots=$(get_uci_camera_value $camera_index "snapshots")
yq eval '.cameras.'$name'.snapshots.enabled = '$snapshots'' -i /opt/docker2/compose/frigate/config.yml
add_warning_comments $name
}
sync_camera_config() {
echo "Syncing camera config..."
# Initializing the camera index
camera_index=0
# Fetching all cameras from frigate YML
yml_cameras=$(yq eval '... comments="" | .cameras | keys' /opt/docker2/compose/frigate/config.yml | sed 's/- //g' | xargs)
echo "Cameras fetched from YML: $yml_cameras"
# Loop through all camera configs in UCI using index
while true; do
# Try to fetch camera configuration
local camera_name=$(get_uci_camera_value $camera_index)
local overwrite_cfg=$(get_uci_camera_value $camera_index "overwrite_cfg")
# Exit loop if no more cameras are found
[ -z "$camera_name" ] && break
# Only proceed if overwrite_cfg is enabled (set to 1)
if [ "$overwrite_cfg" -eq 1 ]
then
if ! echo "$yml_cameras" | grep -q -w "$camera_name"; then
echo "UCI Camera $camera_name not found in YML, adding..."
write_camera_to_yml $camera_name
else
echo "UCI Camera $camera_name found in YML, updating..."
update_camera_in_yml $camera_name
fi
else
echo "Skipping camera $camera_name, overwrite is set to false."
remove_warning_comments $camera_name
fi
# Add to uci_cameras
uci_cameras="${uci_cameras} ${camera_name}"
# Increment camera index
camera_index=$((camera_index+1))
done
# Crosschecking and removing orphaned entry in YML not present in UCI config
for yml_camera in $yml_cameras; do
# Get the comment of this camera
local comment=$(yq ".cameras.$yml_camera | key | line_comment" /opt/docker2/compose/frigate/config.yml)
comment=$(echo $comment | awk '{print tolower($0)}') # convert to lowercase using awk
comment=$(echo $comment | tr -d ' ') # remove whitespaces
if ! echo "$uci_cameras" | grep -q -w "$yml_camera"; then
# If the comment is either non-existent or doesn't contain "donotremove-managedbyprivaterouterscript", keep it
if [ -z "$comment" ] || [[ "$comment" != *"donotremove-managedbyprivaterouterscript"* ]]; then
echo "Manual camera $yml_camera found in YML. Keeping it since it's manually added."
else
echo "Auto generated camera $yml_camera found in YML, but not in UCI , removing..."
yq eval "del(.cameras.$yml_camera)" -i /opt/docker2/compose/frigate/config.yml
fi
fi
done
echo "Camera config syncing completed!"
} }
case "${ACTION}" in case "${ACTION}" in
@ -198,6 +351,7 @@ case "${ACTION}" in
CONTAINER_IDS=$(docker ps -a --filter "name=${APP_NAME}" --format '{{.ID}}') CONTAINER_IDS=$(docker ps -a --filter "name=${APP_NAME}" --format '{{.ID}}')
for ID in $CONTAINER_IDS; do for ID in $CONTAINER_IDS; do
docker "${ACTION}" "${ID}" docker "${ACTION}" "${ID}"
sync_camera_config
done done
;; ;;
"status") "status")

Loading…
Cancel
Save