site/tools/shoot.sh (4690B)
1 #!/usr/bin/env bash
2 # Capture the ommp screenshots the landing page uses.
3 #
4 # Runs the real binary in a real terminal: album art only renders as an image
5 # under a graphics protocol, and Ghostty is the only installed terminal that
6 # speaks one. Anything else would show the block-character fallback.
7 #
8 # One font size for every shot. 14 is the density the program actually runs at
9 # here: everything fits, including all thirteen fields of the info panel with
10 # the sample rate and bit depth. Shooting the modals larger made the app look
11 # stretched next to the wide shot, which is worse than a modal with some room
12 # inside it.
13 #
14 # The window is fullscreen for about fifteen seconds and then closes. Your
15 # player settings and your default sink's mute state are backed up and
16 # restored — the app rewrites the former on exit, and this script changes both.
17 set -euo pipefail
18
19 export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/1000}
20 export WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-wayland-1}
21
22 HERE=$(cd "$(dirname "$0")" && pwd)
23 RAW=${RAW:-$HOME/.cache/ommp-shoot/shots}
24 OUT=${OUT:-$HERE/../static/img}
25 OMMP=${OMMP:-$HERE/../../target/release/ommp}
26 MONITOR=${MONITOR:-DP-1}
27 STATE="$HOME/.config/ommp/state.json"
28
29 mkdir -p "$RAW" "$OUT"
30 BAK="$RAW/state.json.bak"
31 [ -f "$STATE" ] && [ ! -f "$BAK" ] && cp -a "$STATE" "$BAK"
32
33 SINK=$(pactl get-default-sink)
34 SINK_WAS_MUTED=$(pactl get-sink-mute "$SINK" | grep -o 'yes\|no')
35
36 cleanup() {
37 pkill -x ommp 2>/dev/null || true
38 sleep 0.5
39 [ -f "$BAK" ] && cp -a "$BAK" "$STATE"
40 [ "$SINK_WAS_MUTED" = yes ] || pactl set-sink-mute "$SINK" 0
41 }
42 trap cleanup EXIT
43
44 # --background matches the logo's #0A051E so padding is not a different black.
45 # --palette pins the two ANSI colours the app draws selections and the progress
46 # bar with, so the shot matches the page's palette rather than whatever theme
47 # happens to be configured.
48 launch() {
49 ghostty \
50 --font-family="JetBrainsMono Nerd Font" \
51 --font-size="$1" \
52 --background=0A051E \
53 --foreground=E8E6F0 \
54 --palette=6=#00ffff --palette=14=#00ffff \
55 --palette=5=#c850ff --palette=13=#c850ff \
56 --fullscreen=true \
57 --window-decoration=none \
58 --window-padding-x=16 --window-padding-y=12 \
59 --confirm-close-surface=false \
60 -e "$OMMP" >/dev/null 2>&1 &
61 # The splash runs a fixed two seconds; wait it out plus a beat for the draw.
62 sleep 5
63 }
64
65 stop() { pkill -x ommp 2>/dev/null || true; sleep 1; }
66
67 # ── The wide shot ─────────────────────────────────────────────────────────
68 # Playback has to be running for the status bar to read Playing over a partly
69 # filled progress bar. rodio does not create its sink-input until that moment,
70 # so there is no per-stream target to mute in advance — mute the whole sink
71 # across the shot instead and put it back afterwards.
72 python3 "$HERE/prep-state.py" "$STATE"
73 pactl set-sink-mute "$SINK" 1
74 launch 14
75 wtype " "
76 sleep 9
77 grim -o "$MONITOR" "$RAW/01-main.png"
78 stop
79 pactl set-sink-mute "$SINK" 0
80
81 # ── The modals ────────────────────────────────────────────────────────────
82 # Playing here too: the status bar shows through beside every modal, and a shot
83 # that reads Stopped next to two that read Playing looks like a mistake.
84 python3 "$HERE/prep-state.py" "$STATE"
85 pactl set-sink-mute "$SINK" 1
86 launch 14
87 wtype " "
88 sleep 8
89 wtype -M ctrl -k h -m ctrl; sleep 1.2; grim -o "$MONITOR" "$RAW/02-help.png"
90 wtype -k Escape; sleep 0.5
91 wtype -M ctrl -k s -m ctrl; sleep 0.8; wtype -d 40 "yorushika"; sleep 1.2
92 grim -o "$MONITOR" "$RAW/03-search.png"
93 stop
94 pactl set-sink-mute "$SINK" 0
95
96 # ── Crop and encode ───────────────────────────────────────────────────────
97 # Kept at the captured 3840-wide scale rather than downsampled: the pixel grid
98 # of terminal text is already sharp, and resampling it both blurs the glyphs
99 # and *inflates* the PNG by inventing intermediate colours.
100 #
101 # Whole frames, not crops: the page shows each screenshot beside the copy that
102 # explains it, at a size where the full window still reads.
103 for n in 01-main 02-help 03-search; do
104 magick "$RAW/$n.png" -strip "$RAW/$n-stripped.png"
105 avifenc -q 84 -s 0 "$RAW/$n-stripped.png" "$OUT/$n.avif" >/dev/null
106 rm -f "$RAW/$n-stripped.png"
107 done
108
109 for f in "$OUT"/0*.avif; do
110 printf " %-16s %-11s %s\n" "$(basename "$f")" \
111 "$(magick identify -format '%wx%h' "$f")" "$(du -h "$f" | cut -f1)"
112 done