Passed
Pull Request — master (#351)
by
unknown
01:59
created

elodie.media.media.Media.reset_cache()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
"""
2
The media module provides a base :class:`Media` class for media objects that
3
are tracked by Elodie. The Media class provides some base functionality used
4
by all the media types, but isn't itself used to represent anything. Its
5
sub-classes (:class:`~elodie.media.audio.Audio`,
6
:class:`~elodie.media.photo.Photo`, and :class:`~elodie.media.video.Video`)
7
are used to represent the actual files.
8
9
.. moduleauthor:: Jaisen Mathai <[email protected]>
10
"""
11
from __future__ import print_function
12
13
import os
14
15
# load modules
16
from elodie.external.pyexiftool import ExifTool
17
from elodie.media.base import Base
18
19
class Media(Base):
20
21
    """The base class for all media objects.
22
23
    :param str source: The fully qualified path to the video file.
24
    """
25
26
    __name__ = 'Media'
27
28
    d_coordinates = {
29
        'latitude': 'latitude_ref',
30
        'longitude': 'longitude_ref'
31
    }
32
33
    def __init__(self, source=None):
34
        super(Media, self).__init__(source)
35
        self.exif_map = {
36
            'date_taken': [
37
                'EXIF:DateTimeOriginal',
38
                'EXIF:CreateDate',
39
                'EXIF:ModifyDate'
40
            ]
41
        }
42
        self.camera_make_keys = ['EXIF:Make', 'QuickTime:Make']
43
        self.camera_model_keys = ['EXIF:Model', 'QuickTime:Model']
44
        self.album_keys = ['XMP-xmpDM:Album', 'XMP:Album']
45
        self.title_key = 'XMP:Title'
46
        self.latitude_keys = ['EXIF:GPSLatitude']
47
        self.longitude_keys = ['EXIF:GPSLongitude']
48
        self.latitude_ref_key = 'EXIF:GPSLatitudeRef'
49
        self.longitude_ref_key = 'EXIF:GPSLongitudeRef'
50
        self.original_name_key = 'XMP:OriginalFileName'
51
        self.set_gps_ref = True
52
53
    def get_album(self):
54
        """Get album from EXIF
55
56
        :returns: None or string
57
        """
58
        if(not self.is_valid()):
59
            return None
60
61
        exiftool_attributes = self.get_exiftool_attributes()
62
        if exiftool_attributes is None:
63
            return None
64
65
        for album_key in self.album_keys:
66
            if album_key in exiftool_attributes:
67
                return exiftool_attributes[album_key]
68
69
        return None
70
71
    def get_coordinate(self, type='latitude'):
72
        """Get latitude or longitude of media from EXIF
73
74
        :param str type: Type of coordinate to get. Either "latitude" or
75
            "longitude".
76
        :returns: float or None if not present in EXIF or a non-photo file
77
        """
78
79
        exif = self.get_exiftool_attributes()
80
        if not exif:
81
            return None
82
83
        # The lat/lon _keys array has an order of precedence.
84
        # The first key is writable and we will give the writable
85
        #   key precence when reading.
86
        direction_multiplier = 1.0
87
        for key in self.latitude_keys + self.longitude_keys:
88
            if key not in exif:
89
                continue
90
91
            # Cast coordinate to a float due to a bug in exiftool's
92
            #   -json output format.
93
            # https://github.com/jmathai/elodie/issues/171
94
            # http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,7952.0.html  # noqa
95
            this_coordinate = float(exif[key])
96
97
            # TODO: verify that we need to check ref key
98
            #   when self.set_gps_ref != True
99
            if type == 'latitude' and key in self.latitude_keys:
100
                if self.latitude_ref_key in exif and \
101
                        exif[self.latitude_ref_key] == 'S':
102
                    direction_multiplier = -1.0
103
                return this_coordinate * direction_multiplier
104
            elif type == 'longitude' and key in self.longitude_keys:
105
                if self.longitude_ref_key in exif and \
106
                        exif[self.longitude_ref_key] == 'W':
107
                    direction_multiplier = -1.0
108
                return this_coordinate * direction_multiplier
109
110
        return None
111
112
    def get_exiftool_attributes(self):
113
        """Get attributes for the media object from exiftool.
114
115
        :returns: dict, or False if exiftool was not available.
116
        """
117
        source = self.source
118
119
        #Cache metadata results and use if already exists for media
120
        if(self.metadata is None):
121
            metadata = ExifTool().get_metadata(source)
122
            self.metadata = metadata
123
        else:
124
            metadata = self.metadata
125
126
        if not metadata:
127
            return False
128
129
        return metadata
130
131
    def get_camera_make(self):
132
        """Get the camera make stored in EXIF.
133
134
        :returns: str
135
        """
136
        if(not self.is_valid()):
137
            return None
138
139
        exiftool_attributes = self.get_exiftool_attributes()
140
141
        if exiftool_attributes is None:
142
            return None
143
144
        for camera_make_key in self.camera_make_keys:
145
            if camera_make_key in exiftool_attributes:
146
                return exiftool_attributes[camera_make_key]
147
148
        return None
149
150
    def get_camera_model(self):
151
        """Get the camera make stored in EXIF.
152
153
        :returns: str
154
        """
155
        if(not self.is_valid()):
156
            return None
157
158
        exiftool_attributes = self.get_exiftool_attributes()
159
160
        if exiftool_attributes is None:
161
            return None
162
163
        for camera_model_key in self.camera_model_keys:
164
            if camera_model_key in exiftool_attributes:
165
                return exiftool_attributes[camera_model_key]
166
167
        return None
168
169
    def get_original_name(self):
170
        """Get the original name stored in EXIF.
171
172
        :returns: str
173
        """
174
        if(not self.is_valid()):
175
            return None
176
177
        exiftool_attributes = self.get_exiftool_attributes()
178
179
        if exiftool_attributes is None:
180
            return None
181
182
        if(self.original_name_key not in exiftool_attributes):
183
            return None
184
185
        return exiftool_attributes[self.original_name_key]
186
187
    def get_title(self):
188
        """Get the title for a photo of video
189
190
        :returns: str or None if no title is set or not a valid media type
191
        """
192
        if(not self.is_valid()):
193
            return None
194
195
        exiftool_attributes = self.get_exiftool_attributes()
196
197
        if exiftool_attributes is None:
198
            return None
199
200
        if(self.title_key not in exiftool_attributes):
201
            return None
202
203
        return exiftool_attributes[self.title_key]
204
205
    def reset_cache(self):
206
        """Resets any internal cache
207
        """
208
        self.exiftool_attributes = None
209
        super(Media, self).reset_cache()
210
211
    def set_album(self, album):
212
        """Set album for a photo
213
214
        :param str name: Name of album
215
        :returns: bool
216
        """
217
        if(not self.is_valid()):
218
            return None
219
220
        tags = {self.album_keys[0]: album}
221
        status = self.__set_tags(tags)
222
        self.reset_cache()
223
224
        return status
225
226
    def set_date_taken(self, time):
227
        """Set the date/time a photo was taken.
228
229
        :param datetime time: datetime object of when the photo was taken
230
        :returns: bool
231
        """
232
        if(time is None):
233
            return False
234
235
        tags = {}
236
        formatted_time = time.strftime('%Y:%m:%d %H:%M:%S')
237
        for key in self.exif_map['date_taken']:
238
            tags[key] = formatted_time
239
240
        status = self.__set_tags(tags)
241
        self.reset_cache()
242
        return status
243
244
    def set_location(self, latitude, longitude):
245
        if(not self.is_valid()):
246
            return None
247
248
        # The lat/lon _keys array has an order of precedence.
249
        # The first key is writable and we will give the writable
250
        #   key precence when reading.
251
        tags = {
252
            self.latitude_keys[0]: latitude,
253
            self.longitude_keys[0]: longitude,
254
        }
255
256
        # If self.set_gps_ref == True then it means we are writing an EXIF
257
        #   GPS tag which requires us to set the reference key.
258
        # That's because the lat/lon are absolute values.
259
        if self.set_gps_ref:
260
            if latitude < 0:
261
                tags[self.latitude_ref_key] = 'S'
262
263
            if longitude < 0:
264
                tags[self.longitude_ref_key] = 'W'
265
266
        status = self.__set_tags(tags)
267
        self.reset_cache()
268
269
        return status
270
271
    def set_original_name(self, name=None):
272
        """Sets the original name EXIF tag if not already set.
273
274
        :returns: True, False, None
275
        """
276
        if(not self.is_valid()):
277
            return None
278
279
        # If EXIF original name tag is set then we return.
280
        if self.get_original_name() is not None:
281
            return None
282
283
        source = self.source
284
285
        if not name:
286
            name = os.path.basename(source)
287
288
        tags = {self.original_name_key: name}
289
        status = self.__set_tags(tags)
290
        self.reset_cache()
291
        return status
292
293
    def set_title(self, title):
294
        """Set title for a photo.
295
296
        :param str title: Title of the photo.
297
        :returns: bool
298
        """
299
        if(not self.is_valid()):
300
            return None
301
302
        if(title is None):
303
            return None
304
305
        tags = {self.title_key: title}
306
        status = self.__set_tags(tags)
307
        self.reset_cache()
308
309
        return status
310
311
    def __set_tags(self, tags):
312
        if(not self.is_valid()):
313
            return None
314
315
        source = self.source
316
317
        status = ''
318
        ExifTool().set_tags(tags,source)
319
320
        return status != ''
321