Heavy update to song database storage

Switch to mutagen for tagging, will use pydub for conversion.
Change how song is represented in DB to be much more rich.
master
Bradlee Speice 2013-02-05 14:20:45 -05:00
parent 4f9b717674
commit fdfcecc4a2
1 changed files with 80 additions and 68 deletions

View File

@ -9,20 +9,24 @@ This database model is used for storing the metadata information about a song,
and helps in doing sorting etc.
"""
_default_title = "_UNAVAILABLE_"
_default_artist = "_UNAVAILABLE_"
_default_album = "_UNAVAILABLE_"
_default_release_date = datetime.datetime.now #Function will be called per new song, rather than only being called right now.
_default_genre = "_UNAVAILABLE_"
_default_bpm = -1
_default_disc_number = -1
_default_disc_total = -1
_default_track_number = -1
_default_track_total = -1
_default_string = "_UNAVAILABLE_"
_default_date = datetime.datetime.now
_default_int = -1
_default_bit_rate = -1
_default_duration = -1
_default_echonest_song_id = ""
_default_rating = 0
_default_rating_bad = 1
_default_rating_ok = 2
_default_rating_decent = 3
_default_rating_good = 4
_default_rating_excellent = 5
_default_rating_choices = (
(_default_rating, 'Default'),
(_default_rating_bad, 'Bad'),
(_default_rating_ok, 'OK'),
(_default_rating_decent, 'Decent'),
(_default_rating_good, 'Good'),
(_default_rating_excellent, 'Excellent'),
)
class Song (models.Model):
@ -32,24 +36,40 @@ class Song (models.Model):
Note that the Playlist model depends on this model's PK being an int.
"""
#Standard user-populated metadata
title = models.CharField(max_length = 64, default = _default_title)
artist = models.CharField(max_length = 64, default = _default_artist)
album = models.CharField(max_length = 64, default = _default_album)
release_date = models.DateField(default = _default_release_date)
genre = models.CharField(max_length = 64, default = _default_genre)
bpm = models.IntegerField(default = _default_bpm)
disc_number = models.IntegerField(default = _default_disc_number)
disc_total = models.IntegerField(default = _default_disc_total)
track_number = models.IntegerField(default = _default_track_number)
track_total = models.IntegerField(default = _default_track_total)
#Standard song metadata
title = models.CharField(max_length = 64, default = _default_string)
artist = models.CharField(max_length = 64, default = _default_string)
album = models.CharField(max_length = 64, default = _default_string)
year = models.IntegerField(default = _default_string)
genre = models.CharField(max_length = 64, default = _default_string)
bpm = models.IntegerField(default = _default_int)
disc_number = models.IntegerField(default = _default_int)
disc_total = models.IntegerField(default = _default_int)
track_number = models.IntegerField(default = _default_int)
track_total = models.IntegerField(default = _default_int)
comment = models.CharField(default = _default_string, max_length=512)
#File metadata
bit_rate = models.IntegerField(default = _default_bit_rate)
duration = models.IntegerField(default = _default_bit_rate)
echonest_song_id = models.CharField(max_length = 64, default = _default_echonest_song_id)
bit_rate = models.IntegerField(default = _default_int)
duration = models.FloatField(default = _default_int)
add_date = models.DateField(default = _default_date)
echonest_song_id = models.CharField(max_length = 64, default = _default_string)
url = models.CharField(max_length = 255)
file_hash = melodia_settings.HASH_RESULT_DB_TYPE
file_size = models.IntegerField(default = _default_int)
#Melodia metadata
play_count = models.IntegerField(default = _default_int)
skip_count = models.IntegerField(default = _default_int)
rating = models.IntegerField(default = _default_int, choices = _default_rating_choices)
#Set a static reference to the rating options
RATING_DEFAULT = _default_rating
RATING_BAD = _default_rating_bad
RATING_OK = _default_rating_ok
RATING_DECENT = _default_rating_decent
RATING_GOOD = _default_rating_good
RATING_EXCELLENT = _default_rating_excellent
def _file_not_changed(self):
"Make sure the hash for this file is valid - return True if it has not changed."
@ -68,47 +88,50 @@ class Song (models.Model):
return False
def _grab_file_info(self):
"Populate file-based metadata about this song."
import os
#Overload the hash function with whatever Melodia as a whole is using
from Melodia.melodia_settings import HASH_FUNCTION as hash
file_handle = open(self.url, 'rb')
self.file_hash = hash(file_handle.read())
self.file_size = os.stat(self.url).st_size
def _grab_metadata_echonest(self):
"Populate this song's metadata using EchoNest"
pass
def _grab_metadata_local(self):
"Populate this song's metadata using what is locally available"
#It's weird, but we need to wrap importing audiotools
from Melodia.resources import add_resource_dir
add_resource_dir()
import audiotools
#Use mutagen to get the song metadata
import mutagen
try:
track = audiotools.open(self.url)
track_metadata = track.get_metadata()
#Use mutagen to scan local metadata - don't update anything else (i.e. play_count)
track = mutagen.File(self.url)
track_easy = mutagen.File(self.url, easy=True)
self.title = track_metadata.track_name or _default_title
self.artist = track_metadata.artist_name or _default_artist
self.album = track_metadata.album_name or _default_album
self.release_date = datetime.date(int(track_metadata.year or 1), 1, 1)
self.track_number = track_metadata.track_number or _default_track_number
self.track_total = track_metadata.track_total or _default_track_total
self.disc_number = track_metadata.album_number or _default_disc_number
self.disc_total = track_metadata.album_total or _default_disc_total
self.bpm = _default_bpm
self.title = track_easy['title'][0] or _default_string
self.artist = track_easy['artist'][0] or _default_string
self.album_artist = track_easy['albumartist'][0] or _default_string
self.album = track_easy['album'][0] or _default_string
self.year = int(track_easy['date'][0][0:4]) or _default_int
self.genre = track_easy["genre"][0] or _default_string
self.bit_rate = track.bits_per_sample() or _default_bit_rate
self.duration = int(track.seconds_length()) or _default_duration
self.echonest_song_id = _default_echonest_song_id
self.disc_number = int(track_easy['discnumber'][0].split('/')[0]) or _default_int
self.disc_total = int(track_easy['discnumber'][0].split('/')[-1]) or _default_int
self.track_number = int(track_easy['track_number'][0].split('/')[0]) or _default_int
self.track_total = int(track_easy['track_number'][0].split('/')[-1]) or _default_int
self.comment = track_easy['comment'][0] or _default_string
except audiotools.UnsupportedFile, e:
#Couldn't grab the local data - fill in the remaining data for this record, preserving
#anything that already exists.
self.title = self.title or _default_title
self.artist = self.artist or _default_artist
self.album = self.album or _default_album
self.release_date = self.release_date or _default_release_date()
self.bit_rate = track.info.bitrate or _default_int
self.duration = track.info.length or _default_int
self.bpm = self.bpm or _default_bpm
self.bit_rate = self.bit_rate or _default_bitrate
self.duration = self.bit_rate or _default_duration
self.echonest_song_id = self.echonest_song_id or _default_echonest_song_id
except:
#Couldn't grab the local data
return False
def populate_metadata(self, use_echonest = False):
"Populate the metadata of this song (only if file hash has changed), and save the result."
@ -124,15 +147,4 @@ class Song (models.Model):
def convert(self, output_location, output_format, progress_func = lambda x, y: None):
"Convert a song to a new format."
#Note that output_format over-rides the format guessed by output_location
from Melodia.resources import add_resource_dir
add_resource_dir()
import audiotools
convert_from = audiotools.open(self.url)
convert_from.convert(output_location,
output_format,
progress_func)
pass #Need to get pydub code in place