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. and helps in doing sorting etc.
""" """
_default_title = "_UNAVAILABLE_" _default_string = "_UNAVAILABLE_"
_default_artist = "_UNAVAILABLE_" _default_date = datetime.datetime.now
_default_album = "_UNAVAILABLE_" _default_int = -1
_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_bit_rate = -1 _default_rating = 0
_default_duration = -1 _default_rating_bad = 1
_default_echonest_song_id = "" _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): 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. Note that the Playlist model depends on this model's PK being an int.
""" """
#Standard user-populated metadata #Standard song metadata
title = models.CharField(max_length = 64, default = _default_title) title = models.CharField(max_length = 64, default = _default_string)
artist = models.CharField(max_length = 64, default = _default_artist) artist = models.CharField(max_length = 64, default = _default_string)
album = models.CharField(max_length = 64, default = _default_album) album = models.CharField(max_length = 64, default = _default_string)
release_date = models.DateField(default = _default_release_date) year = models.IntegerField(default = _default_string)
genre = models.CharField(max_length = 64, default = _default_genre) genre = models.CharField(max_length = 64, default = _default_string)
bpm = models.IntegerField(default = _default_bpm) bpm = models.IntegerField(default = _default_int)
disc_number = models.IntegerField(default = _default_disc_number) disc_number = models.IntegerField(default = _default_int)
disc_total = models.IntegerField(default = _default_disc_total) disc_total = models.IntegerField(default = _default_int)
track_number = models.IntegerField(default = _default_track_number) track_number = models.IntegerField(default = _default_int)
track_total = models.IntegerField(default = _default_track_total) track_total = models.IntegerField(default = _default_int)
comment = models.CharField(default = _default_string, max_length=512)
#File metadata #File metadata
bit_rate = models.IntegerField(default = _default_bit_rate) bit_rate = models.IntegerField(default = _default_int)
duration = models.IntegerField(default = _default_bit_rate) duration = models.FloatField(default = _default_int)
echonest_song_id = models.CharField(max_length = 64, default = _default_echonest_song_id) add_date = models.DateField(default = _default_date)
echonest_song_id = models.CharField(max_length = 64, default = _default_string)
url = models.CharField(max_length = 255) url = models.CharField(max_length = 255)
file_hash = melodia_settings.HASH_RESULT_DB_TYPE 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): def _file_not_changed(self):
"Make sure the hash for this file is valid - return True if it has not changed." "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 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): def _grab_metadata_echonest(self):
"Populate this song's metadata using EchoNest" "Populate this song's metadata using EchoNest"
pass pass
def _grab_metadata_local(self): def _grab_metadata_local(self):
"Populate this song's metadata using what is locally available" "Populate this song's metadata using what is locally available"
#It's weird, but we need to wrap importing audiotools #Use mutagen to get the song metadata
from Melodia.resources import add_resource_dir import mutagen
add_resource_dir()
import audiotools
try: try:
track = audiotools.open(self.url) #Use mutagen to scan local metadata - don't update anything else (i.e. play_count)
track_metadata = track.get_metadata() track = mutagen.File(self.url)
track_easy = mutagen.File(self.url, easy=True)
self.title = track_metadata.track_name or _default_title self.title = track_easy['title'][0] or _default_string
self.artist = track_metadata.artist_name or _default_artist self.artist = track_easy['artist'][0] or _default_string
self.album = track_metadata.album_name or _default_album self.album_artist = track_easy['albumartist'][0] or _default_string
self.release_date = datetime.date(int(track_metadata.year or 1), 1, 1) self.album = track_easy['album'][0] or _default_string
self.track_number = track_metadata.track_number or _default_track_number self.year = int(track_easy['date'][0][0:4]) or _default_int
self.track_total = track_metadata.track_total or _default_track_total self.genre = track_easy["genre"][0] or _default_string
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.bit_rate = track.bits_per_sample() or _default_bit_rate self.disc_number = int(track_easy['discnumber'][0].split('/')[0]) or _default_int
self.duration = int(track.seconds_length()) or _default_duration self.disc_total = int(track_easy['discnumber'][0].split('/')[-1]) or _default_int
self.echonest_song_id = _default_echonest_song_id 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: self.bit_rate = track.info.bitrate or _default_int
#Couldn't grab the local data - fill in the remaining data for this record, preserving self.duration = track.info.length or _default_int
#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.bpm = self.bpm or _default_bpm except:
self.bit_rate = self.bit_rate or _default_bitrate #Couldn't grab the local data
self.duration = self.bit_rate or _default_duration return False
self.echonest_song_id = self.echonest_song_id or _default_echonest_song_id
def populate_metadata(self, use_echonest = False): def populate_metadata(self, use_echonest = False):
"Populate the metadata of this song (only if file hash has changed), and save the result." "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): def convert(self, output_location, output_format, progress_func = lambda x, y: None):
"Convert a song to a new format." "Convert a song to a new format."
#Note that output_format over-rides the format guessed by output_location pass #Need to get pydub code in place
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)