Skip to content

Commit 3f9b8cd

Browse files
committed
chore: scratch proof workflow for custom clip speeds
1 parent 36599af commit 3f9b8cd

5 files changed

Lines changed: 225 additions & 0 deletions

File tree

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
name: Custom speed proof (scratch)
2+
3+
on:
4+
push:
5+
branches:
6+
- scratch/custom-speed-proof-ci
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
proof:
13+
runs-on: blacksmith-6vcpu-macos-15
14+
timeout-minutes: 170
15+
env:
16+
CARGO_PROFILE_DEV_DEBUG: "0"
17+
TARGET: aarch64-apple-darwin
18+
steps:
19+
- uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Stage tools
24+
run: |
25+
mkdir -p "$RUNNER_TEMP/tools" "$RUNNER_TEMP/proof"
26+
cp proof/* "$RUNNER_TEMP/tools/"
27+
swiftc -O -o "$RUNNER_TEMP/tools/click" proof/click.swift
28+
swiftc -O -o "$RUNNER_TEMP/tools/winid" proof/winid.swift
29+
30+
- uses: dtolnay/rust-toolchain@1.95.0
31+
with:
32+
targets: aarch64-apple-darwin
33+
34+
- uses: ./.github/actions/setup-rust-cache
35+
with:
36+
target: aarch64-apple-darwin
37+
prefix-key: v1-rust-gpui
38+
save-cache: "false"
39+
workspaces: apps/desktop-gpui -> target
40+
41+
- name: Run setup
42+
run: node scripts/setup.js
43+
env:
44+
RUST_TARGET_TRIPLE: aarch64-apple-darwin
45+
46+
- name: Prepare GPUI dependency
47+
run: |
48+
node scripts/sync-desktop-versions.mjs
49+
./scripts/prepare-gpui-dependency.sh
50+
51+
- name: Build app (branch)
52+
run: |
53+
cargo +1.95.0 build --manifest-path apps/desktop-gpui/Cargo.toml --locked --target $TARGET --bin cap-gpui
54+
for f in apps/desktop-gpui/target/$TARGET/debug/Frameworks apps/desktop-gpui/target/$TARGET/Frameworks; do
55+
mkdir -p $f
56+
ln -sfn "$GITHUB_WORKSPACE/target/Frameworks/Spacedrive.framework" $f/Spacedrive.framework
57+
done
58+
59+
- name: Build CLI (branch)
60+
run: |
61+
cargo build -p cap --bin cap
62+
cp target/debug/cap "$RUNNER_TEMP/cap-branch"
63+
64+
- name: Verify cases at 1.2x and the preset speeds (fixture + oracle)
65+
continue-on-error: true
66+
run: |
67+
CAP_VERIFY_FIXTURE_DIR="$RUNNER_TEMP/fx" CAP_VERIFY_TIER=fast \
68+
CAP_VERIFY_REPORT_DIR="$RUNNER_TEMP/proof/verify" \
69+
CAP_VERIFY_CASE='audio/*/speed-custom-all/*,audio/*/speeds-match/*,audio/*/speeds-muted/*' \
70+
cargo test -p cap-verify --test audio export_mp4 -- --nocapture 2>&1 | tee "$RUNNER_TEMP/proof/verify.log"
71+
72+
- name: Build CLI (main)
73+
run: |
74+
branch=$(git rev-parse HEAD)
75+
git checkout --detach origin/main
76+
cargo build -p cap --bin cap
77+
cp target/debug/cap "$RUNNER_TEMP/cap-main"
78+
git checkout --detach "$branch"
79+
80+
- name: Exports
81+
run: bash "$RUNNER_TEMP/tools/exports.sh"
82+
83+
- name: Permissions
84+
run: |
85+
app="$GITHUB_WORKSPACE/apps/desktop-gpui/target/$TARGET/debug/cap-gpui"
86+
db="/Library/Application Support/com.apple.TCC/TCC.db"
87+
now=$(date +%s)
88+
for client in "$app" /bin/bash /bin/zsh /usr/libexec/sshd-keygen-wrapper /usr/sbin/sshd /usr/bin/osascript \
89+
/Users/runner/bin/Runner.Worker /Users/runner/bin/Runner.Listener "$RUNNER_TEMP/tools/click"; do
90+
for service in kTCCServiceScreenCapture kTCCServiceAccessibility kTCCServicePostEvent; do
91+
sudo sqlite3 "$db" "INSERT OR REPLACE INTO access (service,client,client_type,auth_value,auth_reason,auth_version,indirect_object_identifier,flags,last_modified) VALUES ('$service','$client',1,2,4,1,'UNUSED',0,$now);"
92+
done
93+
done
94+
sudo killall tccd || true
95+
sleep 2
96+
97+
- name: Launch editor with the clip menu open
98+
run: |
99+
app="$GITHUB_WORKSPACE/apps/desktop-gpui/target/$TARGET/debug/cap-gpui"
100+
data="$RUNNER_TEMP/appdata"
101+
mkdir -p "$data/recordings" "$data/logs"
102+
printf '%s' '{"general_settings":{"hasCompletedOnboarding":true,"hasCompletedStartup":true,"enableNotifications":false,"enableTelemetry":false,"recordingCountdown":0}}' > "$data/store"
103+
caffeinate -u -t 5
104+
CAP_GPUI_APP_DATA_DIR="$data" CAP_GPUI_RECORDINGS_DIR="$data/recordings" CAP_GPUI_LOG_DIR="$data/logs" \
105+
CAP_GPUI_NO_TRAY=1 CAP_GPUI_MUTE_AUDIO=1 RUST_LOG=cap_gpui=info \
106+
CAP_GPUI_AUTO_EDITOR="$RUNNER_TEMP/gui.cap" CAP_GPUI_AUTO_CLIP_MENU=0:split \
107+
nohup "$app" > "$RUNNER_TEMP/proof/app.log" 2>&1 < /dev/null &
108+
sleep 40
109+
"$RUNNER_TEMP/tools/winid" cap-gpui | tee "$RUNNER_TEMP/proof/windows.txt"
110+
screencapture -x "$RUNNER_TEMP/proof/screen-01-menu.png" || true
111+
grep -E "clip menu opened|clip-speed|error" "$RUNNER_TEMP/proof/app.log" | head -20 || true
112+
113+
- name: Command loop
114+
env:
115+
GH_TOKEN: ${{ github.token }}
116+
run: |
117+
(caffeinate -dimsu -t 7200 &)
118+
remote="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
119+
last=""
120+
for i in $(seq 1 400); do
121+
sha=$(git ls-remote "$remote" refs/heads/scratch/csp-cmd | cut -f1)
122+
if [ -n "$sha" ] && [ "$sha" != "$last" ]; then
123+
last=$sha
124+
git fetch -q "$remote" refs/heads/scratch/csp-cmd
125+
git show FETCH_HEAD:cmd.sh > "$RUNNER_TEMP/cmd.sh"
126+
grep -q '^# exit-loop' "$RUNNER_TEMP/cmd.sh" && break
127+
out="$RUNNER_TEMP/out-$i"
128+
mkdir -p "$out"
129+
OUT="$out" bash "$RUNNER_TEMP/cmd.sh" > "$out/cmd.log" 2>&1
130+
cp "$RUNNER_TEMP/proof/exports.txt" "$RUNNER_TEMP/proof/verify.log" "$out/" 2>/dev/null || true
131+
(cd "$out" && git init -q && git add -A && git -c user.name=ci -c user.email=ci@example.invalid commit -qm "out $i $sha" && git push -qf "$remote" HEAD:refs/heads/scratch/csp-out)
132+
echo "ran $sha -> out-$i"
133+
fi
134+
sleep 15
135+
done
136+
137+
- name: Upload proof
138+
if: always()
139+
uses: actions/upload-artifact@v4
140+
with:
141+
name: custom-speed-proof
142+
path: ${{ runner.temp }}/proof
143+
retention-days: 7

‎proof/click.swift‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import CoreGraphics
2+
import Foundation
3+
4+
// click.swift x y : moves the pointer to (x, y) in global points and left-clicks.
5+
let a = CommandLine.arguments
6+
let p = CGPoint(x: Double(a[1])!, y: Double(a[2])!)
7+
let src = CGEventSource(stateID: .hidSystemState)
8+
CGEvent(mouseEventSource: src, mouseType: .mouseMoved, mouseCursorPosition: p, mouseButton: .left)?.post(tap: .cghidEventTap)
9+
usleep(150_000)
10+
CGEvent(mouseEventSource: src, mouseType: .leftMouseDown, mouseCursorPosition: p, mouseButton: .left)?.post(tap: .cghidEventTap)
11+
usleep(60_000)
12+
CGEvent(mouseEventSource: src, mouseType: .leftMouseUp, mouseCursorPosition: p, mouseButton: .left)?.post(tap: .cghidEventTap)

‎proof/exports.sh‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env bash
2+
set -uo pipefail
3+
out="$RUNNER_TEMP/proof"
4+
mkdir -p "$out"
5+
fx=$(ls -d "$RUNNER_TEMP"/fx/audio-mix-8s-*.cap | grep -v '\.tmp\.' | head -1)
6+
echo "fixture $fx"
7+
command -v ffprobe >/dev/null || brew install ffmpeg >/dev/null
8+
mk() {
9+
cp -R "$fx" "$RUNNER_TEMP/$1.cap"
10+
python3 "$RUNNER_TEMP/tools/set_timeline.py" "$RUNNER_TEMP/$1.cap" "$2"
11+
}
12+
mk presets '{"segments":[[0.3,2.0,1.0,null],[2.0,4.0,2.0,"maintainPitch"],[4.0,5.0,0.5,null],[5.0,7.0,1.5,"matchSpeed"],[7.0,7.8,4.0,"maintainPitch"]]}'
13+
mk custom '{"segments":[[0.3,3.4,1.2,"matchSpeed"],[3.4,5.0,1.2,"matchSpeed"],[5.5,7.8,1.2,"matchSpeed"]]}'
14+
mk custompitch '{"segments":[[0.3,3.4,1.2,"maintainPitch"],[3.4,5.0,1.2,"maintainPitch"],[5.5,7.8,1.2,"maintainPitch"]]}'
15+
mk gui '{"segments":[[0.0,7.9,1.0,null]],"zooms":[{"start":5.0,"end":6.5,"amount":1.5,"mode":"auto"}]}'
16+
report="$out/exports.txt"
17+
: > "$report"
18+
for p in presets custom custompitch; do
19+
for bin in main branch; do
20+
f="$out/$p-$bin.mp4"
21+
"$RUNNER_TEMP/cap-$bin" export "$RUNNER_TEMP/$p.cap" -o "$f" --fps 30 > "$out/$p-$bin.log" 2>&1 || { echo "EXPORT FAILED $p $bin"; tail -20 "$out/$p-$bin.log"; }
22+
[ -f "$f" ] || continue
23+
{
24+
echo "== $p $bin"
25+
ffprobe -v error -show_entries format=duration:stream=codec_type,duration,nb_frames -of compact "$f"
26+
ffmpeg -v error -i "$f" -map 0:v -f md5 - | sed 's/^/video /'
27+
ffmpeg -v error -i "$f" -map 0:a -f md5 - | sed 's/^/audio /'
28+
shasum -a 256 "$f" | cut -d' ' -f1 | sed 's/^/file /'
29+
} >> "$report"
30+
done
31+
if [ -f "$out/$p-main.mp4" ] && [ -f "$out/$p-branch.mp4" ]; then
32+
if cmp -s "$out/$p-main.mp4" "$out/$p-branch.mp4"; then
33+
echo "== $p main vs branch: BYTE-IDENTICAL" >> "$report"
34+
else
35+
echo "== $p main vs branch: bytes differ" >> "$report"
36+
fi
37+
fi
38+
done
39+
cat "$report"

‎proof/set_timeline.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import json
2+
import sys
3+
4+
project, spec = sys.argv[1], json.loads(sys.argv[2])
5+
path = f"{project}/project-config.json"
6+
config = json.load(open(path))
7+
timeline = config.setdefault("timeline", {})
8+
segments = []
9+
for start, end, timescale, mode in spec["segments"]:
10+
segment = {"recordingSegment": 0, "start": start, "end": end, "timescale": timescale}
11+
if mode:
12+
segment["speedAudioMode"] = mode
13+
segments.append(segment)
14+
timeline["segments"] = segments
15+
timeline["zoomSegments"] = spec.get("zooms", [])
16+
json.dump(config, open(path, "w"), indent=2)

‎proof/winid.swift‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import CoreGraphics
2+
import Foundation
3+
4+
// Prints "<id> <x> <y> <w> <h> <title>" for on-screen windows owned by a process whose name contains argv[1].
5+
let needle = CommandLine.arguments.dropFirst().first ?? "cap-gpui"
6+
let list = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: Any]] ?? []
7+
for w in list {
8+
let owner = w[kCGWindowOwnerName as String] as? String ?? ""
9+
guard owner.contains(needle) else { continue }
10+
let id = w[kCGWindowNumber as String] as? Int ?? 0
11+
let b = w[kCGWindowBounds as String] as? [String: Double] ?? [:]
12+
let name = w[kCGWindowName as String] as? String ?? ""
13+
let layer = w[kCGWindowLayer as String] as? Int ?? 0
14+
print(id, Int(b["X"] ?? 0), Int(b["Y"] ?? 0), Int(b["Width"] ?? 0), Int(b["Height"] ?? 0), layer, name)
15+
}

0 commit comments

Comments
 (0)