From 7ace99cf1cd0e278289db7d06e9ac80f91c4b353 Mon Sep 17 00:00:00 2001 From: "Ericson \"Fogo\" Soares" Date: Sat, 2 Sep 2023 20:15:41 -0300 Subject: [PATCH] [ENG-1063] Actually regenerate thumbnails on `Regen Thumbnails` button (#1288) Done Co-authored-by: jake <77554505+brxken128@users.noreply.github.com> --- core/src/api/jobs.rs | 16 ++- core/src/location/mod.rs | 2 + core/src/object/media/media_data_extractor.rs | 10 +- core/src/object/media/media_processor/job.rs | 2 + core/src/object/media/media_processor/mod.rs | 2 + .../object/media/media_processor/shallow.rs | 3 +- core/src/object/media/thumbnail/mod.rs | 125 ++++++++++++------ .../Explorer/ContextMenu/FilePath/Items.tsx | 3 +- .../$libraryId/Explorer/ParentContextMenu.tsx | 3 +- packages/client/src/core.ts | 2 +- 10 files changed, 118 insertions(+), 50 deletions(-) diff --git a/core/src/api/jobs.rs b/core/src/api/jobs.rs index 520bb0626..2abb461ed 100644 --- a/core/src/api/jobs.rs +++ b/core/src/api/jobs.rs @@ -227,17 +227,25 @@ pub(crate) fn mount() -> AlphaRouter { pub struct GenerateThumbsForLocationArgs { pub id: location::id::Type, pub path: PathBuf, + #[serde(default)] + pub regenerate: bool, } R.with2(library()).mutation( - |(node, library), args: GenerateThumbsForLocationArgs| async move { - let Some(location) = find_location(&library, args.id).exec().await? else { - return Err(LocationError::IdNotFound(args.id).into()); + |(node, library), + GenerateThumbsForLocationArgs { + id, + path, + regenerate, + }: GenerateThumbsForLocationArgs| async move { + let Some(location) = find_location(&library, id).exec().await? else { + return Err(LocationError::IdNotFound(id).into()); }; Job::new(MediaProcessorJobInit { location, - sub_path: Some(args.path), + sub_path: Some(path), + regenerate_thumbnails: regenerate, }) .spawn(&node, &library) .await diff --git a/core/src/location/mod.rs b/core/src/location/mod.rs index 60e759471..2e467b951 100644 --- a/core/src/location/mod.rs +++ b/core/src/location/mod.rs @@ -412,6 +412,7 @@ pub async fn scan_location( .queue_next(MediaProcessorJobInit { location: location_base_data, sub_path: None, + regenerate_thumbnails: false, }) .spawn(node, library) .await @@ -450,6 +451,7 @@ pub async fn scan_location_sub_path( .queue_next(MediaProcessorJobInit { location: location_base_data, sub_path: Some(sub_path), + regenerate_thumbnails: false, }) .spawn(node, library) .await diff --git a/core/src/object/media/media_data_extractor.rs b/core/src/object/media/media_data_extractor.rs index 72131056d..9c242512d 100644 --- a/core/src/object/media/media_data_extractor.rs +++ b/core/src/object/media/media_data_extractor.rs @@ -88,7 +88,15 @@ pub async fn process( )]) .select(media_data::select!({ object_id })) .exec() - .await? + .await?; + + if files_paths.len() == objects_already_with_media_data.len() { + // All files already have media data, skipping + run_metadata.skipped = files_paths.len() as u32; + return Ok((run_metadata, JobRunErrors::default())); + } + + let objects_already_with_media_data = objects_already_with_media_data .into_iter() .map(|media_data| media_data.object_id) .collect::>(); diff --git a/core/src/object/media/media_processor/job.rs b/core/src/object/media/media_processor/job.rs index 2baf2409e..fb8c3296a 100644 --- a/core/src/object/media/media_processor/job.rs +++ b/core/src/object/media/media_processor/job.rs @@ -37,6 +37,7 @@ const BATCH_SIZE: usize = 10; pub struct MediaProcessorJobInit { pub location: location::Data, pub sub_path: Option, + pub regenerate_thumbnails: bool, } impl Hash for MediaProcessorJobInit { @@ -181,6 +182,7 @@ impl StatefulJob for MediaProcessorJobInit { self.location.id, &data.location_path, &data.thumbnails_base_dir, + self.regenerate_thumbnails, &ctx.library, |completed_count| { ctx.progress(vec![JobReportUpdate::CompletedTaskCount( diff --git a/core/src/object/media/media_processor/mod.rs b/core/src/object/media/media_processor/mod.rs index 563debeb4..0692b0f96 100644 --- a/core/src/object/media/media_processor/mod.rs +++ b/core/src/object/media/media_processor/mod.rs @@ -120,6 +120,7 @@ async fn process( location_id: location::id::Type, location_path: impl AsRef, thumbnails_base_dir: impl AsRef, + regenerate_thumbnails: bool, library: &Library, ctx_update_fn: impl Fn(usize), ) -> Result<(MediaProcessorMetadata, JobRunErrors), MediaProcessorError> { @@ -168,6 +169,7 @@ async fn process( location_id, location_path, thumbnails_base_dir, + regenerate_thumbnails, library, ctx_update_fn, ) diff --git a/core/src/object/media/media_processor/shallow.rs b/core/src/object/media/media_processor/shallow.rs index c99b031a4..0085093ab 100644 --- a/core/src/object/media/media_processor/shallow.rs +++ b/core/src/object/media/media_processor/shallow.rs @@ -124,6 +124,7 @@ pub async fn shallow( location.id, &location_path, &thumbnails_base_dir, + false, library, |_| {}, ) @@ -135,7 +136,7 @@ pub async fn shallow( } } - debug!("Media shallow processor run metadata: {run_metadata:#?}"); + debug!("Media shallow processor run metadata: {run_metadata:?}"); if run_metadata.media_data.extracted > 0 || run_metadata.thumbnailer.created > 0 { invalidate_query!(library, "search.paths"); diff --git a/core/src/object/media/thumbnail/mod.rs b/core/src/object/media/thumbnail/mod.rs index b539a7db9..f01408c5a 100644 --- a/core/src/object/media/thumbnail/mod.rs +++ b/core/src/object/media/thumbnail/mod.rs @@ -258,6 +258,7 @@ pub(super) async fn process( location_id: location::id::Type, location_path: impl AsRef, thumbnails_base_dir: impl AsRef, + regenerate: bool, library: &Library, ctx_update_fn: impl Fn(usize), ) -> Result<(ThumbnailerMetadata, JobRunErrors), ThumbnailerError> { @@ -354,12 +355,29 @@ pub(super) async fn process( ctx_update_fn(idx + 1); match metadata_res { Ok(_) => { - trace!( - "Thumb already exists, skipping generation for {}", - output_path.display() - ); - run_metadata.skipped += 1; - continue; + if !regenerate { + trace!( + "Thumbnail already exists, skipping generation for {}", + input_path.display() + ); + run_metadata.skipped += 1; + } else { + tracing::debug!( + "Renegerating thumbnail {} to {}", + input_path.display(), + output_path.display() + ); + process_single_thumbnail( + cas_id, + kind, + &input_path, + &output_path, + &mut errors, + &mut run_metadata, + library, + ) + .await; + } } Err(e) if e.kind() == io::ErrorKind::NotFound => { @@ -369,41 +387,16 @@ pub(super) async fn process( output_path.display() ); - match kind { - ThumbnailerEntryKind::Image => { - if let Err(e) = generate_image_thumbnail(&input_path, &output_path).await { - error!( - "Error generating thumb for image \"{}\": {e:#?}", - input_path.display() - ); - errors.push(format!( - "Had an error generating thumbnail for \"{}\"", - input_path.display() - )); - continue; - } - } - #[cfg(feature = "ffmpeg")] - ThumbnailerEntryKind::Video => { - if let Err(e) = generate_video_thumbnail(&input_path, &output_path).await { - error!( - "Error generating thumb for video \"{}\": {e:#?}", - input_path.display() - ); - errors.push(format!( - "Had an error generating thumbnail for \"{}\"", - input_path.display() - )); - continue; - } - } - } - - trace!("Emitting new thumbnail event"); - library.emit(CoreEvent::NewThumbnail { - thumb_key: get_thumb_key(cas_id), - }); - run_metadata.created += 1; + process_single_thumbnail( + cas_id, + kind, + &input_path, + &output_path, + &mut errors, + &mut run_metadata, + library, + ) + .await; } Err(e) => { error!( @@ -420,3 +413,53 @@ pub(super) async fn process( Ok((run_metadata, errors.into())) } + +// Using &Path as this function if private only to this module, always being used with a &Path, so we +// don't pay the compile price for generics +async fn process_single_thumbnail( + cas_id: &str, + kind: ThumbnailerEntryKind, + input_path: &Path, + output_path: &Path, + errors: &mut Vec, + run_metadata: &mut ThumbnailerMetadata, + library: &Library, +) { + match kind { + ThumbnailerEntryKind::Image => { + if let Err(e) = generate_image_thumbnail(&input_path, &output_path).await { + error!( + "Error generating thumb for image \"{}\": {e:#?}", + input_path.display() + ); + errors.push(format!( + "Had an error generating thumbnail for \"{}\"", + input_path.display() + )); + + return; + } + } + #[cfg(feature = "ffmpeg")] + ThumbnailerEntryKind::Video => { + if let Err(e) = generate_video_thumbnail(&input_path, &output_path).await { + error!( + "Error generating thumb for video \"{}\": {e:#?}", + input_path.display() + ); + errors.push(format!( + "Had an error generating thumbnail for \"{}\"", + input_path.display() + )); + + return; + } + } + } + + trace!("Emitting new thumbnail event"); + library.emit(CoreEvent::NewThumbnail { + thumb_key: get_thumb_key(cas_id), + }); + run_metadata.created += 1; +} diff --git a/interface/app/$libraryId/Explorer/ContextMenu/FilePath/Items.tsx b/interface/app/$libraryId/Explorer/ContextMenu/FilePath/Items.tsx index a075a0b00..eb02ed131 100644 --- a/interface/app/$libraryId/Explorer/ContextMenu/FilePath/Items.tsx +++ b/interface/app/$libraryId/Explorer/ContextMenu/FilePath/Items.tsx @@ -208,7 +208,8 @@ export const ParentFolderActions = new ConditionalItem({ try { await generateThumbnails.mutateAsync({ id: parent.location.id, - path: selectedFilePaths[0]?.materialized_path ?? '/' + path: selectedFilePaths[0]?.materialized_path ?? '/', + regenerate: true, }); } catch (error) { toast.error({ diff --git a/interface/app/$libraryId/Explorer/ParentContextMenu.tsx b/interface/app/$libraryId/Explorer/ParentContextMenu.tsx index bd5bc0a61..adf7dd9ee 100644 --- a/interface/app/$libraryId/Explorer/ParentContextMenu.tsx +++ b/interface/app/$libraryId/Explorer/ParentContextMenu.tsx @@ -129,7 +129,8 @@ export default (props: PropsWithChildren) => { try { await generateThumbsForLocation.mutateAsync({ id: parent.location.id, - path: currentPath ?? '/' + path: currentPath ?? '/', + regenerate: true, }); } catch (error) { toast.error({ diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index 89812f1d4..b847829aa 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -177,7 +177,7 @@ export type FromPattern = { pattern: string; replace_all: boolean } export type FullRescanArgs = { location_id: number; reidentify_objects: boolean } -export type GenerateThumbsForLocationArgs = { id: number; path: string } +export type GenerateThumbsForLocationArgs = { id: number; path: string; regenerate?: boolean } export type GetAll = { backups: Backup[]; directory: string }