Skip to content

Commit 17763a7

Browse files
committed
Refactor FFmpeg decoder forcing for export and preview
1 parent e515f45 commit 17763a7

File tree

7 files changed

+37
-41
lines changed

7 files changed

+37
-41
lines changed

apps/desktop/src-tauri/src/export.rs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use image::codecs::jpeg::JpegEncoder;
1010
use serde::{Deserialize, Serialize};
1111
use specta::Type;
1212
use std::{
13-
path::PathBuf,
13+
path::{Path, PathBuf},
1414
sync::{Arc, atomic::Ordering},
1515
};
1616
use tracing::{info, instrument};
@@ -32,11 +32,13 @@ impl ExportSettings {
3232
}
3333

3434
async fn do_export(
35-
project_path: &PathBuf,
35+
project_path: &Path,
3636
settings: &ExportSettings,
3737
progress: &tauri::ipc::Channel<FramesRendered>,
38+
force_ffmpeg: bool,
3839
) -> Result<PathBuf, String> {
39-
let exporter_base = ExporterBase::builder(project_path.clone())
40+
let exporter_base = ExporterBase::builder(project_path.to_path_buf())
41+
.with_force_ffmpeg_decoder(force_ffmpeg)
4042
.build()
4143
.await
4244
.map_err(|e| e.to_string())?;
@@ -80,7 +82,6 @@ async fn do_export(
8082

8183
fn is_frame_decode_error(error: &str) -> bool {
8284
error.contains("Failed to decode video frames")
83-
|| error.contains("FrameDecodeFailed")
8485
|| error.contains("Too many consecutive frame failures")
8586
}
8687

@@ -96,13 +97,11 @@ pub async fn export_video(
9697
ExportSettings::Mp4(s) => s.force_ffmpeg_decoder,
9798
ExportSettings::Gif(_) => false,
9899
};
99-
cap_rendering::set_force_ffmpeg_decoder(force_ffmpeg);
100100

101-
let result = do_export(&project_path, &settings, &progress).await;
101+
let result = do_export(&project_path, &settings, &progress, force_ffmpeg).await;
102102

103103
match result {
104104
Ok(path) => {
105-
cap_rendering::set_force_ffmpeg_decoder(false);
106105
info!("Exported to {} completed", path.display());
107106
Ok(path)
108107
}
@@ -112,11 +111,7 @@ pub async fn export_video(
112111
e
113112
);
114113

115-
cap_rendering::set_force_ffmpeg_decoder(true);
116-
117-
let retry_result = do_export(&project_path, &settings, &progress).await;
118-
119-
cap_rendering::set_force_ffmpeg_decoder(false);
114+
let retry_result = do_export(&project_path, &settings, &progress, true).await;
120115

121116
match retry_result {
122117
Ok(path) => {
@@ -133,7 +128,6 @@ pub async fn export_video(
133128
}
134129
}
135130
Err(e) => {
136-
cap_rendering::set_force_ffmpeg_decoder(false);
137131
sentry::capture_message(&e, sentry::Level::Error);
138132
Err(e)
139133
}
@@ -279,7 +273,7 @@ pub async fn generate_export_preview(
279273
.map_err(|e| format!("Failed to create render constants: {e}"))?,
280274
);
281275

282-
let segments = create_segments(&recording_meta, studio_meta)
276+
let segments = create_segments(&recording_meta, studio_meta, false)
283277
.await
284278
.map_err(|e| format!("Failed to create segments: {e}"))?;
285279

apps/desktop/src/routes/editor/ExportPage.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,12 +1086,13 @@ export function ExportPage() {
10861086
</p>
10871087
</Show>
10881088

1089-
<Show
1090-
when={ostype() === "macos" && settings.format === "Mp4"}
1091-
>
1089+
<Show when={ostype() === "macos"}>
10921090
<div class="mt-4 pt-3 border-t border-gray-4">
10931091
<button
10941092
type="button"
1093+
role="switch"
1094+
aria-checked={forceFfmpegDecoder()}
1095+
aria-label="Force FFmpeg decoder"
10951096
class="flex items-center gap-2 text-xs text-gray-11 hover:text-gray-12 transition-colors w-full"
10961097
onClick={() =>
10971098
setForceFfmpegDecoder(!forceFfmpegDecoder())

crates/editor/src/editor_instance.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ impl EditorInstance {
248248
meta.as_ref(),
249249
)?);
250250

251-
let segments = create_segments(&recording_meta, meta.as_ref()).await?;
251+
let segments = create_segments(&recording_meta, meta.as_ref(), false).await?;
252252

253253
let render_constants = Arc::new(
254254
RenderVideoConstants::new(
@@ -581,6 +581,7 @@ pub struct SegmentMedia {
581581
pub async fn create_segments(
582582
recording_meta: &RecordingMeta,
583583
meta: &StudioRecordingMeta,
584+
force_ffmpeg: bool,
584585
) -> Result<Vec<SegmentMedia>, String> {
585586
match &meta {
586587
cap_project::StudioRecordingMeta::SingleSegment { segment: s } => {
@@ -622,6 +623,7 @@ pub async fn create_segments(
622623
camera: s.camera.as_ref().map(|c| recording_meta.path(&c.path)),
623624
},
624625
0,
626+
force_ffmpeg,
625627
)
626628
.await
627629
.map_err(|e| format!("SingleSegment / {e}"))?;
@@ -667,6 +669,7 @@ pub async fn create_segments(
667669
camera: s.camera.as_ref().map(|c| recording_meta.path(&c.path)),
668670
},
669671
i,
672+
force_ffmpeg,
670673
)
671674
.await
672675
.map_err(|e| format!("MultipleSegments {i} / {e}"))?;

crates/export/src/lib.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub struct ExporterBuilder {
5252
project_path: PathBuf,
5353
config: Option<ProjectConfiguration>,
5454
output_path: Option<PathBuf>,
55+
force_ffmpeg_decoder: bool,
5556
}
5657

5758
impl ExporterBuilder {
@@ -60,6 +61,11 @@ impl ExporterBuilder {
6061
self
6162
}
6263

64+
pub fn with_force_ffmpeg_decoder(mut self, force: bool) -> Self {
65+
self.force_ffmpeg_decoder = force;
66+
self
67+
}
68+
6369
pub async fn build(self) -> Result<ExporterBase, ExporterBuildError> {
6470
type Error = ExporterBuildError;
6571

@@ -90,9 +96,10 @@ impl ExporterBuilder {
9096
.map_err(Error::RendererSetup)?,
9197
);
9298

93-
let segments = cap_editor::create_segments(&recording_meta, studio_meta)
94-
.await
95-
.map_err(Error::MediaLoad)?;
99+
let segments =
100+
cap_editor::create_segments(&recording_meta, studio_meta, self.force_ffmpeg_decoder)
101+
.await
102+
.map_err(Error::MediaLoad)?;
96103

97104
let output_path = self
98105
.output_path
@@ -144,6 +151,7 @@ impl ExporterBase {
144151
project_path,
145152
config: None,
146153
output_path: None,
154+
force_ffmpeg_decoder: false,
147155
}
148156
}
149157
}

crates/rendering/src/decoder/mod.rs

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,12 @@ use ::ffmpeg::Rational;
22
use std::{
33
fmt,
44
path::PathBuf,
5-
sync::{
6-
Arc,
7-
atomic::{AtomicBool, Ordering},
8-
mpsc,
9-
},
5+
sync::{Arc, mpsc},
106
time::Duration,
117
};
128
use tokio::sync::oneshot;
139
use tracing::info;
1410

15-
static FORCE_FFMPEG_DECODER: AtomicBool = AtomicBool::new(false);
16-
17-
pub fn set_force_ffmpeg_decoder(force: bool) {
18-
FORCE_FFMPEG_DECODER.store(force, Ordering::SeqCst);
19-
if force {
20-
tracing::info!("FFmpeg decoder forced via experimental setting");
21-
}
22-
}
23-
24-
pub fn is_ffmpeg_decoder_forced() -> bool {
25-
FORCE_FFMPEG_DECODER.load(Ordering::SeqCst)
26-
}
27-
2811
#[cfg(target_os = "macos")]
2912
mod avassetreader;
3013
mod ffmpeg;
@@ -569,13 +552,14 @@ pub async fn spawn_decoder(
569552
path: PathBuf,
570553
fps: u32,
571554
offset: f64,
555+
force_ffmpeg: bool,
572556
) -> Result<AsyncVideoDecoderHandle, String> {
573557
let path_display = path.display().to_string();
574558
let timeout_duration = Duration::from_secs(30);
575559

576560
#[cfg(target_os = "macos")]
577561
{
578-
if is_ffmpeg_decoder_forced() {
562+
if force_ffmpeg {
579563
info!(
580564
"Video '{}' using FFmpeg decoder (forced via experimental setting)",
581565
name
@@ -668,6 +652,7 @@ pub async fn spawn_decoder(
668652

669653
#[cfg(target_os = "windows")]
670654
{
655+
let _ = force_ffmpeg;
671656
let (ready_tx, ready_rx) = oneshot::channel::<Result<DecoderInitResult, String>>();
672657
let (tx, rx) = mpsc::channel();
673658

@@ -704,6 +689,7 @@ pub async fn spawn_decoder(
704689

705690
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
706691
{
692+
let _ = force_ffmpeg;
707693
let (ready_tx, ready_rx) = oneshot::channel::<Result<DecoderInitResult, String>>();
708694
let (tx, rx) = mpsc::channel();
709695

crates/rendering/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use composite_frame::CompositeVideoFrameUniforms;
77
use core::f64;
88
use cursor_interpolation::{InterpolatedCursorPosition, interpolate_cursor};
99
use decoder::{AsyncVideoDecoderHandle, spawn_decoder};
10-
pub use decoder::{is_ffmpeg_decoder_forced, set_force_ffmpeg_decoder};
1110
use frame_pipeline::{RenderSession, finish_encoder};
1211
use futures::FutureExt;
1312
use futures::future::OptionFuture;
@@ -121,6 +120,7 @@ impl RecordingSegmentDecoders {
121120
meta: &StudioRecordingMeta,
122121
segment: SegmentVideoPaths,
123122
segment_i: usize,
123+
force_ffmpeg: bool,
124124
) -> Result<Self, String> {
125125
let latest_start_time = match &meta {
126126
StudioRecordingMeta::SingleSegment { .. } => None,
@@ -149,6 +149,7 @@ impl RecordingSegmentDecoders {
149149
.unwrap_or(0.0)
150150
}
151151
},
152+
force_ffmpeg,
152153
)
153154
.await
154155
.map_err(|e| format!("Screen:{e}"))?;
@@ -175,6 +176,7 @@ impl RecordingSegmentDecoders {
175176
.unwrap_or(0.0)
176177
}
177178
},
179+
force_ffmpeg,
178180
)
179181
.then(|r| async { r.map_err(|e| format!("Camera:{e}")) })
180182
}))

crates/rendering/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ async fn main() -> Result<()> {
101101
.map(|c| recording_meta.path(&c.path)),
102102
},
103103
0,
104+
false,
104105
)
105106
.await
106107
.map_err(|e| anyhow::anyhow!("Failed to create decoders for single segment: {}", e))?;
@@ -121,6 +122,7 @@ async fn main() -> Result<()> {
121122
camera: s.camera.as_ref().map(|c| recording_meta.path(&c.path)),
122123
},
123124
i,
125+
false,
124126
)
125127
.await
126128
.map_err(|e| {

0 commit comments

Comments
 (0)