Media.get_coordinate()   F
last analyzed

Complexity

Conditions 12

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 40

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
c 1
b 0
f 0
dl 0
loc 40
rs 2.7855

How to fix   Complexity   

Complexity

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