experiment: Implement new raw/jpeg grouping#451
experiment: Implement new raw/jpeg grouping#451EnTeQuAk wants to merge 15 commits intoCyberTimon:mainfrom
Conversation
|
Awesome, thanks a lot for your reply. Yeah, the design is far from final. I first need to play around with it more to ensure it's actually useful the way I built it 🙈
Indeed, I'm currently investigating if it makes sense to make that a bit more explicit and more widely known in other parts of the application. Right now I'm trying to figure out how to generalize different types of files, e.g, other cameras may have a "XXX + RAW" mode, not sure if JPEG is kinda like the absolute default in the industry. As I said, it's experimental and will likely take a while to finish. Your feedback is much appreciated :) |
|
subscribing to this PR since I am very much interested in this (I also shoot in jpeg+raw). This is one thing I am missing after I moved away from Lightroom |
aad3ede to
cc465bc
Compare
|
Alrighty, both of your replies motivated me to pick up the work sooner rather than later 😁 Now, I just pushed a complete rewrite from the original experiment. Given the old branch was 612 commits behind Key differences from the earlier iteration:
This pull request still needs group-aware operations (delete/copy/move with confirmation modals), though, I'd like to open that question rather than implement too much right now. What do you think operations like delete/copy/move should behave with the raw/jpeg grouping enabled? I'd build that as a follow-up in a separate pull request, I'd like to focus on the foundation here.
@CyberTimon, regarding your earlier feedback: the design now follows the existing component patterns (same padding, border-radius, and color tokens as the rest of the dropdowns and toolbars). The associated files detection for delete uses Let me know what you think. Happy to hear your feedback on this 👼 |
1fea563 to
97fa495
Compare
4488163 to
774770d
Compare
|
Cool! I've always wondered why Lightroom didn't have something like this. Really nice, and opens up a lot of possibilities I think. |
split('.').next() returned everything before the first dot, so
'my.photo.2024.ARW' yielded 'my' instead of 'my.photo.2024'. This could
match unrelated files that happened to share a prefix.
Switched to Path::file_stem() which correctly returns everything before
the last dot. Sidecar matching now explicitly handles the
{filename}.rrdata and {filename}.{vc_id}.rrdata naming patterns instead
of relying on the stem shortcut.
|
thanks for that feedback @agirilovich, I started to build more around that kind of usage-pattern and added two new grouping options inspired by Zoner Studio's approach. "Group edited files" checkbox: When unchecked, files that have been independently edited in RapidRAW are excluded from grouping and appear as standalone images. This is particularly, I think, useful for Lightroom migrants where exported JPEGs can represent finished work and shouldn't be collapsed behind the archived RAW. "Require matching metadata" checkbox: When checked, files sharing a filename stem are only grouped if their EXIF creation timestamps match. That should prevent false groupings when unrelated files from different cameras/shoots happen to share a filename. Files without EXIF data are excluded from grouping entirely when this is enabled. Both options appear under the Group Variants filter in the View Options dropdown, alongside the existing RAW/JPEG preference toggle. Defaults preserve current behavior (group edited files: on, require matching metadata: off). Not yet implemented (I think I'd do that in a separate PR): Per-folder grouping settings to allow each folder to override the global grouping preferences independently (similar to Zoner Studio's "Save settings for each folder separately" option). This would let users have different grouping behavior for e.g. their Lightroom import folder vs. their native RAW shooting folders. But, that's a lot of work from what I'm seeing and I don't wanna unnecessarily increase the size of the PR, it's hard to maintain as it is already. What do you think? I'm also still testing it a bit more, so, expect dragons here and there. |
list_images_in_dir and list_images_recursive now compute is_raw (via is_raw_file) and group_id for each file. Files sharing a stem in the same directory get the same group_id. Virtual copies are excluded from the count so a file + its VC don't form a false group, but VCs still inherit the group_id of their source. Also adds group_associated_files and group_preferred_type to AppSettings (defaults: off, 'raw').
Add ImageGroup, GroupPreference types and buildImageGroups() utility in src/utils/imageGrouping.ts. Wire the grouping result into the sortedImageList pipeline in App.tsx, gated behind the groupAssociatedFiles setting. Remove the ~40 lines of RawOverNonRaw stem-matching JS that duplicated what Rust now provides via group_id. The raw status filter now uses image.is_raw directly instead of parsing extensions against supportedTypes. Settings (groupAssociatedFiles, groupPreferredType) are loaded from and persisted to AppSettings.
…p fix Phase 2 + 3 of JPEG+RAW grouping: Library: - Thumbnail shows a variant count badge (bottom-right) when the image belongs to a group with 2+ variants. - Filter dropdown: 'Prefer RAW' replaced with 'Group Variants'. Selecting it activates grouping (collapsing, badges, switcher). - Added GroupVariants to RawStatus enum; old RawOverNonRaw values are treated as grouping for migration. Editor: - EditorToolbar shows variant switcher pills (e.g., [RAF] [JPG]) when the current image belongs to a group. Labels use the actual file extension, not generic RAW/JPEG. - Clicking a pill switches to that variant via handleImageSelect. Filmstrip: - When the user views a non-primary variant (e.g., switched from JPG to RAF), the filmstrip still highlights the group's primary so the active position doesn't jump. - activeDisplayPath prop flows: App -> BottomBar -> Filmstrip.
When 'Group Variants' is selected in the file type filter, a 'Prefer RAW / JPEG' toggle appears below it. Controls which variant is shown as the primary in the library grid. Defaults to RAW, persisted via AppSettings.groupPreferredType.
Replace the O(n*m) startsWith stem-matching with a direct group_id lookup against the pre-computed groupVariantCounts map.
Grouping is now driven entirely by the rawStatus filter (GroupVariants or migrated RawOverNonRaw). The separate boolean toggle had no UI and defaulted to false, so it was dead code.
Three issues found during code review: 1. hasAssociatedFiles relied on groupVariantCounts, which is empty when grouping UI is inactive. Since Rust always populates group_id for files with shared stems, checking group_id != null is correct regardless of the UI state. 2. Virtual copies inherit group_id from their source, so the variant switcher pills would appear when editing a VC and point to the original files. Suppress the switcher for VCs to avoid silently leaving the VC context. 3. getFileExtension had a redundant first lastIndexOf call that was only used for an early return before the real logic. Simplified.
Swap the variant count number for a compact Layers icon (12px) that works at any thumbnail size. Tooltip shows the actual extensions joined (e.g. 'RAF+JPG', 'DNG+ARW+JPG'). Renames groupVariantCounts to groupBadgeInfo carrying both count and label.
Issues found during code review: - filmstripActivePath was missing the is_virtual_copy guard that variantOptions already had. A VC of a grouped file would highlight the group primary in the filmstrip instead of itself. - pickPrimary switch had no default case. A corrupt settings file sending an unknown preference string would return undefined. - Use Set for extension dedup in groupBadgeInfo (was indexOf loop). - Add skip_serializing to deprecated group_associated_files so it stops being written back to the settings file. - Remove stale comment.
Simplify make_group_key to just path.with_extension(""). Drop
group_associated_files entirely since it never shipped. Introduce
GroupId type alias instead of bare strings. Rename the filter label
to 'Group RAW + JPEG' for clarity. Tighten up comments.
When "Group edited files" is unchecked, files with RapidRAW edits (non-rating sidecar adjustments) are excluded from stem-based grouping and appear as standalone images. Useful for Lightroom migrants whose processed JPEGs represent independent work.
When "Require matching metadata" is enabled, files sharing a filename stem are only grouped if their EXIF creation timestamps match within a 5-second tolerance. Files without EXIF data are excluded from grouping entirely. Prevents false groupings when unrelated files share a filename by coincidence.
8918243 to
7a7f404
Compare
|
Right, you understand both options in the same way as Zoner Studio has it implemented. Few additional thinks:
Do you have any way to recognize if JPEG file directly from camera or edited/exported? I do belive, that at this stage of project development it is fully enough just having 3x options on a folder level:
Exactly! This option is more usable on upper level, when hundreds of files from different folders (recursively) are displayed on the same view. And even more in search views.
Hm. I've just compiled RapidRaw from a master branch and didn't find the option to display JPEG over RAW. Will check later....
It would be great if you were able to implement it one day. Great step toward building really mature and professional product. |







I'm using a Fuji camera and regularly shoot JPEG+RAW to have the best of both worlds: straight out of camera JPEGs which are 80% of the time great, and RAWs for special occasions. Seeing every shot twice in the library has always bugged me, so I started experimenting with grouping.
The core idea: Rust assigns a
group_idto images that share a stem in the same directory (e.g.DSCF0644.RAFandDSCF0644.JPG), and the frontend collapses each group into a single entry, picking a primary based on your preference. Virtual copies inherit the group but don't count toward it, so a file + its VC alone won't create a false group.What I implemented:
[RAF] [JPG]instead of generic labelsAlong the way I also fixed a pre-existing bug where
delete_files_with_associatedwould grab the wrong stem for filenames with dots (was usingsplit('.').next(), now usesPath::file_stem()), and simplified thehasAssociatedFilescontext menu check from O(n*m) string matching to a directgroup_idlookup.Group-aware operations (delete/copy/move/export) aren't in this PR yet. When you delete a grouped file, should it delete all variants? Offer a choice? I'd rather discuss that and build it as a follow-up than guess wrong here.