homekit/tools/ipcam_motion_worker.sh

238 lines
5.0 KiB
Bash
Executable File

#!/bin/bash
set -e
DIR="$( cd "$( dirname "$(realpath "${BASH_SOURCE[0]}")" )" &>/dev/null && pwd )"
PROGNAME="$0"
. "$DIR/lib.bash"
curl_opts="-s --connect-timeout 5 --retry 5 --max-time 10 --retry-delay 0 --retry-max-time 40"
allow_multiple=
config_file="$HOME/.config/ipcam_motion_worker/config.txt"
declare -A config=()
usage() {
cat <<EOF
usage: $PROGNAME OPTIONS
Options:
-c|--config FILE configuration file, default is $config_file
-v, -vx be verbose.
-v enables debug logs.
-vx does \`set -x\`, may be used to debug the script.
--allow-multiple don't check for another instance
EOF
exit 1
}
get_recordings_dir() {
curl $curl_opts "${config[api_url]}/api/camera/list" \
| jq ".response.\"${config[camera]}\".recordings_path" | tr -d '"'
}
# returns two words per line:
# filename filesize
get_recordings_list() {
curl $curl_opts "${config[api_url]}/api/recordings/${config[camera]}?filter=motion" \
| jq '.response.files[] | [.name, .size] | join(" ")' | tr -d '"'
}
report_failure() {
local file="$1"
local message="$2"
local response=$(curl $curl_opts -X POST "${config[api_url]}/api/motion/fail/${config[camera]}" \
-F "filename=$file" \
-F "message=$message")
print_response_error "$response" "report_failure"
}
report_timecodes() {
local file="$1"
local timecodes="$2"
local response=$(curl $curl_opts -X POST "${config[api_url]}/api/motion/done/${config[camera]}" \
-F "filename=$file" \
-F "timecodes=$timecodes")
print_response_error "$response" "report_timecodes"
}
print_response_error() {
local resp="$1"
local sufx="$2"
local error="$(echo "$resp" | jq '.error')"
local message
if [ "$error" != "null" ]; then
message="$(echo "$resp" | jq '.message' | tr -d '"')"
error="$(echo "$error" | tr -d '"')"
echoerr "$sufx: $error ($message)"
fi
}
get_roi_file() {
if [ -n "${config[roi_file]}" ]; then
file="${config[roi_file]}"
if ! [[ "$file" =~ ^/.* ]]; then
file="$(dirname "$config_file")/$file"
fi
debug "get_roi_file: detected file $file"
[ -f "$file" ] || die "invalid roi_file: $file: no such file"
echo "$file"
fi
}
process_local() {
local recdir="$(get_recordings_dir)"
local tc
local words
local file
while read line; do
words=($line)
file=${words[0]}
debug "processing $file..."
tc=$(do_motion "${recdir}/$file")
debug "$file: timecodes=$tc"
report_timecodes "$file" "$tc"
done < <(get_recordings_list)
}
process_remote() {
local tc
local url
local words
local file
local size
pushd "${config[fs_root]}" >/dev/null || die "failed to change to ${config[fs_root]}"
touch tmp || die "directory '${config[fs_root]}' is not writable"
rm tmp
[ -f "video.mp4" ] && {
echowarn "video.mp4 already exists in ${config[fs_root]}, removing.."
rm "video.mp4"
}
while read line; do
words=($line)
file=${words[0]}
size=${words[1]}
if (( size > config[fs_max_filesize] )); then
echoerr "won't download $file, size exceedes fs_max_filesize ($size > ${config[fs_max_filesize]})"
report_failure "$file" "too large file"
continue
fi
url="${config[api_url]}/api/recordings/${config[camera]}/download/${file}"
debug "downloading $url..."
if ! download "$url" "video.mp4"; then
echoerr "failed to download $file"
report_failure "$file" "download error"
continue
fi
tc=$(do_motion "video.mp4")
debug "$file: timecodes=$tc"
report_timecodes "$file" "$tc"
rm "video.mp4"
done < <(get_recordings_list)
popd >/dev/null
}
do_motion() {
local input="$1"
local roi_file="$(get_roi_file)"
local timecodes=()
if [ -z "$roi_file" ]; then
timecodes+=($(dvr_scan "$input"))
else
echoinfo "using roi sets from file: ${BOLD}$roi_file"
while read line; do
if ! [[ "$line" =~ ^#.* ]]; then
timecodes+=("$(dvr_scan "$input" "$line")")
fi
done < <(cat "$roi_file")
fi
timecodes="${timecodes[@]}"
timecodes=${timecodes// /,}
echo "$timecodes"
}
dvr_scan() {
local input="$1"
local args=
if [ ! -z "$2" ]; then
args="-roi $2"
echoinfo "dvr_scan(${BOLD}${input}${RST}${CYAN}): roi=($2), mt=${config[threshold]}"
else
echoinfo "dvr_scan(${BOLD}${input}${RST}${CYAN}): no roi, mt=${config[threshold]}"
fi
time_start
dvr-scan -q -i "$input" -so --min-event-length 3s -df 3 --frame-skip 2 -t ${config[threshold]} $args | tail -1
debug "dvr_scan: finished in $(time_elapsed)s"
}
[[ $# -lt 1 ]] && usage
while [[ $# -gt 0 ]]; do
case $1 in
-c|--config)
config_file="$2"
shift; shift
;;
--allow-multiple)
allow_multiple=1
shift
;;
-v)
VERBOSE=1
shift
;;
-vx)
VERBOSE=1
set -x
shift
;;
*)
die "unrecognized argument '$1'"
exit 1
;;
esac
done
if [ -z "$allow_multiple" ] && pidof -o %PPID -x "$(basename "${BASH_SOURCE[0]}")" >/dev/null; then
die "process already running"
fi
read_config "$config_file" config
check_config config "api_url camera threshold"
if [ -n "${config[remote]}" ]; then
check_config config "fs_root fs_max_filesize"
fi
if [ -z "${config[remote]}" ]; then
process_local
else
process_remote
fi