Skip to content

Commit 5db2514

Browse files
committed
clippy
1 parent e9e01ed commit 5db2514

1 file changed

Lines changed: 0 additions & 263 deletions

File tree

crates/export/src/mp4.rs

Lines changed: 0 additions & 263 deletions
Original file line numberDiff line numberDiff line change
@@ -346,269 +346,6 @@ impl Mp4ExportSettings {
346346

347347
Ok(output_path)
348348
}
349-
350-
async fn export_rgba(
351-
self,
352-
base: ExporterBase,
353-
output_size: (u32, u32),
354-
fps: u32,
355-
mut on_progress: impl FnMut(u32) -> bool + Send + 'static,
356-
) -> Result<PathBuf, String> {
357-
let output_path = base.output_path.clone();
358-
let meta = &base.studio_meta;
359-
360-
let (tx_image_data, mut video_rx) =
361-
tokio::sync::mpsc::channel::<(cap_rendering::RenderedFrame, u32)>(32);
362-
let (frame_tx, frame_rx) = std::sync::mpsc::sync_channel::<MP4Input>(32);
363-
364-
let mut video_info =
365-
VideoInfo::from_raw(RawVideoFormat::Rgba, output_size.0, output_size.1, fps);
366-
video_info.time_base = ffmpeg::Rational::new(1, fps as i32);
367-
368-
let audio_segments = get_audio_segments(&base.segments);
369-
370-
let mut audio_renderer = audio_segments
371-
.first()
372-
.filter(|_| !base.project_config.audio.mute)
373-
.map(|_| AudioRenderer::new(audio_segments.clone()));
374-
let has_audio = audio_renderer.is_some();
375-
376-
let encoder_thread = tokio::task::spawn_blocking(move || {
377-
trace!("Creating MP4File encoder (RGBA fallback)");
378-
379-
let mut encoder = MP4File::init(
380-
"output",
381-
base.output_path.clone(),
382-
|o| {
383-
H264Encoder::builder(video_info)
384-
.with_bpp(self.effective_bpp())
385-
.with_export_priority()
386-
.with_export_settings()
387-
.build(o)
388-
},
389-
|o| {
390-
has_audio.then(|| {
391-
AACEncoder::init(AudioRenderer::info(), o)
392-
.map(|v| v.boxed())
393-
.map_err(Into::into)
394-
})
395-
},
396-
)
397-
.map_err(|v| v.to_string())?;
398-
399-
info!("Created MP4File encoder (RGBA fallback, export settings)");
400-
401-
let mut encoded_frames = 0u32;
402-
let encode_start = std::time::Instant::now();
403-
404-
while let Ok(frame) = frame_rx.recv() {
405-
encoder
406-
.queue_video_frame(frame.video, Duration::MAX)
407-
.map_err(|err| err.to_string())?;
408-
if let Some(audio) = frame.audio {
409-
encoder.queue_audio_frame(audio);
410-
}
411-
encoded_frames += 1;
412-
}
413-
414-
let encode_elapsed = encode_start.elapsed();
415-
if encoded_frames > 0 {
416-
let encode_fps = encoded_frames as f64 / encode_elapsed.as_secs_f64().max(0.001);
417-
info!(
418-
encoded_frames = encoded_frames,
419-
elapsed_secs = format!("{:.2}", encode_elapsed.as_secs_f64()),
420-
encode_fps = format!("{:.1}", encode_fps),
421-
"Encoder thread finished (RGBA)"
422-
);
423-
}
424-
425-
let res = encoder
426-
.finish()
427-
.map_err(|e| format!("Failed to finish encoding: {e}"))?;
428-
429-
if let Err(e) = res.video_finish {
430-
return Err(format!("Video encoding failed: {e}"));
431-
}
432-
if let Err(e) = res.audio_finish {
433-
return Err(format!("Audio encoding failed: {e}"));
434-
}
435-
436-
Ok::<_, String>(base.output_path)
437-
})
438-
.then(|r| async { r.map_err(|e| e.to_string()).and_then(|v| v) });
439-
440-
let render_task = tokio::spawn({
441-
let project = base.project_config.clone();
442-
let project_path = base.project_path.clone();
443-
async move {
444-
let mut frame_count = 0;
445-
let mut first_frame = None;
446-
let sample_rate = u64::from(AudioRenderer::SAMPLE_RATE);
447-
let fps_u64 = u64::from(fps);
448-
let mut audio_sample_cursor = 0u64;
449-
450-
let mut consecutive_timeouts = 0u32;
451-
const MAX_CONSECUTIVE_TIMEOUTS: u32 = 3;
452-
453-
loop {
454-
let timeout_secs = if frame_count == 0 { 120 } else { 90 };
455-
let (frame, frame_number) = match tokio::time::timeout(
456-
Duration::from_secs(timeout_secs),
457-
video_rx.recv(),
458-
)
459-
.await
460-
{
461-
Err(_) => {
462-
consecutive_timeouts += 1;
463-
464-
if consecutive_timeouts >= MAX_CONSECUTIVE_TIMEOUTS {
465-
tracing::error!(
466-
frame_count = frame_count,
467-
timeout_secs = timeout_secs,
468-
consecutive_timeouts = consecutive_timeouts,
469-
"Export render_task timed out {} consecutive times - aborting",
470-
MAX_CONSECUTIVE_TIMEOUTS
471-
);
472-
return Err(format!(
473-
"Export timed out {MAX_CONSECUTIVE_TIMEOUTS} times consecutively after {timeout_secs}s each waiting for frame {frame_count} - GPU/decoder may be unresponsive"
474-
));
475-
}
476-
477-
tracing::warn!(
478-
frame_count = frame_count,
479-
timeout_secs = timeout_secs,
480-
consecutive_timeouts = consecutive_timeouts,
481-
"Frame receive timed out, waiting for next frame..."
482-
);
483-
continue;
484-
}
485-
Ok(Some(v)) => {
486-
consecutive_timeouts = 0;
487-
v
488-
}
489-
Ok(None) => {
490-
tracing::debug!(
491-
frame_count = frame_count,
492-
"Render channel closed - rendering complete"
493-
);
494-
break;
495-
}
496-
};
497-
498-
if !(on_progress)(frame_count) {
499-
return Err("Export cancelled".to_string());
500-
}
501-
502-
if frame_count == 0 {
503-
first_frame = Some(frame.clone());
504-
if let Some(audio) = &mut audio_renderer {
505-
audio.set_playhead(0.0, &project);
506-
}
507-
}
508-
509-
let audio_frame = audio_renderer.as_mut().and_then(|audio| {
510-
let n = u64::from(frame_number);
511-
let end = ((n + 1) * sample_rate) / fps_u64;
512-
if end <= audio_sample_cursor {
513-
return None;
514-
}
515-
let pts = audio_sample_cursor as i64;
516-
let samples = (end - audio_sample_cursor) as usize;
517-
audio_sample_cursor = end;
518-
audio.render_frame(samples, &project).map(|mut frame| {
519-
frame.set_pts(Some(pts));
520-
frame
521-
})
522-
});
523-
524-
if frame_tx
525-
.send(MP4Input {
526-
audio: audio_frame,
527-
video: video_info.wrap_frame(
528-
&frame.data,
529-
frame_number as i64,
530-
frame.padded_bytes_per_row as usize,
531-
),
532-
})
533-
.is_err()
534-
{
535-
warn!("Renderer task sender dropped. Exiting");
536-
return Ok(());
537-
}
538-
539-
frame_count += 1;
540-
}
541-
542-
drop(frame_tx);
543-
544-
if let Some(frame) = first_frame {
545-
let project_path = project_path.clone();
546-
let screenshot_task = tokio::task::spawn_blocking(move || {
547-
let rgb_img = ImageBuffer::<image::Rgb<u8>, Vec<u8>>::from_raw(
548-
frame.width,
549-
frame.height,
550-
frame
551-
.data
552-
.chunks(frame.padded_bytes_per_row as usize)
553-
.flat_map(|row| {
554-
row[0..(frame.width * 4) as usize]
555-
.chunks(4)
556-
.flat_map(|chunk| [chunk[0], chunk[1], chunk[2]])
557-
})
558-
.collect::<Vec<_>>(),
559-
);
560-
561-
let Some(rgb_img) = rgb_img else {
562-
return;
563-
};
564-
565-
let screenshots_dir = project_path.join("screenshots");
566-
if std::fs::create_dir_all(&screenshots_dir).is_err() {
567-
return;
568-
}
569-
570-
let screenshot_path = screenshots_dir.join("display.jpg");
571-
let _ = rgb_img.save(&screenshot_path);
572-
});
573-
574-
if let Err(e) = screenshot_task.await {
575-
warn!("Screenshot task failed: {e}");
576-
}
577-
} else {
578-
warn!("No frames were processed, cannot save screenshot or thumbnail");
579-
}
580-
581-
Ok::<_, String>(())
582-
}
583-
})
584-
.then(|r| async {
585-
r.map_err(|e| e.to_string())
586-
.and_then(|v| v.map_err(|e| e.to_string()))
587-
});
588-
589-
let render_video_task = cap_rendering::render_video_to_channel(
590-
&base.render_constants,
591-
&base.project_config,
592-
tx_image_data,
593-
&base.recording_meta,
594-
meta,
595-
base.segments
596-
.iter()
597-
.map(|s| RenderSegment {
598-
cursor: s.cursor.clone(),
599-
decoders: s.decoders.clone(),
600-
})
601-
.collect(),
602-
fps,
603-
self.resolution_base,
604-
&base.recordings,
605-
)
606-
.then(|v| async { v.map_err(|e| e.to_string()) });
607-
608-
tokio::try_join!(encoder_thread, render_video_task, render_task)?;
609-
610-
Ok(output_path)
611-
}
612349
}
613350

614351
struct FirstFrameNv12 {

0 commit comments

Comments
 (0)