Allow filtering photos by rating in EXIF/XMP metadata ("stars")
Is your feature request related to a problem? Please describe. I have a large collection of photos and I carefully tag my photos with ratings so that I can subselect the ones I want to display from an album depending on the setting and the time I have to show them. Not being able to filter photos by rating in Memories defeats this purpose.
Describe the solution you'd like I'd like to be able filter the displayed photos by a minimum rating. The IMHO best solution would be if this applied to all other views (Folder, Album, Favorites, etc.) and thus would require a separate filter feature that currently does not exist. As I presume that this would be more involved, I could also live with a global filter across all views like "only display photos above rating X" that could then easily be changed for the use-case.
Describe alternatives you've considered I currently have a script that creates copies of photos above a given rating into a 3Stars, 4Stars, and 5Stars folder and then I use those. However, wasting space for the copies and keeping them in sync with the library both make this a fairly unsatisfactory solution.
Additional context I'd be happy to work on a PR but before that we'd probably need to discuss a bit the best way to implement this. I'd also benefit a lot from a couple of code pointers. Is there some developer documentation outside of the code as well?
I also rate all my images using the 5 star rating system in apps such as Lightroom/Digikam/etc. These use EXIF/XMP metadata as Tobias said. I believe a "filter by rating" feature in memories would be really useful
@tobiaslangner would you be willing to elaborate further on how the script you mentioned works? Is there a way to say "rsync < master-photos-directory > < gallery-directory >" and somehow only pickup images with x stars or greater rating?
@charles-997 My script iterates over all JPG files in a given directory and uses exiftool to extract the rating of each image. If the rating is above a threshold, it is either copied to a target directory or resized and then copied, which is what I currently use.
You can find the script below. To use it, make sure that you have exiftool and convert installed on your system and that this library is available in the same directory as my script with name shflags.sh. If all of that is done, you can run my script e.g. like this:
photos_bestof.sh --help # to show all options
photos_bestof.sh --photos_dir /media/photos --bestof_dir /tmp/bestof --min_rating 4
Full script:
#!/bin/bash
# Source the shflags.sh file from the same directory
source "$( dirname $( readlink -f "$0" ) )/shflags.sh"
DEFINE_integer min_rating 3 "The min rating for files to be processed (0-5)."
DEFINE_string photos_dir "" "The directory that contains the photos to be processed."
DEFINE_string bestof_dir "" "The directory where the photos above --min_rating should be copied to. Path will be created if it does not exist."
DEFINE_boolean resize true "Whether to resize the photos before copying to --resolution."
DEFINE_string resize_to 1920 "The resolution to which to resize the photos if --resize is set. Can be anything that \\\`convert -resize\\\` accepts."
set -euo pipefail
function validate_flags() {
[[ ! -d "${FLAGS_photos_dir}" ]] && echo "--photos_dir must be set and point to a valid directory." && return 1
[[ -z "${FLAGS_bestof_dir}" ]] && echo "--bestof_dir must be set." && return 1
if [[ ${FLAGS_resize} -eq ${FLAGS_TRUE} ]]; then
[[ -z "${FLAGS_resize_to}" ]] && return 1
fi
return 0
}
function main() {
validate_flags
total=$( find "${FLAGS_photos_dir}" -iname "*.jpg" | wc -l )
count=0
processed_cnt=0
find "${FLAGS_photos_dir}" -iname "*.jpg" | while read photo_path; do
count=$(( $count + 1 ));
if [[ $(( $count % 10 )) -eq 0 ]]; then
echo "$( date +%T ) Processed $count/$total image files, copied to 'Best Of': $processed_cnt..."
fi
rel_path="${photo_path#${FLAGS_photos_dir}}"
bestof_path="${FLAGS_bestof_dir}${rel_path}"
if [[ -f "${bestof_path}" ]]; then
# Image has already been processed.
continue
fi
rating=$( exiftool -T -Rating "${photo_path}" -q )
if [[ -z "${rating}" ]] || \
[[ "${rating}" = "-" ]] || \
[[ "${rating}" -lt "${FLAGS_min_rating}" ]]; then
continue
fi
# Create all parent directories.
mkdir -p "${bestof_path%/*}"
if [[ ${FLAGS_resize} -eq ${FLAGS_TRUE} ]]; then
convert "${photo_path}" -resize "${FLAGS_resize_to}" "${bestof_path}"
else
cp "${photo_path}" "${bestof_path}"
fi
processed_cnt=$(( $processed_cnt + 1 ));
done
}
# Parse the command-line
FLAGS "$@" || exit $?
eval set -- "${FLAGS_ARGV}"
main "$@"
I'd also like to filter by star rating (in all views). If you'd like to have a second (or third) opinion on how these filters should look, I might be able to help as I'd use that feature very frequently. I probably won't find enough time to implement it on my own in reasonable time though.
I do agree though, that it makes sense to get @pulsejet 's opinion first. Would you still be interested in implementing it yourself @tobiaslangner?
It all depends on the complexity, I've never properly looked at the Nextcloud/Memories codebase before... But I'd certainly be up for doing my part in getting something out.
@pulsejet and others: Could we get a bit more context here? I feel that the usecase of using memories as powerful web frontend for an already well tagged and managed local photo library is an important one.
If we can't really have a proper integration, what about at least having a one-way sync/import that extracts image metadata and imports it into memories, potentially overriding whatever is there already? That would bring me a bit step forward.
Thanks!
Could we get a bit more context here?
Like? The only thing holding this back is, it needs to be implemented in a performant manner.
Sorry, my phrasing was a bit off. I meant: "Could we please get a status update/comments by the devs here as this is really important for me?" :)
Could you explain the performance concerns that you are having? And which solution are you talking about? The one where there is a one-off import of file metadata into memories or one where the data is properly kept in sync?
As written above, I'd be happy to contribute to this as well, time permitting, but it'd be great to get a few directions from the dev before I end up sending you a pull request that is outrightly thrown in the trash :)
Could we please get a status update/comments by the devs here as this is really important for me?
There isn't any update, since this isn't on the roadmap.
Could you explain the performance concerns that you are having?
No concern as such; this is relatively straightforward from a design POV. Every EXIF field that we index gets a column in the oc_memories table. The sync of the EXIF to database is handled by the file update hooks, so there's little to nothing to do there (I'm assuming the rating is stored in the file itself, which in turn assumes all or most formats support this).
The "concern" is the simply the time/effort required to implement and test this thorougly for performance regressions. I'm not sure what the most efficient indexing strategy would be in this case, considering rating would almost exclusively be an additional filter over the existing ones (this is brand new). The only way to find out is to analyze how the db is optimizing the queries.
As written above, I'd be happy to contribute to this as well, time permitting, but it'd be great to get a few directions from the dev
I'm happy to answer any particular questions you have about the code. The TimelineQuery and TimelineWrite classes handle everything to do with indexing and querying, and are relatively straightforward (they do generate quite seemingly complicated output SQL, but most of the complexity comes from the filesystem walk CTE which you likely don't need to touch).
Another task, albeit much less challenging, is to implement the UI bits in a non-intrusive way, i.e. without increasing the visual complexity for a user that doesn't use this feature.
related: https://github.com/pulsejet/memories/issues/751