Add support for a %custom placeholder for more complex folder names than %location or %date provide #279 (#283)

This commit is contained in:
Jaisen Mathai 2019-01-18 16:48:32 -08:00 committed by GitHub
parent 91bf181575
commit 69937ca1a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 26 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ build/**
**/*.dng
**/*.nef
**/*.rw2
env/**

View File

@ -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

View File

@ -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.

View File

@ -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()