diff --git a/migrations/20250127210244_video_metadata.sql b/migrations/20250127210244_video_metadata.sql new file mode 100644 index 0000000..a7e5d97 --- /dev/null +++ b/migrations/20250127210244_video_metadata.sql @@ -0,0 +1,4 @@ +-- Add migration script here +alter table uploads + add column duration float, + add column bitrate integer unsigned; \ No newline at end of file diff --git a/src/bin/void_cat_migrate.rs b/src/bin/void_cat_migrate.rs index 4ddc195..2c08fa1 100644 --- a/src/bin/void_cat_migrate.rs +++ b/src/bin/void_cat_migrate.rs @@ -139,6 +139,8 @@ async fn migrate_file( }, blur_hash: None, alt: f.description.clone(), + duration: None, + bitrate: None, }; db.add_file(&fu, uid).await?; Ok(()) diff --git a/src/db.rs b/src/db.rs index 0c9bcdd..b678057 100644 --- a/src/db.rs +++ b/src/db.rs @@ -25,6 +25,10 @@ pub struct FileUpload { pub blur_hash: Option, /// Alt text of the media pub alt: Option, + /// Duration of media in seconds + pub duration: Option, + /// Average bitrate in bits/s + pub bitrate: Option, #[sqlx(skip)] #[cfg(feature = "labels")] @@ -43,6 +47,8 @@ impl From<&NewFileResult> for FileUpload { height: value.height, blur_hash: value.blur_hash.clone(), alt: None, + duration: value.duration, + bitrate: value.bitrate, #[cfg(feature = "labels")] labels: value.labels.clone(), } @@ -145,7 +151,7 @@ impl Database { pub async fn add_file(&self, file: &FileUpload, user_id: u64) -> Result<(), Error> { let mut tx = self.pool.begin().await?; let q = sqlx::query("insert ignore into \ - uploads(id,name,size,mime_type,blur_hash,width,height,alt,created) values(?,?,?,?,?,?,?,?,?)") + uploads(id,name,size,mime_type,blur_hash,width,height,alt,created,duration,bitrate) values(?,?,?,?,?,?,?,?,?,?,?)") .bind(&file.id) .bind(&file.name) .bind(file.size) @@ -154,7 +160,9 @@ impl Database { .bind(file.width) .bind(file.height) .bind(&file.alt) - .bind(file.created); + .bind(file.created) + .bind(file.duration) + .bind(file.bitrate); tx.execute(q).await?; let q2 = sqlx::query("insert ignore into user_uploads(file,user_id) values(?,?)") diff --git a/src/filesystem.rs b/src/filesystem.rs index 7766b6d..52f7085 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -1,5 +1,6 @@ #[cfg(feature = "labels")] use crate::db::FileLabel; +use crate::processing::can_compress; #[cfg(feature = "labels")] use crate::processing::labeling::label_frame; #[cfg(feature = "media-compression")] @@ -36,6 +37,8 @@ pub struct NewFileResult { pub width: Option, pub height: Option, pub blur_hash: Option, + pub duration: Option, + pub bitrate: Option, #[cfg(feature = "labels")] pub labels: Vec, } @@ -74,7 +77,7 @@ impl FileStore { return Ok(FileSystemResult::AlreadyExists(hash)); } - let mut res = if compress { + let mut res = if compress && can_compress(mime_type) { #[cfg(feature = "media-compression")] { let res = match self.compress_file(&temp_file, mime_type).await { @@ -92,7 +95,7 @@ impl FileStore { anyhow::bail!("Compression not supported!"); } } else { - let (width, height, mime_type) = { + let (width, height, mime_type, duration, bitrate) = { #[cfg(feature = "media-compression")] { let probe = probe_file(&temp_file).ok(); @@ -102,10 +105,18 @@ impl FileStore { v_stream.map(|v| v.width as u32), v_stream.map(|v| v.height as u32), mime, + probe.as_ref().map(|p| p.duration), + probe.as_ref().map(|p| p.bitrate as u32), ) } #[cfg(not(feature = "media-compression"))] - (None, None, Self::infer_mime_type(mime_type, &temp_file)) + ( + None, + None, + Self::infer_mime_type(mime_type, &temp_file), + None, + None, + ) }; NewFileResult { path: temp_file, @@ -115,6 +126,8 @@ impl FileStore { width, height, blur_hash: None, + duration, + bitrate, } }; @@ -194,6 +207,8 @@ impl FileStore { height: Some(compressed_result.height as u32), blur_hash: None, mime_type: compressed_result.mime_type, + duration: Some(compressed_result.duration), + bitrate: Some(compressed_result.bitrate), #[cfg(feature = "labels")] labels, }) diff --git a/src/processing/mod.rs b/src/processing/mod.rs index 667466c..6137d6f 100644 --- a/src/processing/mod.rs +++ b/src/processing/mod.rs @@ -66,6 +66,8 @@ impl WebpProcessor { mime_type: "image/webp".to_string(), width: image_stream.width, height: image_stream.height, + duration: probe.duration, + bitrate: probe.bitrate as u32, }) } } @@ -126,6 +128,8 @@ pub struct NewFileProcessorResult { pub mime_type: String, pub width: usize, pub height: usize, + pub duration: f32, + pub bitrate: u32, } pub fn can_compress(mime_type: &str) -> bool { diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 569a16e..dcd9aa2 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -81,6 +81,13 @@ impl Nip94Event { if let (Some(w), Some(h)) = (upload.width, upload.height) { tags.push(vec!["dim".to_string(), format!("{}x{}", w, h)]) } + if let Some(d) = &upload.duration { + tags.push(vec!["duration".to_string(), d.to_string()]); + } + if let Some(b) = &upload.bitrate { + tags.push(vec!["bitrate".to_string(), b.to_string()]); + } + #[cfg(feature = "labels")] for l in &upload.labels { let val = if l.label.contains(',') {