#!/usr/bin/env bash
# Records an App Store preview video that shows the app IN USE: a UI test (PhysarumUITests,
# PhysarumPreview scheme) taps through editor → randomize → library → capture while simctl
# records the simulator. The raw recording is trimmed (drop the build/launch settle) and
# cover-scaled to Apple's exact accepted preview size. Output kept separate from the existing
# preview.mp4 until reviewed: docs/appstore/app-previews/<device>/preview-ui.mp4
#
# DEFAULT trims TRIM=6 s off the front (build + app launch). After reviewing the result, adjust
# TRIM so the footage starts right as the pattern is alive. Usage: [TRIM=6] scripts/shoot-appstore-ui-video.sh [iphone-6.9 ipad-13]
# Requires: Xcode + simulators + ffmpeg. Run with NO other xcodebuild active.
set -euo pipefail
cd "$(dirname "$0")/.."
SCHEME="PhysarumPreview"
PROJ="apple/PhysarumApp/Physarum.xcodeproj"
PREV="docs/appstore/app-previews"
SCRATCH="${TMPDIR:-/tmp}"
# TRIM drops the build/launch settle off the front (the app becomes visible ~11s in). SPEED
# gently compresses the ~35s interaction window into Apple's <=30s limit (1.27x is barely
# perceptible). Re-calibrate both by inspecting the raw .mov if the test timing changes.
TRIM="${TRIM:-11}"
SPEED="${SPEED:-1.27}"
command -v ffmpeg >/dev/null || { echo "error: ffmpeg not found (brew install ffmpeg)" >&2; exit 1; }
( cd apple/PhysarumApp && xcodegen generate )
record_device() {
local sim="$1" dir="$2" pvW="$3" pvH="$4" udid dd raw rec
echo "==> $dir"
udid=$(xcrun simctl list devices available | grep -E "$sim" | grep -oiE '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}' | head -1 || true)
[ -n "$udid" ] || { echo "skip: no sim /$sim/"; return 0; }
xcrun simctl boot "$udid" 2>/dev/null || true
xcrun simctl bootstatus "$udid" -b
dd="$SCRATCH/physarum-uivideo-$dir"
echo " build-for-testing…"
xcodebuild -project "$PROJ" -scheme "$SCHEME" -destination "id=$udid" -derivedDataPath "$dd" build-for-testing >/dev/null 2>&1
raw="$SCRATCH/physarum-uivideo-raw-$dir.mov"
rm -f "$raw"
echo " recording while the UI test drives the app…"
xcrun simctl io "$udid" recordVideo --codec=h264 --force "$raw" &
rec=$!
sleep 2 # let the recorder actually start before the test runner launches
local t0 t1
t0=$(date +%s)
xcodebuild -project "$PROJ" -scheme "$SCHEME" -destination "id=$udid" -derivedDataPath "$dd" test-without-building 2>&1 \
| grep -iE "test case .* (passed|failed)|with [0-9]+ failure|error:" || true
t1=$(date +%s)
echo " ui test ran for $((t1 - t0))s"
kill -INT "$rec" 2>/dev/null || true
wait "$rec" 2>/dev/null || true
# Wait for simctl to finish flushing the .mov to disk before ffmpeg reads it (the cause of the
# earlier 1.7s clip: ffmpeg ran while the file was still tiny). Poll until the size settles.
local prev=-1 cur=0 i
for i in $(seq 1 25); do
cur=$(stat -f%z "$raw" 2>/dev/null || echo 0)
[ "$cur" = "$prev" ] && [ "$cur" -gt 100000 ] && break
prev=$cur; sleep 1
done
echo " raw recording: $(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1 "$raw" 2>/dev/null)s, $(( cur / 1024 ))KB"
mkdir -p "$PREV/$dir"
echo " transcoding ${pvW}x${pvH} (trim ${TRIM}s front, ${SPEED}x speed)…"
local silent="$SCRATCH/physarum-uivideo-silent-$dir.mp4"
ffmpeg -y -loglevel error -ss "$TRIM" -i "$raw" \
-vf "setpts=PTS/${SPEED},scale=${pvW}:${pvH}:force_original_aspect_ratio=increase,crop=${pvW}:${pvH},fps=30" \
-t 30 -c:v libx264 -profile:v high -pix_fmt yuv420p -movflags +faststart -an "$silent"
# App Store Connect rejects a preview with NO audio track ("unsupported or corrupt audio
# content"). Mux in a silent AAC stereo 48kHz track — video copied through, lossless.
ffmpeg -y -loglevel error -i "$silent" -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 \
-c:v copy -c:a aac -b:a 256k -ar 48000 -shortest -movflags +faststart "$PREV/$dir/preview-ui.mp4"
rm -f "$silent"
echo " wrote $PREV/$dir/preview-ui.mp4"
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,codec_name -show_entries format=duration \
-of default=noprint_wrappers=1 "$PREV/$dir/preview-ui.mp4" | sed 's/^/ /'
}
for d in ${@:-iphone-6.9}; do
case "$d" in
iphone-6.9) record_device "iPhone 1[5-9] Pro Max" "iphone-6.9" 886 1920 ;;
ipad-13) record_device "iPad Pro 13-inch" "ipad-13" 1200 1600 ;;
*) echo "unknown device: $d" >&2 ;;
esac
done
echo "Done. Review $PREV/*/preview-ui.mp4 (then rename to preview.mp4 to use, or adjust TRIM and re-run)."