ommp.git

ommp

~ Oh My Music Player — a standalone terminal music player built with Rust and ratatui
git clone https://github.com/debuman/ommp.git

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