1 | #
|
---|
2 | # $Id$
|
---|
3 | #
|
---|
4 | # Description:
|
---|
5 | # This defines the model's for the "music" app in the mediaserv
|
---|
6 | # project. These models represent things like 'tracks' 'albums' etc.
|
---|
7 | #
|
---|
8 |
|
---|
9 | import datetime
|
---|
10 |
|
---|
11 | # We import meta from django.core because that is where all the fields and like
|
---|
12 | # stuff is defined.
|
---|
13 | #
|
---|
14 | from django.core import meta
|
---|
15 |
|
---|
16 | #############################################################################
|
---|
17 | #
|
---|
18 | # Here are the django "models" of objects that the mediaserv "music" app cares
|
---|
19 | # about
|
---|
20 | #
|
---|
21 |
|
---|
22 | #############################################################################
|
---|
23 | #
|
---|
24 | class MusicRoot(meta.Model):
|
---|
25 | """Simply because I wanted to store as much configuration information
|
---|
26 | information in our database as seemed useful I have the MusicRoot class. It
|
---|
27 | describes a path on the local file system were music files may be found.
|
---|
28 |
|
---|
29 | You can have more than one MusicRoot, obviously.
|
---|
30 | """
|
---|
31 |
|
---|
32 | fields = (
|
---|
33 | meta.CharField('directory', maxlength = 1024),
|
---|
34 | meta.DateTimeField('last_scan_started', blank = True, null = True),
|
---|
35 | meta.DateTimeField('last_scan_finished', blank = True, null = True),
|
---|
36 | )
|
---|
37 |
|
---|
38 | admin = meta.Admin()
|
---|
39 |
|
---|
40 | #########################################################################
|
---|
41 | #
|
---|
42 | def __repr__(self):
|
---|
43 | return self.directory
|
---|
44 |
|
---|
45 | #############################################################################
|
---|
46 | #
|
---|
47 | class Artist(meta.Model):
|
---|
48 | """An artist. They will have a one to many relationship with
|
---|
49 | ArtistNames. They may have a simplified_name
|
---|
50 | name, and a many to many relationship with ArtistNames.
|
---|
51 |
|
---|
52 | Not sure what other information we will have under the Artist object but it
|
---|
53 | seems to make sense to say that an artist is more than just a name.
|
---|
54 | """
|
---|
55 |
|
---|
56 | fields = (
|
---|
57 | meta.CharField('comment', null = True, blank = True, maxlength = 1024),
|
---|
58 | meta.DateTimeField('date_added'),
|
---|
59 | )
|
---|
60 |
|
---|
61 | admin = meta.Admin()
|
---|
62 |
|
---|
63 | #########################################################################
|
---|
64 | #
|
---|
65 | def __repr__(self):
|
---|
66 | """Return the highest preference artist name, if this artist has any
|
---|
67 | names. If they do not just return the id.
|
---|
68 | """
|
---|
69 | if self.get_artistname_count() > 0:
|
---|
70 | return repr(self.get_artistname_list()[0])
|
---|
71 | else:
|
---|
72 | return str(self.id)
|
---|
73 |
|
---|
74 | #########################################################################
|
---|
75 | #
|
---|
76 | def get_simple_name(self):
|
---|
77 | """Look through all the names that this artist has and return the first
|
---|
78 | one that is marked as a simplified name.
|
---|
79 |
|
---|
80 | If we find no simplified names then return None.
|
---|
81 | """
|
---|
82 | if self.get_artistname_count() == 0:
|
---|
83 | return None
|
---|
84 | names = self.get_artistname_list()
|
---|
85 | for name in names:
|
---|
86 | if name.simple_char_set:
|
---|
87 | return name
|
---|
88 | return None
|
---|
89 |
|
---|
90 | #############################################################################
|
---|
91 | #
|
---|
92 | class ArtistName(meta.Model):
|
---|
93 | """An artist's name. An artist may have more than one name. Each name has a
|
---|
94 | preference. The higher the preference the more this name is the one we
|
---|
95 | should use if we have to present a single name. This name may also be one
|
---|
96 | that is represented in a simple character set - suitable for devices like
|
---|
97 | the rio receiver that can not display kanji.
|
---|
98 | """
|
---|
99 | fields = (
|
---|
100 | meta.CharField('name', maxlength = 512, core = True, unique = True),
|
---|
101 | meta.IntegerField('preference', default = 0),
|
---|
102 | meta.BooleanField('simple_char_set'),
|
---|
103 | meta.ForeignKey(Artist, edit_inline = meta.TABULAR, num_in_admin = 3),
|
---|
104 | )
|
---|
105 |
|
---|
106 | ordering = ['-preference']
|
---|
107 |
|
---|
108 | #########################################################################
|
---|
109 | #
|
---|
110 | def __repr__(self):
|
---|
111 | return self.name
|
---|
112 |
|
---|
113 | #############################################################################
|
---|
114 | #
|
---|
115 | class Album(meta.Model):
|
---|
116 | """An album is a collection of tracks. A track can only appear in one
|
---|
117 | album.
|
---|
118 |
|
---|
119 | Do we want to bother with artist/album relationships? I guess not, just get
|
---|
120 | the list of tracks, get the list of artists for the tracks and do a unique
|
---|
121 | set of those.
|
---|
122 |
|
---|
123 | Note: We need to have a simple name/name relationship for albums but this
|
---|
124 | is not nearly as complex as it was for artists. Albums are typically only
|
---|
125 | known by one name. We just need a simplified version for devices like the
|
---|
126 | rio receiver.
|
---|
127 |
|
---|
128 | Note: When you get the list of tracks associated with an album it should
|
---|
129 | give you the list sorted in the order indicated by the tracks' track_number
|
---|
130 | and disk_number.
|
---|
131 | """
|
---|
132 |
|
---|
133 | fields = (
|
---|
134 | meta.CharField('name', maxlength = 512),
|
---|
135 | meta.CharField('simplified_name', maxlength = 512, blank = True,
|
---|
136 | null = True),
|
---|
137 | meta.DateTimeField('date_added'),
|
---|
138 | )
|
---|
139 |
|
---|
140 | #########################################################################
|
---|
141 | #
|
---|
142 | def __repr__(self):
|
---|
143 | return self.name
|
---|
144 |
|
---|
145 | #############################################################################
|
---|
146 | #
|
---|
147 | class PlayList(meta.Model):
|
---|
148 | """Like an album this is a collection of tracks. Unlike an album, a track
|
---|
149 | can occur more than once in a play list.
|
---|
150 |
|
---|
151 | Tracks in a play list need to have a defined order that is determined by
|
---|
152 | the playlist. Not sure how to do this yet.
|
---|
153 | """
|
---|
154 | fields = (
|
---|
155 | meta.CharField('name', maxlength = 512, unique = True),
|
---|
156 | meta.CharField('simplified_name', maxlength = 512, blank = True,
|
---|
157 | null = True),
|
---|
158 | )
|
---|
159 |
|
---|
160 | admin = meta.Admin()
|
---|
161 |
|
---|
162 | #########################################################################
|
---|
163 | #
|
---|
164 | def __repr__(self):
|
---|
165 | return self.name
|
---|
166 |
|
---|
167 | #############################################################################
|
---|
168 | #
|
---|
169 | class Genre(meta.Model):
|
---|
170 | """For better or for worse tracks have a fixed single genre field.
|
---|
171 | We will set the 'id' to be the same as the accepted standard set of genres.
|
---|
172 | """
|
---|
173 | fields = (
|
---|
174 | meta.CharField('name', maxlength = 256, unique = True),
|
---|
175 | meta.IntegerField('genre_id'), # To map to the mp3 id field
|
---|
176 | )
|
---|
177 |
|
---|
178 | #########################################################################
|
---|
179 | #
|
---|
180 | def __repr__(self):
|
---|
181 | return self.name
|
---|
182 |
|
---|
183 | #############################################################################
|
---|
184 | #
|
---|
185 | class Track(meta.Model):
|
---|
186 | """A track refers to a single playable file of sound/music media. The basic
|
---|
187 | fields are determined from what is available via the id3 tags.
|
---|
188 |
|
---|
189 | However things like the 'album' and 'artist' are relations to other object
|
---|
190 | model instances.
|
---|
191 |
|
---|
192 | We also keep track of some additional field so that we can echo it back to
|
---|
193 | iTunes potentially. Named things like 'last played time' and 'number of
|
---|
194 | times played.'
|
---|
195 |
|
---|
196 | We also keep track of the encoding format of the track. Some players can
|
---|
197 | not play some encodings.
|
---|
198 |
|
---|
199 | A track may also have a 'limited_name' field which is a representation of
|
---|
200 | the track's name in simple ASCII so that devices which can not display rich
|
---|
201 | character sets can display a simplified (aka romanized for Japanese track
|
---|
202 | names) instead of displaying gobbedly gook.
|
---|
203 | """
|
---|
204 |
|
---|
205 | fields = (
|
---|
206 | meta.CharField('title', maxlength = 512),
|
---|
207 | meta.CharField('filename', maxlength = 1024, db_index = True),
|
---|
208 | meta.CharField('simplified_name', maxlength = 256, blank = True,
|
---|
209 | null = True),
|
---|
210 | meta.IntegerField('year', null = True),
|
---|
211 | meta.IntegerField('play_time'),
|
---|
212 | meta.IntegerField('bit_rate'),
|
---|
213 | meta.BooleanField('vbr'),
|
---|
214 | meta.IntegerField('track_number', default = 0),
|
---|
215 | meta.IntegerField('disc_number', default = 0),
|
---|
216 | meta.IntegerField('play_count', default = 0), # tied to iTunes
|
---|
217 | meta.DateTimeField('last_scanned', blank = True, null = True),
|
---|
218 | meta.DateTimeField('last_played', null = True), # tied to iTunes
|
---|
219 | meta.IntegerField('bpm','beats per minute', null = True), # iTunes
|
---|
220 | meta.CharField('grouping', maxlength = 512, null = True,
|
---|
221 | blank = True), # tied to iTunes
|
---|
222 | meta.CharField('comments', maxlength = 1024, null = True,
|
---|
223 | blank = True), # tied to iTunes
|
---|
224 | meta.ForeignKey(Artist, blank = True, null = True),
|
---|
225 | meta.ForeignKey(Album, blank = True, null = True),
|
---|
226 | meta.ManyToManyField(PlayList, blank = True, null = True),
|
---|
227 | meta.ForeignKey(Genre, blank = True, null = True),
|
---|
228 | meta.ForeignKey(MusicRoot),
|
---|
229 | )
|
---|
230 |
|
---|
231 | ordering = ['track_number', 'disc_number']
|
---|
232 |
|
---|
233 | #########################################################################
|
---|
234 | #
|
---|
235 | def __repr__(self):
|
---|
236 | return self.title
|
---|
237 |
|
---|
238 | #########################################################################
|
---|
239 | #
|
---|
240 | def play_time_string(self):
|
---|
241 | """Convert the playtime we have that is in seconds to a friendlier
|
---|
242 | human readable string. This was cribbed from
|
---|
243 | Eye3D.tag.getPlayTimeString()
|
---|
244 | """
|
---|
245 | total = self.play_time
|
---|
246 | h = total / 3600
|
---|
247 | m = (total % 3600) / 60
|
---|
248 | s = (total % 3600) % 60
|
---|
249 | if h:
|
---|
250 | timeStr = "%d:%.2d:%.2d" % (h, m, s)
|
---|
251 | else:
|
---|
252 | timeStr = "%d:%.2d" % (m, s)
|
---|
253 | return timeStr
|
---|
254 |
|
---|
255 | #############################################################################
|
---|
256 | #
|
---|
257 | class Artwork(meta.Model):
|
---|
258 | """iTunes lets us associate multiple pieces of artwork with tracks.
|
---|
259 | I am thinking of letting albums also have artwork associated with them (who
|
---|
260 | would see this, though? Only the web interface so far.)
|
---|
261 |
|
---|
262 | A track may have more than one piece of art associated with it.
|
---|
263 | """
|
---|
264 |
|
---|
265 | fields = (
|
---|
266 | meta.ImageField('image'),
|
---|
267 | # Maybe we should store some of the file's attributes in our structure?
|
---|
268 | # Image size? encoding?
|
---|
269 | meta.ManyToManyField(Track),
|
---|
270 | )
|
---|
271 |
|
---|
272 | admin = meta.Admin()
|
---|
273 |
|
---|
274 | #########################################################################
|
---|
275 | #
|
---|
276 | ## def __repr__(self):
|
---|
277 | ## return self.file_name
|
---|
278 |
|
---|