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:
Vítor Vasconcellos 2024-05-09 02:48:43 -03:00 committed by GitHub
parent 8e994bedaa
commit 0d451d6d90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 50 additions and 34 deletions

View file

@ -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);

View file

@ -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()),

View file

@ -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)?,
)

View file

@ -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,

View file

@ -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,

View file

@ -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();

View file

@ -475,7 +475,7 @@ async fn generate_video_thumbnail(
to_thumbnail(
file_path,
output_path,
ThumbnailSize::Scale(256),
ThumbnailSize::Scale(1024),
TARGET_QUALITY,
)
.await

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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> {