| 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 |
|
|---|