//! Merge a set of frames into a panorama DNG, headless. //! //! ```sh //! cargo run -p dr-ui --example merge --release -- fixtures/pano/2025-08-05/*.CR2 //! cargo run -p dr-ui --example merge --release -- --out /var/tmp frame1.CR2 … //! ``` //! //! The job the interface runs (`dr_ui::merge`), driven from a loop instead //! of a timer: the same code, the same events, a file at the end. What it //! proves that the alignment example cannot is the second half — the tap, //! the chunked warp, the resolve to sixteen bits and the DNG — and that the //! result opens in the application as one RAW. use std::path::PathBuf; use std::time::Instant; fn main() { env_logger::init(); let mut args: Vec = std::env::args().skip(1).collect(); let mut out_dir = None; if args.first().map(String::as_str) == Some("--out") { args.remove(0); out_dir = Some(PathBuf::from(args.remove(0))); } if args.len() < 2 { eprintln!("usage: merge [--out DIR] ..."); std::process::exit(2); } let Ok(gpu) = pollster::block_on(dr_gpu::GpuContext::new_headless()) else { eprintln!("no GPU"); std::process::exit(1); }; let frames: Vec = args .iter() .map(|a| dr_ui::merge::MergeInput::read(std::path::Path::new(a)).expect("read")) .collect(); let dir = out_dir.unwrap_or_else(|| { PathBuf::from(&args[0]) .parent() .map(std::path::Path::to_path_buf) .unwrap_or_else(|| PathBuf::from(".")) }); let request = dr_ui::merge::MergeRequest::new(frames, dr_ui::merge::MergeDestination::Local(dir)); let (tx, rx) = std::sync::mpsc::channel(); let (decide, decision) = std::sync::mpsc::channel(); let cancel = dr_ui::merge::Cancel::default(); let t = Instant::now(); let worker = std::thread::spawn(move || dr_ui::merge::run(gpu, request, tx, decision, cancel)); let mut last_stage = ""; for event in rx { use dr_ui::merge::MergeEvent::*; match event { Progress { stage, done, total } => { if stage != last_stage { println!("{stage}…"); last_stage = stage; } if done == total || done % 4 == 0 { println!(" {done}/{total}"); } } Aligned(r) => { println!( "aligned: {:.1} mm equivalent, rms {:.2} px, {} links, {:?} {}×{}", r.focal_mm, r.rms_px, r.links, r.projection, r.width, r.height ); for (k, f) in r.frames.iter().enumerate() { match f { Ok((yaw, pitch)) => { println!(" frame {k}: yaw {yaw:7.2}° pitch {pitch:6.2}°") } Err(why) => println!(" frame {k}: NOT ALIGNED — {why}"), } } // Headless: the confirmation the dialog would ask for. let _ = decide.send(dr_ui::merge::Decision::Merge { projection: None }); } Done { path, width, height, .. } => { println!( "wrote {} ({width}×{height}) in {:?}", path.display(), t.elapsed() ); } Failed(e) => { eprintln!("failed: {e}"); std::process::exit(1); } Cancelled => println!("cancelled"), } } let _ = worker.join(); }