Add support for a %custom placeholder for more complex folder names than %location or %date provide #279 (#283)
This commit is contained in:
parent
91bf181575
commit
69937ca1a3
|
@ -9,3 +9,4 @@ build/**
|
||||||
**/*.dng
|
**/*.dng
|
||||||
**/*.nef
|
**/*.nef
|
||||||
**/*.rw2
|
**/*.rw2
|
||||||
|
env/**
|
||||||
|
|
|
@ -194,6 +194,12 @@ month=%m
|
||||||
year=%Y
|
year=%Y
|
||||||
full_path=%year-%month/%location
|
full_path=%year-%month/%location
|
||||||
# -> 2015-12/Sunnyvale, California
|
# -> 2015-12/Sunnyvale, California
|
||||||
|
|
||||||
|
date=%Y
|
||||||
|
location=%city, %state
|
||||||
|
custom=%date %album
|
||||||
|
full_path=%location/%custom
|
||||||
|
# -> Sunnyvale, California/2015 Birthday Party
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using fallback folders
|
#### 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`.
|
* `%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`.
|
* `%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
|
### Reorganize by changing location and dates
|
||||||
|
|
||||||
|
|
|
@ -218,7 +218,7 @@ class FileSystem(object):
|
||||||
def get_folder_path(self, metadata):
|
def get_folder_path(self, metadata):
|
||||||
"""Given a media's metadata this function returns the folder path as a string.
|
"""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
|
:returns: str
|
||||||
"""
|
"""
|
||||||
path_parts = self.get_folder_path_definition()
|
path_parts = self.get_folder_path_definition()
|
||||||
|
@ -232,34 +232,70 @@ class FileSystem(object):
|
||||||
# Unknown Location - when neither an album nor location exist
|
# Unknown Location - when neither an album nor location exist
|
||||||
for this_part in path_part:
|
for this_part in path_part:
|
||||||
part, mask = this_part
|
part, mask = this_part
|
||||||
if part in ('date', 'day', 'month', 'year'):
|
this_path = self.get_dynamic_path(part, mask, metadata)
|
||||||
path.append(
|
if this_path:
|
||||||
time.strftime(mask, metadata['date_taken'])
|
path.append(this_path.strip())
|
||||||
)
|
# We break as soon as we have a value to append
|
||||||
|
# Else we continue for fallbacks
|
||||||
break
|
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)
|
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):
|
def parse_mask_for_location(self, mask, location_parts, place_name):
|
||||||
"""Takes a mask for a location and interpolates the actual place names.
|
"""Takes a mask for a location and interpolates the actual place names.
|
||||||
|
|
||||||
|
|
|
@ -279,6 +279,54 @@ full_path=%date
|
||||||
|
|
||||||
assert path == os.path.join('2015'), path
|
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():
|
def test_get_folder_path_with_int_in_source_path():
|
||||||
# gh-239
|
# gh-239
|
||||||
filesystem = FileSystem()
|
filesystem = FileSystem()
|
||||||
|
|
Loading…
Reference in New Issue