mirror of
https://github.com/EliasKotlyar/Xiaomi-Dafang-Hacks.git
synced 2025-09-02 20:52:41 +02:00
Merge beta into master (#1786)
This commit is contained in:
@@ -1,18 +1,54 @@
|
||||
#!/bin/sh
|
||||
|
||||
CURL="/system/sdcard/bin/curl --silent"
|
||||
JQ="/system/sdcard/bin/jq"
|
||||
|
||||
what="$1"
|
||||
shift
|
||||
data="$@"
|
||||
CURL="/system/sdcard/bin/curl"
|
||||
|
||||
if [ "$what" = "m" ]; then
|
||||
shift
|
||||
sendtext="${@//\"/\\\"}"
|
||||
else
|
||||
filename="$2"
|
||||
datafile="$3"
|
||||
fi
|
||||
|
||||
. /system/sdcard/config/matrix.conf
|
||||
|
||||
sendMessage() {
|
||||
text="$(echo "${@}" | sed 's:\\n:\n:g')"
|
||||
echo "Sending message: $text"
|
||||
uploadData() {
|
||||
mimetype="$1"
|
||||
msgtype="$2"
|
||||
|
||||
$CURL -XPOST -d '{"msgtype":"m.text", "body":"'$text'"}' "https://$host:$port/_matrix/client/r0/rooms/$room_id/send/m.room.message?access_token=$access_token"
|
||||
mxcuri="$($CURL -XPOST -H "Content-Type: $mimetype" --data-binary @"$datafile" "https://$host:$port/_matrix/media/r0/upload?filename=$filename&access_token=$access_token" | $JQ -cMr ".content_uri")"
|
||||
if [ -n "$mxcuri" ] && [ "$mxcuri" != "null" ]; then
|
||||
$CURL -XPOST -d '{"msgtype":"'"$msgtype"'", "body":"'"$filename"'", "url":"'"$mxcuri"'"}' "https://$host:$port/_matrix/client/r0/rooms/$room_id/send/m.room.message?access_token=$access_token"
|
||||
fi
|
||||
}
|
||||
|
||||
[ "$what" = "m" ] && sendMessage $data
|
||||
[ -z "$what" ] && echo -e "$0 <m|f|p> <data>\n m: message\n f: file\n p: picture"
|
||||
sendFile() {
|
||||
echo "Sending file: $datafile"
|
||||
uploadData "application/octet-stream" "m.file"
|
||||
}
|
||||
|
||||
sendMessage() {
|
||||
echo "Sending message: $sendtext"
|
||||
$CURL -XPOST -d '{"msgtype":"m.notice", "body":"'"$sendtext"'"}' "https://$host:$port/_matrix/client/r0/rooms/$room_id/send/m.room.message?access_token=$access_token"
|
||||
}
|
||||
|
||||
sendPicture() {
|
||||
echo "Sending picture: $datafile"
|
||||
filename="$filename.jpg"
|
||||
uploadData "image/jpeg" "m.image"
|
||||
}
|
||||
|
||||
sendVideo() {
|
||||
echo "Sending video: $datafile"
|
||||
filename="$filename.mp4"
|
||||
uploadData "video/mp4" "m.video"
|
||||
}
|
||||
|
||||
[ "$what" = "f" ] && sendFile
|
||||
[ "$what" = "m" ] && sendMessage
|
||||
[ "$what" = "p" ] && sendPicture
|
||||
[ "$what" = "v" ] && sendVideo
|
||||
[ -z "$what" ] && echo -e "$0 <m|f|p|v> <data|filename>\n m: message\n f: file\n p: picture\n v: video"
|
||||
|
@@ -18,45 +18,66 @@ sendMessage() {
|
||||
}
|
||||
|
||||
sendFile() {
|
||||
echo "Sending file: $1"
|
||||
$CURL -s \
|
||||
-X POST \
|
||||
https://api.telegram.org/bot$apiToken/sendDocument \
|
||||
-F chat_id="$userChatId" \
|
||||
-F document=@"$1"
|
||||
if [ -r $1 ]
|
||||
then
|
||||
echo "Sending file: $1"
|
||||
$CURL -s \
|
||||
-X POST \
|
||||
https://api.telegram.org/bot$apiToken/sendDocument \
|
||||
-F chat_id="$userChatId" \
|
||||
-F document=@"$1"
|
||||
else
|
||||
echo "File not found: $1"
|
||||
fi
|
||||
}
|
||||
|
||||
sendPhoto() {
|
||||
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
|
||||
echo "Sending Photo: $1 $caption" >> /tmp/telegram.log
|
||||
$CURL -s \
|
||||
-X POST \
|
||||
https://api.telegram.org/bot$apiToken/sendPhoto \
|
||||
-F chat_id="$userChatId" \
|
||||
-F photo="@${1}" \
|
||||
-F caption="${caption}"
|
||||
if [ -r $1 ]
|
||||
then
|
||||
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
|
||||
echo "Sending Photo: $1 $caption" >> /tmp/telegram.log
|
||||
$CURL -s \
|
||||
-X POST \
|
||||
https://api.telegram.org/bot$apiToken/sendPhoto \
|
||||
-F chat_id="$userChatId" \
|
||||
-F photo="@${1}" \
|
||||
-F caption="${caption}"
|
||||
else
|
||||
echo "File not found: $1"
|
||||
fi
|
||||
}
|
||||
|
||||
sendVideo() {
|
||||
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
|
||||
echo "Sending Video: $1 $caption" >> /tmp/telegram.log
|
||||
$CURL -s \
|
||||
-X POST \
|
||||
https://api.telegram.org/bot$apiToken/sendVideo \
|
||||
-F chat_id="$userChatId" \
|
||||
-F video="@${1}" \
|
||||
-F caption="${caption}"
|
||||
if [ -r $1 ]
|
||||
then
|
||||
bytes=$(busybox stat -c %s $1)
|
||||
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
|
||||
echo "Sending Video: $1 $caption (${bytes}bytes)" >> /tmp/telegram.log
|
||||
$CURL -s \
|
||||
-X POST \
|
||||
https://api.telegram.org/bot$apiToken/sendVideo \
|
||||
-F chat_id="$userChatId" \
|
||||
-F video="@${1}" \
|
||||
-F caption="${caption}"
|
||||
else
|
||||
echo "File not found: $1"
|
||||
fi
|
||||
}
|
||||
|
||||
sendAnimation() {
|
||||
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
|
||||
echo "Sending Animation: $1 $caption" >> /tmp/telegram.log
|
||||
$CURL -s \
|
||||
-X POST \
|
||||
https://api.telegram.org/bot$apiToken/sendAnimation \
|
||||
-F chat_id="$userChatId" \
|
||||
-F animation="@${1}" \
|
||||
-F caption="${caption}"
|
||||
if [ -r $1 ]
|
||||
then
|
||||
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
|
||||
echo "Sending Animation: $1 $caption" >> /tmp/telegram.log
|
||||
$CURL -s \
|
||||
-X POST \
|
||||
https://api.telegram.org/bot$apiToken/sendAnimation \
|
||||
-F chat_id="$userChatId" \
|
||||
-F animation="@${1}" \
|
||||
-F caption="${caption}"
|
||||
else
|
||||
echo "File not found: $1"
|
||||
fi
|
||||
}
|
||||
|
||||
[ "$what" == "m" ] && sendMessage $data
|
||||
|
@@ -24,6 +24,7 @@ send_email=false
|
||||
send_telegram=false
|
||||
telegram_alert_type=image
|
||||
send_matrix=false
|
||||
matrix_alert_type=image
|
||||
night_mode_event_delay=30
|
||||
|
||||
# General
|
||||
@@ -63,6 +64,7 @@ ftp_videos_dir="motion/videos"
|
||||
# Configure Dropbox snapshots and videos
|
||||
dropbox_snapshot=false
|
||||
dropbox_video=false
|
||||
dropbox_url="https://content.dropboxapi.com/2/files/upload"
|
||||
dropbox_token="token"
|
||||
dropbox_stills_dir="/motion/stills"
|
||||
dropbox_videos_dir="/motion/videos"
|
||||
|
@@ -49,7 +49,7 @@ FRAMERATE_NUM=25
|
||||
VIDEOFORMAT=2
|
||||
|
||||
# AudioFormat
|
||||
# Can be: OPUS | MP3 | PCM | PCMU
|
||||
# Can be: OPUS | MP3 | PCM | PCMU | OFF
|
||||
AUDIOFORMAT=MP3
|
||||
# Audio sampling rate
|
||||
AUDIOINBR=16000
|
||||
|
@@ -40,24 +40,29 @@ send_snapshot() {
|
||||
if [ "$telegram_alert_type" = "text" ] ; then
|
||||
debug_msg "Send telegram text"
|
||||
/system/sdcard/bin/telegram m "Motion detected"
|
||||
elif [ "$telegram_alert_type" = "image" -o "$telegram_alert_type" = "video+image" ] ; then
|
||||
elif [ "$telegram_alert_type" = "image" -o "$telegram_alert_type" = "video+image" ] ; then
|
||||
debug_msg "Send telegram image"
|
||||
/system/sdcard/bin/telegram p "$snapshot_tempfile"
|
||||
fi
|
||||
) &
|
||||
fi
|
||||
|
||||
# Send a matrix message
|
||||
# Send a matrix message or image
|
||||
if [ "$send_matrix" = true ]; then
|
||||
(
|
||||
include /system/sdcard/config/matrix.conf
|
||||
debug_msg "Send matrix message"
|
||||
/system/sdcard/bin/matrix m "Motion detected"
|
||||
|
||||
if [ "$matrix_alert_type" = "text" ] ; then
|
||||
debug_msg "Send matrix text"
|
||||
/system/sdcard/bin/matrix m "Motion detected"
|
||||
elif [ "$matrix_alert_type" = "image" -o "$matrix_alert_type" = "video+image" ] ; then
|
||||
debug_msg "Send matrix image"
|
||||
/system/sdcard/bin/matrix p "$filename" "$snapshot_tempfile"
|
||||
fi
|
||||
) &
|
||||
fi
|
||||
|
||||
|
||||
#save FTP snapshot
|
||||
# Save FTP snapshot
|
||||
if [ "$ftp_snapshot" = true ]; then
|
||||
(
|
||||
ftpput_cmd="/system/sdcard/bin/busybox ftpput"
|
||||
@@ -78,12 +83,11 @@ send_snapshot() {
|
||||
) &
|
||||
fi
|
||||
|
||||
#save Dropbox snapshot
|
||||
# Save Dropbox snapshot
|
||||
if [ "$dropbox_snapshot" = true ]; then
|
||||
(
|
||||
|
||||
debug_msg "Sending Dropbox snapshot to $dropbox_stills_dir/$filename.jpg"
|
||||
/system/sdcard/bin/curl -X POST https://content.dropboxapi.com/2/files/upload \
|
||||
/system/sdcard/bin/curl -X POST "$dropbox_url" \
|
||||
--header "Authorization: Bearer $dropbox_token" \
|
||||
--header "Dropbox-API-Arg: {\"path\": \"$dropbox_stills_dir/$filename.jpg\"}" \
|
||||
--header "Content-Type: application/octet-stream" \
|
||||
@@ -91,17 +95,15 @@ send_snapshot() {
|
||||
) &
|
||||
fi
|
||||
|
||||
# Save a snapshot
|
||||
if [ "$save_snapshot" = true ] ; then
|
||||
(
|
||||
debug_msg "Save snapshot to $save_snapshot_dir/$groupname/$filename.jpg"
|
||||
|
||||
|
||||
# Save a snapshot
|
||||
if [ "$save_snapshot" = true ] ; then
|
||||
(
|
||||
debug_msg "Save snapshot to $save_snapshot_dir/$groupname/$filename.jpg"
|
||||
|
||||
if [ ! -d "$save_snapshot_dir/$groupname" ]; then
|
||||
mkdir -p "$save_snapshot_dir/$groupname"
|
||||
chmod "$save_dirs_attr" "$save_snapshot_dir/$groupname"
|
||||
fi
|
||||
if [ ! -d "$save_snapshot_dir/$groupname" ]; then
|
||||
mkdir -p "$save_snapshot_dir/$groupname"
|
||||
chmod "$save_dirs_attr" "$save_snapshot_dir/$groupname"
|
||||
fi
|
||||
|
||||
# Limit the number of snapshots
|
||||
if [ "$(ls "$save_snapshot_dir" | wc -l)" -ge "$max_snapshot_days" ]; then
|
||||
@@ -113,7 +115,7 @@ send_snapshot() {
|
||||
) &
|
||||
fi
|
||||
|
||||
## Save SMB snapshot
|
||||
# Save SMB snapshot
|
||||
if [ "$smb_snapshot" = true ]; then
|
||||
(
|
||||
smbclient_cmd="/system/bin/smbclient $smb_share"
|
||||
@@ -131,6 +133,7 @@ send_snapshot() {
|
||||
$smbclient_cmd -D "$smb_stills_path" -c "lcd /tmp; mkdir $groupname; cd $groupname; put $snapshot_tempfilename; rename $snapshot_tempfilename $filename.jpg"
|
||||
) &
|
||||
fi
|
||||
|
||||
# Wait for all background jobs to finish before existing
|
||||
debug_msg "Waiting for background jobs to end in send_snapshot function:"
|
||||
for jobpid in $(jobs -p); do
|
||||
@@ -141,11 +144,11 @@ send_snapshot() {
|
||||
}
|
||||
|
||||
record_video () {
|
||||
# We only want one video stream at a time. Try to grab an
|
||||
# exclusive flock on file descriptor 5. Bail out if another
|
||||
# process already has it. Touch the flock to update it's mod
|
||||
# time as a signal to the background process to keep recording
|
||||
# when motion is repeatedly observed.
|
||||
# We only want one video stream at a time. Try to grab an exclusive
|
||||
# flock on file descriptor 5. Bail out if another process already has
|
||||
# it. Touch the flock to update its mod time as a signal to the
|
||||
# background process to keep recording when motion is repeatedly
|
||||
# observed.
|
||||
touch /run/recording_video.flock
|
||||
exec 5<> /run/recording_video.flock
|
||||
if /system/sdcard/bin/busybox flock -n -x 5; then
|
||||
@@ -159,7 +162,6 @@ record_video () {
|
||||
else
|
||||
/system/sdcard/bin/openRTSP -4 -w "$video_rtsp_w" -h "$video_rtsp_h" -f "$video_rtsp_f" -d "$video_duration" -b "$output_buffer_size" rtsp://$USERNAME:$USERPASSWORD@127.0.0.1:$PORT/unicast > "$video_tempfile"
|
||||
fi
|
||||
|
||||
else
|
||||
# Use avconv to stitch multiple JPEGs into 1fps video.
|
||||
# I couldn't get it working another way.
|
||||
@@ -205,18 +207,23 @@ filename=$(date "$filename_pattern")
|
||||
/system/sdcard/bin/getimage > "$snapshot_tempfile"
|
||||
debug_msg "Got snapshot_tempfile=$snapshot_tempfile"
|
||||
|
||||
#Next send picture alerts in the background
|
||||
# Next send picture alerts in the background
|
||||
|
||||
send_snapshot &
|
||||
|
||||
# Then, record video (if necessary)
|
||||
if [ "$save_video" = true -o "$smb_video" = true -o "$telegram_alert_type" = "video+image" -o "$telegram_alert_type" = "video" -o "$publish_mqtt_video" = true ] ; then
|
||||
if [ "$save_video" = true ] ||
|
||||
[ "$smb_video" = true ] ||
|
||||
[ "$dropbox_video" = true ] ||
|
||||
([ "$send_telegram" = true ] && ([ "$telegram_alert_type" = video+image ] || [ "$telegram_alert_type" = video ])) ||
|
||||
([ "$send_matrix" = true ] && ([ "$matrix_alert_type" = video+image ] || [ "$matrix_alert_type" = video ])) ||
|
||||
[ "$publish_mqtt_video" = true ]
|
||||
then
|
||||
record_video
|
||||
fi
|
||||
|
||||
# Next, start background tasks for all configured video notifications
|
||||
|
||||
|
||||
# Save the video
|
||||
if [ "$save_video" = true ] ; then
|
||||
(
|
||||
@@ -252,11 +259,11 @@ if [ "$ftp_video" = true ]; then
|
||||
fi
|
||||
ftpput_cmd="$ftpput_cmd $ftp_host"
|
||||
|
||||
# We only want one video stream at a time. Try to grab an
|
||||
# exclusive flock on file descriptor 5. Bail out if another
|
||||
# process already has it. Touch the flock to update it's mod
|
||||
# time as a signal to the background process to keep recording
|
||||
# when motion is repeatedly observed.
|
||||
# We only want one video stream at a time. Try to grab an exclusive
|
||||
# flock on file descriptor 5. Bail out if another process already has
|
||||
# it. Touch the flock to update it's mod time as a signal to the
|
||||
# background process to keep recording when motion is repeatedly
|
||||
# observed.
|
||||
touch /run/ftp_motion_video_stream.flock
|
||||
exec 5<> /run/ftp_motion_video_stream.flock
|
||||
if /system/sdcard/bin/busybox flock -n -x 5; then
|
||||
@@ -265,10 +272,10 @@ if [ "$ftp_video" = true ]; then
|
||||
|
||||
# XXX Uses avconv to stitch multiple JPEGs into 1fps video.
|
||||
# I couldn't get it working another way. /dev/videoX inputs
|
||||
# fail. Localhost rtsp takes very long (10+ seconds) to
|
||||
# start streaming and gets flaky when when memory or cpu
|
||||
# are pegged. This is a clugy method, but works well even
|
||||
# at high res, fps, cpu, and memory load!
|
||||
# fail. Localhost rtsp takes very long (10+ seconds) to start
|
||||
# streaming and gets flaky when when memory or cpu are pegged.
|
||||
# This is a clugy method, but works well even at high res,
|
||||
# fps, cpu, and memory load!
|
||||
( while [ "$(/system/sdcard/bin/busybox date "+%s")" -le "$(/system/sdcard/bin/busybox expr "$(/system/sdcard/bin/busybox stat -c "%X" /run/ftp_motion_video_stream.flock)" + "$video_duration")" ]; do
|
||||
/system/sdcard/bin/getimage
|
||||
sleep 1
|
||||
@@ -285,21 +292,17 @@ if [ "$ftp_video" = true ]; then
|
||||
) &
|
||||
fi
|
||||
|
||||
#save Dropbox video
|
||||
if [ "$dropbox_video" = true ]; then
|
||||
(
|
||||
|
||||
debug_msg "Saving Dropbox snapshot to $dropbox_videos_dir/$filename.mp4"
|
||||
/system/sdcard/bin/curl -X POST https://content.dropboxapi.com/2/files/upload \
|
||||
--header "Authorization: Bearer $dropbox_token" \
|
||||
--header "Dropbox-API-Arg: {\"path\": \"$dropbox_videos_dir/$filename.mp4\"}" \
|
||||
--header "Content-Type: application/octet-stream" \
|
||||
--data-binary @"$video_tempfile"
|
||||
) &
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Save Dropbox video
|
||||
if [ "$dropbox_video" = true ]; then
|
||||
(
|
||||
debug_msg "Saving Dropbox snapshot to $dropbox_videos_dir/$filename.mp4"
|
||||
/system/sdcard/bin/curl -X POST "$dropbox_url" \
|
||||
--header "Authorization: Bearer $dropbox_token" \
|
||||
--header "Dropbox-API-Arg: {\"path\": \"$dropbox_videos_dir/$filename.mp4\"}" \
|
||||
--header "Content-Type: application/octet-stream" \
|
||||
--data-binary @"$video_tempfile"
|
||||
) &
|
||||
fi
|
||||
|
||||
# SMB snapshot and video
|
||||
if [ "$smb_video" = true ]; then
|
||||
@@ -342,14 +345,20 @@ if [ "$send_telegram" = true ]; then
|
||||
(
|
||||
include /system/sdcard/config/telegram.conf
|
||||
|
||||
if [ "$telegram_alert_type" = "video" -o "$telegram_alert_type" = "video+image" ] ; then
|
||||
debug_msg "Send telegram video"
|
||||
if [ "$telegram_alert_type" = "video" -o "$telegram_alert_type" = "video+image" ] ; then
|
||||
if [ "$video_use_rtsp" = true ]; then
|
||||
#Convert file to mp4 and remove audio stream so video plays in telegram app
|
||||
/system/sdcard/bin/avconv -i "$video_tempfile" -c:v copy -an "$video_tempfile"-telegram.mp4
|
||||
/system/sdcard/bin/telegram v "$video_tempfile"-telegram.mp4
|
||||
rm "$video_tempfile"-telegram.mp4
|
||||
if [ "$AUDIOFORMAT" = "PCMU" ] || [ "$AUDIOFORMAT" = "OFF" ] ; then
|
||||
# Convert file to mp4 and remove audio stream so video plays in telegram app
|
||||
debug_msg "Send telegram video"
|
||||
/system/sdcard/bin/avconv -i "$video_tempfile" -c:v copy -an "$video_tempfile"-telegram.mp4
|
||||
/system/sdcard/bin/telegram v "$video_tempfile"-telegram.mp4
|
||||
rm "$video_tempfile"-telegram.mp4
|
||||
else
|
||||
# avconv can't strip audio it doesn't understand
|
||||
debug_msg "Send telegram video (only viable for external playback)"
|
||||
/system/sdcard/bin/telegram v "$video_tempfile"
|
||||
fi
|
||||
else
|
||||
/system/sdcard/bin/avconv -i "$video_tempfile" "$video_tempfile-lo.mp4"
|
||||
/system/sdcard/bin/telegram v "$video_tempfile-lo.mp4"
|
||||
rm "$video_tempfile-lo.mp4"
|
||||
@@ -358,15 +367,33 @@ if [ "$send_telegram" = true ]; then
|
||||
) &
|
||||
fi
|
||||
|
||||
# Send a matrix video
|
||||
if [ "$send_matrix" = true ]; then
|
||||
(
|
||||
include /system/sdcard/config/matrix.conf
|
||||
|
||||
if [ "$matrix_alert_type" = "video" -o "$matrix_alert_type" = "video+image" ] ; then
|
||||
debug_msg "Send matrix video"
|
||||
if [ "$video_use_rtsp" = true ]; then
|
||||
/system/sdcard/bin/matrix v "$filename" "$video_tempfile"
|
||||
else
|
||||
/system/sdcard/bin/avconv -i "$video_tempfile" "$video_tempfile-lo.mp4"
|
||||
/system/sdcard/bin/matrix v "$filename" "$video_tempfile-lo.mp4"
|
||||
rm -f "$video_tempfile-lo.mp4"
|
||||
fi
|
||||
fi
|
||||
) &
|
||||
fi
|
||||
|
||||
# Run any user scripts.
|
||||
for i in /system/sdcard/config/userscripts/motiondetection/*; do
|
||||
if [ -x "$i" ]; then
|
||||
debug_msg "Running: $i on $snapshot_tempfile"
|
||||
$i on "$snapshot_tempfile" "$video_tempfile" &
|
||||
fi
|
||||
if [ -x "$i" ]; then
|
||||
debug_msg "Running: $i on $snapshot_tempfile"
|
||||
$i on "$snapshot_tempfile" "$video_tempfile" &
|
||||
fi
|
||||
done
|
||||
|
||||
# Wait for all background jobs to finish before existing and deleting tempfile
|
||||
# Wait for all background jobs to finish before exiting and deleting tempfile
|
||||
debug_msg "Waiting for background jobs to end:"
|
||||
for jobpid in $(jobs -p); do
|
||||
wait "$jobpid"
|
||||
|
@@ -11,6 +11,10 @@ JQ="/system/sdcard/bin/jq"
|
||||
[ -z $apiToken ] && echo "api token not configured yet" && exit 1
|
||||
[ -z $userChatId ] && echo "chat id not configured yet" && exit 1
|
||||
|
||||
status() {
|
||||
$TELEGRAM m "Motion detection `motion_detection status`\nNight mode `night_mode status`\nAlert type `get_config /system/sdcard/config/motion.conf telegram_alert_type`"
|
||||
}
|
||||
|
||||
sendShot() {
|
||||
/system/sdcard/bin/getimage > "/tmp/telegram_image.jpg" &&\
|
||||
$TELEGRAM p "/tmp/telegram_image.jpg"
|
||||
@@ -52,10 +56,16 @@ videoAlerts() {
|
||||
$TELEGRAM m "Video alerts on motion detection enabled"
|
||||
}
|
||||
|
||||
imageThenVideoAlerts() {
|
||||
rewrite_config /system/sdcard/config/motion.conf telegram_alert_type "video+image"
|
||||
$TELEGRAM m "Image then video alerts on motion detection enabled"
|
||||
}
|
||||
|
||||
respond() {
|
||||
cmd=$1
|
||||
[ $chatId -lt 0 ] && cmd=${1%%@*}
|
||||
case $cmd in
|
||||
/status) status;;
|
||||
/mem) sendMem;;
|
||||
/shot) sendShot;;
|
||||
/on) detectionOn;;
|
||||
@@ -65,7 +75,8 @@ respond() {
|
||||
/textalerts) textAlerts;;
|
||||
/imagealerts) imageAlerts;;
|
||||
/videoalerts) videoAlerts;;
|
||||
/help | /start) $TELEGRAM m "######### Bot commands #########\n# /mem - show memory information\n# /shot - take a snapshot\n# /on - motion detection on\n# /off - motion detection off\n# /nighton - night mode on\n# /nightoff - night mode off\n# /textalerts - Text alerts on motion detection\n# /imagealerts - Image alerts on motion detection\n# /videoalerts - Video alerts on motion detection";;
|
||||
/dualalerts) imageThenVideoAlerts;;
|
||||
/help | /start) $TELEGRAM m "######### Bot commands #########\n# /mem - show memory information\n# /status - show current camera status\n# /shot - take a snapshot\n# /on - motion detection on\n# /off - motion detection off\n# /nighton - night mode on\n# /nightoff - night mode off\n# /textalerts - Text alerts on motion detection\n# /imagealerts - Image alerts on motion detection\n# /videoalerts - Video alerts on motion detection\n# /dualalerts - Image snapshot then video alerts on motion detection";;
|
||||
/*) $TELEGRAM m "I can't respond to '$cmd' command"
|
||||
esac
|
||||
}
|
||||
@@ -109,7 +120,8 @@ main() {
|
||||
if [ "$chatId" != "$userChatId" ]; then
|
||||
username=$(echo "$json" | $JQ -r ".result[0].$messageAttr.from.username // \"\"")
|
||||
firstName=$(echo "$json" | $JQ -r ".result[0].$messageAttr.from.first_name // \"\"")
|
||||
$TELEGRAM m "Received message from unauthorized chat id: $chatId\nUser: $username($firstName)\nMessage: $cmd"
|
||||
# Uncomment to get notified of attempted chat spam
|
||||
# $TELEGRAM m "Received message from unauthorized chat id: $chatId\nUser: $username($firstName)\nMessage: $cmd"
|
||||
else
|
||||
respond $cmd
|
||||
fi;
|
||||
|
14
firmware_mod/www/cgi-bin/audio_upload.cgi
Normal file
14
firmware_mod/www/cgi-bin/audio_upload.cgi
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Content-type: text/html"
|
||||
echo "Pragma: no-cache"
|
||||
echo "Cache-Control: max-age=0, no-store, no-cache"
|
||||
echo ""
|
||||
|
||||
if [ "${REQUEST_METHOD}" = "POST" ]
|
||||
then
|
||||
in_raw=`dd bs=1 count=${CONTENT_LENGTH} 1>/tmp/playback.wav`
|
||||
sed -i -e '1,/Content-Type:/d' /tmp/playback.wav
|
||||
echo " CONTENT LENGTH ${CONTENT_LENGTH}"
|
||||
/system/sdcard/bin/audioplay /tmp/playback.wav 60
|
||||
fi
|
@@ -41,7 +41,7 @@ if [ -n "$F_cmd" ]; then
|
||||
do
|
||||
if [[ -f $file ]]; then
|
||||
file_size=$(ls -lh $file | awk '{print $5}')
|
||||
file_url=$(ls -lh $file | awk '{print $9}' | sed 's/\/system\/sdcard\/DCIM/\/viewer/')
|
||||
file_url=$(ls -lh $file | awk '{print $9}' | sed 's/\/system\/sdcard\/DCIM/viewer/')
|
||||
file_date=$(ls -lh $file | awk '{print $6 "-" $7 "-" $8}')
|
||||
file_name=$(ls -lh $file | awk '{print $9}' | awk -F / '{print $(NF)}')
|
||||
echo "${file_name}#:#${file_size}#:#${file_date}#:#${file_url}"
|
||||
@@ -51,12 +51,12 @@ if [ -n "$F_cmd" ]; then
|
||||
;;
|
||||
|
||||
del_config)
|
||||
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/system\/sdcard\/DCIM/')
|
||||
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/\/system\/sdcard\/DCIM/')
|
||||
echo "Remove ${F_file}"
|
||||
rm $F_file
|
||||
;;
|
||||
restore_config)
|
||||
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/system\/sdcard\/DCIM/')
|
||||
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/\/system\/sdcard\/DCIM/')
|
||||
tar -xf $F_file -C /system/sdcard/config/
|
||||
echo "Restore done"
|
||||
/sbin/reboot
|
||||
|
@@ -40,6 +40,7 @@ if [ -n "$F_cmd" ]; then
|
||||
echo "dropboxVideosDir#:#${dropbox_videos_dir}"
|
||||
echo "dropboxSnapshot#:#${dropbox_snapshot}"
|
||||
echo "dropboxVideo#:#${dropbox_video}"
|
||||
echo "dropboxUrl#:#${dropbox_url}"
|
||||
echo "dropboxToken#:#${dropbox_token}"
|
||||
echo "smbSnapshot#:#${smb_snapshot}"
|
||||
echo "smbVideo#:#${smb_video}"
|
||||
@@ -56,6 +57,7 @@ if [ -n "$F_cmd" ]; then
|
||||
echo "sendTelegram#:#${send_telegram}"
|
||||
echo "telegramAlertType#:#${telegram_alert_type}"
|
||||
echo "sendMatrix#:#${send_matrix}"
|
||||
echo "matrixAlertType#:#${matrix_alert_type}"
|
||||
echo "nightModeEventDelay#:#${night_mode_event_delay}"
|
||||
|
||||
;;
|
||||
@@ -197,6 +199,11 @@ if [ -n "$F_cmd" ]; then
|
||||
rewrite_config /system/sdcard/config/motion.conf dropbox_video $F_dropboxVideo
|
||||
echo "Save video to dropbox set to $F_dropboxVideo<br/>"
|
||||
fi
|
||||
if [ -n "${F_dropboxUrl+x}" ]; then
|
||||
F_dropboxUrl=$(printf '%b' "${F_dropboxUrl//%/\\x}" | sed 's/\//\\\//g')
|
||||
rewrite_config /system/sdcard/config/motion.conf dropbox_url "\"$F_dropboxUrl\""
|
||||
echo "dropbox url set<br/>"
|
||||
fi
|
||||
if [ -n "${F_dropboxToken+x}" ]; then
|
||||
F_dropboxToken=$(printf '%b' "${F_dropboxToken//%/\\x}" | sed 's/\//\\\//g')
|
||||
rewrite_config /system/sdcard/config/motion.conf dropbox_token "\"$F_dropboxToken\""
|
||||
@@ -252,6 +259,11 @@ if [ -n "$F_cmd" ]; then
|
||||
rewrite_config /system/sdcard/config/motion.conf send_matrix $F_sendMatrix
|
||||
echo "Send Matrix on motion set to $F_sendMatrix<br/>"
|
||||
fi
|
||||
if [ -n "${F_matrixAlertType+x}" ]; then
|
||||
F_matrixAlertType=$(printf '%b' "${F_matrixAlertType//%/\\x}")
|
||||
rewrite_config /system/sdcard/config/motion.conf matrix_alert_type $F_matrixAlertType
|
||||
echo "Matrix alert type set to $F_matrixAlertType<br/>"
|
||||
fi
|
||||
if [ -n "${F_nightModeEventDelay+x}" ]; then
|
||||
F_nightModeEventDelay=$(printf '%b' "${F_nightModeEventDelay//%/\\x}")
|
||||
rewrite_config /system/sdcard/config/motion.conf night_mode_event_delay $F_nightModeEventDelay
|
||||
|
@@ -31,7 +31,7 @@ if [ -n "$F_cmd" ]; then
|
||||
do
|
||||
if [[ -f $file ]]; then
|
||||
file_size=$(ls -lh $file | awk '{print $5}')
|
||||
file_url=$(ls -lh $file | awk '{print $9}' | sed 's/\/system\/sdcard\/DCIM/\/viewer/')
|
||||
file_url=$(ls -lh $file | awk '{print $9}' | sed 's/\/system\/sdcard\/DCIM/viewer/')
|
||||
file_date=$(ls -lh $file | awk '{print $6 "-" $7 "-" $8}')
|
||||
file_name=$(ls -lh $file | awk '{print $9}' | awk -F / '{print $(NF)}')
|
||||
echo "${file_name}#:#${file_size}#:#${file_date}#:#${file_url}"
|
||||
@@ -45,7 +45,7 @@ if [ -n "$F_cmd" ]; then
|
||||
echo "sdcardUsedPercent#:#$(df -h /system/sdcard | awk 'NR==2{print$5}')"
|
||||
;;
|
||||
del_file)
|
||||
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/system\/sdcard\/DCIM/')
|
||||
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/\/system\/sdcard\/DCIM/')
|
||||
echo "Remove ${F_file}"
|
||||
rm $F_file
|
||||
;;
|
||||
@@ -54,4 +54,4 @@ if [ -n "$F_cmd" ]; then
|
||||
;;
|
||||
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
@@ -17,12 +17,12 @@
|
||||
<script type="text/javascript" src="lib/datatable/datatables.min.js"></script>
|
||||
<!-- smoothie-->
|
||||
<script type="text/javascript" src="lib/smoothie/smoothie.js"></script>
|
||||
<script src="/lib/d3/d3.min.js"></script>
|
||||
<script src="/lib/event-drops/index.js"></script>
|
||||
<script src="lib/d3/d3.min.js"></script>
|
||||
<script src="lib/event-drops/index.js"></script>
|
||||
<!-- Dafang hack CSS-->
|
||||
<link rel="stylesheet" href="css/w3-toggle.css" type="text/css" />
|
||||
<link rel="stylesheet" href="css/w3-accordion.css" type="text/css" />
|
||||
<link href="/lib/event-drops/style.css" rel="stylesheet" />
|
||||
<link href="lib/event-drops/style.css" rel="stylesheet" />
|
||||
<script type="text/javascript">
|
||||
//Check if theme configured
|
||||
var css = localStorage.getItem('theme')
|
||||
|
85
firmware_mod/www/js/audio_capture.js
Normal file
85
firmware_mod/www/js/audio_capture.js
Normal file
@@ -0,0 +1,85 @@
|
||||
var gumStream; //stream from getUserMedia()
|
||||
var rec; //Recorder.js object
|
||||
var input; //MediaStreamAudioSourceNode we'll be recording
|
||||
|
||||
// shim for AudioContext when it's not avb.
|
||||
var AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
var audioContext //audio context to help us record
|
||||
|
||||
function startRecording() {
|
||||
console.log("Recording started");
|
||||
/*
|
||||
Simple constraints object, for more advanced audio features see
|
||||
https://addpipe.com/blog/audio-constraints-getusermedia/
|
||||
*/
|
||||
|
||||
var constraints = {
|
||||
audio: true,
|
||||
video: false
|
||||
}
|
||||
|
||||
/*
|
||||
We're using the standard promise based getUserMedia()
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
|
||||
*/
|
||||
|
||||
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
|
||||
console.log("getUserMedia() success, stream created, initializing Recorder.js ...");
|
||||
|
||||
/*
|
||||
create an audio context after getUserMedia is called
|
||||
sampleRate might change after getUserMedia is called, like it does on macOS when recording through AirPods
|
||||
the sampleRate defaults to the one set in your OS for your playback device
|
||||
*/
|
||||
audioContext = new AudioContext({
|
||||
sampleRate: 8000
|
||||
});
|
||||
|
||||
/* assign to gumStream for later use */
|
||||
gumStream = stream;
|
||||
|
||||
/* use the stream */
|
||||
input = audioContext.createMediaStreamSource(stream);
|
||||
|
||||
/*
|
||||
Create the Recorder object and configure to record mono sound (1 channel)
|
||||
Recording 2 channels will double the file size
|
||||
*/
|
||||
rec = new Recorder(input, {
|
||||
numChannels: 1
|
||||
})
|
||||
|
||||
//start the recording process
|
||||
rec.record()
|
||||
});
|
||||
}
|
||||
|
||||
function stopRecording() {
|
||||
//tell the recorder to stop the recording
|
||||
|
||||
// The Dafang fails to play the last two seconds because some bug on audioplay...
|
||||
setTimeout(() => {
|
||||
rec.stop();
|
||||
|
||||
//stop microphone access
|
||||
gumStream.getAudioTracks()[0].stop();
|
||||
|
||||
rec.exportWAV(sendAudio);
|
||||
|
||||
function sendAudio(blob) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onload = function (e) {
|
||||
if (this.readyState === 4) {
|
||||
console.log("Server returned: ", e.target.responseText);
|
||||
}
|
||||
};
|
||||
var fd = new FormData();
|
||||
fd.append("audio_data", blob, 'recording.wav');
|
||||
xhr.open("POST", "cgi-bin/audio_upload.cgi", true);
|
||||
xhr.send(fd);
|
||||
}
|
||||
console.log("Audio sent");
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
|
357
firmware_mod/www/js/audio_recorder.js
Normal file
357
firmware_mod/www/js/audio_recorder.js
Normal file
@@ -0,0 +1,357 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Recorder = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
|
||||
module.exports = require("./recorder").Recorder;
|
||||
|
||||
},{"./recorder":2}],2:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
var _createClass = (function () {
|
||||
function defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);
|
||||
}
|
||||
}return function (Constructor, protoProps, staticProps) {
|
||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
|
||||
};
|
||||
})();
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.Recorder = undefined;
|
||||
|
||||
var _inlineWorker = require('inline-worker');
|
||||
|
||||
var _inlineWorker2 = _interopRequireDefault(_inlineWorker);
|
||||
|
||||
function _interopRequireDefault(obj) {
|
||||
return obj && obj.__esModule ? obj : { default: obj };
|
||||
}
|
||||
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
}
|
||||
|
||||
var Recorder = exports.Recorder = (function () {
|
||||
function Recorder(source, cfg) {
|
||||
var _this = this;
|
||||
|
||||
_classCallCheck(this, Recorder);
|
||||
|
||||
this.config = {
|
||||
bufferLen: 4096,
|
||||
numChannels: 2,
|
||||
mimeType: 'audio/wav'
|
||||
};
|
||||
this.recording = false;
|
||||
this.callbacks = {
|
||||
getBuffer: [],
|
||||
exportWAV: []
|
||||
};
|
||||
|
||||
Object.assign(this.config, cfg);
|
||||
this.context = source.context;
|
||||
this.node = (this.context.createScriptProcessor || this.context.createJavaScriptNode).call(this.context, this.config.bufferLen, this.config.numChannels, this.config.numChannels);
|
||||
|
||||
this.node.onaudioprocess = function (e) {
|
||||
if (!_this.recording) return;
|
||||
|
||||
var buffer = [];
|
||||
for (var channel = 0; channel < _this.config.numChannels; channel++) {
|
||||
buffer.push(e.inputBuffer.getChannelData(channel));
|
||||
}
|
||||
_this.worker.postMessage({
|
||||
command: 'record',
|
||||
buffer: buffer
|
||||
});
|
||||
};
|
||||
|
||||
source.connect(this.node);
|
||||
this.node.connect(this.context.destination); //this should not be necessary
|
||||
|
||||
var self = {};
|
||||
this.worker = new _inlineWorker2.default(function () {
|
||||
var recLength = 0,
|
||||
recBuffers = [],
|
||||
sampleRate = undefined,
|
||||
numChannels = undefined;
|
||||
|
||||
self.onmessage = function (e) {
|
||||
switch (e.data.command) {
|
||||
case 'init':
|
||||
init(e.data.config);
|
||||
break;
|
||||
case 'record':
|
||||
record(e.data.buffer);
|
||||
break;
|
||||
case 'exportWAV':
|
||||
exportWAV(e.data.type);
|
||||
break;
|
||||
case 'getBuffer':
|
||||
getBuffer();
|
||||
break;
|
||||
case 'clear':
|
||||
clear();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
function init(config) {
|
||||
sampleRate = config.sampleRate;
|
||||
numChannels = config.numChannels;
|
||||
initBuffers();
|
||||
}
|
||||
|
||||
function record(inputBuffer) {
|
||||
for (var channel = 0; channel < numChannels; channel++) {
|
||||
recBuffers[channel].push(inputBuffer[channel]);
|
||||
}
|
||||
recLength += inputBuffer[0].length;
|
||||
}
|
||||
|
||||
function exportWAV(type) {
|
||||
var buffers = [];
|
||||
for (var channel = 0; channel < numChannels; channel++) {
|
||||
buffers.push(mergeBuffers(recBuffers[channel], recLength));
|
||||
}
|
||||
var interleaved = undefined;
|
||||
if (numChannels === 2) {
|
||||
interleaved = interleave(buffers[0], buffers[1]);
|
||||
} else {
|
||||
interleaved = buffers[0];
|
||||
}
|
||||
var dataview = encodeWAV(interleaved);
|
||||
var audioBlob = new Blob([dataview], { type: type });
|
||||
|
||||
self.postMessage({ command: 'exportWAV', data: audioBlob });
|
||||
}
|
||||
|
||||
function getBuffer() {
|
||||
var buffers = [];
|
||||
for (var channel = 0; channel < numChannels; channel++) {
|
||||
buffers.push(mergeBuffers(recBuffers[channel], recLength));
|
||||
}
|
||||
self.postMessage({ command: 'getBuffer', data: buffers });
|
||||
}
|
||||
|
||||
function clear() {
|
||||
recLength = 0;
|
||||
recBuffers = [];
|
||||
initBuffers();
|
||||
}
|
||||
|
||||
function initBuffers() {
|
||||
for (var channel = 0; channel < numChannels; channel++) {
|
||||
recBuffers[channel] = [];
|
||||
}
|
||||
}
|
||||
|
||||
function mergeBuffers(recBuffers, recLength) {
|
||||
var result = new Float32Array(recLength);
|
||||
var offset = 0;
|
||||
for (var i = 0; i < recBuffers.length; i++) {
|
||||
result.set(recBuffers[i], offset);
|
||||
offset += recBuffers[i].length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function interleave(inputL, inputR) {
|
||||
var length = inputL.length + inputR.length;
|
||||
var result = new Float32Array(length);
|
||||
|
||||
var index = 0,
|
||||
inputIndex = 0;
|
||||
|
||||
while (index < length) {
|
||||
result[index++] = inputL[inputIndex];
|
||||
result[index++] = inputR[inputIndex];
|
||||
inputIndex++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function floatTo16BitPCM(output, offset, input) {
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]));
|
||||
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
|
||||
}
|
||||
}
|
||||
|
||||
function writeString(view, offset, string) {
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
function encodeWAV(samples) {
|
||||
var buffer = new ArrayBuffer(44 + samples.length * 2);
|
||||
var view = new DataView(buffer);
|
||||
|
||||
/* RIFF identifier */
|
||||
writeString(view, 0, 'RIFF');
|
||||
/* RIFF chunk length */
|
||||
view.setUint32(4, 36 + samples.length * 2, true);
|
||||
/* RIFF type */
|
||||
writeString(view, 8, 'WAVE');
|
||||
/* format chunk identifier */
|
||||
writeString(view, 12, 'fmt ');
|
||||
/* format chunk length */
|
||||
view.setUint32(16, 16, true);
|
||||
/* sample format (raw) */
|
||||
view.setUint16(20, 1, true);
|
||||
/* channel count */
|
||||
view.setUint16(22, numChannels, true);
|
||||
/* sample rate */
|
||||
view.setUint32(24, sampleRate, true);
|
||||
/* byte rate (sample rate * block align) */
|
||||
view.setUint32(28, sampleRate * 4, true);
|
||||
/* block align (channel count * bytes per sample) */
|
||||
view.setUint16(32, numChannels * 2, true);
|
||||
/* bits per sample */
|
||||
view.setUint16(34, 16, true);
|
||||
/* data chunk identifier */
|
||||
writeString(view, 36, 'data');
|
||||
/* data chunk length */
|
||||
view.setUint32(40, samples.length * 2, true);
|
||||
|
||||
floatTo16BitPCM(view, 44, samples);
|
||||
|
||||
return view;
|
||||
}
|
||||
}, self);
|
||||
|
||||
this.worker.postMessage({
|
||||
command: 'init',
|
||||
config: {
|
||||
sampleRate: this.context.sampleRate,
|
||||
numChannels: this.config.numChannels
|
||||
}
|
||||
});
|
||||
|
||||
this.worker.onmessage = function (e) {
|
||||
var cb = _this.callbacks[e.data.command].pop();
|
||||
if (typeof cb == 'function') {
|
||||
cb(e.data.data);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_createClass(Recorder, [{
|
||||
key: 'record',
|
||||
value: function record() {
|
||||
this.recording = true;
|
||||
}
|
||||
}, {
|
||||
key: 'stop',
|
||||
value: function stop() {
|
||||
this.recording = false;
|
||||
}
|
||||
}, {
|
||||
key: 'clear',
|
||||
value: function clear() {
|
||||
this.worker.postMessage({ command: 'clear' });
|
||||
}
|
||||
}, {
|
||||
key: 'getBuffer',
|
||||
value: function getBuffer(cb) {
|
||||
cb = cb || this.config.callback;
|
||||
if (!cb) throw new Error('Callback not set');
|
||||
|
||||
this.callbacks.getBuffer.push(cb);
|
||||
|
||||
this.worker.postMessage({ command: 'getBuffer' });
|
||||
}
|
||||
}, {
|
||||
key: 'exportWAV',
|
||||
value: function exportWAV(cb, mimeType) {
|
||||
mimeType = mimeType || this.config.mimeType;
|
||||
cb = cb || this.config.callback;
|
||||
if (!cb) throw new Error('Callback not set');
|
||||
|
||||
this.callbacks.exportWAV.push(cb);
|
||||
|
||||
this.worker.postMessage({
|
||||
command: 'exportWAV',
|
||||
type: mimeType
|
||||
});
|
||||
}
|
||||
}], [{
|
||||
key: 'forceDownload',
|
||||
value: function forceDownload(blob, filename) {
|
||||
var url = (window.URL || window.webkitURL).createObjectURL(blob);
|
||||
var link = window.document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename || 'output.wav';
|
||||
var click = document.createEvent("Event");
|
||||
click.initEvent("click", true, true);
|
||||
link.dispatchEvent(click);
|
||||
}
|
||||
}]);
|
||||
|
||||
return Recorder;
|
||||
})();
|
||||
|
||||
exports.default = Recorder;
|
||||
|
||||
},{"inline-worker":3}],3:[function(require,module,exports){
|
||||
"use strict";
|
||||
|
||||
module.exports = require("./inline-worker");
|
||||
},{"./inline-worker":4}],4:[function(require,module,exports){
|
||||
(function (global){
|
||||
"use strict";
|
||||
|
||||
var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
|
||||
|
||||
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
|
||||
|
||||
var WORKER_ENABLED = !!(global === global.window && global.URL && global.Blob && global.Worker);
|
||||
|
||||
var InlineWorker = (function () {
|
||||
function InlineWorker(func, self) {
|
||||
var _this = this;
|
||||
|
||||
_classCallCheck(this, InlineWorker);
|
||||
|
||||
if (WORKER_ENABLED) {
|
||||
var functionBody = func.toString().trim().match(/^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/)[1];
|
||||
var url = global.URL.createObjectURL(new global.Blob([functionBody], { type: "text/javascript" }));
|
||||
|
||||
return new global.Worker(url);
|
||||
}
|
||||
|
||||
this.self = self;
|
||||
this.self.postMessage = function (data) {
|
||||
setTimeout(function () {
|
||||
_this.onmessage({ data: data });
|
||||
}, 0);
|
||||
};
|
||||
|
||||
setTimeout(function () {
|
||||
func.call(self);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
_createClass(InlineWorker, {
|
||||
postMessage: {
|
||||
value: function postMessage(data) {
|
||||
var _this = this;
|
||||
|
||||
setTimeout(function () {
|
||||
_this.self.onmessage({ data: data });
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return InlineWorker;
|
||||
})();
|
||||
|
||||
module.exports = InlineWorker;
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{}]},{},[1])(1)
|
||||
});
|
@@ -51,7 +51,7 @@ function accordion() {
|
||||
|
||||
|
||||
function checkSDCard() {
|
||||
$.get("/cgi-bin/ui_sdcard.cgi", {cmd: "check_sdcard"}, function (result) {
|
||||
$.get("cgi-bin/ui_sdcard.cgi", {cmd: "check_sdcard"}, function (result) {
|
||||
if ( result == "nok") {
|
||||
$('#notifAlarm').attr('style','color:red');
|
||||
$('#notifContent').html("<p></p>Your sdcard is mounted read-only. Settings can't be saved. \
|
||||
|
@@ -4,24 +4,24 @@ var stateSideBar = false;
|
||||
|
||||
//Function for flip image
|
||||
function flip() {
|
||||
$.get("/cgi-bin/ui_live.cgi", {cmd: "flip"});
|
||||
$.get("cgi-bin/ui_live.cgi", {cmd: "flip"});
|
||||
}
|
||||
|
||||
//Functions for live stream with images
|
||||
function refreshLiveImage() {
|
||||
var ts = new Date().getTime();
|
||||
$("#liveview").attr("src", "/cgi-bin/currentpic.cgi?" + ts);
|
||||
$("#liveview").attr("src", "cgi-bin/currentpic.cgi?" + ts);
|
||||
}
|
||||
|
||||
//Function to download a screenshot
|
||||
function downloadScreenshot() {
|
||||
var ts = new Date().getTime();
|
||||
window.location.href = "/cgi-bin/downloadpic.cgi?" + ts;
|
||||
window.location.href = "cgi-bin/downloadpic.cgi?" + ts;
|
||||
}
|
||||
|
||||
//Function to refresh side bar buttons
|
||||
function refreshSideBar() {
|
||||
$.get("/cgi-bin/ui_live.cgi", {cmd: "status_all"}, function (result) {
|
||||
$.get("cgi-bin/ui_live.cgi", {cmd: "status_all"}, function (result) {
|
||||
var switches = result.split("\n");
|
||||
for (var i = 0; i < switches.length-1; i++) {
|
||||
var switch_info = switches[i].split(":");
|
||||
@@ -70,6 +70,21 @@ function PTZControl(view) {
|
||||
}
|
||||
}
|
||||
|
||||
function pushToTalk(action) {
|
||||
if (action == "on") {
|
||||
$("#btn-ptt span").attr("style","color:red");
|
||||
$("#btn-ptt span").removeAttr("onpointerdown");
|
||||
$("#btn-ptt").attr("onpointerup","pushToTalk('off')");
|
||||
startRecording();
|
||||
}
|
||||
else {
|
||||
stopRecording();
|
||||
$("#btn-ptt span").removeAttr("style");
|
||||
$("#btn-ptt span").removeAttr("onpointerup");
|
||||
$("#btn-ptt").attr("onpointerdown","pushToTalk('on')");
|
||||
}
|
||||
}
|
||||
|
||||
function record(action) {
|
||||
$.get("cgi-bin/ui_live.cgi",{cmd: "recording", action: action},function (result) {
|
||||
if (action == "on" || result == "ON\n") {
|
||||
|
@@ -1,11 +1,12 @@
|
||||
//Function to delete a file
|
||||
function deleteFile(fileName,dir,confirm) {
|
||||
if (confirm)
|
||||
function deleteFile(fileName,dir,askuser) {
|
||||
if (askuser) {
|
||||
var del = confirm("Confirm delete file: "+fileName);
|
||||
if ( del ) {
|
||||
$.get("cgi-bin/ui_sdcard.cgi", {cmd: "del_file",file: fileName});
|
||||
getFiles(dir);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$.get("cgi-bin/ui_sdcard.cgi", {cmd: "del_file",file: fileName});
|
||||
getFiles(dir);
|
||||
@@ -48,7 +49,7 @@ function getFiles(dir) {
|
||||
<td>"+config_info[2]+"</td> \
|
||||
<td> \
|
||||
<a href=\""+config_info[3]+"\" download><i class='fas fa-download' title='Download file'></i></a> \
|
||||
<span onclick=\"deleteFile('"+config_info[3]+"','"+dir+",true')\"><i class='fas fa-trash' title='Delete file'></i></span>\
|
||||
<span onclick=\"deleteFile('"+config_info[3]+"','"+dir+"',true)\"><i class='fas fa-trash' title='Delete file'></i></span>\
|
||||
"+html_photo+"\
|
||||
</td></tr>");
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ var iridix = new TimeSeries();
|
||||
var gain = new TimeSeries();
|
||||
|
||||
function readIspInfo(){
|
||||
$.get("/cgi-bin/ui_softnight.cgi", function(data, status)
|
||||
$.get("cgi-bin/ui_softnight.cgi", function(data, status)
|
||||
{
|
||||
parseIspInfo(data);
|
||||
});
|
||||
@@ -78,7 +78,7 @@ function startGraph(){
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$.get("/cgi-bin/action.cgi?cmd=get_sw_night_config", function(data, status)
|
||||
$.get("cgi-bin/action.cgi?cmd=get_sw_night_config", function(data, status)
|
||||
{
|
||||
var list = data.split('-');
|
||||
var valid=false;
|
||||
@@ -365,4 +365,4 @@ $('#swNightForm').submit(function (event) {
|
||||
showResult(res);
|
||||
});
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="w3-panel w3-card">
|
||||
<p id="hostname"> Hostname</p>
|
||||
<div class="w3-display-container" onmouseover="camControl('show')" onmouseleave="camControl('hide');PTZControl('hide')">
|
||||
<img id="liveview" src="/cgi-bin/currentpic.cgi" onerror="this.src='img/unable_load.png';" alt="Camera" style="width:100%" onmouseover="camControl('show')" onmouseout="camControl('hide')" >
|
||||
<img id="liveview" src="cgi-bin/currentpic.cgi" onerror="this.src='img/unable_load.png';" alt="Camera" style="width:100%" onmouseover="camControl('show')" onmouseout="camControl('hide')" >
|
||||
<div id="btn-ptz-left" class="w3-display-left w3-container w3-hide">
|
||||
<button class="w3-button w3-circle w3-black" onclick="moveCamera('left')"><i class="fa fa-angle-left"></i></button>
|
||||
</div>
|
||||
@@ -23,6 +23,7 @@
|
||||
<button class="w3-button w3-circle w3-black" onclick="flip()">Flip</button>
|
||||
</div>
|
||||
<div id="cam-control" class="w3-display-bottomright w3-container w3-hide">
|
||||
<button id="btn-ptt" class="w3-button w3-circle w3-black" onpointerdown="pushToTalk('on')" title="Push To Talk"><span><i class="fas fa-microphone"></i></span></button>
|
||||
<button id="btn-record" class="w3-button w3-circle w3-black" onclick="record('on')" title="Record On/Off"><span><i class="fas fa-video"></i></span></button>
|
||||
<button id="btn-screenshot" class="w3-button w3-circle w3-black" onclick="downloadScreenshot()" title="Download Screenshot"><span><i class="fas fa-image"></i></span></button>
|
||||
<button id="btn-ptz" class="w3-button w3-circle w3-black" onclick="PTZControl('show')" title="PTZ Control"> <i class="fa fa-arrows-alt"></i></button>
|
||||
@@ -33,7 +34,6 @@
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript" src="js/live.js"></script>
|
||||
<script type="text/javascript" src="js/audio_recorder.js"></script>
|
||||
<script type="text/javascript" src="js/audio_capture.js"></script>
|
||||
<script type="text/javascript" src="js/live.js"></script>
|
||||
|
@@ -215,18 +215,14 @@
|
||||
<button class="accordion" type='button'>Dropbox storage</button>
|
||||
<div class="panel">
|
||||
<p></p>
|
||||
<div class="w3-row-padding">
|
||||
<div class="w3-half">
|
||||
<label>Dropbox Long Lived Token</label>
|
||||
<input class="w3-input" id="dropboxToken">
|
||||
</div>
|
||||
<div class="w3-container">
|
||||
<label>Dropbox URL</label>
|
||||
<input class="w3-input" id="dropboxUrl" type="text">
|
||||
</div>
|
||||
<p></p>
|
||||
<div class="w3-row-padding">
|
||||
<div class="w3-half">
|
||||
</div>
|
||||
<div class="w3-half">
|
||||
</div>
|
||||
<div class="w3-container">
|
||||
<label>Dropbox Long Lived Token</label>
|
||||
<input class="w3-input" id="dropboxToken" type="text">
|
||||
</div>
|
||||
<p></p>
|
||||
<div class="w3-row-padding">
|
||||
@@ -321,6 +317,15 @@
|
||||
<option value="true">Activate</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w3-container">
|
||||
<label>Matrix alert type</label>
|
||||
<select id="matrixAlertType" class="w3-select" name="option">
|
||||
<option value="text">Text</option>
|
||||
<option value="image">Image</option>
|
||||
<option value="video">Video</option>
|
||||
<option value="video+image">Video and Image</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w3-container">
|
||||
<label>Night mode delay (ignore motion events for 'delay' seconds after switching to night mode)</label>
|
||||
<input class="w3-input" id="nightModeEventDelay" type="number" size="6">
|
||||
|
142
integration/custom/dafangstor.php
Normal file
142
integration/custom/dafangstor.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/* A simple Dropbox-like API for your own server
|
||||
Copyright (C) 2021 Chris Osgood
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
##### CONFIG ###############################################################
|
||||
|
||||
# Change this to some random long password.
|
||||
# This key should match the key under Settings->Motion Settings->Storage->Dropbox storage->Dropbox Long Lived Token.
|
||||
$APIKEY = "aw4QrDrz5Z94oyaYLx9ojh8ZDARt7tA8hRqosNG305shGD6NJtAcjOC3WS3odsBMdkPFvl0hJSZyIkCJ";
|
||||
|
||||
# This is the directory where all images and video will be stored. Missing subdirectories will be created here
|
||||
# based on the values of "Dropbox snapshots remote directory" and "Dropbox videos remote directory".
|
||||
# Make sure the web user has access permissions for this directory.
|
||||
$OUTDIR = "/media/securitycam";
|
||||
|
||||
# Allowed file extensions
|
||||
$ALLOWEDFILETYPES = array('jpg', 'mp4', 'avi');
|
||||
|
||||
# Maximum upload file size (50MB shown)
|
||||
$MAXFILESIZE = 50 * 1024 * 1024;
|
||||
|
||||
############################################################################
|
||||
|
||||
if (!array_key_exists('HTTP_AUTHORIZATION', $_SERVER) || !array_key_exists('HTTP_DROPBOX_API_ARG', $_SERVER))
|
||||
{
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
error_log("ERROR: Missing authorization or API argument");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($_SERVER['HTTP_AUTHORIZATION'] != "Bearer $APIKEY")
|
||||
{
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
exit();
|
||||
}
|
||||
|
||||
$path = json_decode($_SERVER['HTTP_DROPBOX_API_ARG'], true);
|
||||
|
||||
if ($path == null || !array_key_exists('path', $path))
|
||||
{
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
error_log("ERROR: Missing or bad file path `".$_SERVER['HTTP_DROPBOX_API_ARG']."'");
|
||||
exit();
|
||||
}
|
||||
|
||||
$path = $path['path'];
|
||||
|
||||
#----------- PATH SANITIZING -----------------------
|
||||
|
||||
# Only allow basic characters and digits
|
||||
$path = str_replace("\\", "/", $path);
|
||||
$path = preg_replace("([^a-zA-Z\d \-_./])", "_", $path);
|
||||
|
||||
# Clean beginning of path
|
||||
$path = preg_replace("(^[/.]+)", "", $path);
|
||||
|
||||
# No path backwards; NOTE: this is a simplistic approach that will
|
||||
# disallow any paths with multiple repeating periods.
|
||||
$path = preg_replace("(\.+)", ".", $path);
|
||||
|
||||
# Clean repeated path seperators
|
||||
$path = preg_replace("(\/+)", "/", $path);
|
||||
|
||||
# Remove all "current directory" paths
|
||||
do {
|
||||
$oldpath = $path;
|
||||
$path = str_replace("/./", "/", $path);
|
||||
} while ($path != $oldpath);
|
||||
|
||||
#---------------------------------------------------
|
||||
|
||||
$dirname = dirname($path);
|
||||
$filename = basename($path);
|
||||
|
||||
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
|
||||
if (!$ext || strlen($filename) < 1)
|
||||
{
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
error_log("ERROR: Bad file path");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!in_array(strtolower($ext), $ALLOWEDFILETYPES))
|
||||
{
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
error_log("ERROR: File extension not allowed");
|
||||
exit();
|
||||
}
|
||||
|
||||
$dirname = "$OUTDIR/$dirname";
|
||||
$filename = "$dirname/$filename";
|
||||
|
||||
if (!is_dir($dirname))
|
||||
{
|
||||
if (!mkdir($dirname, 0777, true))
|
||||
{
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
error_log("ERROR: Failed to create output directory `$dirname'");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
if (file_exists($filename))
|
||||
{
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
error_log("ERROR: Output file already exists `$filename'");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!($fp = fopen($filename, "wb")))
|
||||
{
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
error_log("ERROR: Failed to create output file `$filename'");
|
||||
exit();
|
||||
}
|
||||
|
||||
$data = file_get_contents('php://input', false, null, 0, $MAXFILESIZE);
|
||||
$numbytes = fwrite($fp, $data);
|
||||
fclose($fp);
|
||||
|
||||
if (!$numbytes)
|
||||
{
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
error_log("ERROR: Failed to write output file `$filename'");
|
||||
exit();
|
||||
}
|
||||
?>
|
14
integration/custom/webserverstorage.md
Normal file
14
integration/custom/webserverstorage.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## Web server storage
|
||||
|
||||
It's possible to use the Dropbox functionality to store snapshots or video on your own server.
|
||||
|
||||
Look under Motion Settings»Motion Detection»Storage»Dropbox storage. There should be an option to change the Dropbox URL. This can be set to an internal or external web server. The default uses the Dropbox server.
|
||||
Example: https[]()://yourserver/dafangstor.php
|
||||
|
||||
[dafangstor.php](dafangstor.php) is an example PHP script that can be used on your own server. Inside this file there are two options:
|
||||
* APIKEY
|
||||
* OUTDIR
|
||||
|
||||
APIKEY is the Long Lived Token value that is used to authenticate requests. This must match the value entered under Settings»Motion Detection»Storage»Dropbox storage»Dropbox Long Lived Token. The PHP script can be modified to include more fancy features like maybe for example changing the OUTDIR based on the authentication token or something similar.
|
||||
|
||||
OUTDIR sets the output directory relative to where PHP is running. The www user (eg. www-data) will need access permissions for this directory so that it can create sub directories and output files or you can manually create directories beforehand. Paths specified by "Dropbox snapshots remote directory" and "Dropbox videos remote directory" will be relative to this OUTDIR directory.
|
Reference in New Issue
Block a user