Commit graph

3141 commits

Author SHA1 Message Date
notsafeforgit
00ff6d9936 perf(ui): optimize duplicate checker UI to prevent browser freezing
This fixes an issue where Chrome would become unresponsive and prompt the user to kill the page when a large number of duplicates (e.g. 30,000+ groups) were found.

1. Changed the fetchPolicy on FindDuplicateImages to 'no-cache'. Loading 30k+ complex objects into the Apollo normalized cache blocked the main thread for an extended period. Bypassing the cache for this massive one-off query resolves the blocking.
2. Optimized the sorting algorithm in both Image and Scene duplicate checkers. Previously, the group size was recalculated by iterating over all nested files inside the sort's comparison function, resulting in millions of unnecessary iterations (O(N log N) with a heavy inner loop). Now, group sizes are precalculated into a map (O(N)) before sorting.
2026-03-23 01:16:27 -07:00
notsafeforgit
eeba66e598 perf(ui): use slim image data in duplicate checker
This fixes a severe performance bottleneck where the image duplicate checker would hang indefinitely or crash the server when finding many duplicates.

Previously, the GraphQL query requested the full 'ImageData' fragment for every duplicate found, forcing the backend to resolve and serialize all related entities (galleries, studios, tags, performers) for thousands of images at once.

By switching to the 'SlimImageData' fragment (mirroring how the Scene duplicate checker operates), the payload size and resolution time are drastically reduced, allowing the tool to scale correctly.
2026-03-23 00:48:21 -07:00
notsafeforgit
f2f299307a perf: eliminate O(N^2) bottlenecks in image and scene duplicate detection
This update resolves major performance regressions when processing large libraries:

1. Optimized FindMany in both Image and Scene stores to use map-based ID lookups. Previously, this function used slices.Index in a loop, resulting in O(N^2) complexity. On a library with 300k items, this was causing the server to hang indefinitely.

2. Refined the exact image duplicate SQL query to match the scene checker's level of optimization. It now joins the files table and orders results by total duplicate file size, ensuring that the most impactful duplicates are shown first.

3. Removed the temporary LIMIT 1000 from the image duplicate query now that the algorithmic bottlenecks have been resolved.
2026-03-20 15:29:04 -07:00
notsafeforgit
6de7195ed6 perf: further optimize image duplicate detection
This update provides additional performance improvements specifically targeted at large image libraries (e.g. 300k+ images):

1. Optimized the exact match SQL query for images:
   - Added filtering for zero/empty fingerprints to avoid massive false-positive groups.
   - Added a LIMIT of 1000 duplicate groups to prevent excessive memory consumption and serialization overhead.
   - Simplified the join structure to ensure better use of the database index.

2. Parallelized the Go comparison loop in pkg/utils/phash.go:
   - Utilizes all available CPU cores to perform Hamming distance calculations.
   - Uses a lock-free design to minimize synchronization overhead.
   - This makes non-zero distance searches significantly faster on multi-core systems.
2026-03-20 15:26:18 -07:00
notsafeforgit
b3d002ccf9 fix: remove unused goimagehash import in phash utility 2026-03-20 15:22:19 -07:00
notsafeforgit
acc5438af2 perf: massive optimization for image and scene duplicate detection
This update provides significant performance improvements for both image and scene duplicate searching:

1. Optimized the core Hamming distance algorithm in pkg/utils/phash.go:
   - Uses native CPU popcount instructions (math/bits) for bit counting.
   - Pre-calculates hash values to eliminate object allocations in the hot loop.
   - Halves the number of comparisons by leveraging the symmetry of the Hamming distance.
   - The loop is now several orders of magnitude faster and allocation-free.

2. Solved the N+1 database query bottleneck:
   - Replaced individual database lookups for each duplicate group with a single batched query for all duplicate IDs.
   - This optimization was applied to both Image and Scene repositories.

3. Simplified the SQL fast path for exact image matches to remove redundant table joins.
2026-03-20 15:06:05 -07:00
notsafeforgit
3924acdb0f perf(sqlite): implement SQL-based fast path for exact image duplicate detection
This change adds a specialized SQL query to find exact image duplicate matches (distance 0) directly in the database.

