Skip to content

Comprehensive export pipeline fixes and improvements#150

Merged
AIEraDev merged 9 commits into
masterfrom
fix/export-pipeline-comprehensive
Jul 12, 2026
Merged

Comprehensive export pipeline fixes and improvements#150
AIEraDev merged 9 commits into
masterfrom
fix/export-pipeline-comprehensive

Conversation

@AIEraDev

Copy link
Copy Markdown
Owner

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 export
  • exportSequence.ts - PNG sequence export

Also cleaned up double-release pattern in exportFrame.ts to use finally block.

C2: Export Cancel Button Not Working

Problem: Cancel button in export dialog didn't actually kill the FFmpeg process. Module-level AbortController caused state pollution across export sessions.

Solution:

  • Implemented onSessionReady callback pattern to pass live cancel() function to dialog
  • Replaced module-level abort controller with session-scoped cancellation
  • Cancel button now properly terminates FFmpeg process mid-export

C3: Module-Level AbortController State Pollution

Problem: Shared AbortController at module level caused cross-session contamination and export session leaks.

Solution: Replaced with onCancelReady callback for session-scoped cancellation in exportSequence.ts.

High Priority Fixes

H1: ProRes Preset Pixel Format

Problem: ProRes preset didn't map pixel_format to 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:

  • Hardened with saturating_sub
  • Added progress.min(1.0) clamp

H3: 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=1 for proper IDR frame insertion.

H4: Blocking ffprobe Call

Problem: has_audio_stream used synchronous std::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 finally block.

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 Uint8Array binary 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.ts as single source of truth shared by both UI and backend.

L6: Partial File Cleanup on Cancel

Problem: If process.kill() failed in cancel_video_export, function returned early without cleaning up partial file.

Solution: Made kill operation non-fatal; partial file cleanup always runs.

Performance Improvements

  • Video Seek Throttling: Increased readyState polling interval from 5ms to 50ms to mitigate WebKit's seek-suspension bottleneck and reduce CPU thrashing

Files Changed

  • src/core/render/pixiSceneCompositor.ts - Added isReady/waitForReady with timeout
  • src/lib/export/videoExport.ts - Await compositor + session-scoped cancellation
  • src/lib/export/exportFrame.ts - Await compositor + finally cleanup
  • src/lib/export/exportSequence.ts - Await compositor + onCancelReady callback
  • src/components/ui/ExportDialog.tsx - onSessionReady callback pattern
  • src/core/resources/VideoElementPool.ts - Seek throttling optimization
  • src-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 definitions

Testing

  • ✅ All 1350 tests passed across 113 test files
  • ✅ Zero TypeScript errors
  • ✅ Clean Rust cargo check compilation
  • ✅ No regressions detected

Next Steps

Real-world export testing recommended, especially:

  • H.265/HEVC clips at 4K resolution
  • ProRes exports with different profiles
  • Export cancellation mid-process
  • PNG sequence exports
  • Single frame exports

AIEraDev added 8 commits July 12, 2026 07:55
- 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
@AIEraDev AIEraDev merged commit ddc7e10 into master Jul 12, 2026
2 checks passed
@AIEraDev AIEraDev deleted the fix/export-pipeline-comprehensive branch July 12, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants