Files
stop-motion/stopmotion.sh
T

114 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<EOF
Usage : $(basename "$0") [options]
Crée une vidéo stop motion à partir d'un dossier d'images.
Options :
-i DIR dossier d'images (défaut : ./images)
-o FILE fichier de sortie (défaut : ./stop_motion.mp4)
-f FPS cadence en images/seconde, > 0 (défaut : 12)
-s SIZE taille max du côté long en pixels, > 0 (défaut : 1080)
-y écraser le fichier de sortie sans demander
-h afficher cette aide
EOF
}
INPUT="images"
OUTPUT="stop_motion.mp4"
FPS="12"
SIZE="1080"
ASSUME_YES=0
while getopts ":i:o:f:s:yh" opt; do
case $opt in
i) INPUT=$OPTARG ;;
o) OUTPUT=$OPTARG ;;
f) FPS=$OPTARG ;;
s) SIZE=$OPTARG ;;
y) ASSUME_YES=1 ;;
h) usage ; exit 0 ;;
\?) echo "Erreur : option inconnue -$OPTARG" >&2 ; usage >&2 ; exit 1 ;;
:) echo "Erreur : valeur manquante pour -$OPTARG" >&2 ; usage >&2 ; exit 1 ;;
esac
done
OUTPUT=${OUTPUT%/}
if ! [[ $FPS =~ ^[0-9]+([.][0-9]+)?$ ]] || ! awk -v f="$FPS" 'BEGIN{exit !(f>0)}'; then
echo "Erreur : -f doit être un nombre strictement positif (reçu : $FPS)" >&2
exit 1
fi
if ! [[ $SIZE =~ ^[0-9]+$ ]] || ! awk -v s="$SIZE" 'BEGIN{exit !(s>0)}'; then
echo "Erreur : -s doit être un entier strictement positif (reçu : $SIZE)" >&2
exit 1
fi
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "Erreur : ffmpeg est introuvable. Installation : brew install ffmpeg" >&2
exit 1
fi
if [ ! -d "$INPUT" ]; then
echo "Erreur : le dossier d'images '$INPUT' n'existe pas." >&2
exit 1
fi
IMAGES_ABS_DIR=$(cd "$INPUT" && pwd)
IMAGES=$(LC_ALL=C find "$IMAGES_ABS_DIR" -maxdepth 1 -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.heic' \) | LC_ALL=C sort)
if [ -z "$IMAGES" ]; then
echo "Erreur : aucune image (jpg, jpeg, png, heic) trouvée dans '$INPUT'." >&2
exit 1
fi
COUNT=$(printf '%s\n' "$IMAGES" | wc -l | tr -d ' ')
if [ ! -d "$(dirname "$OUTPUT")" ]; then
echo "Erreur : le dossier de sortie '$(dirname "$OUTPUT")' n'existe pas." >&2
exit 1
fi
if [ -e "$OUTPUT" ] && [ "$ASSUME_YES" -ne 1 ]; then
read -r -p "'$OUTPUT' existe déjà. L'écraser ? [o/N] " reponse
case $reponse in
o|O|y|Y|oui|Oui) ;;
*) echo "Abandon." ; exit 0 ;;
esac
fi
OUT_DIR=$(cd "$(dirname "$OUTPUT")" && pwd)
OUT_NAME=$(basename "$OUTPUT")
LIST_FILE=$(mktemp "$OUT_DIR/.stopmotion.XXXXXX")
trap 'rm -f "$LIST_FILE"' EXIT
quote_ffconcat() {
local s=$1
s=${s//\'/\'\\\'\'}
printf "file '%s'\n" "$s"
}
DURATION=$(awk -v f="$FPS" 'BEGIN{printf "%.6f", 1/f}')
{
echo "ffconcat version 1.0"
while IFS= read -r img; do
quote_ffconcat "$img"
printf 'duration %s\n' "$DURATION"
done <<< "$IMAGES"
} > "$LIST_FILE"
if ! ffmpeg -y -hide_banner -loglevel error -stats \
-f concat -safe 0 -i "$LIST_FILE" \
-vf "scale=${SIZE}:${SIZE}:force_original_aspect_ratio=decrease:force_divisible_by=2" \
-r "$FPS" \
-c:v libx264 -pix_fmt yuv420p -movflags +faststart \
"$OUT_DIR/$OUT_NAME"; then
rm -f "$OUT_DIR/$OUT_NAME"
echo "Erreur : l'encodage ffmpeg a échoué." >&2
exit 1
fi
DURATION_VIDEO=$(awk -v c="$COUNT" -v f="$FPS" 'BEGIN{printf "%.2f", c/f}')
SIZE_HUMAN=$(du -h "$OUT_DIR/$OUT_NAME" | cut -f1 | tr -d ' ')
echo "Vidéo créée : $OUT_NAME ($COUNT images, ${FPS} fps, ${DURATION_VIDEO} s, ${SIZE_HUMAN})"