2015-10-07 08:47:51 +02:00
|
|
|
"""
|
2016-04-07 10:08:33 +02:00
|
|
|
The media module provides a base :class:`Media` class for media objects that
|
2016-01-08 23:49:06 +01:00
|
|
|
are tracked by Elodie. The Media class provides some base functionality used
|
|
|
|
by all the media types, but isn't itself used to represent anything. Its
|
|
|
|
sub-classes (:class:`~elodie.media.audio.Audio`,
|
|
|
|
:class:`~elodie.media.photo.Photo`, and :class:`~elodie.media.video.Video`)
|
|
|
|
are used to represent the actual files.
|
|
|
|
|
|
|
|
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
2015-10-07 08:47:51 +02:00
|
|
|
"""
|
2016-03-12 20:09:28 +01:00
|
|
|
from __future__ import print_function
|
2015-10-07 08:47:51 +02:00
|
|
|
|
2017-01-22 09:19:44 +01:00
|
|
|
import os
|
|
|
|
|
2015-10-07 08:47:51 +02:00
|
|
|
# load modules
|
2015-10-21 08:51:14 +02:00
|
|
|
from elodie import constants
|
2016-01-08 01:45:55 +01:00
|
|
|
from elodie.dependencies import get_exiftool
|
2016-06-21 20:19:40 +02:00
|
|
|
from elodie.external.pyexiftool import ExifTool
|
2016-04-07 10:08:33 +02:00
|
|
|
from elodie.media.base import Base
|
2015-10-21 08:51:14 +02:00
|
|
|
|
2016-01-02 08:23:06 +01:00
|
|
|
|
2016-04-07 10:08:33 +02:00
|
|
|
class Media(Base):
|
2015-10-20 10:17:09 +02:00
|
|
|
|
2016-01-08 23:49:06 +01:00
|
|
|
"""The base class for all media objects.
|
|
|
|
|
|
|
|
:param str source: The fully qualified path to the video file.
|
2015-10-07 08:47:51 +02:00
|
|
|
"""
|
2016-01-08 23:49:06 +01:00
|
|
|
|
|
|
|
__name__ = 'Media'
|
|
|
|
|
2016-02-12 20:22:26 +01:00
|
|
|
d_coordinates = {
|
2016-02-14 09:55:39 +01:00
|
|
|
'latitude': 'latitude_ref',
|
|
|
|
'longitude': 'longitude_ref'
|
|
|
|
}
|
2016-02-12 20:22:26 +01:00
|
|
|
|
2015-10-07 08:47:51 +02:00
|
|
|
def __init__(self, source=None):
|
2016-04-07 10:08:33 +02:00
|
|
|
super(Media, self).__init__(source)
|
2015-10-14 09:39:30 +02:00
|
|
|
self.exif_map = {
|
2016-06-21 20:19:40 +02:00
|
|
|
'date_taken': [
|
|
|
|
'EXIF:DateTimeOriginal',
|
2016-06-24 06:47:04 +02:00
|
|
|
'EXIF:CreateDate',
|
|
|
|
'EXIF:ModifyDate'
|
2016-06-21 20:19:40 +02:00
|
|
|
]
|
2015-10-14 09:39:30 +02:00
|
|
|
}
|
2016-06-24 06:34:51 +02:00
|
|
|
self.album_keys = ['XMP-xmpDM:Album', 'XMP:Album']
|
2016-06-21 20:19:40 +02:00
|
|
|
self.title_key = 'XMP:Title'
|
|
|
|
self.latitude_keys = ['EXIF:GPSLatitude']
|
|
|
|
self.longitude_keys = ['EXIF:GPSLongitude']
|
|
|
|
self.latitude_ref_key = 'EXIF:GPSLatitudeRef'
|
|
|
|
self.longitude_ref_key = 'EXIF:GPSLongitudeRef'
|
2017-01-22 09:19:44 +01:00
|
|
|
self.original_name_key = 'XMP:OriginalFileName'
|
2016-06-21 20:19:40 +02:00
|
|
|
self.set_gps_ref = True
|
|
|
|
self.exiftool_addedargs = [
|
|
|
|
'-overwrite_original',
|
|
|
|
u'-config',
|
|
|
|
u'"{}"'.format(constants.exiftool_config)
|
|
|
|
]
|
2015-10-14 09:39:30 +02:00
|
|
|
|
|
|
|
def get_album(self):
|
2016-01-08 23:49:06 +01:00
|
|
|
"""Get album from EXIF
|
|
|
|
|
|
|
|
:returns: None or string
|
|
|
|
"""
|
2015-10-14 09:39:30 +02:00
|
|
|
if(not self.is_valid()):
|
|
|
|
return None
|
|
|
|
|
2015-10-21 08:51:14 +02:00
|
|
|
exiftool_attributes = self.get_exiftool_attributes()
|
2016-06-21 20:19:40 +02:00
|
|
|
if exiftool_attributes is None:
|
2015-10-14 09:39:30 +02:00
|
|
|
return None
|
2016-01-02 08:23:06 +01:00
|
|
|
|
2016-06-24 06:34:51 +02:00
|
|
|
for album_key in self.album_keys:
|
|
|
|
if album_key in exiftool_attributes:
|
|
|
|
return exiftool_attributes[album_key]
|
2015-10-07 08:47:51 +02:00
|
|
|
|
2016-06-24 06:34:51 +02:00
|
|
|
return None
|
2016-01-08 23:49:06 +01:00
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
def get_coordinate(self, type='latitude'):
|
|
|
|
"""Get latitude or longitude of media from EXIF
|
2016-01-08 23:49:06 +01:00
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
:param str type: Type of coordinate to get. Either "latitude" or
|
|
|
|
"longitude".
|
|
|
|
:returns: float or None if not present in EXIF or a non-photo file
|
2016-01-08 23:49:06 +01:00
|
|
|
"""
|
2015-10-14 09:39:30 +02:00
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
exif = self.get_exiftool_attributes()
|
|
|
|
if not exif:
|
|
|
|
return None
|
2015-10-14 09:39:30 +02:00
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
# The lat/lon _keys array has an order of precedence.
|
|
|
|
# The first key is writable and we will give the writable
|
|
|
|
# key precence when reading.
|
2017-01-03 09:49:56 +01:00
|
|
|
direction_multiplier = 1.0
|
2016-06-21 20:19:40 +02:00
|
|
|
for key in self.latitude_keys + self.longitude_keys:
|
2017-01-03 09:49:56 +01:00
|
|
|
if key not in exif:
|
|
|
|
continue
|
|
|
|
|
2017-01-22 09:19:44 +01:00
|
|
|
# Cast coordinate to a float due to a bug in exiftool's
|
2017-01-03 09:49:56 +01:00
|
|
|
# -json output format.
|
|
|
|
# https://github.com/jmathai/elodie/issues/171
|
2017-01-22 09:19:44 +01:00
|
|
|
# http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,7952.0.html # noqa
|
2017-01-03 09:49:56 +01:00
|
|
|
this_coordinate = float(exif[key])
|
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
# TODO: verify that we need to check ref key
|
|
|
|
# when self.set_gps_ref != True
|
2017-01-03 09:49:56 +01:00
|
|
|
if type == 'latitude' and key in self.latitude_keys:
|
2016-06-24 06:31:58 +02:00
|
|
|
if self.latitude_ref_key in exif and \
|
|
|
|
exif[self.latitude_ref_key] == 'S':
|
2017-01-03 09:49:56 +01:00
|
|
|
direction_multiplier = -1.0
|
|
|
|
return this_coordinate * direction_multiplier
|
|
|
|
elif type == 'longitude' and key in self.longitude_keys:
|
2016-06-24 06:31:58 +02:00
|
|
|
if self.longitude_ref_key in exif and \
|
|
|
|
exif[self.longitude_ref_key] == 'W':
|
2017-01-03 09:49:56 +01:00
|
|
|
direction_multiplier = -1.0
|
|
|
|
return this_coordinate * direction_multiplier
|
2016-06-21 20:19:40 +02:00
|
|
|
|
|
|
|
return None
|
2015-10-14 09:39:30 +02:00
|
|
|
|
2015-10-21 08:51:14 +02:00
|
|
|
def get_exiftool_attributes(self):
|
2016-01-08 23:49:06 +01:00
|
|
|
"""Get attributes for the media object from exiftool.
|
|
|
|
|
|
|
|
:returns: dict, or False if exiftool was not available.
|
|
|
|
"""
|
2016-06-21 20:19:40 +02:00
|
|
|
source = self.source
|
2016-01-08 01:45:55 +01:00
|
|
|
exiftool = get_exiftool()
|
2015-10-21 08:51:14 +02:00
|
|
|
if(exiftool is None):
|
|
|
|
return False
|
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
with ExifTool(addedargs=self.exiftool_addedargs) as et:
|
|
|
|
metadata = et.get_metadata(source)
|
|
|
|
if not metadata:
|
|
|
|
return False
|
2015-10-21 08:51:14 +02:00
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
return metadata
|
2015-10-21 08:51:14 +02:00
|
|
|
|
2017-01-22 09:19:44 +01:00
|
|
|
def get_original_name(self):
|
|
|
|
"""Get the original name 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
|
|
|
|
|
|
|
|
if(self.original_name_key not in exiftool_attributes):
|
|
|
|
return None
|
|
|
|
|
|
|
|
return exiftool_attributes[self.original_name_key]
|
|
|
|
|
2015-10-28 08:19:21 +01:00
|
|
|
def get_title(self):
|
2016-01-08 23:49:06 +01:00
|
|
|
"""Get the title for a photo of video
|
|
|
|
|
|
|
|
:returns: str or None if no title is set or not a valid media type
|
|
|
|
"""
|
2015-10-28 08:19:21 +01:00
|
|
|
if(not self.is_valid()):
|
|
|
|
return None
|
|
|
|
|
|
|
|
exiftool_attributes = self.get_exiftool_attributes()
|
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
if exiftool_attributes is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if(self.title_key not in exiftool_attributes):
|
2015-10-28 08:19:21 +01:00
|
|
|
return None
|
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
return exiftool_attributes[self.title_key]
|
2015-10-14 05:26:55 +02:00
|
|
|
|
2016-03-13 21:37:06 +01:00
|
|
|
def reset_cache(self):
|
2016-04-07 10:08:33 +02:00
|
|
|
"""Resets any internal cache
|
|
|
|
"""
|
2016-03-13 21:37:06 +01:00
|
|
|
self.exiftool_attributes = None
|
2016-04-07 10:08:33 +02:00
|
|
|
super(Media, self).reset_cache()
|
2016-03-13 21:37:06 +01:00
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
def set_album(self, album):
|
2016-01-08 23:49:06 +01:00
|
|
|
"""Set album for a photo
|
|
|
|
|
|
|
|
:param str name: Name of album
|
|
|
|
:returns: bool
|
|
|
|
"""
|
2016-06-21 20:19:40 +02:00
|
|
|
if(not self.is_valid()):
|
|
|
|
return None
|
2015-10-21 08:51:14 +02:00
|
|
|
|
2016-06-24 06:34:51 +02:00
|
|
|
tags = {self.album_keys[0]: album}
|
2016-06-21 20:19:40 +02:00
|
|
|
status = self.__set_tags(tags)
|
|
|
|
self.reset_cache()
|
|
|
|
|
|
|
|
return status
|
|
|
|
|
|
|
|
def set_date_taken(self, time):
|
|
|
|
"""Set the date/time a photo was taken.
|
|
|
|
|
|
|
|
:param datetime time: datetime object of when the photo was taken
|
|
|
|
:returns: bool
|
|
|
|
"""
|
|
|
|
if(time is None):
|
2015-10-21 08:51:14 +02:00
|
|
|
return False
|
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
tags = {}
|
|
|
|
formatted_time = time.strftime('%Y:%m:%d %H:%M:%S')
|
|
|
|
for key in self.exif_map['date_taken']:
|
|
|
|
tags[key] = formatted_time
|
2015-12-01 09:39:05 +01:00
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
status = self.__set_tags(tags)
|
|
|
|
self.reset_cache()
|
|
|
|
return status
|
2015-12-03 08:47:54 +01:00
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
def set_location(self, latitude, longitude):
|
|
|
|
if(not self.is_valid()):
|
|
|
|
return None
|
|
|
|
|
|
|
|
# The lat/lon _keys array has an order of precedence.
|
|
|
|
# The first key is writable and we will give the writable
|
|
|
|
# key precence when reading.
|
|
|
|
tags = {
|
|
|
|
self.latitude_keys[0]: latitude,
|
|
|
|
self.longitude_keys[0]: longitude,
|
|
|
|
}
|
|
|
|
|
|
|
|
# If self.set_gps_ref == True then it means we are writing an EXIF
|
|
|
|
# GPS tag which requires us to set the reference key.
|
|
|
|
# That's because the lat/lon are absolute values.
|
|
|
|
if self.set_gps_ref:
|
|
|
|
if latitude < 0:
|
|
|
|
tags[self.latitude_ref_key] = 'S'
|
|
|
|
|
|
|
|
if longitude < 0:
|
|
|
|
tags[self.longitude_ref_key] = 'W'
|
|
|
|
|
|
|
|
status = self.__set_tags(tags)
|
|
|
|
self.reset_cache()
|
|
|
|
|
|
|
|
return status
|
|
|
|
|
2017-01-22 09:19:44 +01:00
|
|
|
def set_original_name(self):
|
|
|
|
"""Sets the original name EXIF tag if not already set.
|
|
|
|
|
|
|
|
:returns: True, False, None
|
|
|
|
"""
|
|
|
|
if(not self.is_valid()):
|
|
|
|
return None
|
|
|
|
|
|
|
|
# If EXIF original name tag is set then we return.
|
|
|
|
if self.get_original_name() is not None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
source = self.source
|
|
|
|
name = os.path.basename(source)
|
|
|
|
tags = {self.original_name_key: name}
|
|
|
|
status = self.__set_tags(tags)
|
|
|
|
self.reset_cache()
|
|
|
|
return status
|
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
def set_title(self, title):
|
|
|
|
"""Set title for a photo.
|
|
|
|
|
|
|
|
:param str title: Title of the photo.
|
|
|
|
:returns: bool
|
|
|
|
"""
|
|
|
|
if(not self.is_valid()):
|
|
|
|
return None
|
|
|
|
|
|
|
|
if(title is None):
|
|
|
|
return None
|
|
|
|
|
|
|
|
tags = {self.title_key: title}
|
|
|
|
status = self.__set_tags(tags)
|
2016-03-13 21:37:06 +01:00
|
|
|
self.reset_cache()
|
2016-06-21 20:19:40 +02:00
|
|
|
|
|
|
|
return status
|
|
|
|
|
|
|
|
def __set_tags(self, tags):
|
|
|
|
if(not self.is_valid()):
|
|
|
|
return None
|
|
|
|
|
|
|
|
source = self.source
|
|
|
|
|
|
|
|
status = ''
|
|
|
|
with ExifTool(addedargs=self.exiftool_addedargs) as et:
|
|
|
|
status = et.set_tags(tags, source)
|
|
|
|
|
|
|
|
return status != ''
|