mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 11:03:27 +00:00
Improve Thumbnail quality + fixes (#2467)
* Improve Thumbnail quality - Increase thumbnail size to 1024 - Increse webp quality to 60% * Fix thumbnails reactivity for ephemeral files * Fix negative BigInt convertion * Fix overflow in javascript --------- Co-authored-by: Ericson Soares <ericson.ds999@gmail.com>
This commit is contained in:
parent
8e994bedaa
commit
0d451d6d90
|
@ -57,7 +57,7 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
|
|||
.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);
|
||||
|
|
|
@ -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<bool, FileIOError> {
|
||||
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<DateTime<Utc>>) {
|
||||
let notification = Notification {
|
||||
id: NotificationId::Node(self.notifications._internal_next_id()),
|
||||
|
|
|
@ -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)?,
|
||||
)
|
||||
|
|
|
@ -249,9 +249,9 @@ pub async fn save_ffmpeg_data(
|
|||
|
||||
async fn create_ffmpeg_data(
|
||||
formats: Vec<String>,
|
||||
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,
|
||||
|
|
|
@ -90,15 +90,15 @@ pub fn ffmpeg_data_from_prisma_data(
|
|||
formats: formats.split(',').map(String::from).collect::<Vec<_>>(),
|
||||
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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -475,7 +475,7 @@ async fn generate_video_thumbnail(
|
|||
to_thumbnail(
|
||||
file_path,
|
||||
output_path,
|
||||
ThumbnailSize::Scale(256),
|
||||
ThumbnailSize::Scale(1024),
|
||||
TARGET_QUALITY,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -21,9 +21,9 @@ use program::Program;
|
|||
#[derive(Debug, Serialize, Deserialize, Type)]
|
||||
pub struct FFmpegMetadata {
|
||||
pub formats: Vec<String>,
|
||||
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<Chapter>,
|
||||
pub programs: Vec<Program>,
|
||||
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,
|
||||
|
|
|
@ -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<T extends string>(string: T): Capitalize<T> {
|
||||
|
|
Loading…
Reference in a new issue