* add test
This commit is contained in:
parent
6777c32588
commit
e3123872c4
20
Readme.md
20
Readme.md
|
@ -231,18 +231,30 @@ year=%Y
|
|||
date=%year-%month
|
||||
full_path=%date/%location
|
||||
# -> 2015-12/Sunnyvale, California
|
||||
|
||||
full_path=%country/%state/%city
|
||||
# -> US/California/Sunnyvale
|
||||
```
|
||||
|
||||
#### Using fallback folders
|
||||
|
||||
There are times when the EXIF needed to correctly name a folder doesn't exist on a photo. I came up with fallback folders to help you deal with situations such as this. Here's how it works.
|
||||
|
||||
You can specify a series of folder names by separating them with a `|`. That's a pipe, not an L. Let's look at an example.
|
||||
|
||||
```
|
||||
month=%m
|
||||
year=%Ykkkk
|
||||
location=%city
|
||||
full_path=%month/%year/%album|%location|%"Beats me"
|
||||
```
|
||||
|
||||
What this asks me to do is to name the last folder the same as the album I find in EXIF. If I don't find an album in EXIF then I should use the location. If there's no GPS in the EXIf then I should name the last folder `Beats me`.
|
||||
|
||||
#### How folder customization works
|
||||
|
||||
You can construct your folder structure using a combination of the location and dates. Under the `Directory` section of your `config.ini` file you can define placeholder names and assign each a value. For example, `date=%Y-%m` would create a date placeholder with a value of YYYY-MM which would be filled in with the date from the EXIF on the photo.
|
||||
|
||||
The placeholders can be used to define the folder structure you'd like to create. The example above happens to be the default structure and would look like `2015-07-Jul/Mountain View`.
|
||||
|
||||
I have a few built-in location placeholders you can use.
|
||||
I have a few built-in location placeholders you can use. Use this to construct the `%location` you use in `full_path`.
|
||||
|
||||
* `%city` the name of the city the photo was taken. Requires geolocation data in EXIF.
|
||||
* `%state` the name of the state the photo was taken. Requires geolocation data in EXIF.
|
||||
|
|
|
@ -24,10 +24,15 @@ class FileSystem(object):
|
|||
|
||||
def __init__(self):
|
||||
# The default folder path is along the lines of 2015-01-Jan/Chicago
|
||||
self.default_folder_path_definition = [
|
||||
('date', '%Y-%m-%b'), ('location', '%city')
|
||||
]
|
||||
self.default_folder_path_definition = {
|
||||
'date': '%Y-%m-%b',
|
||||
'location': '%city',
|
||||
'full_path': '%date/%album|%location|"{}"'.format(
|
||||
geolocation.__DEFAULT_LOCATION__
|
||||
),
|
||||
}
|
||||
self.cached_folder_path_definition = None
|
||||
self.default_parts = ['album', 'city', 'state', 'country']
|
||||
|
||||
def create_directory(self, directory_path):
|
||||
"""Create a directory if it does not already exist.
|
||||
|
@ -149,6 +154,22 @@ class FileSystem(object):
|
|||
return file_name.lower()
|
||||
|
||||
def get_folder_path_definition(self):
|
||||
"""Returns a list of folder definitions.
|
||||
|
||||
Each element in the list represents a folder.
|
||||
Fallback folders are supported and are nested lists.
|
||||
Return values take the following form.
|
||||
[
|
||||
('date', '%Y-%m-%d'),
|
||||
[
|
||||
('location', '%city'),
|
||||
('album', ''),
|
||||
('"Unknown Location", '')
|
||||
]
|
||||
]
|
||||
|
||||
:returns: list
|
||||
"""
|
||||
# If we've done this already then return it immediately without
|
||||
# incurring any extra work
|
||||
if self.cached_folder_path_definition is not None:
|
||||
|
@ -158,28 +179,46 @@ class FileSystem(object):
|
|||
|
||||
# If Directory is in the config we assume full_path and its
|
||||
# corresponding values (date, location) are also present
|
||||
if('Directory' not in config):
|
||||
return self.default_folder_path_definition
|
||||
|
||||
config_directory = config['Directory']
|
||||
config_directory = self.default_folder_path_definition
|
||||
if('Directory' in config):
|
||||
config_directory = config['Directory']
|
||||
|
||||
# Find all subpatterns of full_path that map to directories.
|
||||
# I.e. %foo/%bar => ['foo', 'bar']
|
||||
# I.e. %foo/%bar|%example|"something" => ['foo', 'bar|example|"something"']
|
||||
path_parts = re.findall(
|
||||
'\%([a-z]+)',
|
||||
'(\%[^/]+)',
|
||||
config_directory['full_path']
|
||||
)
|
||||
|
||||
if not path_parts or len(path_parts) == 0:
|
||||
return self.default_folder_path_definition
|
||||
|
||||
self.cached_folder_path_definition = [
|
||||
(part, config_directory[part]) for part in path_parts
|
||||
]
|
||||
self.cached_folder_path_definition = []
|
||||
for part in path_parts:
|
||||
if part in config_directory:
|
||||
part = part[1:]
|
||||
self.cached_folder_path_definition.append(
|
||||
[(part, config_directory[part])]
|
||||
)
|
||||
elif part in self.default_parts:
|
||||
part = part[1:]
|
||||
self.cached_folder_path_definition.append(
|
||||
[(part, '')]
|
||||
)
|
||||
else:
|
||||
this_part = []
|
||||
for p in part.split('|'):
|
||||
p = p[1:]
|
||||
this_part.append(
|
||||
(p, config_directory[p] if p in config_directory else '')
|
||||
)
|
||||
self.cached_folder_path_definition.append(this_part)
|
||||
|
||||
return self.cached_folder_path_definition
|
||||
|
||||
def get_folder_path(self, metadata):
|
||||
"""Get folder path by various parameters.
|
||||
"""Given a media's metadata this function returns the folder path as a string.
|
||||
|
||||
:param metadata dict: Metadata dictionary.
|
||||
:returns: str
|
||||
|
@ -187,31 +226,39 @@ class FileSystem(object):
|
|||
path_parts = self.get_folder_path_definition()
|
||||
path = []
|
||||
for path_part in path_parts:
|
||||
part, mask = path_part
|
||||
if part in ('date', 'day', 'month', 'year'):
|
||||
path.append(time.strftime(mask, metadata['date_taken']))
|
||||
elif part in ('location', 'city', 'state', 'country'):
|
||||
place_name = geolocation.place_name(
|
||||
metadata['latitude'],
|
||||
metadata['longitude']
|
||||
)
|
||||
# We support fallback values so that
|
||||
# 'album|city|"Unknown Location"
|
||||
# %album|%city|"Unknown Location" results in
|
||||
# My Album - when an album exists
|
||||
# Sunnyvale - when no album exists but a city exists
|
||||
# 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'])
|
||||
)
|
||||
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)
|
||||
|
||||
# For now we always make the leaf folder an album if it's in the EXIF.
|
||||
# This is to preserve backwards compatability until we figure out how
|
||||
# to include %album in the config.ini syntax.
|
||||
if(metadata['album'] is not None):
|
||||
if(len(path) == 1):
|
||||
path.append(metadata['album'])
|
||||
elif(len(path) == 2):
|
||||
path[1] = metadata['album']
|
||||
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'):
|
||||
if metadata['album']:
|
||||
path.append(metadata['album'])
|
||||
break
|
||||
elif part.startswith('"') and part.endswith('"'):
|
||||
path.append(part[1:-1])
|
||||
|
||||
return os.path.join(*path)
|
||||
|
||||
|
|
|
@ -430,38 +430,6 @@ def test_update_time_on_video():
|
|||
assert metadata['date_taken'] != metadata_processed['date_taken']
|
||||
assert metadata_processed['date_taken'] == helper.time_convert((2000, 1, 1, 12, 0, 0, 5, 1, 0)), metadata_processed['date_taken']
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-multiple-directories' % gettempdir())
|
||||
def test_update_with_more_than_two_levels_of_directories():
|
||||
with open('%s/config.ini-multiple-directories' % gettempdir(), 'w') as f:
|
||||
f.write("""
|
||||
[Directory]
|
||||
year=%Y
|
||||
month=%m
|
||||
day=%d
|
||||
full_path=%year/%month/%day
|
||||
""")
|
||||
|
||||
temporary_folder, folder = helper.create_working_folder()
|
||||
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
||||
|
||||
origin = '%s/plain.jpg' % folder
|
||||
shutil.copyfile(helper.get_file('plain.jpg'), origin)
|
||||
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
cfg = load_config()
|
||||
helper.reset_dbs()
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(elodie._import, ['--destination', folder_destination, folder])
|
||||
runner2 = CliRunner()
|
||||
result = runner2.invoke(elodie._update, ['--title', 'test title', folder_destination])
|
||||
helper.restore_dbs()
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
||||
updated_file_path = '{}/2015/12/05/2015-12-05_00-59-26-plain-test-title.jpg'.format(folder_destination)
|
||||
assert os.path.isfile(updated_file_path), updated_file_path
|
||||
|
||||
def test_update_with_directory_passed_in():
|
||||
temporary_folder, folder = helper.create_working_folder()
|
||||
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
||||
|
|
|
@ -226,6 +226,20 @@ def test_get_folder_path_with_location():
|
|||
|
||||
assert path == os.path.join('2015-12-Dec','Sunnyvale'), path
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-original-default-unknown-location' % gettempdir())
|
||||
def test_get_folder_path_with_original_default_unknown_location():
|
||||
with open('%s/config.ini-original-default-with-unknown-location' % gettempdir(), 'w') as f:
|
||||
f.write('')
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
filesystem = FileSystem()
|
||||
media = Photo(helper.get_file('plain.jpg'))
|
||||
path = filesystem.get_folder_path(media.get_metadata())
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
||||
assert path == os.path.join('2015-12-Dec','Unknown Location'), path
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-custom-path' % gettempdir())
|
||||
def test_get_folder_path_with_custom_path():
|
||||
with open('%s/config.ini-custom-path' % gettempdir(), 'w') as f:
|
||||
|
@ -248,10 +262,33 @@ full_path=%date/%location
|
|||
|
||||
assert path == os.path.join('2015-12-05','United States of America-California-Sunnyvale'), path
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-fallback' % gettempdir())
|
||||
def test_get_folder_path_with_fallback_folder():
|
||||
with open('%s/config.ini-fallback' % gettempdir(), 'w') as f:
|
||||
f.write("""
|
||||
[Directory]
|
||||
year=%Y
|
||||
month=%m
|
||||
full_path=%year/%month/%album|%"No Album Fool"/%month
|
||||
""")
|
||||
#full_path=%year/%album|"No Album"
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
filesystem = FileSystem()
|
||||
media = Photo(helper.get_file('plain.jpg'))
|
||||
path = filesystem.get_folder_path(media.get_metadata())
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
||||
assert path == os.path.join('2015','12','No Album Fool','12'), path
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-location-date' % gettempdir())
|
||||
def test_get_folder_path_with_with_more_than_two_levels():
|
||||
with open('%s/config.ini-location-date' % gettempdir(), 'w') as f:
|
||||
f.write("""
|
||||
[MapQuest]
|
||||
key=czjNKTtFjLydLteUBwdgKAIC8OAbGLUx
|
||||
|
||||
[Directory]
|
||||
year=%Y
|
||||
month=%m
|
||||
|
@ -551,6 +588,37 @@ def test_process_video_with_album_then_title():
|
|||
assert origin_checksum != destination_checksum, destination_checksum
|
||||
assert helper.path_tz_fix(os.path.join('2015-01-Jan','test_album','2015-01-19_12-45-11-movie-test_title.mov')) in destination, destination
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-multiple-directories' % gettempdir())
|
||||
def test_process_twice_more_than_two_levels_of_directories():
|
||||
with open('%s/config.ini-multiple-directories' % gettempdir(), 'w') as f:
|
||||
f.write("""
|
||||
[Directory]
|
||||
year=%Y
|
||||
month=%m
|
||||
day=%d
|
||||
full_path=%year/%month/%day
|
||||
""")
|
||||
|
||||
filesystem = FileSystem()
|
||||
temporary_folder, folder = helper.create_working_folder()
|
||||
|
||||
origin = os.path.join(folder,'plain.jpg')
|
||||
shutil.copyfile(helper.get_file('plain.jpg'), origin)
|
||||
|
||||
media = Photo(origin)
|
||||
destination = filesystem.process_file(origin, temporary_folder, media, allowDuplicate=True)
|
||||
|
||||
assert helper.path_tz_fix(os.path.join('2015','12','05', '2015-12-05_00-59-26-plain.jpg')) in destination, destination
|
||||
|
||||
media_second = Photo(destination)
|
||||
media_second.set_title('foo')
|
||||
destination_second = filesystem.process_file(destination, temporary_folder, media_second, allowDuplicate=True)
|
||||
|
||||
assert destination.replace('.jpg', '-foo.jpg') == destination_second, destination_second
|
||||
|
||||
shutil.rmtree(folder)
|
||||
shutil.rmtree(os.path.dirname(os.path.dirname(destination)))
|
||||
|
||||
def test_set_utime_with_exif_date():
|
||||
filesystem = FileSystem()
|
||||
temporary_folder, folder = helper.create_working_folder()
|
||||
|
@ -618,7 +686,7 @@ def test_get_folder_path_definition_default():
|
|||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
||||
assert path_definition == filesystem.default_folder_path_definition, path_definition
|
||||
assert path_definition == [[('date', '%Y-%m-%b')], [('album', ''), ('location', '%city'), ('Unknown Location"', '')]], path_definition
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-date-location' % gettempdir())
|
||||
def test_get_folder_path_definition_date_location():
|
||||
|
@ -635,7 +703,7 @@ full_path=%date/%location
|
|||
filesystem = FileSystem()
|
||||
path_definition = filesystem.get_folder_path_definition()
|
||||
expected = [
|
||||
('date', '%Y-%m-%d'), ('location', '%country')
|
||||
[('date', '%Y-%m-%d')], [('location', '%country')]
|
||||
]
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
@ -657,7 +725,7 @@ full_path=%location/%date
|
|||
filesystem = FileSystem()
|
||||
path_definition = filesystem.get_folder_path_definition()
|
||||
expected = [
|
||||
('location', '%country'), ('date', '%Y-%m-%d')
|
||||
[('location', '%country')], [('date', '%Y-%m-%d')]
|
||||
]
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
@ -679,7 +747,7 @@ full_path=%date/%location
|
|||
filesystem = FileSystem()
|
||||
path_definition = filesystem.get_folder_path_definition()
|
||||
expected = [
|
||||
('date', '%Y-%m-%d'), ('location', '%country')
|
||||
[('date', '%Y-%m-%d')], [('location', '%country')]
|
||||
]
|
||||
|
||||
assert path_definition == expected, path_definition
|
||||
|
@ -696,7 +764,7 @@ full_path=%date/%location
|
|||
filesystem = FileSystem()
|
||||
path_definition = filesystem.get_folder_path_definition()
|
||||
expected = [
|
||||
('date', '%Y-%m-%d'), ('location', '%country')
|
||||
[('date', '%Y-%m-%d')], [('location', '%country')]
|
||||
]
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
@ -717,7 +785,7 @@ full_path=%year/%month/%day
|
|||
filesystem = FileSystem()
|
||||
path_definition = filesystem.get_folder_path_definition()
|
||||
expected = [
|
||||
('year', '%Y'), ('month', '%m'), ('day', '%d')
|
||||
[('year', '%Y')], [('month', '%m')], [('day', '%d')]
|
||||
]
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
@ -738,9 +806,30 @@ full_path=%year
|
|||
filesystem = FileSystem()
|
||||
path_definition = filesystem.get_folder_path_definition()
|
||||
expected = [
|
||||
('year', '%Y')
|
||||
[('year', '%Y')]
|
||||
]
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
||||
assert path_definition == expected, path_definition
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-multi-level-custom' % gettempdir())
|
||||
def test_get_folder_path_definition_multi_level_custom():
|
||||
with open('%s/config.ini-multi-level-custom' % gettempdir(), 'w') as f:
|
||||
f.write("""
|
||||
[Directory]
|
||||
year=%Y
|
||||
month=%M
|
||||
full_path=%year/%album|%month|%"foo"/%month
|
||||
""")
|
||||
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
filesystem = FileSystem()
|
||||
path_definition = filesystem.get_folder_path_definition()
|
||||
|
||||
expected = [[('year', '%Y')], [('album', ''), ('month', '%M'), ('"foo"', '')], [('month', '%M')]]
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
||||
assert path_definition == expected, path_definition
|
||||
|
|
Loading…
Reference in New Issue