diff --git a/core/src/api/ephemeral_files.rs b/core/src/api/ephemeral_files.rs index b098144a1..f16982bcb 100644 --- a/core/src/api/ephemeral_files.rs +++ b/core/src/api/ephemeral_files.rs @@ -57,7 +57,7 @@ pub(crate) fn mount() -> AlphaRouter { .await .map(Into::into); match kind { - Some(v) if v == ObjectKind::Image => { + Some(ObjectKind::Image) => { let Some(extension) = full_path.extension().and_then(|ext| ext.to_str()) else { return Ok(None); diff --git a/core/src/lib.rs b/core/src/lib.rs index 49762e857..037f57e1a 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,6 +9,7 @@ use crate::{ #[cfg(feature = "ai")] use sd_ai::old_image_labeler::{DownloadModelError, OldImageLabeler, YoloV8}; +use sd_utils::error::FileIOError; use api::notifications::{Notification, NotificationData, NotificationId}; use chrono::{DateTime, Utc}; @@ -23,7 +24,7 @@ use std::{ }; use thiserror::Error; -use tokio::{fs, sync::broadcast}; +use tokio::{fs, io, sync::broadcast}; use tracing::{error, info, warn}; use tracing_appender::{ non_blocking::{NonBlocking, WorkerGuard}, @@ -51,6 +52,8 @@ pub(crate) mod volume; pub use env::Env; +use object::media::old_thumbnail::get_ephemeral_thumbnail_path; + pub(crate) use sd_core_sync as sync; /// Represents a single running instance of the Spacedrive core. @@ -268,6 +271,16 @@ impl Node { } } + pub async fn ephemeral_thumbnail_exists(&self, cas_id: &str) -> Result { + let thumb_path = get_ephemeral_thumbnail_path(self, cas_id); + + match fs::metadata(&thumb_path).await { + Ok(_) => Ok(true), + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false), + Err(e) => Err(FileIOError::from((thumb_path, e))), + } + } + pub async fn emit_notification(&self, data: NotificationData, expires: Option>) { let notification = Notification { id: NotificationId::Node(self.notifications._internal_next_id()), diff --git a/core/src/location/non_indexed.rs b/core/src/location/non_indexed.rs index 6c69cc7b3..b5977cb67 100644 --- a/core/src/location/non_indexed.rs +++ b/core/src/location/non_indexed.rs @@ -223,8 +223,7 @@ pub async fn walk( ( Some(get_ephemeral_thumb_key(&cas_id)), - library - .thumbnail_exists(&node, &cas_id) + node.ephemeral_thumbnail_exists(&cas_id) .await .map_err(NonIndexedLocationError::from)?, ) diff --git a/core/src/object/media/ffmpeg_metadata_extractor.rs b/core/src/object/media/ffmpeg_metadata_extractor.rs index 14e5fcc4d..754098ecc 100644 --- a/core/src/object/media/ffmpeg_metadata_extractor.rs +++ b/core/src/object/media/ffmpeg_metadata_extractor.rs @@ -249,9 +249,9 @@ pub async fn save_ffmpeg_data( async fn create_ffmpeg_data( formats: Vec, - bit_rate: (u32, u32), - duration: Option<(u32, u32)>, - start_time: Option<(u32, u32)>, + bit_rate: (i32, u32), + duration: Option<(i32, u32)>, + start_time: Option<(i32, u32)>, metadata: Metadata, object_id: i32, db: &PrismaClient, diff --git a/core/src/object/media/mod.rs b/core/src/object/media/mod.rs index 952aa2092..271abe873 100644 --- a/core/src/object/media/mod.rs +++ b/core/src/object/media/mod.rs @@ -90,15 +90,15 @@ pub fn ffmpeg_data_from_prisma_data( formats: formats.split(',').map(String::from).collect::>(), duration: duration.map(|duration| { let duration = ffmpeg_data_field_from_db(&duration); - ((duration >> 32) as u32, duration as u32) + ((duration >> 32) as i32, duration as u32) }), start_time: start_time.map(|start_time| { let start_time = ffmpeg_data_field_from_db(&start_time); - ((start_time >> 32) as u32, start_time as u32) + ((start_time >> 32) as i32, start_time as u32) }), bit_rate: { let bit_rate = ffmpeg_data_field_from_db(&bit_rate); - ((bit_rate >> 32) as u32, bit_rate as u32) + ((bit_rate >> 32) as i32, bit_rate as u32) }, chapters: chapters .into_iter() @@ -115,11 +115,11 @@ pub fn ffmpeg_data_from_prisma_data( id: chapter_id, start: { let start = ffmpeg_data_field_from_db(&start); - ((start >> 32) as u32, start as u32) + ((start >> 32) as i32, start as u32) }, end: { let end = ffmpeg_data_field_from_db(&end); - ((end >> 32) as u32, end as u32) + ((end >> 32) as i32, end as u32) }, time_base_den, time_base_num, diff --git a/core/src/object/media/old_thumbnail/mod.rs b/core/src/object/media/old_thumbnail/mod.rs index 0880800a9..b55cf833d 100644 --- a/core/src/object/media/old_thumbnail/mod.rs +++ b/core/src/object/media/old_thumbnail/mod.rs @@ -42,11 +42,11 @@ const EPHEMERAL_DIR: &str = "ephemeral"; /// This is the target pixel count for all thumbnails to be resized to, and it is eventually downscaled /// to [`TARGET_QUALITY`]. -const TARGET_PX: f32 = 262144_f32; +const TARGET_PX: f32 = 1048576.0; // 1024x1024 /// This is the target quality that we render thumbnails at, it is a float between 0-100 -/// and is treated as a percentage (so 30% in this case, or it's the same as multiplying by `0.3`). -const TARGET_QUALITY: f32 = 30_f32; +/// and is treated as a percentage (so 60% in this case, or it's the same as multiplying by `0.6`). +const TARGET_QUALITY: f32 = 60.0; // Some time constants const ONE_SEC: Duration = Duration::from_secs(1); @@ -63,6 +63,10 @@ pub fn get_indexed_thumbnail_path(node: &Node, cas_id: &str, library_id: Library get_thumbnail_path(node, cas_id, ThumbnailKind::Indexed(library_id)) } +pub fn get_ephemeral_thumbnail_path(node: &Node, cas_id: &str) -> PathBuf { + get_thumbnail_path(node, cas_id, ThumbnailKind::Ephemeral) +} + /// This does not check if a thumbnail exists, it just returns the path that it would exist at fn get_thumbnail_path(node: &Node, cas_id: &str, kind: ThumbnailKind) -> PathBuf { let mut thumb_path = node.config.data_directory(); diff --git a/core/src/object/media/old_thumbnail/process.rs b/core/src/object/media/old_thumbnail/process.rs index f9983f9e5..136680551 100644 --- a/core/src/object/media/old_thumbnail/process.rs +++ b/core/src/object/media/old_thumbnail/process.rs @@ -475,7 +475,7 @@ async fn generate_video_thumbnail( to_thumbnail( file_path, output_path, - ThumbnailSize::Scale(256), + ThumbnailSize::Scale(1024), TARGET_QUALITY, ) .await diff --git a/crates/ffmpeg/src/thumbnailer.rs b/crates/ffmpeg/src/thumbnailer.rs index 88109f2c6..afd008813 100644 --- a/crates/ffmpeg/src/thumbnailer.rs +++ b/crates/ffmpeg/src/thumbnailer.rs @@ -146,7 +146,7 @@ impl Default for ThumbnailerBuilder { fn default() -> Self { Self { maintain_aspect_ratio: true, - size: ThumbnailSize::Scale(128), + size: ThumbnailSize::Scale(1024), seek_percentage: 0.1, quality: 80.0, prefer_embedded_metadata: true, diff --git a/crates/media-metadata/src/ffmpeg/chapter.rs b/crates/media-metadata/src/ffmpeg/chapter.rs index 7944213bf..681ff9679 100644 --- a/crates/media-metadata/src/ffmpeg/chapter.rs +++ b/crates/media-metadata/src/ffmpeg/chapter.rs @@ -6,8 +6,8 @@ use super::metadata::Metadata; #[derive(Debug, Serialize, Deserialize, Type)] pub struct Chapter { pub id: i32, - pub start: (u32, u32), - pub end: (u32, u32), + pub start: (i32, u32), + pub end: (i32, u32), pub time_base_den: i32, pub time_base_num: i32, pub metadata: Metadata, diff --git a/crates/media-metadata/src/ffmpeg/mod.rs b/crates/media-metadata/src/ffmpeg/mod.rs index 074dd42f6..af7740413 100644 --- a/crates/media-metadata/src/ffmpeg/mod.rs +++ b/crates/media-metadata/src/ffmpeg/mod.rs @@ -21,9 +21,9 @@ use program::Program; #[derive(Debug, Serialize, Deserialize, Type)] pub struct FFmpegMetadata { pub formats: Vec, - pub duration: Option<(u32, u32)>, - pub start_time: Option<(u32, u32)>, - pub bit_rate: (u32, u32), + pub duration: Option<(i32, u32)>, + pub start_time: Option<(i32, u32)>, + pub bit_rate: (i32, u32), pub chapters: Vec, pub programs: Vec, pub metadata: Metadata, @@ -70,24 +70,24 @@ mod extract_data { Self { formats, duration: duration.map(|duration| { - #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] { // SAFETY: We're splitting in (high, low) parts, so we're not going to lose data on truncation - ((duration >> 32) as u32, duration as u32) + ((duration >> 32) as i32, duration as u32) } }), start_time: start_time.map(|start_time| { - #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] { // SAFETY: We're splitting in (high, low) parts, so we're not going to lose data on truncation - ((start_time >> 32) as u32, start_time as u32) + ((start_time >> 32) as i32, start_time as u32) } }), bit_rate: { - #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] { // SAFETY: We're splitting in (high, low) parts, so we're not going to lose data on truncation - ((bit_rate >> 32) as u32, bit_rate as u32) + ((bit_rate >> 32) as i32, bit_rate as u32) } }, chapters: chapters.into_iter().map(Into::into).collect(), @@ -118,17 +118,17 @@ mod extract_data { }, // TODO: FIX these 2 when rspc/specta supports bigint start: { - #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] { // SAFETY: We're splitting in (high, low) parts, so we're not going to lose data on truncation - ((start >> 32) as u32, start as u32) + ((start >> 32) as i32, start as u32) } }, end: { - #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] { // SAFETY: We're splitting in (high, low) parts, so we're not going to lose data on truncation - ((end >> 32) as u32, end as u32) + ((end >> 32) as i32, end as u32) } }, time_base_num, diff --git a/packages/client/src/utils/index.ts b/packages/client/src/utils/index.ts index b4ab41ed4..fb650c5d0 100644 --- a/packages/client/src/utils/index.ts +++ b/packages/client/src/utils/index.ts @@ -137,9 +137,9 @@ export function insertLibrary(queryClient: QueryClient, library: LibraryConfigWr }); } -// [int32, int32] => BigInt export function int32ArrayToBigInt([high, low]: [number, number]) { - return (BigInt(high) << 32n) | BigInt(low); + // Note: These magic shift operations internally convert the high into i32 and the low into u32 + return (BigInt(high | 0) << 32n) | BigInt(low >>> 0); } export function capitalize(string: T): Capitalize {