Previously, the image duplicate checker always used an O(N^2) Go-based comparison loop, which caused indefinite loading and timeouts on libraries with a large number of images. The new SQL fast path reduces the time to find exact duplicates from minutes/hours to milliseconds.
2026-03-20 14:35:19 -07:00
notsafeforgit
fa5725e709 fix(sqlite): fix image duplicate detection by scanning phash as integer
This fixes a bug where identical image duplicates were not being detected.

The implementation was incorrectly scanning the phash BLOB into a string and then attempting to parse it as a hex string. Since phashes are stored as 64-bit integers, they were being converted to decimal strings. For phashes with the MSB set (negative when treated as int64), the resulting decimal string started with a '-', which caused the hex parser to fail and skip the image entirely.

Additionally, even for non-negative phashes, parsing a decimal string as hex yielded incorrect hash values.

Scanning directly into the utils.Phash struct (which uses int64) matches how Scene phashes are handled and ensures the hash values are correct.
2026-03-20 04:53:39 -07:00
DogmaDragon
8d19df6a72
Update capitalization in localization strings 2026-03-17 14:48:14 +02:00
notsafeforgit
27ab865d70 fix: update image duplicate checker UI and API handling
- Fixes 400 error in ImageDuplicateChecker

- Updates UI and frontend types

