diff --git a/doc/source/_static/tutorials/clipboard.mp4 b/doc/source/_static/tutorials/clipboard.mp4 index de2a783..33ca66d 100644 Binary files a/doc/source/_static/tutorials/clipboard.mp4 and b/doc/source/_static/tutorials/clipboard.mp4 differ diff --git a/doc/source/_static/tutorials/connect_by_drag.mp4 b/doc/source/_static/tutorials/connect_by_drag.mp4 index 405a87e..42c6160 100644 Binary files a/doc/source/_static/tutorials/connect_by_drag.mp4 and b/doc/source/_static/tutorials/connect_by_drag.mp4 differ diff --git a/doc/source/_static/tutorials/context_menus.mp4 b/doc/source/_static/tutorials/context_menus.mp4 index e1b5e4c..90bae21 100644 Binary files a/doc/source/_static/tutorials/context_menus.mp4 and b/doc/source/_static/tutorials/context_menus.mp4 differ diff --git a/doc/source/_static/tutorials/create_by_drag.mp4 b/doc/source/_static/tutorials/create_by_drag.mp4 index 263c176..4ca1835 100644 Binary files a/doc/source/_static/tutorials/create_by_drag.mp4 and b/doc/source/_static/tutorials/create_by_drag.mp4 differ diff --git a/doc/source/_static/tutorials/delete_and_select.mp4 b/doc/source/_static/tutorials/delete_and_select.mp4 index 00ace50..be5451b 100644 Binary files a/doc/source/_static/tutorials/delete_and_select.mp4 and b/doc/source/_static/tutorials/delete_and_select.mp4 differ diff --git a/doc/source/_static/tutorials/first_graph.mp4 b/doc/source/_static/tutorials/first_graph.mp4 index e9213f9..0eeee0b 100644 Binary files a/doc/source/_static/tutorials/first_graph.mp4 and b/doc/source/_static/tutorials/first_graph.mp4 differ diff --git a/doc/source/_static/tutorials/groups.mp4 b/doc/source/_static/tutorials/groups.mp4 index 59bf8d6..94bbd69 100644 Binary files a/doc/source/_static/tutorials/groups.mp4 and b/doc/source/_static/tutorials/groups.mp4 differ diff --git a/doc/source/_static/tutorials/navigation.mp4 b/doc/source/_static/tutorials/navigation.mp4 index 70e2493..487bd2d 100644 Binary files a/doc/source/_static/tutorials/navigation.mp4 and b/doc/source/_static/tutorials/navigation.mp4 differ diff --git a/doc/source/_static/tutorials/styling.mp4 b/doc/source/_static/tutorials/styling.mp4 index 9e032c7..75b23bb 100644 Binary files a/doc/source/_static/tutorials/styling.mp4 and b/doc/source/_static/tutorials/styling.mp4 differ diff --git a/tests/integration/record_clipboard.das b/tests/integration/record_clipboard.das index 3f8d7eb..cc8f3a5 100644 --- a/tests/integration/record_clipboard.das +++ b/tests/integration/record_clipboard.das @@ -37,10 +37,10 @@ def chord_under_voice(app : ImguiApp; dwell : uint; key : string) { // Fire the Ctrl chord partway into the already-posted voice line, so the keycap + Ctrl // pill pop while the line names it, then hold out the rest of the dwell. let lead = dwell > 1200u ? 700u : dwell / 3u - sleep(lead) + hold_content(app, lead) post_command(app, "imgui_key_chord", JV((mods = ["Ctrl"], key = key))) wait_for_key_idle(app, 4.0f) - sleep(dwell > lead ? dwell - lead : 0u) + hold_content(app, dwell > lead ? dwell - lead : 0u) } [export] diff --git a/tests/integration/record_connect_by_drag.das b/tests/integration/record_connect_by_drag.das index 5ad0db6..119872f 100644 --- a/tests/integration/record_connect_by_drag.das +++ b/tests/integration/record_connect_by_drag.das @@ -77,7 +77,7 @@ def main { let interval = dwell / uint(pulses) for (_i in range(pulses)) { ne_flow(s, 100) - sleep(interval) + hold_content(app, interval) } } } diff --git a/tests/integration/record_context_menus.das b/tests/integration/record_context_menus.das index 492c0ed..48e4e98 100644 --- a/tests/integration/record_context_menus.das +++ b/tests/integration/record_context_menus.das @@ -62,7 +62,7 @@ def main { let interval = dwell / uint(pulses) for (_i in range(pulses)) { ne_flow(s, 100) - sleep(interval) + hold_content(app, interval) } // ---- Beat 2: right-click A -> node menu ---- diff --git a/tests/integration/record_create_by_drag.das b/tests/integration/record_create_by_drag.das index dd0f37c..a4ee4ea 100644 --- a/tests/integration/record_create_by_drag.das +++ b/tests/integration/record_create_by_drag.das @@ -88,7 +88,7 @@ def main { let interval = dwell / uint(pulses) for (_i in range(pulses)) { ne_flow(s, 100) - sleep(interval) + hold_content(app, interval) } } } diff --git a/tests/integration/record_delete_and_select.das b/tests/integration/record_delete_and_select.das index 016c057..4bb486e 100644 --- a/tests/integration/record_delete_and_select.das +++ b/tests/integration/record_delete_and_select.das @@ -61,7 +61,7 @@ def main { for (_i in range(pulses)) { ne_flow(s, 100) ne_flow(s, 101) - sleep(interval) + hold_content(app, interval) } // ---- Beat 3: select B (title-row click) ---- @@ -82,13 +82,13 @@ def main { let dwell2 = say_begin(app, "Delete -> B + both links cascade", "{CANVAS}/node_1", [voice = "Press Delete and the editor raises B through begin_delete; accepting it drops B and cascades the two links on its pins. A and C remain, disconnected."]) let lead = dwell2 > 1200u ? 700u : dwell2 / 3u - sleep(lead) + hold_content(app, lead) post_command(app, "imgui_key_press", JV((key = del))) post_command(app, "imgui_key_release", JV((key = del))) // Hard self-verify: B is gone AND both links cascaded out, or abort at teardown. record_check_rendered(app, T_B, false) record_check_rendered(app, T_L100, false) record_check_rendered(app, T_L101, false) - sleep(dwell2 > lead ? dwell2 - lead : 0u) + hold_content(app, dwell2 > lead ? dwell2 - lead : 0u) } } diff --git a/tests/integration/record_first_graph.das b/tests/integration/record_first_graph.das index ed2aa57..16a99c2 100644 --- a/tests/integration/record_first_graph.das +++ b/tests/integration/record_first_graph.das @@ -91,7 +91,7 @@ def main { let interval = dwell / uint(pulses) for (_i in range(pulses)) { ne_flow(s, 100) - sleep(interval) + hold_content(app, interval) } } } diff --git a/tests/integration/record_styling.das b/tests/integration/record_styling.das index 911613a..3f16e35 100644 --- a/tests/integration/record_styling.das +++ b/tests/integration/record_styling.das @@ -96,7 +96,7 @@ def main { let interval = dwell / uint(pulses) for (_i in range(pulses)) { ne_flow(s, 100) - sleep(interval) + hold_content(app, interval) } } } diff --git a/utils/rerecord.ps1 b/utils/rerecord.ps1 new file mode 100644 index 0000000..eb03596 --- /dev/null +++ b/utils/rerecord.ps1 @@ -0,0 +1,141 @@ +# utils/rerecord.ps1 - clean full re-record of THIS repo's tutorial videos. +# +# Default (no args) re-records the ENTIRE repo in four phases: +# Clean wipe apng/mp4/music/ffmpeg.txt + voiceover/ under doc/source/_static/tutorials +# Prepare per driver: prepare_recording.das -> TTS each say() line via Kokoro (:8880) +# Record per driver: run it -> .apng + voiceover sidecar +# Convert per .apng: convert_recording.das -> mux video + music bed + voiceovers -> .mp4 +# +# Run one repo at a time. Kokoro TTS must be up at :8880 for the Prepare phase. +# The recording infra (prepare/convert) + the imgui module live in dasImgui; -DasImguiRoot +# defaults to the sibling checkout (../dasImgui), else D:/DASPKG/dasImgui. +# Filters: -Only NAME (single driver, NAME = stem without record_/.das), -From NAME (resume), +# -Skip "a,b", -Skip, -DryRun, -StopOnFail. + +param( + [string]$DaslangExe = $(if ($env:DASLANG) { $env:DASLANG } else { "D:/Work/daScript/bin/Release/daslang.exe" }), + [string]$DasRoot = "D:/Work/daScript", # convert: music render + sf2 live under the daslang source tree + [string]$DasImguiRoot= "", # holds prepare/convert + the imgui module; default below + [string]$Voice = "bf_emma", + [switch]$SkipClean, + [switch]$SkipPrepare, + [switch]$SkipRecord, + [switch]$SkipConvert, + [string]$From = "", + [string]$Skip = "", + [string]$Only = "", + [switch]$DryRun, + [switch]$StopOnFail +) + +$ErrorActionPreference = "Continue" +$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path + +# ===== per-repo config (depends on dasImgui; loads both modules) ===== +if (-not $DasImguiRoot) { + $sib = Join-Path $RepoRoot "../dasImgui" + $DasImguiRoot = if (Test-Path $sib) { (Resolve-Path $sib).Path } else { "D:/DASPKG/dasImgui" } +} +$ModuleArgs = @("-load_module", $DasImguiRoot, "-load_module", $RepoRoot) +$AssetRoot = $RepoRoot +# ==================================================================== + +$tutDir = Join-Path $RepoRoot "doc/source/_static/tutorials" +$voDir = Join-Path $tutDir "voiceover" +$prepareScript = Join-Path $DasImguiRoot "utils/prepare_recording.das" +$convertScript = Join-Path $DasImguiRoot "utils/convert_recording.das" +$scanDir = Join-Path $RepoRoot "tests/integration" + +if (-not (Test-Path $DaslangExe)) { Write-Host "FAIL: daslang not found: $DaslangExe" -ForegroundColor Red; exit 2 } +if (-not (Test-Path $prepareScript)) { Write-Host "FAIL: missing $prepareScript (DasImguiRoot=$DasImguiRoot)" -ForegroundColor Red; exit 2 } +if (-not (Test-Path $scanDir)) { Write-Host "FAIL: no $scanDir" -ForegroundColor Red; exit 2 } + +$drivers = Get-ChildItem -Path $scanDir -Filter "record_*.das" | Sort-Object Name +Write-Host "[rerecord] repo: $RepoRoot" +Write-Host "[rerecord] daslang: $DaslangExe" +Write-Host "[rerecord] drivers: $($drivers.Count)" +Write-Host "[rerecord] modules: $($ModuleArgs -join ' ')" +Write-Host "[rerecord] assets: $(if ($AssetRoot) { $AssetRoot } else { '(dasImgui default)' })" + +$skipSet = @{} +if ($Skip) { foreach ($s in $Skip.Split(",")) { $k = $s.Trim(); if ($k) { $skipSet[$k] = $true } } } +$skipUntil = $From -ne "" + +function Sel($name) { + if ($Only -ne "" -and $name -ne $Only) { return $false } + if ($script:skipUntil) { if ($name -eq $From) { $script:skipUntil = $false } else { return $false } } + if ($skipSet.ContainsKey($name)) { return $false } + return $true +} + +# ---- Clean ---- +if (-not $SkipClean) { + Write-Host "`n==== CLEAN ====" -ForegroundColor Cyan + if ($DryRun) { + Write-Host " [dry-run] would remove *.apng *.mp4 *_music.wav *.mp4.ffmpeg.txt and voiceover/ under $tutDir" + } else { + Get-ChildItem -Path (Join-Path $tutDir '*') -Include *.apng,*.mp4,*_music.wav,*.mp4.ffmpeg.txt -File -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue + if (Test-Path $voDir) { Remove-Item -Recurse -Force $voDir -ErrorAction SilentlyContinue } + Write-Host " cleaned $tutDir" + } +} + +$fail = @() + +# ---- Prepare ---- +if (-not $SkipPrepare) { + Write-Host "`n==== PREPARE (TTS) ====" -ForegroundColor Cyan + foreach ($d in $drivers) { + $name = $d.BaseName -replace "^record_", "" + if (-not (Sel $name)) { continue } + $pargs = @($ModuleArgs) + @($prepareScript, "--", "--driver", $d.FullName, "--voice", $Voice) + if ($AssetRoot) { $pargs += @("--asset-root", $AssetRoot) } + if ($DryRun) { Write-Host " [dry-run] $DaslangExe $($pargs -join ' ')"; continue } + Write-Host " prepare $name" -ForegroundColor DarkCyan + & $DaslangExe @pargs + if ($LASTEXITCODE -ne 0) { $fail += "prepare:$name"; Write-Host " FAIL prepare $name" -ForegroundColor Red; if ($StopOnFail) { break } } + } +} + +# ---- Record ---- +if (-not $SkipRecord -and $fail.Count -eq 0) { + Write-Host "`n==== RECORD ====" -ForegroundColor Cyan + foreach ($d in $drivers) { + $name = $d.BaseName -replace "^record_", "" + if (-not (Sel $name)) { continue } + $rargs = @($ModuleArgs) + @($d.FullName) + if ($DryRun) { Write-Host " [dry-run] $DaslangExe $($rargs -join ' ')"; continue } + Write-Host " record $name" -ForegroundColor DarkCyan + $t0 = Get-Date + & $DaslangExe @rargs + $sec = [int]((Get-Date) - $t0).TotalSeconds + if ($LASTEXITCODE -ne 0) { $fail += "record:$name"; Write-Host " FAIL record $name (${sec}s)" -ForegroundColor Red; if ($StopOnFail) { break } } + else { Write-Host " ok (${sec}s)" -ForegroundColor Green } + } +} + +# ---- Convert ---- +if (-not $SkipConvert) { + Write-Host "`n==== CONVERT ====" -ForegroundColor Cyan + $apngs = Get-ChildItem -Path $tutDir -Filter "*.apng" -File -ErrorAction SilentlyContinue | Sort-Object Name + Write-Host " $($apngs.Count) apng(s) to convert" + foreach ($a in $apngs) { + if (-not (Sel $a.BaseName)) { continue } + if ($DryRun) { Write-Host " [dry-run] convert $($a.Name)"; continue } + Write-Host " convert $($a.BaseName)" -ForegroundColor DarkCyan + & $DaslangExe "-load_module" $DasImguiRoot $convertScript "--" "--apng" $a.FullName "--das_root" $DasRoot + if ($LASTEXITCODE -ne 0) { $fail += "convert:$($a.BaseName)"; Write-Host " FAIL convert $($a.BaseName)" -ForegroundColor Red; if ($StopOnFail) { break } } + } +} + +# ---- Summary ---- +Write-Host "`n==== SUMMARY ($RepoRoot) ====" -ForegroundColor Cyan +$mp4s = @(Get-ChildItem -Path $tutDir -Filter "*.mp4" -File -ErrorAction SilentlyContinue) +Write-Host " mp4s produced: $($mp4s.Count)" +if ($fail.Count -gt 0) { + Write-Host " FAILURES ($($fail.Count)):" -ForegroundColor Red + foreach ($f in $fail) { Write-Host " $f" -ForegroundColor Red } + exit 1 +} +Write-Host " all phases clean" -ForegroundColor Green +exit 0