feat: implement media metadata management with EXIF and TrackInfo support
This commit is contained in:
@@ -3,7 +3,7 @@ use std::{path::PathBuf, sync::Arc};
|
||||
use futures_util::StreamExt;
|
||||
use libertas_core::plugins::PluginContext;
|
||||
use libertas_infra::factory::{
|
||||
build_album_repository, build_database_pool, build_media_repository, build_user_repository,
|
||||
build_album_repository, build_database_pool, build_media_metadata_repository, build_media_repository, build_user_repository
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tokio::fs;
|
||||
@@ -37,11 +37,14 @@ async fn main() -> anyhow::Result<()> {
|
||||
let media_repo = build_media_repository(&config, db_pool.clone()).await?;
|
||||
let album_repo = build_album_repository(&config.database, db_pool.clone()).await?;
|
||||
let user_repo = build_user_repository(&config.database, db_pool.clone()).await?;
|
||||
let metadata_repo =
|
||||
build_media_metadata_repository(&config.database, db_pool.clone()).await?;
|
||||
|
||||
let context = Arc::new(PluginContext {
|
||||
media_repo,
|
||||
album_repo,
|
||||
user_repo,
|
||||
metadata_repo,
|
||||
media_library_path: config.media_library_path.clone(),
|
||||
config: Arc::new(config.clone()),
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use async_trait::async_trait;
|
||||
use uuid::Uuid;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use libertas_core::{
|
||||
error::CoreResult, media_utils::extract_exif_data, models::Media, plugins::{MediaProcessorPlugin, PluginContext, PluginData}
|
||||
error::CoreResult, media_utils::extract_exif_data, models::{Media, MediaMetadata}, plugins::{MediaProcessorPlugin, PluginContext, PluginData}
|
||||
};
|
||||
|
||||
pub struct ExifReaderPlugin;
|
||||
@@ -16,8 +17,8 @@ impl MediaProcessorPlugin for ExifReaderPlugin {
|
||||
async fn process(&self, media: &Media, context: &PluginContext) -> CoreResult<PluginData> {
|
||||
let file_path = PathBuf::from(&context.media_library_path).join(&media.storage_path);
|
||||
|
||||
let (width, height, location, date_taken) = match extract_exif_data(&file_path).await {
|
||||
Ok(data) => (data.width, data.height, data.location, data.date_taken),
|
||||
let extracted_data = match extract_exif_data(&file_path).await {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return Ok(PluginData {
|
||||
message: format!("Could not parse EXIF: {}", e),
|
||||
@@ -25,21 +26,28 @@ impl MediaProcessorPlugin for ExifReaderPlugin {
|
||||
}
|
||||
};
|
||||
|
||||
if width.is_some() || height.is_some() || location.is_some() || date_taken.is_some() {
|
||||
context
|
||||
.media_repo
|
||||
.update_exif_data(media.id, width, height, location.clone(), date_taken)
|
||||
.await?;
|
||||
|
||||
let message = format!(
|
||||
"Extracted EXIF: width={:?}, height={:?}, location={:?}, date_taken={:?}",
|
||||
width, height, location, date_taken
|
||||
);
|
||||
Ok(PluginData { message })
|
||||
} else {
|
||||
Ok(PluginData {
|
||||
message: "No EXIF width/height or GPS location found.".to_string(),
|
||||
})
|
||||
let mut metadata_models = Vec::new();
|
||||
for (source, tag_name, tag_value) in extracted_data.all_tags {
|
||||
metadata_models.push(MediaMetadata {
|
||||
id: Uuid::new_v4(),
|
||||
media_id: media.id,
|
||||
source,
|
||||
tag_name,
|
||||
tag_value,
|
||||
});
|
||||
}
|
||||
|
||||
let message;
|
||||
if !metadata_models.is_empty() {
|
||||
context
|
||||
.metadata_repo
|
||||
.create_batch(&metadata_models)
|
||||
.await?;
|
||||
message = format!("Saved {} metadata tags.", metadata_models.len());
|
||||
} else {
|
||||
message = "No metadata tags found.".to_string();
|
||||
}
|
||||
|
||||
Ok(PluginData { message })
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,8 @@ impl MediaProcessorPlugin for XmpWriterPlugin {
|
||||
}
|
||||
|
||||
async fn process(&self, media: &Media, context: &PluginContext) -> CoreResult<PluginData> {
|
||||
let fresh_media = context
|
||||
.media_repo
|
||||
.find_by_id(media.id)
|
||||
.await?
|
||||
.ok_or(CoreError::NotFound("Media".to_string(), media.id))?;
|
||||
|
||||
let file_path = PathBuf::from(&context.media_library_path).join(&fresh_media.storage_path);
|
||||
let metadata = context.metadata_repo.find_by_media_id(media.id).await?;
|
||||
let file_path = PathBuf::from(&context.media_library_path).join(&media.storage_path);
|
||||
let xmp_path = format!("{}.xmp", file_path.to_string_lossy());
|
||||
|
||||
let mut xmp = XmpMeta::new()
|
||||
@@ -33,28 +28,24 @@ impl MediaProcessorPlugin for XmpWriterPlugin {
|
||||
xmp.set_property(
|
||||
"http://purl.org/dc/elements/1.1/",
|
||||
"description",
|
||||
&fresh_media.original_filename.into(),
|
||||
&XmpValue::from(media.original_filename.as_str()),
|
||||
)
|
||||
.map_err(|e| {
|
||||
CoreError::Unknown(format!("Failed to set description property in XMP: {}", e))
|
||||
})?;
|
||||
|
||||
if let Some(date_taken) = &fresh_media.date_taken {
|
||||
let date_str = date_taken.to_rfc3339();
|
||||
if let Some(date_tag) = metadata.iter().find(|m| m.tag_name == "DateTimeOriginal") {
|
||||
let date_str = &date_tag.tag_value;
|
||||
xmp.set_property(
|
||||
"http://ns.adobe.com/exif/1.0/",
|
||||
"DateTimeOriginal",
|
||||
&XmpValue::from(date_str),
|
||||
&XmpValue::from(date_str.as_str()),
|
||||
)
|
||||
.map_err(|e| {
|
||||
CoreError::Unknown(format!("Failed to set DateTimeOriginal in XMP: {}", e))
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Some(_location) = &fresh_media.extracted_location {
|
||||
// TODO: Set location properties in XMP
|
||||
}
|
||||
|
||||
let xmp_str = xmp.to_string();
|
||||
|
||||
fs::write(&xmp_path, xmp_str).await?;
|
||||
|
||||
Reference in New Issue
Block a user