feat: video duration / bitrate

This commit is contained in:
kieran 2025-01-27 21:19:11 +00:00
parent 5fbe40faae
commit 3ba5e7bc4c
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
6 changed files with 45 additions and 5 deletions

View File

@ -0,0 +1,4 @@
-- Add migration script here
alter table uploads
add column duration float,
add column bitrate integer unsigned;

View File

@ -139,6 +139,8 @@ async fn migrate_file(
}, },
blur_hash: None, blur_hash: None,
alt: f.description.clone(), alt: f.description.clone(),
duration: None,
bitrate: None,
}; };
db.add_file(&fu, uid).await?; db.add_file(&fu, uid).await?;
Ok(()) Ok(())

View File

@ -25,6 +25,10 @@ pub struct FileUpload {
pub blur_hash: Option<String>, pub blur_hash: Option<String>,
/// Alt text of the media /// Alt text of the media
pub alt: Option<String>, pub alt: Option<String>,
/// Duration of media in seconds
pub duration: Option<f32>,
/// Average bitrate in bits/s
pub bitrate: Option<u32>,
#[sqlx(skip)] #[sqlx(skip)]
#[cfg(feature = "labels")] #[cfg(feature = "labels")]
@ -43,6 +47,8 @@ impl From<&NewFileResult> for FileUpload {
height: value.height, height: value.height,
blur_hash: value.blur_hash.clone(), blur_hash: value.blur_hash.clone(),
alt: None, alt: None,
duration: value.duration,
bitrate: value.bitrate,
#[cfg(feature = "labels")] #[cfg(feature = "labels")]
labels: value.labels.clone(), labels: value.labels.clone(),
} }
@ -145,7 +151,7 @@ impl Database {
pub async fn add_file(&self, file: &FileUpload, user_id: u64) -> Result<(), Error> { pub async fn add_file(&self, file: &FileUpload, user_id: u64) -> Result<(), Error> {
let mut tx = self.pool.begin().await?; let mut tx = self.pool.begin().await?;
let q = sqlx::query("insert ignore into \ 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.id)
.bind(&file.name) .bind(&file.name)
.bind(file.size) .bind(file.size)
@ -154,7 +160,9 @@ impl Database {
.bind(file.width) .bind(file.width)
.bind(file.height) .bind(file.height)
.bind(&file.alt) .bind(&file.alt)
.bind(file.created); .bind(file.created)
.bind(file.duration)
.bind(file.bitrate);
tx.execute(q).await?; tx.execute(q).await?;
let q2 = sqlx::query("insert ignore into user_uploads(file,user_id) values(?,?)") let q2 = sqlx::query("insert ignore into user_uploads(file,user_id) values(?,?)")

View File

@ -1,5 +1,6 @@
#[cfg(feature = "labels")] #[cfg(feature = "labels")]
use crate::db::FileLabel; use crate::db::FileLabel;
use crate::processing::can_compress;
#[cfg(feature = "labels")] #[cfg(feature = "labels")]
use crate::processing::labeling::label_frame; use crate::processing::labeling::label_frame;
#[cfg(feature = "media-compression")] #[cfg(feature = "media-compression")]
@ -36,6 +37,8 @@ pub struct NewFileResult {
pub width: Option<u32>, pub width: Option<u32>,
pub height: Option<u32>, pub height: Option<u32>,
pub blur_hash: Option<String>, pub blur_hash: Option<String>,
pub duration: Option<f32>,
pub bitrate: Option<u32>,
#[cfg(feature = "labels")] #[cfg(feature = "labels")]
pub labels: Vec<FileLabel>, pub labels: Vec<FileLabel>,
} }
@ -74,7 +77,7 @@ impl FileStore {
return Ok(FileSystemResult::AlreadyExists(hash)); return Ok(FileSystemResult::AlreadyExists(hash));
} }
let mut res = if compress { let mut res = if compress && can_compress(mime_type) {
#[cfg(feature = "media-compression")] #[cfg(feature = "media-compression")]
{ {
let res = match self.compress_file(&temp_file, mime_type).await { let res = match self.compress_file(&temp_file, mime_type).await {
@ -92,7 +95,7 @@ impl FileStore {
anyhow::bail!("Compression not supported!"); anyhow::bail!("Compression not supported!");
} }
} else { } else {
let (width, height, mime_type) = { let (width, height, mime_type, duration, bitrate) = {
#[cfg(feature = "media-compression")] #[cfg(feature = "media-compression")]
{ {
let probe = probe_file(&temp_file).ok(); 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.width as u32),
v_stream.map(|v| v.height as u32), v_stream.map(|v| v.height as u32),
mime, mime,
probe.as_ref().map(|p| p.duration),
probe.as_ref().map(|p| p.bitrate as u32),
) )
} }
#[cfg(not(feature = "media-compression"))] #[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 { NewFileResult {
path: temp_file, path: temp_file,
@ -115,6 +126,8 @@ impl FileStore {
width, width,
height, height,
blur_hash: None, blur_hash: None,
duration,
bitrate,
} }
}; };
@ -194,6 +207,8 @@ impl FileStore {
height: Some(compressed_result.height as u32), height: Some(compressed_result.height as u32),
blur_hash: None, blur_hash: None,
mime_type: compressed_result.mime_type, mime_type: compressed_result.mime_type,
duration: Some(compressed_result.duration),
bitrate: Some(compressed_result.bitrate),
#[cfg(feature = "labels")] #[cfg(feature = "labels")]
labels, labels,
}) })

View File

@ -66,6 +66,8 @@ impl WebpProcessor {
mime_type: "image/webp".to_string(), mime_type: "image/webp".to_string(),
width: image_stream.width, width: image_stream.width,
height: image_stream.height, height: image_stream.height,
duration: probe.duration,
bitrate: probe.bitrate as u32,
}) })
} }
} }
@ -126,6 +128,8 @@ pub struct NewFileProcessorResult {
pub mime_type: String, pub mime_type: String,
pub width: usize, pub width: usize,
pub height: usize, pub height: usize,
pub duration: f32,
pub bitrate: u32,
} }
pub fn can_compress(mime_type: &str) -> bool { pub fn can_compress(mime_type: &str) -> bool {

View File

@ -81,6 +81,13 @@ impl Nip94Event {
if let (Some(w), Some(h)) = (upload.width, upload.height) { if let (Some(w), Some(h)) = (upload.width, upload.height) {
tags.push(vec!["dim".to_string(), format!("{}x{}", w, h)]) 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")] #[cfg(feature = "labels")]
for l in &upload.labels { for l in &upload.labels {
let val = if l.label.contains(',') { let val = if l.label.contains(',') {