From 69937ca1a3c84b7509a80015f80d18fba66c0763 Mon Sep 17 00:00:00 2001 From: Jaisen Mathai Date: Fri, 18 Jan 2019 16:48:32 -0800 Subject: [PATCH] Add support for a %custom placeholder for more complex folder names than %location or %date provide #279 (#283) --- .gitignore | 1 + Readme.md | 7 +++ elodie/filesystem.py | 88 +++++++++++++++++++++++---------- elodie/tests/filesystem_test.py | 48 ++++++++++++++++++ 4 files changed, 118 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 192a5e2..4ba6258 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build/** **/*.dng **/*.nef **/*.rw2 +env/** diff --git a/Readme.md b/Readme.md index 10d5568..9054a15 100644 --- a/Readme.md +++ b/Readme.md @@ -194,6 +194,12 @@ month=%m year=%Y full_path=%year-%month/%location # -> 2015-12/Sunnyvale, California + +date=%Y +location=%city, %state +custom=%date %album +full_path=%location/%custom +# -> Sunnyvale, California/2015 Birthday Party ``` #### Using fallback folders @@ -239,6 +245,7 @@ In addition to my built-in and date placeholders you can combine them into a sin * `%location` can be used to combine multiple values of `%city`, `%state` and `%country`. For example, `location=%city, %state` would result in folder names like `Sunnyvale, California`. * `%date` can be used to combine multiple values from [the standard Python time directives](https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior). For example, `date=%Y-%m` would result in folder names like `2015-12`. +* `%custom` can be used to combine multiple values from anything else. Think of it as a catch-all when `%location` and `%date` don't meet your needs. ### Reorganize by changing location and dates diff --git a/elodie/filesystem.py b/elodie/filesystem.py index 6c5d83e..877cd5b 100644 --- a/elodie/filesystem.py +++ b/elodie/filesystem.py @@ -218,7 +218,7 @@ class FileSystem(object): def get_folder_path(self, metadata): """Given a media's metadata this function returns the folder path as a string. - :param metadata dict: Metadata dictionary. + :param dict metadata: Metadata dictionary. :returns: str """ path_parts = self.get_folder_path_definition() @@ -232,34 +232,70 @@ class FileSystem(object): # Unknown Location - when neither an album nor location exist for this_part in path_part: part, mask = this_part - if part in ('date', 'day', 'month', 'year'): - path.append( - time.strftime(mask, metadata['date_taken']) - ) + this_path = self.get_dynamic_path(part, mask, metadata) + if this_path: + path.append(this_path.strip()) + # We break as soon as we have a value to append + # Else we continue for fallbacks break - elif part in ('location', 'city', 'state', 'country'): - place_name = geolocation.place_name( - metadata['latitude'], - metadata['longitude'] - ) - - location_parts = re.findall('(%[^%]+)', mask) - parsed_folder_name = self.parse_mask_for_location( - mask, - location_parts, - place_name, - ) - path.append(parsed_folder_name) - break - elif part in ('album', 'camera_make', 'camera_model'): - if metadata[part]: - path.append(metadata[part]) - break - elif part.startswith('"') and part.endswith('"'): - path.append(part[1:-1]) - return os.path.join(*path) + def get_dynamic_path(self, part, mask, metadata): + """Parse a specific folder's name given a mask and metadata. + + :param part: Name of the part as defined in the path (i.e. date from %date) + :param mask: Mask representing the template for the path (i.e. %city %state + :param metadata: Metadata dictionary. + :returns: str + """ + + # Each part has its own custom logic and we evaluate a single part and return + # the evaluated string. + if part in ('custom'): + custom_parts = re.findall('(%[a-z_]+)', mask) + folder = mask + for i in custom_parts: + folder = folder.replace( + i, + self.get_dynamic_path(i[1:], i, metadata) + ) + return folder + elif part in ('date'): + config = load_config() + # If Directory is in the config we assume full_path and its + # corresponding values (date, location) are also present + config_directory = self.default_folder_path_definition + if('Directory' in config): + config_directory = config['Directory'] + date_mask = '' + if 'date' in config_directory: + date_mask = config_directory['date'] + return time.strftime(date_mask, metadata['date_taken']) + elif part in ('day', 'month', 'year'): + return time.strftime(mask, metadata['date_taken']) + elif part in ('location', 'city', 'state', 'country'): + place_name = geolocation.place_name( + metadata['latitude'], + metadata['longitude'] + ) + + location_parts = re.findall('(%[^%]+)', mask) + parsed_folder_name = self.parse_mask_for_location( + mask, + location_parts, + place_name, + ) + return parsed_folder_name + elif part in ('album', 'camera_make', 'camera_model'): + if metadata[part]: + return metadata[part] + elif part.startswith('"') and part.endswith('"'): + # Fallback string + return part[1:-1] + + return '' + + def parse_mask_for_location(self, mask, location_parts, place_name): """Takes a mask for a location and interpolates the actual place names. diff --git a/elodie/tests/filesystem_test.py b/elodie/tests/filesystem_test.py index ee28913..93d3dc0 100644 --- a/elodie/tests/filesystem_test.py +++ b/elodie/tests/filesystem_test.py @@ -279,6 +279,54 @@ full_path=%date assert path == os.path.join('2015'), path +@mock.patch('elodie.config.config_file', '%s/config.ini-combined-date-and-album' % gettempdir()) +def test_get_folder_path_with_combined_date_and_album(): + # gh-239 + with open('%s/config.ini-combined-date-and-album' % gettempdir(), 'w') as f: + f.write(""" +[Directory] +date=%Y-%m-%b +custom=%date %album +full_path=%custom + """) + if hasattr(load_config, 'config'): + del load_config.config + filesystem = FileSystem() + media = Photo(helper.get_file('with-album.jpg')) + path = filesystem.get_folder_path(media.get_metadata()) + if hasattr(load_config, 'config'): + del load_config.config + + assert path == '2015-12-Dec Test Album', path + +@mock.patch('elodie.config.config_file', '%s/config.ini-combined-date-album-location-fallback' % gettempdir()) +def test_get_folder_path_with_album_and_location_fallback(): + # gh-279 + with open('%s/config.ini-combined-date-album-location-fallback' % gettempdir(), 'w') as f: + f.write(""" +[Directory] +date=%Y-%m-%b +custom=%album +full_path=%custom|%city + """) + if hasattr(load_config, 'config'): + del load_config.config + filesystem = FileSystem() + + # Test with no location + media = Photo(helper.get_file('plain.jpg')) + path_plain = filesystem.get_folder_path(media.get_metadata()) + + # Test with City + media = Photo(helper.get_file('with-location.jpg')) + path_city = filesystem.get_folder_path(media.get_metadata()) + if hasattr(load_config, 'config'): + del load_config.config + + assert path_plain == 'Unknown Location', path_plain + assert path_city == 'Sunnyvale', path_city + + def test_get_folder_path_with_int_in_source_path(): # gh-239 filesystem = FileSystem()