diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 630a9d0a..99a1f30b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,12 +85,15 @@ jobs: } if versions != {version}: raise SystemExit(f"tag/version mismatch: {tag} != {sorted(versions)}") + wix_version = tauri["bundle"]["windows"]["wix"]["version"] + if wix_version != "1.0.0.3": + raise SystemExit(f"unexpected Windows installer version: {wix_version}") notes = Path("docs/releases") / f"{version}.md" if not notes.is_file() or not notes.read_text(encoding="utf-8").strip(): raise SystemExit(f"release notes are missing or empty: {notes}") prerelease = "-" in version.split("+", 1)[0] - if version == "1.0.0-beta.2" and not prerelease: - raise SystemExit("OpenTake 1.0.0-beta.2 must remain a prerelease") + if version == "1.0.0-beta.3" and not prerelease: + raise SystemExit("OpenTake 1.0.0-beta.3 must remain a prerelease") if not prerelease: raise SystemExit("this release workflow publishes prereleases only") @@ -636,6 +639,7 @@ jobs: RELEASE_SHA: ${{ needs.validate.outputs.source_sha }} RELEASE_VERSION: ${{ needs.validate.outputs.version }} NOTES_PATH: ${{ needs.validate.outputs.notes_path }} + PYTHONDONTWRITEBYTECODE: '1' steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: diff --git a/Cargo.lock b/Cargo.lock index 32cbf23e..5382b037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3680,7 +3680,7 @@ dependencies = [ [[package]] name = "opentake-agent" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "anyhow", "async-trait", @@ -3715,7 +3715,7 @@ dependencies = [ [[package]] name = "opentake-core" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "opentake-domain", "opentake-ops", @@ -3729,7 +3729,7 @@ dependencies = [ [[package]] name = "opentake-domain" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "serde", "serde_json", @@ -3737,7 +3737,7 @@ dependencies = [ [[package]] name = "opentake-gen" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "anyhow", "async-trait", @@ -3755,7 +3755,7 @@ dependencies = [ [[package]] name = "opentake-media" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "anyhow", "byteorder", @@ -3793,7 +3793,7 @@ dependencies = [ [[package]] name = "opentake-motion" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "base64 0.22.1", "hex", @@ -3811,7 +3811,7 @@ dependencies = [ [[package]] name = "opentake-ops" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "opentake-domain", "serde_json", @@ -3819,7 +3819,7 @@ dependencies = [ [[package]] name = "opentake-process-tree" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3827,7 +3827,7 @@ dependencies = [ [[package]] name = "opentake-project" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "cap-fs-ext", "cap-std", @@ -3846,7 +3846,7 @@ dependencies = [ [[package]] name = "opentake-render" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "bytemuck", "cosmic-text", @@ -3863,7 +3863,7 @@ dependencies = [ [[package]] name = "opentake-tauri" -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" dependencies = [ "axum", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index aafc48fc..a5d8b32d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ members = [ ] [workspace.package] -version = "1.0.0-beta.2" +version = "1.0.0-beta.3" edition = "2021" license = "GPL-3.0-or-later" repository = "https://github.com/appergb/OpenTake" diff --git a/README.md b/README.md index 7b956fb3..13ceb125 100644 --- a/README.md +++ b/README.md @@ -303,9 +303,9 @@ cd .. cargo tauri dev ``` -> **Current Status**: `1.0.0-beta.2` candidate. The local editing, preview, +> **Current Status**: `1.0.0-beta.3` candidate. The local editing, preview, > persistence, export, Agent, Motion Canvas, and reviewed AI workflow verticals -> are implemented. See the [Beta release notes](docs/releases/1.0.0-beta.2.md) +> are implemented. See the [Beta release notes](docs/releases/1.0.0-beta.3.md) > for validation scope and platform/provider limits. The sibling directory `palmier-pro-upstream/` contains upstream Swift sources for reference during porting. @@ -318,7 +318,8 @@ The sibling directory `palmier-pro-upstream/` contains upstream Swift sources fo |:--|:--|:--| | `0.1.0-dev` | 2026-06 | Phase 0+1: Cargo workspace + Domain models + Edit ops + Tauri scaffold | | `1.0.0-beta.1` | 2026-08-01 | First installable Beta: end-to-end local editor, Agent, Motion and reviewed AI workflows | -| `1.0.0-beta.2` | 2026-08-03 | Hardened Beta: official Codex login, atomic timeline gestures, secure MCP and interaction polish | +| `1.0.0-beta.2` | 2026-08-08 | Hardened Beta: official Codex login, atomic timeline gestures, secure MCP and interaction polish | +| `1.0.0-beta.3` | 2026-08-09 | Playback Beta: app-wide Space transport, native HEVC source preview and release-pipeline hardening | | *(planned)* `1.0.0` | TBD | Phase 10: Full release — CapCut parity + deep Agent integration | 📖 [Full Roadmap](docs/architecture/ROADMAP.md) diff --git a/crates/opentake-motion/src/renderer.rs b/crates/opentake-motion/src/renderer.rs index 15be845d..b2fae88c 100644 --- a/crates/opentake-motion/src/renderer.rs +++ b/crates/opentake-motion/src/renderer.rs @@ -1779,6 +1779,26 @@ mod chromium_backend { width: u32, height: u32, ) -> MotionResult<()> { + // Screencast sizing is based on the compositor surface, so resize + // the browser contents before overriding the logical viewport. + let window = self.command("Browser.getWindowForTarget", json!({}), Some(session))?; + let window_id = window + .get("windowId") + .and_then(Value::as_u64) + .ok_or_else(|| { + MotionError::render_failed(format!( + "Chromium CDP response is missing integer field \"windowId\": {window}" + )) + })?; + self.command( + "Browser.setContentsSize", + json!({ + "windowId": window_id, + "width": width, + "height": height + }), + None, + )?; self.command( "Emulation.setDeviceMetricsOverride", device_metrics_params(width, height), @@ -3167,7 +3187,7 @@ mod chromium_backend { } #[test] - fn device_metrics_keep_layout_and_capture_viewport_exact() { + fn browser_contents_are_resized_before_device_metrics() { let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let client_stream = TcpStream::connect(listener.local_addr().unwrap()).unwrap(); let (server_stream, _) = listener.accept().unwrap(); @@ -3177,15 +3197,64 @@ mod chromium_backend { None, ); let mut server_socket = WebSocket::from_raw_socket(server_stream, Role::Server, None); + let (observed_sender, observed_receiver) = mpsc::channel(); let server = thread::spawn(move || { - let request = match server_socket.read().unwrap() { - Message::Text(text) => serde_json::from_str::(text.as_ref()).unwrap(), - other => panic!("expected device-metrics request, got {other:?}"), - }; - assert_eq!( - request, + let mut observed = Vec::new(); + loop { + let request = match server_socket.read().unwrap() { + Message::Text(text) => { + serde_json::from_str::(text.as_ref()).unwrap() + } + other => panic!("expected viewport-sizing request, got {other:?}"), + }; + let id = request["id"].as_u64().unwrap(); + let method = request["method"].as_str().unwrap().to_owned(); + let result = if method == "Browser.getWindowForTarget" { + json!({"windowId": 42, "bounds": {}}) + } else { + json!({}) + }; + server_socket + .send(Message::text( + json!({"id": id, "result": result}).to_string(), + )) + .unwrap(); + observed.push(request); + if method == "Emulation.setDeviceMetricsOverride" { + break; + } + } + observed_sender.send(observed).unwrap(); + }); + + let mut cdp = Cdp::new( + client_socket, + SandboxPolicy::default(), + MotionCancellationToken::new(), + Instant::now() + Duration::from_secs(1), + ); + cdp.set_device_metrics("render-session", 48, 32).unwrap(); + server.join().unwrap(); + assert_eq!( + observed_receiver.recv().unwrap(), + vec![ json!({ "id": 1, + "method": "Browser.getWindowForTarget", + "params": {}, + "sessionId": "render-session" + }), + json!({ + "id": 2, + "method": "Browser.setContentsSize", + "params": { + "windowId": 42, + "width": 48, + "height": 32 + } + }), + json!({ + "id": 3, "method": "Emulation.setDeviceMetricsOverride", "params": { "width": 48, @@ -3197,20 +3266,8 @@ mod chromium_backend { }, "sessionId": "render-session" }) - ); - server_socket - .send(Message::text(json!({"id": 1, "result": {}}).to_string())) - .unwrap(); - }); - - let mut cdp = Cdp::new( - client_socket, - SandboxPolicy::default(), - MotionCancellationToken::new(), - Instant::now() + Duration::from_secs(1), + ] ); - cdp.set_device_metrics("render-session", 48, 32).unwrap(); - server.join().unwrap(); } #[test] diff --git a/docs/architecture/PLAYBACK-ENGINE.md b/docs/architecture/PLAYBACK-ENGINE.md index d7ecc36d..320bdc5e 100644 --- a/docs/architecture/PLAYBACK-ENGINE.md +++ b/docs/architecture/PLAYBACK-ENGINE.md @@ -32,6 +32,23 @@ surface. Reverse and positive-speed media can remain on this route when no compositor-only content is present. This route has no libmpv dependency or transparent native-player hole. +## Source-media preview route + +The source tab uses the Rust streaming engine whenever the packaged playback +capability is available. `playback_start(mediaId)` projects the selected video +into a private one-track timeline at the project fps, then reuses the same +FFmpeg RGBA decode, compositor, cpal audio clock, exact publication, and +identity-scoped pause/seek/stop lifecycle as timeline playback. The source +asset is read directly; this route does not create a whole-file proxy or +transcode by default. + +Paused source frames and paused seeks use `composite_frame(sourceMediaId)` so +they also decode through FFmpeg instead of switching back to WebKit. A terminal +frame remains painted, Play rewinds it to frame zero, and selecting another +asset retires the previous source session before accepting its publications. +The ordinary `