use std::{fs::metadata, io::Write, path::Path}; use byteorder::{BigEndian, WriteBytesExt}; use hex::FromHex; use image::DynamicImage; use itertools::Itertools; use md5::Digest; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use zerocopy::IntoBytes; use crate::{ RawImage, RawImageData, RawlerError, Result, decoders::{RawDecodeParams, RawMetadata, cr2::Cr2Format, cr3::Cr3Format, dng::DngFormat, iiq::IiqFormat, nef::NefFormat, pef::PefFormat, tfr::TfrFormat}, formats::tiff::Rational, formats::tiff::SRational, imgop::{Rect, develop::RawDevelop}, rawimage::{BlackLevel, WhiteLevel}, rawsource::RawSource, }; #[derive(Debug, Clone, PartialEq)] pub struct Md5Digest { digest: md5::Digest, } impl From for Md5Digest { fn from(digest: md5::Digest) -> Self { Self { digest } } } impl Serialize for Md5Digest { fn serialize(&self, serializer: S) -> std::result::Result where S: Serializer, { let s = format!("{:x}", self.digest); serializer.serialize_str(&s) } } impl<'de> Deserialize<'de> for Md5Digest { fn deserialize(deserializer: D) -> std::result::Result where D: Deserializer<'de>, { use serde::de::Error; let s = String::deserialize(deserializer)?; if s.len() != 32 { Err(D::Error::custom(format!("Invalid digest value: {}", s))) } else { Ok(Md5Digest { digest: Digest(<[u8; 16]>::from_hex(s).map_err(D::Error::custom)?), }) } } } #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FileMetadata { file_size: u64, file_name: String, digest: Option, } #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AnalyzerMetadata { pub raw_params: RawParams, pub raw_metadata: RawMetadata, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[allow(clippy::large_enum_variant)] pub enum AnalyzerData { FileStructure(FormatDump), Metadata(AnalyzerMetadata), RawParams(RawParams), } #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AnalyzerResult { pub file: FileMetadata, pub data: Option, } #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CaptureInfo { pub make: String, pub model: String, pub exposure_time: Option, pub shutter_speed: Option, pub exposure_bias: Option, pub lens_make: Option, pub lens_model: Option, pub lens_spec: Option<[Rational; 4]>, } #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RawParams { pub raw_width: usize, pub raw_height: usize, pub bit_depth: usize, pub crop_area: Option, pub active_area: Option, pub blacklevels: BlackLevel, pub whitelevels: WhiteLevel, pub wb_coeffs: (Option, Option, Option, Option), } impl From<&RawImage> for RawParams { fn from(rawimage: &RawImage) -> Self { Self { raw_width: rawimage.width, raw_height: rawimage.height, bit_depth: rawimage.bps, crop_area: rawimage.crop_area, active_area: rawimage.active_area, blacklevels: rawimage.blacklevel.clone(), whitelevels: rawimage.whitelevel.clone(), wb_coeffs: rawimage .wb_coeffs .iter() .map(|c| if c.is_nan() { None } else { Some(*c) }) .collect_tuple() .unwrap(), } } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[allow(clippy::large_enum_variant)] pub enum FormatDump { Cr3(Cr3Format), Cr2(Cr2Format), Pef(PefFormat), Iiq(IiqFormat), Tfr(TfrFormat), Nef(NefFormat), Dng(DngFormat), } fn file_metadata>(path: P, rawfile: &RawSource) -> Result { let fs_meta = metadata(&path).map_err(|e| RawlerError::with_io_error("read metadata", &path, e))?; let digest = rawfile.digest(); Ok(FileMetadata { file_name: path.as_ref().file_name().unwrap().to_string_lossy().to_string(), file_size: fs_meta.len(), digest: Some(digest.into()), }) } pub fn analyze_metadata>(path: P) -> Result { //let input = BufReader::new(File::open(&path).map_err(|e| RawlerError::with_io_error("load into buffer", &path, e))?); let rawfile = RawSource::new(path.as_ref())?; let decoder = crate::get_decoder(&rawfile)?; let rawimage = decoder.raw_image(&rawfile, &RawDecodeParams::default(), true)?; let mut result = AnalyzerResult { file: file_metadata(path, &rawfile)?, ..Default::default() }; let md = decoder.raw_metadata(&rawfile, &RawDecodeParams::default())?; result.data = Some(AnalyzerData::Metadata(AnalyzerMetadata { raw_params: RawParams::from(&rawimage), raw_metadata: md, })); Ok(result) } pub fn analyze_file_structure>(path: P) -> Result { let rawfile = RawSource::new(path.as_ref())?; let decoder = crate::get_decoder(&rawfile)?; let result = AnalyzerResult { file: file_metadata(path, &rawfile)?, data: Some(AnalyzerData::FileStructure(decoder.format_dump())), }; Ok(result) } pub fn extract_raw_pixels>(path: P, params: &RawDecodeParams) -> Result { let rawfile = RawSource::new(path.as_ref())?; let decoder = crate::get_decoder(&rawfile)?; let rawimage = decoder.raw_image(&rawfile, params, false)?; Ok(rawimage) } pub fn raw_pixels_digest>(path: P, params: &RawDecodeParams) -> Result<[u8; 16]> { let rawimage = extract_raw_pixels(path, params)?; let byte_buf = match &rawimage.data { RawImageData::Integer(items) => items.as_slice().as_bytes(), RawImageData::Float(items) => items.as_slice().as_bytes(), }; Ok(md5::compute(byte_buf).into()) } pub fn extract_full_pixels>(path: P, params: &RawDecodeParams) -> Result { let rawfile = RawSource::new(path.as_ref())?; let decoder = crate::get_decoder(&rawfile)?; match decoder.full_image(&rawfile, params)? { Some(preview) => Ok(preview), None => Err("Unable to extract full image from RAW".into()), } } pub fn extract_preview_pixels>(path: P, params: &RawDecodeParams) -> Result { let rawfile = RawSource::new(path.as_ref())?; let decoder = crate::get_decoder(&rawfile)?; match decoder.preview_image(&rawfile, params)? { Some(preview) => Ok(preview), None => match decoder.full_image(&rawfile, params)? { Some(preview) => Ok(preview), None => Err("Unable to extract preview image from RAW".into()), }, } } pub fn extract_thumbnail_pixels>(path: P, params: &RawDecodeParams) -> Result { let rawfile = RawSource::new(path.as_ref())?; let decoder = crate::get_decoder(&rawfile)?; match decoder.thumbnail_image(&rawfile, params)? { Some(thumbnail) => Ok(thumbnail), None => match decoder.preview_image(&rawfile, params)? { Some(thumbnail) => Ok(thumbnail), None => match decoder.full_image(&rawfile, params)? { Some(thumbnail) => Ok(thumbnail), None => Err("Unable to extract thumbnail image from RAW".into()), }, }, } } pub fn raw_to_srgb>(path: P, params: &RawDecodeParams) -> Result { let rawfile = RawSource::new(path.as_ref())?; // Get decoder or return let decoder = crate::get_decoder(&rawfile)?; //decoder.decode_metadata(&mut rawfile)?; let rawimage = decoder.raw_image(&rawfile, params, false)?; let dev = RawDevelop::default(); assert_eq!(rawimage.cpp, 1); Ok(dev.develop_intermediate(&rawimage)?.to_dynamic_image().unwrap()) } /// Dump raw pixel data as PGM pub fn raw_as_pgm(width: usize, height: usize, buf: &[u16], writer: &mut dyn Write) -> std::io::Result<()> { let header = format!("P5 {} {} {}\n", width, height, 65535); writer.write_all(header.as_bytes())?; for px in buf { writer.write_u16::(*px)?; } Ok(()) } /// Dump pixel data as PGM pub fn rgb8_as_ppm8(width: usize, height: usize, buf: &[u8], writer: &mut dyn Write) -> std::io::Result<()> { let header = format!("P6 {} {} {}\n", width, height, u8::MAX); writer.write_all(header.as_bytes())?; writer.write_all(buf)?; Ok(()) } /// Dump raw pixel data as PPM pub fn raw_as_ppm16(width: usize, height: usize, buf: &[u16], writer: &mut dyn Write) -> std::io::Result<()> { let header = format!("P6 {} {} {}\n", width, height, 65535); writer.write_all(header.as_bytes())?; for px in buf { writer.write_u16::(*px)?; } Ok(()) }