From fdfcecc4a203645cbb6a02e9a74ad1a7fe324823 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Tue, 5 Feb 2013 14:20:45 -0500 Subject: [PATCH] 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. --- archiver/song.py | 148 +++++++++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 68 deletions(-) diff --git a/archiver/song.py b/archiver/song.py index 360fcc8..d433869 100644 --- a/archiver/song.py +++ b/archiver/song.py @@ -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