feat: add date_taken field to media model and update related functionalities
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1245,6 +1245,7 @@ dependencies = [
|
|||||||
"async-nats",
|
"async-nats",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"libertas_core",
|
"libertas_core",
|
||||||
"libertas_infra",
|
"libertas_infra",
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE media
|
||||||
|
ADD COLUMN date_taken TIMESTAMPTZ;
|
||||||
|
|
||||||
|
CREATE INDEX idx_media_date_taken ON media (date_taken);
|
||||||
@@ -259,6 +259,7 @@ impl MediaServiceImpl {
|
|||||||
extracted_location: None,
|
extracted_location: None,
|
||||||
width: None,
|
width: None,
|
||||||
height: None,
|
height: None,
|
||||||
|
date_taken: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.repo.create(&media_model).await?;
|
self.repo.create(&media_model).await?;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ pub struct Media {
|
|||||||
pub extracted_location: Option<String>,
|
pub extracted_location: Option<String>,
|
||||||
pub width: Option<i32>,
|
pub width: Option<i32>,
|
||||||
pub height: Option<i32>,
|
pub height: Option<i32>,
|
||||||
|
pub date_taken: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub trait MediaRepository: Send + Sync {
|
|||||||
width: Option<i32>,
|
width: Option<i32>,
|
||||||
height: Option<i32>,
|
height: Option<i32>,
|
||||||
location: Option<String>,
|
location: Option<String>,
|
||||||
|
date_taken: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
) -> CoreResult<()>;
|
) -> CoreResult<()>;
|
||||||
async fn delete(&self, id: Uuid) -> CoreResult<()>;
|
async fn delete(&self, id: Uuid) -> CoreResult<()>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ pub struct PostgresMedia {
|
|||||||
pub extracted_location: Option<String>,
|
pub extracted_location: Option<String>,
|
||||||
pub width: Option<i32>,
|
pub width: Option<i32>,
|
||||||
pub height: Option<i32>,
|
pub height: Option<i32>,
|
||||||
|
pub date_taken: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, sqlx::Type, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, Clone, Copy, sqlx::Type, PartialEq, Eq, Deserialize)]
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ impl From<PostgresMedia> for Media {
|
|||||||
extracted_location: pg_media.extracted_location,
|
extracted_location: pg_media.extracted_location,
|
||||||
width: pg_media.width,
|
width: pg_media.width,
|
||||||
height: pg_media.height,
|
height: pg_media.height,
|
||||||
|
date_taken: pg_media.date_taken,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ impl MediaRepository for PostgresMediaRepository {
|
|||||||
PostgresMedia,
|
PostgresMedia,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at,
|
SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at,
|
||||||
extracted_location, width, height
|
extracted_location, width, height, date_taken
|
||||||
FROM media
|
FROM media
|
||||||
WHERE hash = $1
|
WHERE hash = $1
|
||||||
"#,
|
"#,
|
||||||
@@ -68,7 +68,7 @@ impl MediaRepository for PostgresMediaRepository {
|
|||||||
PostgresMedia,
|
PostgresMedia,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at,
|
SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at,
|
||||||
extracted_location, width, height
|
extracted_location, width, height, date_taken
|
||||||
FROM media
|
FROM media
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
"#,
|
"#,
|
||||||
@@ -86,7 +86,7 @@ impl MediaRepository for PostgresMediaRepository {
|
|||||||
PostgresMedia,
|
PostgresMedia,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at,
|
SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at,
|
||||||
extracted_location, width, height
|
extracted_location, width, height, date_taken
|
||||||
FROM media
|
FROM media
|
||||||
WHERE owner_id = $1
|
WHERE owner_id = $1
|
||||||
"#,
|
"#,
|
||||||
@@ -105,17 +105,19 @@ impl MediaRepository for PostgresMediaRepository {
|
|||||||
width: Option<i32>,
|
width: Option<i32>,
|
||||||
height: Option<i32>,
|
height: Option<i32>,
|
||||||
location: Option<String>,
|
location: Option<String>,
|
||||||
|
date_taken: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
) -> CoreResult<()> {
|
) -> CoreResult<()> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE media
|
UPDATE media
|
||||||
SET width = $2, height = $3, extracted_location = $4
|
SET width = $2, height = $3, extracted_location = $4, date_taken = $5
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
location
|
location,
|
||||||
|
date_taken
|
||||||
)
|
)
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -25,3 +25,4 @@ uuid = { version = "1.18.1", features = ["v4", "serde"] }
|
|||||||
nom-exif = { version = "2.5.4", features = ["serde", "tokio", "async"] }
|
nom-exif = { version = "2.5.4", features = ["serde", "tokio", "async"] }
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
xmp_toolkit = "1.11.0"
|
xmp_toolkit = "1.11.0"
|
||||||
|
chrono = "0.4.42"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use libertas_core::{
|
use libertas_core::{
|
||||||
@@ -59,15 +60,20 @@ impl MediaProcessorPlugin for ExifReaderPlugin {
|
|||||||
.and_then(|f| f.as_u32())
|
.and_then(|f| f.as_u32())
|
||||||
.map(|v| v as i32);
|
.map(|v| v as i32);
|
||||||
|
|
||||||
if width.is_some() || height.is_some() || location.is_some() {
|
let date_taken = exif
|
||||||
|
.get(ExifTag::DateTimeOriginal)
|
||||||
|
.and_then(|f| f.as_str())
|
||||||
|
.and_then(parse_exif_datetime);
|
||||||
|
|
||||||
|
if width.is_some() || height.is_some() || location.is_some() || date_taken.is_some() {
|
||||||
context
|
context
|
||||||
.media_repo
|
.media_repo
|
||||||
.update_metadata(media.id, width, height, location.clone())
|
.update_metadata(media.id, width, height, location.clone(), date_taken)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"Extracted EXIF: width={:?}, height={:?}, location={:?}",
|
"Extracted EXIF: width={:?}, height={:?}, location={:?}, date_taken={:?}",
|
||||||
width, height, location
|
width, height, location, date_taken
|
||||||
);
|
);
|
||||||
Ok(PluginData { message })
|
Ok(PluginData { message })
|
||||||
} else {
|
} else {
|
||||||
@@ -77,3 +83,10 @@ impl MediaProcessorPlugin for ExifReaderPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_exif_datetime(s: &str) -> Option<DateTime<Utc>> {
|
||||||
|
// EXIF datetime format is 'YYYY:MM:DD HH:MM:SS'
|
||||||
|
NaiveDateTime::parse_from_str(s, "%Y:%m:%d %H:%M:%S")
|
||||||
|
.ok()
|
||||||
|
.map(|ndt| ndt.and_local_timezone(Utc).unwrap())
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use libertas_core::{
|
|||||||
plugins::{MediaProcessorPlugin, PluginContext, PluginData},
|
plugins::{MediaProcessorPlugin, PluginContext, PluginData},
|
||||||
};
|
};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use xmp_toolkit::XmpMeta;
|
use xmp_toolkit::{XmpMeta, XmpValue};
|
||||||
|
|
||||||
pub struct XmpWriterPlugin;
|
pub struct XmpWriterPlugin;
|
||||||
|
|
||||||
@@ -39,6 +39,18 @@ impl MediaProcessorPlugin for XmpWriterPlugin {
|
|||||||
CoreError::Unknown(format!("Failed to set description property in XMP: {}", 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();
|
||||||
|
xmp.set_property(
|
||||||
|
"http://ns.adobe.com/exif/1.0/",
|
||||||
|
"DateTimeOriginal",
|
||||||
|
&XmpValue::from(date_str),
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
CoreError::Unknown(format!("Failed to set DateTimeOriginal in XMP: {}", e))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(_location) = &fresh_media.extracted_location {
|
if let Some(_location) = &fresh_media.extracted_location {
|
||||||
// TODO: Set location properties in XMP
|
// TODO: Set location properties in XMP
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user