Passed
Push — master ( 9ddb09...7fc5b5 )
by Jaisen
01:14
created

elodie.media.base   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 110
dl 0
loc 253
rs 9.2
c 0
b 0
f 0
wmc 40

21 Methods

Rating   Name   Duplication   Size   Complexity  
A Base.get_metadata() 0 31 4
A Base.get_original_name() 0 8 1
B Base.get_class_by_file() 0 15 6
A Base.get_camera_model() 0 2 1
A Base.format_metadata() 0 2 1
A Base.get_coordinate() 0 2 1
A Base.set_metadata() 0 9 3
A Base.set_album() 0 6 1
A Base.get_title() 0 6 1
A Base.set_metadata_basename() 0 14 1
A Base.get_valid_extensions() 0 7 1
A Base.reset_cache() 0 4 1
A Base.__init__() 0 3 1
A Base.get_mimetype() 0 14 3
A Base.get_camera_make() 0 2 1
A Base.get_extension() 0 10 2
A Base.get_file_path() 0 6 1
A Base.get_album() 0 6 1
A Base.set_original_name() 0 5 1
A Base.is_valid() 0 9 1
A Base.set_album_from_folder() 0 18 4

1 Function

Rating   Name   Duplication   Size   Complexity  
A get_all_subclasses() 0 16 3

How to fix   Complexity   

Complexity

