From f6b1802021eae6f6e5d361242561243c55cff0ab Mon Sep 17 00:00:00 2001 From: accusys Date: Thu, 19 Mar 2026 02:20:40 +0800 Subject: [PATCH] feat(player): add frame export (E key) to save PNG --- src/main.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/main.rs b/src/main.rs index cfc3613..996e407 100644 --- a/src/main.rs +++ b/src/main.rs @@ -406,6 +406,24 @@ fn run(config: &Config) -> Result<()> { player_state.speed = 1.0; info!("Speed: 1.0x"); } + sdl2::keyboard::Keycode::E => { + if let Some(ref info) = video_info { + let frame_num = player_state.current_frame; + let output_dir = std::env::current_dir().unwrap_or_default(); + let filename = format!("frame_{:06}.png", frame_num); + let output_path = output_dir.join(&filename); + if let Err(e) = export_current_frame( + &mut texture, + info.width, + info.height, + &output_path, + ) { + error!("Failed to export frame: {}", e); + } else { + info!("Exported: {:?}", output_path); + } + } + } _ => {} } } @@ -927,6 +945,48 @@ fn sync_audio( } } +fn export_current_frame( + texture: &mut Option, + width: u32, + height: u32, + output_path: &std::path::Path, +) -> anyhow::Result<()> { + if let Some(ref mut tex) = texture { + let mut pixels = vec![0u8; (width * height * 3) as usize]; + tex.with_lock(None, |buffer, _pitch| { + pixels.copy_from_slice(buffer); + }); + + let surf = sdl2::surface::Surface::from_data( + &mut pixels, + width, + height, + (width * 3) as u32, + sdl2::pixels::PixelFormatEnum::RGB24, + ) + .map_err(|e| anyhow::anyhow!("Failed to create surface: {}", e))?; + + surf.save_bmp(output_path) + .map_err(|e| anyhow::anyhow!("Failed to save BMP: {}", e))?; + + let png_path = output_path.with_extension("png"); + let cmd = std::process::Command::new("ffmpeg") + .args(["-y", "-i"]) + .arg(output_path.to_str().unwrap_or("")) + .arg(png_path.to_str().unwrap_or("")) + .output() + .map_err(|e| anyhow::anyhow!("Failed to convert to PNG: {}", e))?; + + if cmd.status.success() { + std::fs::remove_file(output_path).ok(); + } + + Ok(()) + } else { + anyhow::bail!("No texture available") + } +} + fn format_time(ms: u64) -> String { let total_secs = ms / 1000; let hours = total_secs / 3600;