Comprehensive export pipeline fixes and improvements#150
Merged
Conversation
- Added isReady getter and waitForReady() method with 10s timeout - Prevents rendering before Pixi WebGL context is fully initialized - Throws descriptive error on GPU/WebGL unavailability - Critical fix for blank export frames caused by initialization race condition
- Call await compositor.waitForReady() before frame rendering loop - Ensures WebGL context is fully initialized before encoding begins - Prevents exporting blank frames due to compositor initialization race - Adds abort controller for proper cancellation handling
- Added waitForReady() call before rendering single PNG frames - Prevents blank output from frame export feature - Cleaned up double-release pattern to finally block - Ensures WebGL initialization completes before capture
- Added waitForReady() before sequence rendering loop - Replaced module-level AbortController with session-scoped onCancelReady callback - Prevents global state pollution across export sessions - Ensures WebGL initialization completes before frame capture
- Added onSessionReady callback to pass live cancel() function to dialog - Cancel button now properly kills FFmpeg process during export - Replaced module-level AbortController with session-scoped cancellation - Prevents export session leaks and ensures clean termination
- Increased video readyState polling interval from 5ms to 50ms - Reduces CPU thrashing during video seeking operations - Mitigates WebKit's seek-suspension throttling behavior - Improves performance without affecting functionality
- Added Apple HEVC hvc1 tag for macOS/iOS compatibility - Made has_audio_stream async to prevent blocking Tokio runtime - Hardened u32 arithmetic with saturating_sub and progress clamping - Fixed H.265 IDR frame insertion with x265-params force-idr=1 - Added ProRes pixel_format to profile mapping (4444/LT/HQ support) - Replaced Vec::remove(0) with VecDeque for O(1) ring buffer operations - Made cancel_video_export non-fatal on kill failure + always cleanup partial files - Switched to Uint8Array binary IPC to prevent Array.from OOM
- Extracted export preset definitions to dedicated exportPresets.ts module - Shared by both ExportDialog UI and getExportPresets backend - Eliminates duplication and ensures consistency - Centralized preset configuration for easier maintenance
theunavailableguy
approved these changes
Jul 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR addresses all critical, high, and medium-priority issues in the export pipeline, along with important low-priority improvements. All 1350 tests pass across 113 test files with zero TypeScript errors and clean Rust compilation.
Critical Fixes
C1: Blank PNG/Sequence Export
Problem: Single-frame PNG exports and sequence exports were producing blank output due to the same WebGL initialization race condition that affected video export.
Solution: Added
waitForReady()calls to:exportFrame.ts- Single PNG frame exportexportSequence.ts- PNG sequence exportAlso cleaned up double-release pattern in exportFrame.ts to use
finallyblock.C2: Export Cancel Button Not Working
Problem: Cancel button in export dialog didn't actually kill the FFmpeg process. Module-level
AbortControllercaused state pollution across export sessions.Solution:
onSessionReadycallback pattern to pass livecancel()function to dialogC3: Module-Level AbortController State Pollution
Problem: Shared
AbortControllerat module level caused cross-session contamination and export session leaks.Solution: Replaced with
onCancelReadycallback for session-scoped cancellation in exportSequence.ts.High Priority Fixes
H1: ProRes Preset Pixel Format
Problem: ProRes preset didn't map
pixel_formatto appropriate profile, limiting codec options.Solution: Added pixel format to ProRes profile mapping in export.rs, enabling ProRes 4444/LT/HQ variants.
H2: u32 Arithmetic Overflow
Problem: Unchecked u32 subtraction and unbounded progress calculation could cause panics.
Solution:
saturating_subprogress.min(1.0)clampH3: H.265 IDR Frame Insertion
Problem:
-force_key_frames expr:flag was incorrect for H.265/x265 encoder.Solution: Replaced with
-x265-params force-idr=1for proper IDR frame insertion.H4: Blocking ffprobe Call
Problem:
has_audio_streamused synchronousstd::process::Command, blocking Tokio runtime.Solution: Made function async using
tokio::process::Command.H5: Double-Release Pattern
Problem: Manual cleanup before early returns in exportFrame.ts could skip cleanup on error paths.
Solution: Consolidated cleanup into
finallyblock.Medium Priority Improvements
M3: Array.from() OOM Risk
Problem: Converting large Uint8Array to JavaScript array via
Array.from()could cause out-of-memory errors.Solution: Switched to direct
Uint8Arraybinary IPC.M5: waitForReady Timeout
Problem:
waitForReady()could hang indefinitely if GPU context never initializes.Solution: Added 10-second timeout with descriptive error message.
M6: Redundant flushFrameBatch
Problem: Unnecessary
flushFrameBatch()call after export loop.Solution: Removed redundant call.
M7: Vec::remove(0) Performance
Problem: Ring buffer implemented with
Vec::remove(0)was O(n) per operation.Solution: Replaced with
VecDeque::pop_front()for O(1) performance.Low Priority Improvements
L4: Export Preset Duplication
Problem: Export preset definitions duplicated between ExportDialog and backend.
Solution: Created
exportPresets.tsas single source of truth shared by both UI and backend.L6: Partial File Cleanup on Cancel
Problem: If
process.kill()failed incancel_video_export, function returned early without cleaning up partial file.Solution: Made kill operation non-fatal; partial file cleanup always runs.
Performance Improvements
Files Changed
src/core/render/pixiSceneCompositor.ts- Added isReady/waitForReady with timeoutsrc/lib/export/videoExport.ts- Await compositor + session-scoped cancellationsrc/lib/export/exportFrame.ts- Await compositor + finally cleanupsrc/lib/export/exportSequence.ts- Await compositor + onCancelReady callbacksrc/components/ui/ExportDialog.tsx- onSessionReady callback patternsrc/core/resources/VideoElementPool.ts- Seek throttling optimizationsrc-tauri/src/commands/export.rs- All Rust improvements (HEVC tag, async ffprobe, ProRes, arithmetic hardening, IDR fix, ring buffer, cancel cleanup)src/lib/export/exportPresets.ts- New shared preset definitionsTesting
cargo checkcompilationNext Steps
Real-world export testing recommended, especially: