use async_trait::async_trait; use std::path::PathBuf; use libertas_core::{ error::{CoreError, CoreResult}, models::Media, plugins::{MediaProcessorPlugin, PluginContext, PluginData}, }; use nom_exif::{AsyncMediaParser, AsyncMediaSource, Exif, ExifIter, ExifTag}; pub struct ExifReaderPlugin; #[async_trait] impl MediaProcessorPlugin for ExifReaderPlugin { fn name(&self) -> &'static str { "exif_reader" } async fn process(&self, media: &Media, context: &PluginContext) -> CoreResult { let file_path = PathBuf::from(&context.media_library_path).join(&media.storage_path); let ms = match AsyncMediaSource::file_path(file_path).await { Ok(ms) => ms, Err(e) => return Err(CoreError::Unknown(format!("Failed to open a file: {}", e))), }; if !ms.has_exif() { return Ok(PluginData { message: "No EXIF data found in file.".to_string(), }); } let mut parser = AsyncMediaParser::new(); let iter: ExifIter = match parser.parse(ms).await { Ok(iter) => iter, Err(e) => { // It's not a fatal error, just means parsing failed (e.g., corrupt data) return Ok(PluginData { message: format!("Could not parse EXIF: {}", e), }); } }; let location: Option = match iter.parse_gps_info() { Ok(Some(gps_info)) => Some(gps_info.format_iso6709()), Ok(None) => None, Err(_) => None, }; let exif: Exif = iter.into(); let width = exif .get(ExifTag::ExifImageWidth) .and_then(|f| f.as_u32()) .map(|v| v as i32); let height = exif .get(ExifTag::ExifImageHeight) .and_then(|f| f.as_u32()) .map(|v| v as i32); if width.is_some() || height.is_some() || location.is_some() { context .media_repo .update_metadata(media.id, width, height, location.clone()) .await?; let message = format!( "Extracted EXIF: width={:?}, height={:?}, location={:?}", width, height, location ); Ok(PluginData { message }) } else { Ok(PluginData { message: "No EXIF width/height or GPS location found.".to_string(), }) } } }