#!/usr/bin/env bash # Copyright: Julien Moutinho # License: AGPL-3.0-or-later # Usage: # $ mv julm/2023/src/02/IMG_20230228_111645{,.fav}.jpg # $ echo >julm/2023/src/02/IMG_20230228_111645.fav.txt "Some HTML comment about the picture" # $ ./index.sh julm/2023/src/02 # $ sensible-browser julm/2023/fav/02/index.html # # NginxConfig: # locations."/julm/perso/camera/" = { # alias = "${root}/julm/perso/camera/"; # basicAuthFile = "/run/credentials/nginx.service/autogeree.net.www.julm.perso.camera.htpasswd"; # }; # # Disable basicAuthFile for by-uuid # locations."~ ^/julm/perso/camera/([0-9]+/[0-9][0-9]/by-uuid/[0-9a-f-]+/.+)$" = { # alias = "${root}/julm/perso/camera/$1"; # }; # shellcheck disable=SC2086 cd "${0%/*}" set -eu shopt -s nullglob ffmpeg_ () { local -; set -x; nice -19 ffmpeg &2 "$@"; } for dir in "$@"; do IFS=/ read -r user year x month x <<<"$dir" test -n "$year" || exec "$0" "$user"/2*/src/*/ test -n "$month" || exec "$0" "$user"/"$year"/src/*/ pushd "$user/$year" >/dev/null test -d src || { popd; continue; } mkdir -p fav # cp, not ln, because in push.sh --copy-links cannot be used for by-uuid/ cp -f --remove-destination -t fav ../../index.css test -d src/"$month" || { popd; continue; } mkdir -p fav/"$month" mkdir -p {tmp,wip}/fav/"$month" { genDate=$(date +%s) cat < camera/$user/$year/$month
    EOF echo >&2 "$dir: processing favorites in chronological order" # Time is encoded in filenames like so: MM/{IMG,VID}_YYYYMMDD_hhmmss.* # hence sorting after the underscore (2nd field, 5th char). (cd src; find -L "$month" -name '*.fav.*' -not -name "*.txt") | sort -n -t/ -k2.5 | while read -r src; do echo >&2 "$dir: processing $year/src/$src" base=${src%.*} name=${base#*/} id=${name%%.*} creationDate=$(printf %s "${src##*/???_}" | sed -e 's/\(....\)\(..\)\(..\)_\(..\)\(..\)\(..\).*/\1-\2-\3 \4:\5:\6/') echo "
  • " test -e fav/"$base".uuid || uuidgen --random >fav/"$base".uuid uuid=$(cat fav/"$base".uuid) mkdir -p fav/"$month"/by-uuid/"$uuid" case "$src" in *.jpg) echo "" printf "" w=600 test fav/"$base"."$w"x.avif -nt src/"$src" || { magick_ -define jpeg:size=$((w * 2))x src/"$src" \ -sampling-factor 4:2:0 -interlace JPEG -colorspace sRGB \ -auto-orient -thumbnail "$w"x -unsharp 0x.5 \ wip/fav/"$base"."$w"x.jpg mv_ {wip/,}fav/"$base"."$w"x.avif } echo "" echo "$creationDate" echo "" dst="$name".1200x.avif ln -fs -t fav/"$month"/by-uuid/"$uuid" ../../"$dst" ;; *.mp4) need_scale () { orient=$(ffprobe -v 0 -select_streams v:0 -show_entries stream_side_data=rotation -of default=nw=1:nk=1 src/"$src") if [ -z "$orient" ]; then scale="-2:360"; else scale="360:-2"; fi } test fav/"$base".avif -nt src/"$src" || { need_scale ffmpeg_ -i src/"$src" \ -map 0:v \ -filter:v format=yuv420p10le,scale="$scale" \ -frames:v 1 -crf 35 \ wip/fav/"$base".avif mv_ {wip/,}fav/"$base".avif } need_opus () { test tmp/fav/"$base".opus -nt src/"$src" || { ffmpeg_ -i src/"$src" \ -map 0:a -c:a libopus -b:a 64k -application voip \ wip/fav/"$base".opus mv_ {wip,tmp}/fav/"$base".opus } } test fav/"$base".360p.av1.webm -nt src/"$src" || { need_scale need_opus # See https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/CommonQuestions.md # See https://gist.github.com/BlueSwordM/86dfcb6ab38a93a524472a0cbe4c4100 # -preset Range from 0 to 13, with higher numbers providing a higher encoding speed; # -crf Range is 0-63, with the default being 50. # Lower values correspond to higher quality and greater file size # yuv420p # yuv420p10le can represent more shades of grey and colors # and is less prone to certain artifacts, such as color # banding and loss of detail in low luma areas # cost in terms of resulting file size (~5%), # can also be more compute-intensive than 8-bit in some decoders # tune=0 subjective mode often results in an image with greater # sharpness and is intended to produce a result that appears to humans # to be of high quality (as opposed to doing well on basic objective measures, # such as PSNR) # scd=1 biases the encoders' scene detection to insert more intra refreshes if needed, # and reduce temporal dependencies around scene-changes. # scm=0 screen content mode decision making. 0 is best for live-action. ffmpeg_ -i src/"$src" -i tmp/fav/"$base".opus \ -map 0:v \ -filter:v format=yuv420p,scale="$scale" \ -c:v libsvtav1 -preset 10 -crf 35 \ -svtav1-params keyint=30s:tune=0:enable-overlays=1:scd=1:scm=0:fast-decode=1:tile-columns=2 \ -map 1:a -c:a copy \ wip/fav/"$base".360p.av1.webm mv_ {wip/,}fav/"$base".360p.av1.webm } dst="$name".360p.av1.webm ln -fs -t fav/"$month"/by-uuid/"$uuid" ../../"$dst" #test fav/"$base".360p.vp9.webm -nt src/"$src" || { # need_scale # need_opus # # See https://developers.google.com/media/vp9/settings/vod#recommended_settings # # See https://gist.github.com/mrintrepide/3033c35ee9557e66cff7806f48dbd339 # set -- \ # -filter:v format=yuv420p10le,scale="$scale" \ # -c:v libvpx-vp9 -quality good -crf 20 \ # -minrate 138k -b:v 276k -maxrate 400k \ # -cpu-used 4 -static-thresh 0 -tile-columns 0 -tile-rows 0 -frame-parallel 0 \ # -row-mt 1 -aq-mode 0 -auto-alt-ref 6 -lag-in-frames 25 -enable-tpl 1 # test -e tmp/fav/"$base".360p.vp9.webm-0.log || { # # FIXME: how to disable h264 debug log? # ffmpeg 2>/dev/null -i src/"$src" \ # -map 0:v "$@" -pass 1 -passlogfile wip/fav/"$base".360p.vp9.webm \ # -f null \ # /dev/null # mv_ -t tmp/fav/"${base%/*}" wip/fav/"$base".360p.vp9.webm-*.log # } # ffmpeg -i src/"$src" -i tmp/fav/"$base".opus \ # -map 0:v "$@" -pass 2 -passlogfile tmp/fav/"$base".360p.vp9.webm \ # -map 1:a -c:a copy \ # wip/fav/"$base".360p.vp9.webm # mkdir -p fav/"$month"/by-uuid/"$uuid" # mv_ wip/fav/"$base".360p.vp9.webm fav/"$base".360p.vp9.webm # rm -fv tmp/fav/"$base".360p.vp9.webm-*.log #} #test fav/"$base".mp4 -nt src/"$src" || { # ffmpeg -i src/"$src" \ # -filter:v format=yuv420p \ # -c:v libx264 -preset medium -crf 22 \ # -tune zerolatency \ # -maxrate 1M -bufsize 2M \ # -movflags use_metadata_tags +faststart \ # -c:a libopus -base:a 64k -application voip \ # -filter:v "scale=iw/2:ih/2" \ # wip/fav/"$base".mp4 # mv_ wip/fav/"$base".mp4 fav/"$base".mp4 #} # class='orient$orient' echo "" esac touch -a src/"$base".txt printf %s "" sed -e '$q;s/$/
    \n/' src/"$base".txt echo "
    " echo "
    " echo "" echo " $creationDate" echo "" echo "" echo " @" echo " #" echo "" echo "
  • " echo done cat <
EOF } >tmp/fav/"$month"/index.html mv_ -f {tmp/,}fav/"$month"/index.html echo >&2 "$dir: removing any deleted favorites" (cd fav; find -L "$month" -mindepth 1 -not -path '*/by-uuid/*' -type f -not -name index.html) | while read -r fav; do base=${fav%.fav.*} name=${base#*/} unset hasSrc for src in src/"$base".fav.*; do hasSrc=set; done test "${hasSrc:+set}" || { echo >&2 "$dir: removing $year/fav/$fav" rm -f fav/"$fav" rm -fv fav/"$month"/by-uuid/*/"${name%.*}".* } done rmdir -p 2>/dev/null fav/"$month"/by-uuid/*/ || true rmdir -p 2>/dev/null wip/fav/* || true popd >/dev/null done