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.
157 lines
6.0 KiB
Bash
Executable File
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
|