Last active
April 14, 2023 23:42
-
-
Save hereisderek/6a2ca0afcc957858d8a465392212edb8 to your computer and use it in GitHub Desktop.
script to combine xiaomi camera recordings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
## to get the latest version of this script, see: https://gist.githubusercontent.com/hereisderek/6a2ca0afcc957858d8a465392212edb8/raw/combine.sh | |
## to run this script as a cron task: | |
## * * * * * /usr/bin/wget -qO- https://gist.githubusercontent.com/hereisderek/6a2ca0afcc957858d8a465392212edb8/raw/combine.sh | /bin/bash -s /mnt/xiaomi/xiaomi_camera_videos /mnt/xiaomi/xiaomi_camera_videos/output >/dev/null 2>&1 | |
function example_usage { | |
export your_healthcheck_url="https://hc-ping.com/37174a73-****-****" && wget "${your_healthcheck_url}/start" -T 10 -t 5 -O /dev/null && wget --no-cache 'https://gist.githubusercontent.com/hereisderek/6a2ca0afcc957858d8a465392212edb8/raw/combine.sh' -qO /tmp/combine.sh && chmod a+x /tmp/combine.sh && /tmp/combine.sh && wget "${your_healthcheck_url}" -T 10 -t 5 -O /dev/null | |
} | |
# Get the name of the script without the path | |
SCRIPT_NAME=$(basename "$0") | |
LOCK_FILE="/tmp/combine.sh.lock" | |
PROCESSING_EXTENSION=".processing" | |
PROCESSED_EXTENSION=".processed" | |
FFMPEG_PARAMS="-hide_banner" | |
### hardware acceleration, see https://trac.ffmpeg.org/wiki/HWAccelIntro | |
## intel quick sync | |
# FFMPEG_PARAMS="${FFMPEG_PARAMS} -hwaccel qsv -qsv_device /dev/dri/renderD128" # linux | |
# FFMPEG_PARAMS="${FFMPEG_PARAMS} -hwaccel dxva2" # windows | |
# FFMPEG_PARAMS+=" -crf 28 -preset fast -map 0:v:0 -map 0:a:0 -map 0:s? -map_metadata -1 -movflags +faststart -shortest" | |
### video | |
## for direct video | |
VIDEO_CODEC="copy" | |
# VIDEO_CODEC="hevc_videotoolbox" # m1 mac | |
### audio | |
## direct copy audio | |
# AUDIO_CODEC="copy" | |
## convert audio to aac | |
AUDIO_CODEC="aac" | |
trap "rm -rf ${LOCK_FILE}" EXIT # remove the lockdir on exit | |
if [ -e "${LOCK_FILE}" ]; then | |
echo "Script is already running, exiting." | |
exit 1 | |
fi | |
touch "${LOCK_FILE}" | |
# Set the path to the directory containing the camera footage folders | |
INPUT_DIR="/Volumes/Xiaomi/xiaomi_camera_videos" | |
# Set the output directory for the processed footage and thumbnail image | |
OUTPUT_DIR="/Volumes/Xiaomi/xiaomi_camera_videos/output" | |
if [ "$#" -eq 2 ]; then | |
INPUT_DIR="$1" | |
OUTPUT_DIR="$2" | |
fi | |
echo "INPUT_DIR: $INPUT_DIR, OUTPUT_DIR: $OUTPUT_DIR" | |
# make sure output folder exists | |
mkdir -p ${OUTPUT_DIR} | |
# footage_folder, output_folder | |
function process_footage_folder { | |
footage_folder=$1 | |
output_folder=$2 | |
footage_name=$(basename "$footage_folder") | |
# rename footage folder to add processing extension | |
footage_folder_processing="${footage_folder}${PROCESSING_EXTENSION}" | |
footage_folder_proceed="${footage_folder}${PROCESSED_EXTENSION}" | |
footage_output_file_name="${output_folder}/${footage_name:0:8}_${footage_name:8:2}.mkv" | |
# rm -rf "$footage_folder_processing" "$footage_folder_proceed" | |
# check if footage folder is already processed | |
if [ -d "$footage_folder_proceed" ]; then | |
echo -e "footage folder already processed: $footage_folder_proceed" | |
exit 1 | |
fi | |
echo -e "process_footage_folder footage_folder:$footage_folder output_folder:$output_folder footage_name:$footage_name" | |
mv "$footage_folder" "${footage_folder_processing}" | |
# make sure output folder exists | |
mkdir -p ${output_folder} | |
# get all mp4 files under footage folder | |
mp4_files=($(find "$footage_folder_processing" -type f -name "*.mp4" | sort)) | |
echo -e "\n" | |
for i in "${!mp4_files[@]}"; do | |
mp4_file=${mp4_files[$i]} | |
mp4_file_name=$(basename "$mp4_file" | cut -f 1 -d '.') | |
thumbnail_output_file_name="${footage_name:0:8}_${footage_name:8:2}${mp4_file_name:0:2}.jpg" | |
thumbnail_output_file="${output_folder}/${thumbnail_output_file_name}" | |
# skip if thumbnail already exists | |
if [ -f "$thumbnail_output_file" ]; then | |
echo -ne "Progress:$i/${#mp4_files[@]} skipping:${mp4_file} \r" | |
continue | |
fi | |
# generate thumbnail for mp4 file and save it to output folder with the same name as the mp4 file | |
ffmpeg -y -i "${mp4_file}" -ss 00:00:01.000 -vframes 1 "${thumbnail_output_file}" >/dev/null 2>&1 | |
echo -ne "Progress:$i/${#mp4_files[@]} processing:${mp4_file} \r" | |
done | |
echo -e "\nThumbnails generated, conbining footage..\n" | |
# rm "${footage_output_file_name}" > /dev/null 2>&1 | |
ffmpeg ${FFMPEG_PARAMS} -f concat -safe 0 -i <(for f in "${mp4_files[@]}"; do echo "file '$f'"; done) -c:v ${VIDEO_CODEC} -c:a ${AUDIO_CODEC} "${footage_output_file_name}" | grep "frame=" || true | |
## chose one of the following three: | |
# mv "${footage_folder_processing}" "${footage_folder_proceed}" | |
# mv "${footage_folder_processing}" "${footage_folder}" | |
rm -rf "${footage_folder_processing}" | |
} | |
# Get a list of all camera folders, sorted by name in ascending order, and skip the last one, also skip the output folder | |
# Get a list of all camera folders, sorted by name in ascending order | |
CAMERA_FOLDERS=($(find "$INPUT_DIR" -mindepth 1 -maxdepth 1 -type d | sort)) | |
# Loop through the camera folders | |
for CAMERA_FOLDER in "${CAMERA_FOLDERS[@]}"; do | |
# Skip the output directory | |
if [[ "$CAMERA_FOLDER" =~ ^"$OUTPUT_DIR" ]]; then | |
echo "skip CAMERA_FOLDER folder: $CAMERA_FOLDER" | |
continue | |
fi | |
CAMERA_ID=$(basename "$CAMERA_FOLDER") | |
FOOTAGE_FOLDERS=() | |
FOOTAGE_FOLDERS_TEMP=($(find "$CAMERA_FOLDER" -mindepth 1 -maxdepth 1 -type d | sort)) | |
for folder in "${FOOTAGE_FOLDERS_TEMP[@]}"; do | |
if [[ "$folder" = "$OUTPUT_DIR"/* ]]; then | |
echo "skip FOOTAGE_FOLDER folder: $folder, because it is in the output folder" | |
continue | |
fi | |
if [[ "$folder" = *"$PROCESSING_EXTENSION" ]]; then | |
echo "skip FOOTAGE_FOLDER folder: $folder, because it is processing" | |
continue | |
fi | |
if [[ "$folder" = *"$PROCESSED_EXTENSION" ]]; then | |
echo "skip FOOTAGE_FOLDER folder: $folder, because it is proceed" | |
continue | |
fi | |
FOOTAGE_FOLDERS+=("$folder") | |
done | |
# loop through the footage folders except for the last one | |
for FOOTAGE_FOLDER in "${FOOTAGE_FOLDERS[@]:0:${#FOOTAGE_FOLDERS[@]}-1}"; do | |
FOOTAGE_NAME=$(basename "$FOOTAGE_FOLDER") | |
FOOTAGE_DATE="${FOOTAGE_NAME:0:8}" | |
output_folder="${OUTPUT_DIR}/${CAMERA_ID}/${FOOTAGE_DATE}" | |
echo -e "FOOTAGE_FOLDER:$FOOTAGE_FOLDER output_folder:$output_folder" | |
process_footage_folder "$FOOTAGE_FOLDER" "$output_folder" | |
done | |
done | |
rm -rf "${LOCK_FILE}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
works pretty well especially if you combine with healcheck.io like shown in the example