- Fixes tools casing
2026-03-16 04:15:28 -07:00
notsafeforgit
df8c025e09 chore: revert changes to en-US.json 2026-03-14 05:10:01 -07:00
notsafeforgit
9295655d19 fix: resolve image duplicate finder issues
- Wrap FindDuplicateImages query in r.withReadTxn() to ensure a database transaction in context.
- Use queryFunc instead of queryStruct for fetching multiple hashes, preventing runtime errors.
- Fix N+1 query issue in duplicate grouping by using qb.FindMany() instead of qb.Find() for each duplicate image.
- Revert searchColumns array to exclude "images.details" which was from another PR and remove related failing test.
2026-03-13 18:39:34 -07:00
notsafeforgit
8358c794ba fix: resolve unused import and undefined reference in sqlite image repository
- Removed unused `strconv` import from `pkg/sqlite/image.go`.
- Added missing `github.com/stashapp/stash/pkg/utils` import to resolve the undefined `utils` reference.
- Fixed pagination prop in ImageDuplicateChecker component.
- Formatted modified go files using gofmt.
- Ran prettier over the UI codebase to resolve the formatting check CI failure.
2026-03-13 18:11:05 -07:00
notsafeforgit
ea84613f0c feat: add edit and delete actions to image duplicate checker
This adds checkboxes to select duplicate images and integrates the existing EditImagesDialog and DeleteImagesDialog, allowing users to resolve duplicates directly from the tool.
2026-03-13 16:37:38 -07:00
notsafeforgit
af75c8c1b4 feat: improve Image Duplicate Checker implementation
This change unifies the duplicate detection logic by leveraging the shared phash utility. It also enhances the UI with:
- Pagination for large result sets.
- Sorting duplicate groups by total file size.
- A more detailed table view with image thumbnails, paths, and dimensions.
- Consistency with the existing Scene Duplicate Checker tool.
2026-03-13 15:35:29 -07:00
notsafeforgit
3b1fccb010 feat: Implement Image Duplicate Checker
This change introduces a new tool to identify duplicate images based on their perceptual hash (phash). It includes:
- Backend implementation for phash distance comparison and grouping.
- GraphQL schema updates and API resolvers.
- Frontend UI for the Image Duplicate Checker tool.
- Unit tests for the image search and duplicate detection logic.
2026-03-13 15:23:02 -07:00
hyper440
300e7edb75
fix: support string-based fingerprints in hashes filter (#6654)
* fix: support string-based fingerprints in hashes filter
* Fix tests and add phash test

File fingerprints weren't using correct types. Filter test wasn't using correct types. Add phash to general files.
---------

Co-authored-by: hyper440 <hyper440@users.noreply.github.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2026-03-10 15:07:46 +11:00
smith113-p
490fa3ea14
Show scene resolution and duration in tagger (#6663)
* Show scene resolution and duration in tagger

A scene's duration and resolution is often useful to ensure you have
found the right scene. This PR adds the same resolution/duration
overlay from the grid view to the tagger view.
2026-03-10 14:53:20 +11:00
smith113-p
69a49c9ab8
Show the stash box for each stash ID in the scene merge dialog (#6656)
* Show the stash box for each stash ID in the scene merge dialog

Currently, this dialog only shows the ID but not the stash box it
corresponds to. This is not very useful because the ID does not mean
anything to a user.

This renders the ID as "Stashdb | 1234...", mimicing the StashIDPill.

* Use StashIDPill instead
2026-03-10 14:12:17 +11:00
Gykes
ae5d065da1
Fix infinite re-render loop in gallery image list (#6651) 2026-03-10 13:50:57 +11:00
smith113-p
cacaf36347
Use StashIDPill in the performer modal dialog (#6655)
Currently, this dialog just shows a text "Stash-Box Source".
This change instead re-uses the StashIDPill, with the main advantage
that you can immediately tell which stash box is being used.
2026-03-10 08:01:46 +11:00
WithoutPants
74a8f2e5d5
Disable links on wall items when selecting (#6649) 2026-03-06 08:27:25 +11:00
WithoutPants
717f968a2c
Add folder criteria to scenes, images and galleries and sidebars (#6636)
* Add useDebouncedState hook
* Add basename to folder sort whitelist
* Add parent_folder criterion to gallery
* Add selection on enter if single result
2026-03-05 08:02:13 +11:00
WithoutPants
697c66ae62
Allow stash path to non-existing directory (#6644) 2026-03-05 07:59:13 +11:00
WithoutPants
69e781b0ee
Use ffmpeg as a general fallback when generating phash (#6641) 2026-03-05 07:58:51 +11:00
Gykes
fbf91b2526
New: Add From Clipboard to Set Image (#6637)
* add from clipboard to UI
* only trigger when input not focused
2026-03-04 12:01:31 +11:00
WithoutPants
f7da37400b
Fix preview scrubber scaling on smaller sizes (#6640) 2026-03-04 10:10:07 +11:00
Matt Stone
cd0980201c
feat: Add .stashignore support for gitignore-style scan exclusions (#6485)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2026-03-04 08:17:14 +11:00
puc9
1457ad590d
Add Selective generate (#6621) 2026-03-03 09:11:28 +11:00
WithoutPants
b9baa7ea9f Fix gallery image list styling 2026-03-03 08:26:04 +11:00
puc9
99a0d01371
Fix new panic in IsFsPathCaseSensitive: Use filepath operations to check for file system case sensitivity (#6635)
* Use filepath operations to check for file system case sensitivity
2026-03-03 08:11:55 +11:00
Abdu Dihan
52bd9392fb
Fix stale browser-cached thumbnails after file content changes during scan. (#6622)
* Fix stale thumbnails after file content changes

When a file's content changed (e.g. after renaming files in a gallery),
the scan handler updated fingerprints but did not bump the entity's
updated_at timestamp. Since thumbnail URLs use updated_at as a cache
buster and are served with immutable/1-year cache headers, browsers
would indefinitely serve the old cached thumbnail.

Update image, scene, and gallery scan handlers to call UpdatePartial
(which sets updated_at to now) whenever file content changes, not only
when a new file association is created.
2026-03-02 15:53:02 +11:00
dev-null-life
b8dff73696
Fix datepicker button border radius in input groups (#6630)
Add missing .input-group-append .btn border-radius rule to zero out
the left-side radius, matching the existing .input-group-prepend rule.

Fixes #6518

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:47:43 +11:00
dev-null-life
784795660b
Skip scanning zip contents when fingerprint is unchanged (#6633)
* Skip scanning zip contents when fingerprint is unchanged

When a zip-based gallery's modification time changes but its content
hash (oshash/md5) remains the same, skip walking and rescanning every
file inside the zip. This avoids expensive per-file fingerprint
recalculation when zip metadata changes without actual content changes.

Closes #6512

* Log a debug message when skipping a zip scan due to unchanged
  fingerprint

---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2026-03-02 15:47:23 +11:00
WithoutPants
09e2b2bd4e Wrap CleanCaptions with database. Refactor AssociateCaptions. 2026-03-02 15:45:37 +11:00
dev-null-life
bc75d47f15
Fix edit modal not opening inside gallery view (#6629)
* Fix edit modal not opening inside gallery view

The modal element was only rendered in the sidebar layout branch, but
gallery images use the non-sidebar path which returned content without
the modal. Also stabilize onEdit/onDelete with useCallback and add
missing dependency array to the Mousetrap useEffect.

Closes #6624

* Render modal once above sidebar conditional

Move {modal} above the withSidebar ternary so it is rendered
exactly once, avoiding the duplication that caused the original bug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:45:33 +11:00
WithoutPants
681ccbf380
Fix caption handling during scan and check before correcting path (#6634)
* Handle case where folder entry exists for corrected path in correctSubFolderHierarchy
* Log scan start
* Handle caption files during scan
2026-03-02 14:44:20 +11:00
DogmaDragon
b46fbb2e7a
Update capitalization for sprite generation heading (#6623) 2026-03-02 14:30:38 +11:00
Gykes
c874bd560e
Fix: Custom Field Filtering (#6614)
* add tests
* Refactor queryBuilder: split args into per-clause fields
2026-02-28 11:05:13 +11:00
WithoutPants
c7e1c3da69
Fix panic when library path has trailing path separator (#6619)
* Replace panic with warning if creating a folder hierarchy where parent is equal to current
* Clean stash paths so that comparison works correctly when creating folder hierarchies
2026-02-28 10:51:02 +11:00
Gykes
3b8f6bd94c
update logs and fix UNIQUE constraint failure (#6617) 2026-02-28 09:11:13 +11:00
WithoutPants
d8448ba37e
Add basename and parent_folders fields to Folder graphql interface (#6494)
* Add basename field to folder
* Add parent_folders field to folder
* Add basename column to folder table
* Add basename filter field
* Create missing folder hierarchies during migration
* Treat files/folders in zips where path can't be made relative as not found

Addresses an issue during clean where corrupt folder entries in zip files could not be removed due to an error during the call to Rel.
2026-02-27 10:58:11 +11:00
WithoutPants
ead0c7fe07
Add sidebar to Tag list (#6610)
* Fix image export dialog
* Add sidebar to TagList
* Update plugin docs and types
* Remove ItemList as it is no longer referenced
2026-02-27 07:44:23 +11:00
WithoutPants
660feabced
Update minimatch and ajv dependencies (#6609)
* Update minimatch
* Update ajv
2026-02-27 07:43:16 +11:00
WithoutPants
e52ac14d56
Fix missing folder corruption during scanning (#6608)
* Add root paths parameter to GetOrCreateFolderHierarchy

Ensures that folders are only created up to the root library paths.

* Create full folder hierarchy when scanning a new folder

During a recursive scan, folders should be created as they are encountered (folders are handled in a single thread). This change applies only during a selective scan. Creates up to the root library folder.

* Create folder hierarchy on new file scan

This should only apply when scanning a specific file, as parent folders should be been created during a recursive scan.

* Fix existing folders with missing parents during scan
2026-02-27 07:42:53 +11:00
Gykes
b77abd64e2
FR: Add Missing is-missing Filter Options Across all Object Types (#6565)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2026-02-26 16:36:54 +11:00
WithoutPants
ed58d18334
Add sidebar to images list (#6607)
* Use effective filter for keybinds/view random
* Refactor ImageList to use sidebar
* Add performer age filter to gallery sidebar
* Port metadata info changes
* Fix incorrect patch component parameter
* Update plugin doc and types
2026-02-26 14:13:15 +11:00
WithoutPants
c522e54805
Show unsupported filter criteria in filter tags (#6604)
* Show unsupported filter criteria in filter tags

Shows a warning coloured filter tag, with warning icon and text "<type> (unsupported) ...". Cannot be edited, can only be removed. Won't be saved to saved filters.

* Generalise filtered recommendation rows. Include warning popover for unsupported criteria
2026-02-26 07:55:26 +11:00
WithoutPants
5734ee43ff
Add sidebar to scene markers list (#6603)
* Add tag markers filter
* Add marker count and markers filter to performer filter
* Add sidebar to marker list
2026-02-26 07:54:40 +11:00
DogmaDragon
c9f0dba62f
Fix capitalization in custom localisation heading [skip-ci] (#6606) 2026-02-26 07:54:12 +11:00