diff --git a/Cargo.lock b/Cargo.lock index 91e1d6ab6..d550dc526 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -603,10 +603,12 @@ dependencies = [ "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.23.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "path-slash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "ruffle_core 0.1.0", "ruffle_render_wgpu 0.1.0", "sample 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "wgpu 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "wgpu-native 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/exporter/Cargo.toml b/exporter/Cargo.toml index d972911e7..4b9af4cbc 100644 --- a/exporter/Cargo.toml +++ b/exporter/Cargo.toml @@ -15,6 +15,8 @@ structopt = "0.3.14" futures = "0.3.4" wgpu = "0.5" wgpu-native = "0.5" +path-slash = "0.1.1" +walkdir = "2.3.1" [features] avm_debug = ["ruffle_core/avm_debug"] diff --git a/exporter/src/main.rs b/exporter/src/main.rs index 8f4c04c17..156e18556 100644 --- a/exporter/src/main.rs +++ b/exporter/src/main.rs @@ -1,4 +1,5 @@ use futures::executor::block_on; +use image::RgbaImage; use ruffle_core::backend::audio::NullAudioBackend; use ruffle_core::backend::input::NullInputBackend; use ruffle_core::backend::navigator::NullNavigatorBackend; @@ -6,41 +7,38 @@ use ruffle_core::tag_utils::SwfMovie; use ruffle_core::Player; use ruffle_render_wgpu::target::TextureTarget; use ruffle_render_wgpu::WgpuRenderBackend; +use std::error::Error; use std::fs::create_dir_all; use std::path::{Path, PathBuf}; use structopt::StructOpt; +use walkdir::{DirEntry, WalkDir}; #[derive(StructOpt, Debug)] struct Opt { - /// The swf file to export frames from + /// The file or directory of files to export frames from #[structopt(name = "swf", parse(from_os_str))] swf: PathBuf, - /// The file or directory (if multiple frames) to store the capture in + /// The file or directory (if multiple frames/files) to store the capture in. + /// The default value will either be: + /// - If given one swf and one frame, the name of the swf + ".png" + /// - If given one swf and multiple frames, the name of the swf as a directory + /// - If given multiple swfs, this field is required. #[structopt(name = "output", parse(from_os_str))] output_path: Option, - /// Number of frames to capture + /// Number of frames to capture per file #[structopt(short = "f", long = "frames", default_value = "1")] frames: u32, } -fn take_screenshot>( - swf_path: P, - output: P, +fn take_screenshot( + adapter: &wgpu::Adapter, + swf_path: &Path, frames: u32, -) -> Result<(), Box> { +) -> Result, Box> { let movie = SwfMovie::from_path(swf_path)?; - let adapter = block_on(wgpu::Adapter::request( - &wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::Default, - compatible_surface: None, - }, - wgpu::BackendBit::PRIMARY, - )) - .unwrap(); - let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor { extensions: wgpu::Extensions { anisotropic_filtering: false, @@ -56,6 +54,7 @@ fn take_screenshot>( movie, )?; + let mut result = Vec::new(); for i in 0..frames { player.lock().unwrap().run_frame(); player.lock().unwrap().render(); @@ -67,54 +66,155 @@ fn take_screenshot>( .unwrap(); let target = renderer.target(); if let Some(image) = target.capture(renderer.device()) { - if frames > 1 { - let mut path = PathBuf::from(output.as_ref()); - path.push(format!("frame_{}.png", i)); - image.save(&path)?; - } else { - image.save(&output)?; - } + result.push(image); + } else { + return Err(format!("Unable to capture frame {} of {:?}", i, swf_path).into()); } } + Ok(result) +} + +fn find_files(root: &Path) -> Vec { + let mut results = Vec::new(); + + for entry in WalkDir::new(root) + .follow_links(true) + .into_iter() + .filter_map(|e| e.ok()) + { + let f_name = entry.file_name().to_string_lossy(); + + if f_name.ends_with(".swf") { + results.push(entry); + } + } + + results +} + +fn capture_single_swf( + adapter: wgpu::Adapter, + swf: &Path, + frames: u32, + output: Option, +) -> Result<(), Box> { + let output = output.unwrap_or_else(|| { + let mut result = PathBuf::new(); + if frames == 1 { + result.set_file_name(swf.file_stem().unwrap()); + result.set_extension("png"); + } else { + result.set_file_name(swf.file_stem().unwrap()); + } + result + }); + + if frames > 1 { + let _ = create_dir_all(&output); + } + + let frames = take_screenshot(&adapter, &swf, frames)?; + + if frames.len() == 1 { + frames.get(0).unwrap().save(&output)?; + println!( + "Saved first frame of {} to {}", + swf.to_string_lossy(), + output.to_string_lossy() + ); + } else { + for (frame, image) in frames.iter().enumerate() { + let mut path = PathBuf::from(&output); + path.push(format!("{}.png", frame)); + image.save(&path)?; + } + println!( + "Saved first {} frames of {} to {}", + frames.len(), + swf.to_string_lossy(), + output.to_string_lossy() + ); + } + Ok(()) } -fn main() { - let opt: Opt = Opt::from_args(); - let output = opt.output_path.clone().unwrap_or_else(|| { - let mut result = PathBuf::new(); - if opt.frames == 1 { - result.set_file_name(opt.swf.file_stem().unwrap()); - result.set_extension("png"); +fn capture_multiple_swfs( + adapter: wgpu::Adapter, + directory: &Path, + frames: u32, + output: &Path, +) -> Result<(), Box> { + let files = find_files(directory); + + for file in &files { + let frames = take_screenshot(&adapter, &file.path(), frames)?; + let mut relative_path = file + .path() + .strip_prefix(directory) + .unwrap_or_else(|_| &file.path()) + .to_path_buf(); + + if frames.len() == 1 { + let mut destination = PathBuf::from(output); + relative_path.set_extension("png"); + destination.push(relative_path); + if let Some(parent) = destination.parent() { + let _ = create_dir_all(parent); + } + frames.get(0).unwrap().save(&destination)?; } else { - result.set_file_name(opt.swf.file_stem().unwrap()); - } - result - }); - if opt.frames > 1 { - let _ = create_dir_all(&output); - } - match take_screenshot(opt.swf.clone(), output.clone(), opt.frames) { - Ok(_) => { - if opt.frames == 1 { - println!( - "Saved first frame of {} to {}", - opt.swf.to_string_lossy(), - output.to_string_lossy() - ); - } else { - println!( - "Saved first {} frames of {} to {}", - opt.frames, - opt.swf.to_string_lossy(), - output.to_string_lossy() - ); + let mut parent = PathBuf::from(output); + relative_path.set_extension(""); + parent.push(&relative_path); + let _ = create_dir_all(&parent); + for (frame, image) in frames.iter().enumerate() { + let mut destination = parent.clone(); + destination.push(format!("{}.png", frame)); + image.save(&destination)?; } } - Err(e) => { - println!("Couldn't capture swf: {}", e); - std::process::exit(1); - } } + + if frames == 1 { + println!( + "Saved first frame of {} files to {}", + files.len(), + output.to_string_lossy() + ); + } else { + println!( + "Saved first {} frames of {} files to {}", + frames, + files.len(), + output.to_string_lossy() + ); + } + + Ok(()) +} + +fn main() -> Result<(), Box> { + let opt: Opt = Opt::from_args(); + let adapter = block_on(wgpu::Adapter::request( + &wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::Default, + compatible_surface: None, + }, + wgpu::BackendBit::PRIMARY, + )) + .ok_or_else(|| { + "This tool requires hardware acceleration, but no compatible graphics device was found." + })?; + + if opt.swf.is_file() { + capture_single_swf(adapter, &opt.swf, opt.frames, opt.output_path)?; + } else if let Some(output) = opt.output_path { + capture_multiple_swfs(adapter, &opt.swf, opt.frames, &output)?; + } else { + return Err("Output directory is required when exporting multiple files.".into()); + } + + Ok(()) }