Complex classes like elodie.media.base 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 base module provides a base :class:`Base` class for all objects that
3
are tracked by Elodie. The Base 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`, :class:`~elodie.media.video.Video`, and
7
:class:`~elodie.media.text.Text`)
8
are used to represent the actual files.
9
10
.. moduleauthor:: Jaisen Mathai <[email protected]>
11
"""
12
13
import mimetypes
14
import os
15
16
try:        # Py3k compatibility
17
    basestring
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable basestring does not seem to be defined.
Loading history...
18
except NameError:
19
    basestring = (bytes, str)
20
21
22
class Base(object):
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__ = 'Base'
30
31
    extensions = ()
32
33
    def __init__(self, source=None):
34
        self.source = source
35
        self.reset_cache()
36
37
    def format_metadata(self, **kwargs):
38
        """Method to consistently return a populated metadata dictionary.
39
40
        :returns: dict
41
        """
42
43
    def get_album(self):
44
        """Base method for getting an album
45
46
        :returns: None
47
        """
48
        return None
49
50
    def get_file_path(self):
51
        """Get the full path to the video.
52
53
        :returns: string
54
        """
55
        return self.source
56
57
    def get_coordinate(self, type):
58
        return None
59
60
    def get_extension(self):
61
        """Get the file extension as a lowercased string.
62
63
        :returns: string or None for a non-video
64
        """
65
        if(not self.is_valid()):
66
            return None
67
68
        source = self.source
69
        return os.path.splitext(source)[1][1:].lower()
70
71
    def get_camera_make(self):
72
        return None
73
74
    def get_camera_model(self):
75
        return None
76
77
    def get_metadata(self, update_cache=False):
78
        """Get a dictionary of metadata for any file.
79
80
        All keys will be present and have a value of None if not obtained.
81
82
        :returns: dict or None for non-text files
83
        """
84
        if(not self.is_valid()):
85
            return None
86
87
        if(isinstance(self.metadata, dict) and update_cache is False):
88
            return self.metadata
89
90
        source = self.source
91
92
        self.metadata = {
93
            'date_taken': self.get_date_taken(),
94
            'camera_make': self.get_camera_make(),
95
            'camera_model': self.get_camera_model(),
96
            'latitude': self.get_coordinate('latitude'),
97
            'longitude': self.get_coordinate('longitude'),
98
            'album': self.get_album(),
99
            'title': self.get_title(),
100
            'mime_type': self.get_mimetype(),
101
            'original_name': self.get_original_name(),
102
            'base_name': os.path.splitext(os.path.basename(source))[0],
103
            'extension': self.get_extension(),
104
            'directory_path': os.path.dirname(source)
105
        }
106
107
        return self.metadata
108
109
    def get_mimetype(self):
110
        """Get the mimetype of the file.
111
112
        :returns: str or None for unsupported files.
113
        """
114
        if(not self.is_valid()):
115
            return None
116
117
        source = self.source
118
        mimetype = mimetypes.guess_type(source)
119
        if(mimetype is None):
120
            return None
121
122
        return mimetype[0]
123
124
    def get_original_name(self):
125
        """Get the original name of the file from before it was imported.
126
        Does not include the extension.
127
        Overridden by Media class for files with EXIF.
128
129
        :returns: str or None for unsupported files.
130
        """
131
        return None
132
133
    def get_title(self):
134
        """Base method for getting the title of a file
135
136
        :returns: None
137
        """
138
        return None
139
140
    def is_valid(self):
141
        """Check the file extension against valid file extensions.
142
143
        The list of valid file extensions come from self.extensions.
144
145
        :returns: bool
146
        """
147
        source = self.source
148
        return os.path.splitext(source)[1][1:].lower() in self.extensions
149
150
    def reset_cache(self):
151
        """Resets any internal cache
152
        """
153
        self.metadata = None
154
155
    def set_album(self, name):
156
        """Base method for setting the album of a file
157
158
        :returns: None
159
        """
160
        return None
161
162
    def set_album_from_folder(self):
163
        """Set the album attribute based on the leaf folder name
164
165
        :returns: bool
166
        """
167
        metadata = self.get_metadata()
168
169
        # If this file has an album already set we do not overwrite EXIF
170
        if(not isinstance(metadata, dict) or metadata['album'] is not None):
171
            return False
172
173
        folder = os.path.basename(metadata['directory_path'])
174
        # If folder is empty we skip
175
        if(len(folder) == 0):
176
            return False
177
178
        self.set_album(folder)
179
        return True
180
181
    def set_metadata_basename(self, new_basename):
182
        """Update the basename attribute in the metadata dict for this instance.
183
184
        This is used for when we update the EXIF title of a media file. Since
185
        that determines the name of a file if we update the title of a file
186
        more than once it appends to the file name.
187
188
        i.e. 2015-12-31_00-00-00-my-first-title-my-second-title.jpg
189
190
        :param str new_basename: New basename of file (with the old title
191
            removed).
192
        """
193
        self.get_metadata()
194
        self.metadata['base_name'] = new_basename
195
196
    def set_metadata(self, **kwargs):
197
        """Method to manually update attributes in metadata.
198
199
        :params dict kwargs: Named parameters to update.
200
        """
201
        metadata = self.get_metadata()
202
        for key in kwargs:
203
            if(key in metadata):
204
                self.metadata[key] = kwargs[key]
205
206
    def set_original_name(self):
207
        """Stores the original file name into EXIF/metadata.
208
        :returns: bool
209
        """
210
        return False
211
212
    @classmethod
213
    def get_class_by_file(cls, _file, classes):
214
        """Static method to get a media object by file.
215
        """
216
        if not isinstance(_file, basestring) or not os.path.isfile(_file):
217
            return None
218
219
        extension = os.path.splitext(_file)[1][1:].lower()
220
221
        if len(extension) > 0:
222
            for i in classes:
223
                if(extension in i.extensions):
224
                    return i(_file)
225
226
        return None
227
228
    @classmethod
229
    def get_valid_extensions(cls):
230
        """Static method to access static extensions variable.
231
232
        :returns: tuple(str)
233
        """
234
        return cls.extensions
235
236
237
def get_all_subclasses(cls=None):
238
    """Module method to get all subclasses of Base.
239
    """
240
    subclasses = set()
241
242
    this_class = Base
243
    if cls is not None:
244
        this_class = cls
245
246
    subclasses.add(this_class)
247
248
    this_class_subclasses = this_class.__subclasses__()
249
    for child_class in this_class_subclasses:
250
        subclasses.update(get_all_subclasses(child_class))
251
252
    return subclasses
253