Completed
Push9950e8...a82114
passed — Build
created

get_all_subclasses()   A

↳ Parent: Project

Complexity

Conditions 3

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 16

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
dl 0
loc 16
rs 9.4285
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
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_metadata(self, update_cache=False):
72
        """Get a dictionary of metadata for any file.
73
74
        All keys will be present and have a value of None if not obtained.
75
76
        :returns: dict or None for non-text files
77
        """
78
        if(not self.is_valid()):
79
            return None
80
81
        if(isinstance(self.metadata, dict) and update_cache is False):
82
            return self.metadata
83
84
        source = self.source
85
86
        self.metadata = {
87
            'date_taken': self.get_date_taken(),
88
            'latitude': self.get_coordinate('latitude'),
89
            'longitude': self.get_coordinate('longitude'),
90
            'album': self.get_album(),
91
            'title': self.get_title(),
92
            'mime_type': self.get_mimetype(),
93
            'base_name': os.path.splitext(os.path.basename(source))[0],
94
            'extension': self.get_extension(),
95
            'directory_path': os.path.dirname(source)
96
        }
97
98
        return self.metadata
99
100
    def get_mimetype(self):
101
        """Get the mimetype of the file.
102
103
        :returns: str or None for a non-video
104
        """
105
        if(not self.is_valid()):
106
            return None
107
108
        source = self.source
109
        mimetype = mimetypes.guess_type(source)
110
        if(mimetype is None):
111
            return None
112
113
        return mimetype[0]
114
115
    def get_title(self):
116
        """Base method for getting the title of a file
117
118
        :returns: None
119
        """
120
        return None
121
122
    def is_valid(self):
123
        """Check the file extension against valid file extensions.
124
125
        The list of valid file extensions come from self.extensions.
126
127
        :returns: bool
128
        """
129
        source = self.source
130
        return os.path.splitext(source)[1][1:].lower() in self.extensions
131
132
    def reset_cache(self):
133
        """Resets any internal cache
134
        """
135
        self.metadata = None
136
137
    def set_album(self, name):
138
        """Base method for setting the album of a file
139
140
        :returns: None
141
        """
142
        return None
143
144
    def set_album_from_folder(self):
145
        """Set the album attribute based on the leaf folder name
146
147
        :returns: bool
148
        """
149
        metadata = self.get_metadata()
150
151
        # If this file has an album already set we do not overwrite EXIF
152
        if(not isinstance(metadata, dict) or metadata['album'] is not None):
153
            return False
154
155
        folder = os.path.basename(metadata['directory_path'])
156
        # If folder is empty we skip
157
        if(len(folder) == 0):
158
            return False
159
160
        self.set_album(folder)
161
        return True
162
163
    def set_metadata_basename(self, new_basename):
164
        """Update the basename attribute in the metadata dict for this instance.
165
166
        This is used for when we update the EXIF title of a media file. Since
167
        that determines the name of a file if we update the title of a file
168
        more than once it appends to the file name.
169
170
        i.e. 2015-12-31_00-00-00-my-first-title-my-second-title.jpg
171
172
        :param str new_basename: New basename of file (with the old title
173
            removed).
174
        """
175
        self.get_metadata()
176
        self.metadata['base_name'] = new_basename
177
178
    def set_metadata(self, **kwargs):
179
        """Method to manually update attributes in metadata.
180
181
        :params dict kwargs: Named parameters to update.
182
        """
183
        metadata = self.get_metadata()
184
        for key in kwargs:
185
            if(key in metadata):
186
                self.metadata[key] = kwargs[key]
187
188
    @classmethod
189
    def get_class_by_file(cls, _file, classes):
190
        """Static method to get a media object by file.
191
        """
192
        if not isinstance(_file, basestring) or not os.path.isfile(_file):
193
            return None
194
195
        extension = os.path.splitext(_file)[1][1:].lower()
196
197
        if len(extension) > 0:
198
            for i in classes:
199
                if(extension in i.extensions):
200
                    return i(_file)
201
202
        return None
203
204
    @classmethod
205
    def get_valid_extensions(cls):
206
        """Static method to access static extensions variable.
207
208
        :returns: tuple(str)
209
        """
210
        return cls.extensions
211
212
213
def get_all_subclasses(cls=None):
214
    """Module method to get all subclasses of Base.
215
    """
216
    subclasses = set()
217
218
    this_class = Base
219
    if cls is not None:
220
        this_class = cls
221
222
    subclasses.add(this_class)
223
224
    this_class_subclasses = this_class.__subclasses__()
225
    for child_class in this_class_subclasses:
226
        subclasses.update(get_all_subclasses(child_class))
227
228
    return subclasses
229