feat: implement media processing plugins and update repository structure
This commit is contained in:
@@ -8,9 +8,11 @@ use libertas_infra::factory::{
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::load_config;
|
||||
use crate::{config::load_config, plugin_manager::PluginManager};
|
||||
|
||||
pub mod config;
|
||||
pub mod plugin_manager;
|
||||
pub mod plugins;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MediaJob {
|
||||
@@ -30,14 +32,16 @@ async fn main() -> anyhow::Result<()> {
|
||||
let album_repo = build_album_repository(&config.database, db_pool.clone()).await?;
|
||||
let user_repo = build_user_repository(&config.database, db_pool.clone()).await?;
|
||||
|
||||
// 3. Create the abstracted PluginContext
|
||||
let context = Arc::new(PluginContext {
|
||||
media_repo,
|
||||
album_repo,
|
||||
user_repo,
|
||||
media_library_path: config.media_library_path.clone(),
|
||||
});
|
||||
println!("Plugin context created.");
|
||||
|
||||
let plugin_manager = Arc::new(PluginManager::new());
|
||||
|
||||
let nats_client = async_nats::connect(&config.broker_url).await?;
|
||||
|
||||
println!("Connected to NATS server at {}", config.broker_url);
|
||||
@@ -49,8 +53,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
while let Some(msg) = subscriber.next().await {
|
||||
let context = context.clone();
|
||||
let manager = plugin_manager.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process_job(msg, context).await {
|
||||
if let Err(e) = process_job(msg, context, manager).await {
|
||||
eprintln!("Job failed: {}", e);
|
||||
}
|
||||
});
|
||||
@@ -59,7 +64,11 @@ async fn main() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_job(msg: async_nats::Message, context: Arc<PluginContext>) -> anyhow::Result<()> {
|
||||
async fn process_job(
|
||||
msg: async_nats::Message,
|
||||
context: Arc<PluginContext>,
|
||||
plugin_manager: Arc<PluginManager>,
|
||||
) -> anyhow::Result<()> {
|
||||
let job: MediaJob = serde_json::from_slice(&msg.payload)?;
|
||||
|
||||
let media = context
|
||||
@@ -70,10 +79,8 @@ async fn process_job(msg: async_nats::Message, context: Arc<PluginContext>) -> a
|
||||
|
||||
println!("Processing media: {}", media.original_filename);
|
||||
|
||||
// 3. Pass to the (future) PluginManager
|
||||
// plugin_manager.process(&media, &context).await?;
|
||||
plugin_manager.process_media(&media, &context).await;
|
||||
|
||||
// For now, we'll just print a success message
|
||||
println!("Successfully processed job for media_id: {}", media.id);
|
||||
|
||||
Ok(())
|
||||
|
||||
38
libertas_worker/src/plugin_manager.rs
Normal file
38
libertas_worker/src/plugin_manager.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use libertas_core::{
|
||||
models::Media,
|
||||
plugins::{MediaProcessorPlugin, PluginContext},
|
||||
};
|
||||
|
||||
use crate::plugins::exif_reader::ExifReaderPlugin;
|
||||
|
||||
pub struct PluginManager {
|
||||
plugins: Vec<Arc<dyn MediaProcessorPlugin>>,
|
||||
}
|
||||
|
||||
impl PluginManager {
|
||||
pub fn new() -> Self {
|
||||
let mut plugins: Vec<Arc<dyn MediaProcessorPlugin>> = Vec::new();
|
||||
|
||||
plugins.push(Arc::new(ExifReaderPlugin));
|
||||
|
||||
println!("PluginManager loaded {} plugins", plugins.len());
|
||||
Self { plugins }
|
||||
}
|
||||
|
||||
pub async fn process_media(&self, media: &Media, context: &Arc<PluginContext>) {
|
||||
println!(
|
||||
"PluginManager processing media: {}",
|
||||
media.original_filename
|
||||
);
|
||||
for plugin in &self.plugins {
|
||||
println!("Running plugin: {}", plugin.name());
|
||||
match plugin.process(media, context).await {
|
||||
Ok(data) => println!("Plugin {} succeeded: {}", plugin.name(), data.message),
|
||||
Err(e) => eprintln!("Plugin {} failed: {}", plugin.name(), e),
|
||||
}
|
||||
}
|
||||
println!("PluginManager finished processing media: {}", media.id);
|
||||
}
|
||||
}
|
||||
79
libertas_worker/src/plugins/exif_reader.rs
Normal file
79
libertas_worker/src/plugins/exif_reader.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
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<PluginData> {
|
||||
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<String> = 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
1
libertas_worker/src/plugins/mod.rs
Normal file
1
libertas_worker/src/plugins/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod exif_reader;
|
||||
Reference in New Issue
Block a user