From 14a3392d41a14deabd2e7d659054e7e2b8fa9cf5 Mon Sep 17 00:00:00 2001 From: Gabriel Radureau Date: Sun, 12 Oct 2025 00:19:52 +0200 Subject: [PATCH] most of program1 is done, shape of program2, actual app to be started --- README.md | 6 +-- model/register_video.sh | 25 ++++++++++ model/videos.sql | 14 ++++++ program1/metadata.sh | 98 ++++++++++++++++++++++++++++++++++++++ program1/program1.sh | 67 ++++++++++++++++++++++++++ program1/rotation.sh | 46 ++++++++++++++++++ program2/program2.sh | 14 ++++++ trigger/onSD_DANSEMount.sh | 3 ++ 8 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 model/register_video.sh create mode 100644 model/videos.sql create mode 100644 program1/metadata.sh create mode 100644 program1/rotation.sh create mode 100755 program2/program2.sh diff --git a/README.md b/README.md index 09bfdff..23209f7 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ flowchart TB dll L_dll_raw_0@== "onAirDropDownload.sh" ==> raw raw L_raw_pgrm1_0@=== pgrm1 pgrm1 L_pgrm1_videos_0@== ffmpeg ==> videos - pgrm1 -- register videos --> bdd - app["DanceVideos App"] -- classify, register playlists --> bdd - pgrm2 -- convert playlists to directories of symlink --> playlists + pgrm1 -- 1) register videos --> bdd + app["DanceVideos App"] -- 2) classify, register playlists --> bdd + pgrm2 -- 3) convert playlists to directories of symlink --> playlists pgrm2 --- bdd videos L_videos_playlists_0@-.- playlists raw@{ shape: docs} diff --git a/model/register_video.sh b/model/register_video.sh new file mode 100644 index 0000000..aac481b --- /dev/null +++ b/model/register_video.sh @@ -0,0 +1,25 @@ +# register_video \ +# file_name raw_file duration mp4_file \ +# rotated_file thumbnail_file record_datetime \ +# day_of_week record_time \ +# lat long address +register_video() { + local file_name=$1 + local raw_file=$2 + local duration=$3 + local mp4_file=$4 + local rotated_file=$5 + local thumbnail_file=$6 + local record_datetime=${7:-$(date +%s)} + local day_of_week=${8:-$(date +'%A')} + local lat=${9:-0.000000} + local long=${10:-0.000000} + local address=${11:-Unknown} + if [ -z "$raw_file" ] || [ -z "$mp4_file" ]; then + echo "Error: raw_file and mp4_file are required" + exit 1 + fi + + sqlite3 $DANCE_VIDEOS_DB "INSERT OR REPLACE INTO videos (file_name, raw_file, duration, mp4_file, rotated_file, thumbnail_file, record_datetime, day_of_week, lat, long, address) VALUES('$file_name','$raw_file', '$duration', '$mp4_file', '$rotated_file', '$thumbnail_file', $record_datetime, '$day_of_week', $lat, $long, '$address')" +} +export -f register_video \ No newline at end of file diff --git a/model/videos.sql b/model/videos.sql new file mode 100644 index 0000000..b109040 --- /dev/null +++ b/model/videos.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS videos ( + file_name VARCHAR(255) PRIMARY KEY, + raw_file VARCHAR(255) UNIQUE, + duration DECIMAL(10,2), + mp4_file VARCHAR(255), + rotated_file VARCHAR(255), + thumbnail_file VARCHAR(255), + record_datetime TIMESTAMP, + day_of_week VARCHAR(50), + record_time TIME GENERATED ALWAYS AS (TIME(record_datetime)) VIRTUAL, + lat DECIMAL(10,6), + long DECIMAL(11,7), + address VARCHAR(255) +); \ No newline at end of file diff --git a/program1/metadata.sh b/program1/metadata.sh new file mode 100644 index 0000000..ef6aec6 --- /dev/null +++ b/program1/metadata.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' + +# === CONFIGURATION === + +# === Utilitaires === +esc() { printf '%s' "$1" | sed 's/"/""/g'; } +export -f esc + +safe_weekday() { + local iso="$1" + python3 - </dev/null || true) + [ -n "$ct" ] && break + done + if [ -z "$ct" ]; then + ct="$(date -r "$f" +"%Y-%m-%dT%H:%M:%S")" + fi + echo "$ct" +} +export -f get_creation_time + +get_duration() { + local f="$1" + ffprobe -v error -show_entries format=duration \ + -of default=noprint_wrappers=1:nokey=1 "$f" 2>/dev/null || echo "" +} +export -f get_duration + +get_gps() { + local f="$1" + local lat lon + lat="$(exiftool -GPSLatitude -n -s3 "$f" 2>/dev/null || true)" + lon="$(exiftool -GPSLongitude -n -s3 "$f" 2>/dev/null || true)" + echo "$lat|$lon" +} +export -f get_gps + +reverse_geocode() { + local lat="$1" lon="$2" + local json="" postal city district + + if [ -n "$lat" ] && [ -n "$lon" ]; then + json="$(curl -s "https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=16&addressdetails=1")" + + # Extraction sécurisée avec jq + postal=$(echo "$json" | jq -r '.address.postcode // ""') + city=$(echo "$json" | jq -r '.address.city // .address.town // .address.village // ""') + district=$(echo "$json" | jq -r '.address.neighbourhood // .address.suburb // .address.residential // .address.village // .address.municipality // ""') + + # Concaténation formatée, ignorer champs vides + formatted="" + [ -n "$postal" ] && formatted="$formatted$postal" + [ -n "$city" ] && formatted="${formatted:+$formatted, } + $city" + [ -n "$district" ] && formatted="${formatted:+$formatted, } + $district" + + echo "$formatted" | tr '\n' ' ' | tr -s ' ' ' ' | sed 's/ *$//' + else + echo "" + fi +} +export -f reverse_geocode + +process_video() { + local f="$1" + + local ct weekday duration lat lon address + ct="$(get_creation_time "$f")" + weekday="$(safe_weekday "$ct")" + duration="$(get_duration "$f")" + IFS="|" read -r lat lon <<<"$(get_gps "$f")" + address="$(reverse_geocode "$lat" "$lon")" + + echo "$ct|$weekday|$duration|$lat|$lon|$address" + # --- CSV row --- + # echo "\"$(basename "$f")\",\"$(esc "$f")\",\"$(esc "$ct")\",\"$(esc "$weekday")\",\"$(esc "$duration")\",\"$(esc "$lat")\",\"$(esc "$lon")\",\"$(esc "$formatted")\"" +} +export -f process_video + diff --git a/program1/program1.sh b/program1/program1.sh index 60becdf..d3cd49c 100755 --- a/program1/program1.sh +++ b/program1/program1.sh @@ -2,6 +2,8 @@ set -euo pipefail IFS=$'\n\t' +SCRIPTS_DIR=$(dirname `realpath ${BASH_SOURCE[0]}`) + # === CONFIGURATION === export DOSSIER_SOURCE="${HOME}/Downloads" export DOSSIER_DESTINATION_RAW="${HOME}/Documents/.DanceVideos/raw" @@ -42,19 +44,84 @@ import_downloaded_file export DOSSIER_DESTINATION_MP4="$(dirname $DOSSIER_DESTINATION_RAW)/videos" +## DB utilities +export DANCE_VIDEOS_DB="$(dirname $DOSSIER_DESTINATION_RAW)/db.sqlite" +export MODEL_DIR=$(realpath $SCRIPTS_DIR/../model) + +cat $MODEL_DIR/videos.sql | sqlite3 $DANCE_VIDEOS_DB + +source $MODEL_DIR/register_video.sh +# register_video \ +# file_name raw_file duration mp4_file \ +# rotated_file thumbnail_file record_datetime day_of_week \ +# lat long address + +source $SCRIPTS_DIR/metadata.sh +source $SCRIPTS_DIR/rotation.sh + +mp4_dir() { + local raw="$1" weekday="$2" address="${3:-wherever}" + address=$(sed 's/[^a-zA-Z0-9]/_/g' <<< $(sanitize_name "$address") | tr -s '_' '_') + local dir=$DOSSIER_DESTINATION_MP4 + dir=$dir/$(date -jf "%Y-%m-%dT%H:%M:%S" "$(get_creation_time $raw)" +%Y%m%d_%H%M%S) + dir=${dir}_${weekday}_${address} + mkdir -p $dir + echo $dir +} +export -f mp4_dir + +write_thumbnail() { + set -x + local raw="$1" + local thumbnail="$2" + ffmpeg -ss 00:00:03 -i $raw -vframes 1 $thumbnail 2>/dev/null + set +x +} +export -f write_thumbnail + +write_mp4() { + local src="$1" + local dst="$2" + [ -e "$dst" ] || reencode_with_rotation $src $dst +} +export -f write_mp4 + +process_raw_file() { + local raw="$1" + + local ct weekday duration lat lon address + IFS="|" read -r ct weekday duration lat lon address <<<"$(process_video "$raw")" + + local dir=$(mp4_dir $raw $weekday "$address") + + local thumbnail=${dir}/thumbnail.jpg + $(write_thumbnail $raw $thumbnail) + + local mp4=${dir}/video.mp4 + $(write_mp4 $raw $mp4) + + register_video $(basename $raw) "$raw" "$duration" "$mp4" \ + "$mp4" "$thumbnail" "'$ct'" "$weekday" \ + "$lat" "$lon" "$address" +} +export -f process_raw_file + iphone_video() { local raw="$1" echo iphone $raw + process_raw_file $raw } export -f iphone_video screen_video() { local raw="$1" echo screen $raw + process_raw_file $raw } export -f screen_video whatsapp_video() { local raw="$1" echo whatsapp $raw + process_raw_file $raw } export -f whatsapp_video diff --git a/program1/rotation.sh b/program1/rotation.sh new file mode 100644 index 0000000..be14475 --- /dev/null +++ b/program1/rotation.sh @@ -0,0 +1,46 @@ +get_rotation_filter() { + local f="$1" + local rotation + rotation=$(exiftool -Rotation -n "$f" | awk '{print $3}') + # echo $rotation $f >> "rotations.txt" + case "$rotation" in + # 90) echo "transpose=1" ;; + # 270) echo "transpose=2" ;; + # 180) echo "hflip,vflip" ;; + # *) echo "" ;; + *) echo "transpose=1" ;; + esac +} +export -f get_rotation_filter + +reencode_with_rotation() { + local src="$1" + local dst="$2" + local filter + filter="$(get_rotation_filter "$src")" + + if [ -n "$filter" ]; then + echo " Correction d’orientation (rotation=${filter})" + if ffmpeg -encoders 2>/dev/null | grep -q 'h264_videotoolbox'; then + ffmpeg -nostdin -i "$src" -vf "$filter" \ + -c:v h264_videotoolbox -b:v 5M -c:a aac -map_metadata -1 -y "$dst" + else + ffmpeg -nostdin -i "$src" -vf "$filter" \ + -c:v libx264 -preset fast -crf 18 -c:a aac -map_metadata -1 -y "$dst" + fi + else + # Pas de rotation → simple copie (ou réencodage minimal) + if ffmpeg -y -i "$src" -c copy -map 0 -movflags +faststart "$dst" < /dev/null 2>/dev/null; then + : + else + if ffmpeg -encoders 2>/dev/null | grep -q 'h264_videotoolbox'; then + ffmpeg -nostdin -i "$src" \ + -c:v h264_videotoolbox -b:v 5M -c:a aac -map_metadata -1 -y "$dst" + else + ffmpeg -nostdin -i "$src" \ + -c:v libx264 -preset fast -crf 18 -c:a aac -map_metadata -1 -y "$dst" + fi + fi + fi +} +export -f reencode_with_rotation \ No newline at end of file diff --git a/program2/program2.sh b/program2/program2.sh new file mode 100755 index 0000000..24d1314 --- /dev/null +++ b/program2/program2.sh @@ -0,0 +1,14 @@ +set -euo pipefail +SCRIPTS_DIR=$(dirname `realpath ${BASH_SOURCE[0]}`) + +export DANCE_VIDEOS_DB="${HOME}/Documents/.DanceVideos/db.sqlite" +export DOSSIER_PLAYLIST="$(dirname $DANCE_VIDEOS_DB)/playlists" + +rm -rf $DOSSIER_PLAYLIST + +export PLAYLIST_ALL=$DOSSIER_PLAYLIST/all +mkdir -p $PLAYLIST_ALL + +for v in $(sqlite3 $DANCE_VIDEOS_DB "select rotated_file from videos"); do + ln -s $v $PLAYLIST_ALL/$(basename $(dirname $v)).mp4 +done \ No newline at end of file diff --git a/trigger/onSD_DANSEMount.sh b/trigger/onSD_DANSEMount.sh index d5a26a5..f50ca1e 100755 --- a/trigger/onSD_DANSEMount.sh +++ b/trigger/onSD_DANSEMount.sh @@ -1,5 +1,6 @@ #! /bin/bash set -eux +SCRIPTS_DIR=$(dirname `realpath ${BASH_SOURCE[0]}`) LOCK_FILE="/tmp/.lock.onSD_DANSE" playsound="afplay /System/Library/Sounds/Morse.aiff" @@ -21,6 +22,8 @@ fi SOURCE_DIR=${HOME}/Documents/.DanceVideos/playlists/ # final / is important to delete mkdir -p ${SOURCE_DIR} +$SCRIPTS_DIR/../program2/program2.sh + DESTINATION_DIR=TEST mkdir -p /Volumes/SD_DANSE/${DESTINATION_DIR}