use std::io::SeekFrom;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use log::debug;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use crate::heic::Heic;
use crate::ExtractRawExif;
#[async_trait]
impl ExtractRawExif for Heic {
async fn extract(&self) -> Result<Option<Vec<u8>>> {
let (_, (exif_ptr, _)) = self.exif_ptr().ok_or(anyhow!("Exif item not found"))?;
debug!("exif ptr: {:?}", exif_ptr);
const MAX_LEN: usize = 1024 * 1024 * 8;
if (exif_ptr.length - 4) > MAX_LEN {
return Err(anyhow!("Exif length is too large: {}", exif_ptr.length - 4));
}
let mut guard = self.file.lock().await;
guard.seek(SeekFrom::Start(exif_ptr.offset + 4)).await?;
let mut exif_data = vec![0u8; exif_ptr.length - 4];
guard.read_exact(&mut exif_data).await.map_err(|e| {
anyhow!(
"Failed to read exif (offset: {}, len: {}): {}",
exif_ptr.offset,
exif_ptr.length,
e.to_string()
)
})?;
if &exif_data[0..6] != b"Exif\0\0" {
return Err(anyhow!("Invalid exif data: not started with 'Exif'"));
}
exif_data.drain(0..6);
Ok(Some(exif_data))
}
}
#[cfg(test)]
mod tests {
use crate::{heic, internal::init_logger, ExtractRawExif};
const SAMPLES: [&str; 2] = [
"sample/sample_by_iphone15-pro-max.heic",
"sample/sample_by_hasselblad-x2d.heic",
];
#[tokio::test]
async fn extract_exif_from_heic() {
init_logger();
for file in SAMPLES.into_iter() {
let heic = heic(file).await.expect("Failed to open file");
let exif_data = heic
.extract()
.await
.expect("Failed to extract exif")
.expect("Exif data must be existed");
println!(
"extracted exif from '{}' (exif length: {})\n{}",
file,
exif_data.len(),
hex::encode(&exif_data)
);
}
}
}