Files
DarkRoom/tools/manual/record.sh
T
dtourolle 7428f6f845 Re-record the manual for 0.17.0, with albums and the new preset sheet
Every scene is recorded again on the 0.17.0 build, because the header
(Export to Exports), the sidebar (Albums) and the develop column had
all moved. The launch pictures showed the typed folder field, and the
export settings "Export to"; the presets picture predated the sections.

New scenes: presets_film scrolls the sheet down through the shipped
sections and applies Ilford HP5 Plus, a look that changes the film and
nothing else; albums exports four New York frames to an album, selects
it to show the originals behind its files, and opens a new album's
sheet before deleting what it made. record.sh now points the profile's
old export folder at DR_HOME/Exports, which the app turns into the
album "Exports" on first open - the one way to have an album without
the portal's dialogue, which Xvfb cannot show. The launch scene signs
out of a remembered folder instead of typing one, so it shows
"Open folder" beside the folder used last.

Not recorded: the server browser's New folder, which needs a Nextcloud
server, and the download screen, which a folder library never reaches
(the original is a local read, over before the first poll). The manual
says so where it describes each. film_reach and duplicates passed;
inference was pinned to the CPU.
2026-09-26 15:15:51 -04:00

157 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Re-record the manual: docs/manual/README.md's pictures, from the app itself.
#
# tools/manual/record.sh LIBRARY [SCENE...] every scene, or those named
# tools/manual/record.sh --changed LIBRARY the scenes whose sources
# changed since their pictures
# tools/manual/record.sh --check the manual and the scenes
# agree (no app, no display)
# tools/manual/record.sh --list the scenes and their pictures
#
# Needs Xvfb, xdotool, ImageMagick's `import`, ffmpeg and optipng. Builds the
# desktop binary with the `automation` feature — the hook scenes.py finds
# controls by name through — unless DR_BIN names one. Starts a private X
# server, opens LIBRARY in a fresh throwaway profile under DR_HOME, runs the
# scenes and writes PNGs and GIFs into docs/manual/media/.
#
# LIBRARY is the author's demo set — seventy face-free frames, the
# twelve-frame panorama from fixtures/pano among them — and the scenes open
# its photographs by file name. Recording changes it (ratings, collections,
# the merged DNG), so DR_LIBRARY_SNAPSHOT, if set, is copied over it first.
#
# Environment: DR_HOME (/var/tmp/dr-manual), DR_DISPLAY (:7), DR_BIN,
# DR_LIBRARY_SNAPSHOT, DR_EXPORT_DIR (DR_HOME/Exports), DR_INFERENCE
# (`cpu`, the default, pins inference to the CPU so a recording does not
# fight a training run for the GPU; `probe` lets the app choose), CARGO
# (cargo; a wrapper taking cargo's arguments works too).
set -euo pipefail
here="$(cd "$(dirname "$0")" && pwd)"
repo="$(cd "$here/../.." && pwd)"
scenes="$here/scenes.py"
mode=record
case "${1:-}" in
--check) exec python3 "$scenes" check ;;
--list) exec python3 "$scenes" list ;;
--changed) mode=changed; shift ;;
""|-h|--help) sed -n '2,/^set -e/p' "$0" | sed '$d; s/^# \{0,1\}//'; exit 0 ;;
esac
library="$(cd "${1:?library folder}" && pwd)"
shift
if [ "$mode" = changed ]; then
python3 "$scenes" changed | tee /dev/stderr > /dev/null
mapfile -t chosen < <(python3 "$scenes" changed | cut -f1)
if [ ${#chosen[@]} -eq 0 ]; then
echo "nothing to re-record: every picture is newer than what it depends on"
exit 0
fi
set -- "${chosen[@]}"
fi
export DR_HOME="${DR_HOME:-/var/tmp/dr-manual}"
export DR_DISPLAY="${DR_DISPLAY:-:7}"
export DR_LIBRARY="$library"
export DR_AUTOMATION="$DR_HOME/automation.sock"
media="$repo/docs/manual/media"
if [ -z "${DR_BIN:-}" ]; then
(cd "$repo" && ${CARGO:-cargo} build --release -p darkroom-desktop --features automation)
DR_BIN="$repo/target/release/darkroom-desktop"
fi
export DR_BIN
if [ -n "${DR_LIBRARY_SNAPSHOT:-}" ]; then
rsync -a --delete "$DR_LIBRARY_SNAPSHOT/" "$library/"
fi
# A fresh profile every time, so a scene sees the library and not whatever
# the last run left in the catalog. The inference cache is kept — it is
# minutes of compiling — and so are the links to the models. The marker is
# what makes the wipe safe: nothing without it is deleted.
profile="$DR_HOME/xdg"
if [ -d "$profile" ] && [ ! -e "$DR_HOME/.manual-profile" ]; then
echo "$profile exists and was not made by record.sh; set DR_HOME elsewhere" >&2
exit 1
fi
mkdir -p "$DR_HOME" && touch "$DR_HOME/.manual-profile"
rm -rf "$profile/config" "$profile/state"
if [ -d "$profile/data/darkroom" ]; then
find "$profile/data/darkroom" -mindepth 1 -maxdepth 1 \
! -name inference ! -name models ! -name runtime -exec rm -rf {} +
fi
mkdir -p "$profile/config/darkroom" "$profile/data/darkroom"
# The models and the runtime are shared with the real profile: a segmenter
# that is not there makes "Find subjects" a picture of nothing.
for d in models runtime; do
src="${XDG_DATA_HOME:-$HOME/.local/share}/darkroom/$d"
[ -e "$src" ] && ln -sfn "$src" "$profile/data/darkroom/$d"
done
# Keep the inference choice the app last made, but on the CPU unless asked.
backend="$profile/data/darkroom/inference/backend.json"
if [ "${DR_INFERENCE:-cpu}" = cpu ] && [ -e "$backend" ]; then
python3 - "$backend" <<'PY'
import json, sys
p = sys.argv[1]
d = json.load(open(p))
d['rung'], d['reason'] = 'Cpu', 'forced for the demo'
json.dump(d, open(p, 'w'), indent=2)
PY
fi
# A folder library, chosen: no launch screen on the way in.
cat > "$profile/config/darkroom/sessions.json" <<JSON
{ "version": 0, "sessions": [ { "backend": "folder", "server": "$library",
"login": "", "user_id": "", "root": "", "root_chosen": true,
"formats": [], "last_scan": null } ] }
JSON
# An export folder set before albums becomes the album "Exports" on first
# open (albums_ui::adopt_old_destination), which is how the albums scene has
# an album without the portal's dialogue. In the throwaway profile's folder,
# outside the library, and the scene deletes what it exported.
export DR_EXPORT_DIR="${DR_EXPORT_DIR:-$DR_HOME/Exports}"
export_dir="$DR_EXPORT_DIR"
mkdir -p "$export_dir"
cat > "$profile/config/darkroom/settings.json" <<JSON
{ "export": { "destination": "$export_dir" } }
JSON
started_x=""
if ! DISPLAY="$DR_DISPLAY" xdpyinfo >/dev/null 2>&1; then
Xvfb "$DR_DISPLAY" -screen 0 1920x1200x24 +extension GLX +render -noreset \
> "$DR_HOME/xvfb.log" 2>&1 &
echo $! > "$DR_HOME/xvfb.pid"
started_x=1
sleep 2
fi
cleanup() {
python3 "$here/drive.py" stop || true
if [ -n "$started_x" ]; then kill "$(cat "$DR_HOME/xvfb.pid")" || true; fi
}
trap cleanup EXIT
stamp="$DR_HOME/.recording-started"
touch "$stamp"
python3 "$here/drive.py" launch
DR_TOOLS="$here" python3 -c '
import os, sys, time
sys.path.insert(0, os.environ["DR_TOOLS"])
import drive as dr
dr.wait_ready()
dr.wait_for("id:grid-scroll", 120)
time.sleep(10) # the first thumbnails, and the scan settling
'
python3 "$scenes" record "$media" "${@:-all}"
# GIFs for the page; the MP4s are working files and are not kept.
for mp4 in "$media"/*.mp4; do
[ -e "$mp4" ] || continue
"$here/gif.sh" "$mp4" "${mp4%.mp4}.gif" 960 10
rm "$mp4"
done
find "$media" -name '*.png' -newer "$stamp" -exec optipng -quiet -o2 {} + || true