parent
362b1b1363
commit
4cd91e9f2d
25
Readme.md
25
Readme.md
|
@ -214,22 +214,27 @@ What this asks me to do is to name the last folder the same as the album I find
|
|||
|
||||
#### 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.
|
||||
You can construct your folder structure using a combination of the location, dates and camera make/model. 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`.
|
||||
The placeholders can be used to define the folder structure you'd like to create. The default structure would look like `2015-07-Jul/Mountain View`.
|
||||
|
||||
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.
|
||||
* `%country` the name of the country the photo was taken. Requires geolocation data in EXIF.
|
||||
|
||||
I also have some date placeholders you can customize. You can use any of [the standard Python time directives](https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior) to customize the date format to your liking.
|
||||
I have some date placeholders you can customize. You can use any of [the standard Python time directives](https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior) to customize the date format to your liking.
|
||||
|
||||
* `%day` the day the photo was taken.
|
||||
* `%month` the month the photo was taken.
|
||||
* `%year` the year the photo was taken.
|
||||
|
||||
I have camera make and model placeholders which can be used to include the camera make and model into the folder path.
|
||||
|
||||
* `%camera_make` the make of the camera which took the photo.
|
||||
* `%camera_model` the model of the camera which took the photo.
|
||||
|
||||
I also 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.
|
||||
* `%country` the name of the country the photo was taken. Requires geolocation data in EXIF.
|
||||
|
||||
In addition to my built-in and date placeholders you can combine them into a single folder name using my complex placeholders.
|
||||
|
||||
* `%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`.
|
||||
|
@ -322,6 +327,8 @@ When I organize photos I look at the embedded metadata. Here are the details of
|
|||
| Title (photo) | XMP:Title | |
|
||||
| Title (video, audio) | XMP:DisplayName | |
|
||||
| Album | XMP-xmpDM:Album, XMP:Album | XMP:Album is user defined in `configs/ExifTool_config` for backwards compatability |
|
||||
| Camera Make (photo, video) | EXIF:Make, QuickTime:Make | |
|
||||
| Camera Model (photo, video) | EXIF:Model, QuickTime:Model | |
|
||||
|
||||
## Using OpenStreetMap data from MapQuest
|
||||
|
||||
|
|
|
@ -251,9 +251,9 @@ class FileSystem(object):
|
|||
)
|
||||
path.append(parsed_folder_name)
|
||||
break
|
||||
elif part in ('album'):
|
||||
if metadata['album']:
|
||||
path.append(metadata['album'])
|
||||
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])
|
||||
|
|
|
@ -68,6 +68,12 @@ class Base(object):
|
|||
source = self.source
|
||||
return os.path.splitext(source)[1][1:].lower()
|
||||
|
||||
def get_camera_make(self):
|
||||
return None
|
||||
|
||||
def get_camera_model(self):
|
||||
return None
|
||||
|
||||
def get_metadata(self, update_cache=False):
|
||||
"""Get a dictionary of metadata for any file.
|
||||
|
||||
|
@ -85,6 +91,8 @@ class Base(object):
|
|||
|
||||
self.metadata = {
|
||||
'date_taken': self.get_date_taken(),
|
||||
'camera_make': self.get_camera_make(),
|
||||
'camera_model': self.get_camera_model(),
|
||||
'latitude': self.get_coordinate('latitude'),
|
||||
'longitude': self.get_coordinate('longitude'),
|
||||
'album': self.get_album(),
|
||||
|
|
|
@ -42,6 +42,8 @@ class Media(Base):
|
|||
'EXIF:ModifyDate'
|
||||
]
|
||||
}
|
||||
self.camera_make_keys = ['EXIF:Make', 'QuickTime:Make']
|
||||
self.camera_model_keys = ['EXIF:Model', 'QuickTime:Model']
|
||||
self.album_keys = ['XMP-xmpDM:Album', 'XMP:Album']
|
||||
self.title_key = 'XMP:Title'
|
||||
self.latitude_keys = ['EXIF:GPSLatitude']
|
||||
|
@ -132,6 +134,44 @@ class Media(Base):
|
|||
|
||||
return metadata
|
||||
|
||||
def get_camera_make(self):
|
||||
"""Get the camera make stored in EXIF.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
if(not self.is_valid()):
|
||||
return None
|
||||
|
||||
exiftool_attributes = self.get_exiftool_attributes()
|
||||
|
||||
if exiftool_attributes is None:
|
||||
return None
|
||||
|
||||
for camera_make_key in self.camera_make_keys:
|
||||
if camera_make_key in exiftool_attributes:
|
||||
return exiftool_attributes[camera_make_key]
|
||||
|
||||
return None
|
||||
|
||||
def get_camera_model(self):
|
||||
"""Get the camera make stored in EXIF.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
if(not self.is_valid()):
|
||||
return None
|
||||
|
||||
exiftool_attributes = self.get_exiftool_attributes()
|
||||
|
||||
if exiftool_attributes is None:
|
||||
return None
|
||||
|
||||
for camera_model_key in self.camera_model_keys:
|
||||
if camera_model_key in exiftool_attributes:
|
||||
return exiftool_attributes[camera_model_key]
|
||||
|
||||
return None
|
||||
|
||||
def get_original_name(self):
|
||||
"""Get the original name stored in EXIF.
|
||||
|
||||
|
|
|
@ -226,23 +226,44 @@ def test_get_folder_path_with_location():
|
|||
|
||||
assert path == os.path.join('2015-12-Dec','Sunnyvale'), path
|
||||
|
||||
def test_get_folder_path_with_int_in_source_path():
|
||||
# gh-239
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-original-with-camera-make-and-model' % gettempdir())
|
||||
def test_get_folder_path_with_camera_make_and_model():
|
||||
with open('%s/config.ini-original-with-camera-make-and-model' % gettempdir(), 'w') as f:
|
||||
f.write("""
|
||||
[Directory]
|
||||
full_path=%camera_make/%camera_model
|
||||
""")
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
filesystem = FileSystem()
|
||||
temporary_folder, folder = helper.create_working_folder('int')
|
||||
|
||||
origin = os.path.join(folder,'plain.jpg')
|
||||
shutil.copyfile(helper.get_file('plain.jpg'), origin)
|
||||
|
||||
media = Photo(origin)
|
||||
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
|
||||
assert path == os.path.join('Canon', 'Canon EOS REBEL T2i'), path
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-int-in-path' % gettempdir())
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-original-with-camera-make-and-model-fallback' % gettempdir())
|
||||
def test_get_folder_path_with_camera_make_and_model_fallback():
|
||||
with open('%s/config.ini-original-with-camera-make-and-model-fallback' % gettempdir(), 'w') as f:
|
||||
f.write("""
|
||||
[Directory]
|
||||
full_path=%camera_make|"nomake"/%camera_model|"nomodel"
|
||||
""")
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
filesystem = FileSystem()
|
||||
media = Photo(helper.get_file('no-exif.jpg'))
|
||||
path = filesystem.get_folder_path(media.get_metadata())
|
||||
if hasattr(load_config, 'config'):
|
||||
del load_config.config
|
||||
|
||||
assert path == os.path.join('nomake', 'nomodel'), path
|
||||
|
||||
@mock.patch('elodie.config.config_file', '%s/config.ini-int-in-component-path' % gettempdir())
|
||||
def test_get_folder_path_with_int_in_config_component():
|
||||
# gh-239
|
||||
with open('%s/config.ini-int-in-path' % gettempdir(), 'w') as f:
|
||||
with open('%s/config.ini-int-in-component-path' % gettempdir(), 'w') as f:
|
||||
f.write("""
|
||||
[Directory]
|
||||
date=%Y
|
||||
|
@ -258,6 +279,19 @@ full_path=%date
|
|||
|
||||
assert path == os.path.join('2015'), path
|
||||
|
||||
def test_get_folder_path_with_int_in_source_path():
|
||||
# gh-239
|
||||
filesystem = FileSystem()
|
||||
temporary_folder, folder = helper.create_working_folder('int')
|
||||
|
||||
origin = os.path.join(folder,'plain.jpg')
|
||||
shutil.copyfile(helper.get_file('plain.jpg'), origin)
|
||||
|
||||
media = Photo(origin)
|
||||
path = filesystem.get_folder_path(media.get_metadata())
|
||||
|
||||
assert path == os.path.join('2015-12-Dec','Unknown Location'), 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:
|
||||
|
|
|
@ -35,6 +35,18 @@ def test_get_coordinate():
|
|||
|
||||
assert helper.isclose(coordinate, 29.758938), coordinate
|
||||
|
||||
def test_get_camera_make():
|
||||
audio = Audio(helper.get_file('audio.m4a'))
|
||||
coordinate = audio.get_camera_make()
|
||||
|
||||
assert coordinate is None, coordinate
|
||||
|
||||
def test_get_camera_model():
|
||||
audio = Audio(helper.get_file('audio.m4a'))
|
||||
coordinate = audio.get_camera_model()
|
||||
|
||||
assert coordinate is None, coordinate
|
||||
|
||||
def test_get_coordinate_latitude():
|
||||
audio = Audio(helper.get_file('audio.m4a'))
|
||||
coordinate = audio.get_coordinate('latitude')
|
||||
|
|
|
@ -125,6 +125,30 @@ def test_get_date_taken_without_exif():
|
|||
|
||||
assert date_taken == date_taken_from_file, date_taken
|
||||
|
||||
def test_get_camera_make():
|
||||
photo = Photo(helper.get_file('with-location.jpg'))
|
||||
make = photo.get_camera_make()
|
||||
|
||||
assert make == 'Canon', make
|
||||
|
||||
def test_get_camera_make_not_set():
|
||||
photo = Photo(helper.get_file('no-exif.jpg'))
|
||||
make = photo.get_camera_make()
|
||||
|
||||
assert make is None, make
|
||||
|
||||
def test_get_camera_model():
|
||||
photo = Photo(helper.get_file('with-location.jpg'))
|
||||
model = photo.get_camera_model()
|
||||
|
||||
assert model == 'Canon EOS REBEL T2i', model
|
||||
|
||||
def test_get_camera_model_not_set():
|
||||
photo = Photo(helper.get_file('no-exif.jpg'))
|
||||
model = photo.get_camera_model()
|
||||
|
||||
assert model is None, model
|
||||
|
||||
def test_is_valid():
|
||||
photo = Photo(helper.get_file('with-location.jpg'))
|
||||
|
||||
|
|
|
@ -35,6 +35,19 @@ def test_empty_album():
|
|||
video = Video(helper.get_file('video.mov'))
|
||||
assert video.get_album() is None
|
||||
|
||||
def test_get_camera_make():
|
||||
video = Video(helper.get_file('video.mov'))
|
||||
print(video.get_metadata())
|
||||
make = video.get_camera_make()
|
||||
|
||||
assert make == 'Apple', make
|
||||
|
||||
def test_get_camera_model():
|
||||
video = Video(helper.get_file('video.mov'))
|
||||
model = video.get_camera_model()
|
||||
|
||||
assert model == 'iPhone 5', model
|
||||
|
||||
def test_get_coordinate():
|
||||
video = Video(helper.get_file('video.mov'))
|
||||
coordinate = video.get_coordinate()
|
||||
|
|
Loading…
Reference in New Issue