elodie.media.media   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 331
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 176
dl 0
loc 331
rs 3.44
c 0
b 0
f 0
wmc 62

15 Methods

Rating   Name   Duplication   Size   Complexity  
A Media.get_original_name() 0 17 4
A Media.set_date_taken() 0 17 3
D Media.get_coordinate() 0 40 12
A Media.get_title() 0 17 4
A Media.set_title() 0 17 3
A Media.__init__() 0 22 1
A Media.get_album() 0 17 5
A Media.set_location() 0 26 5
A Media.get_camera_model() 0 18 5
A Media.get_camera_make() 0 18 5
A Media.set_original_name() 0 21 4
A Media.set_album() 0 14 2
A Media.reset_cache() 0 5 1
A Media.get_exiftool_attributes() 0 16 4
A Media.__set_tags() 0 15 4

How to fix   Complexity   

Complexity

Complex classes like elodie.media.media often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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