1
0
mirror of https://github.com/bspeice/Melodia synced 2024-11-16 04:58:20 -05:00
Melodia/archiver/song.py
Bradlee Speice fdfcecc4a2 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.
2013-02-05 14:20:45 -05:00

151 lines
5.6 KiB
Python

from django.db import models
from Melodia import melodia_settings
import datetime
"""
The Song model
Each instance of a Song represents a single music file.
This database model is used for storing the metadata information about a song,
and helps in doing sorting etc.
"""
_default_string = "_UNAVAILABLE_"
_default_date = datetime.datetime.now
_default_int = -1
_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):
"""
This class defines the fields and functions related to controlling
individual music files.
Note that the Playlist model depends on this model's PK being an int.
"""
#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_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."
#Overload the hash function with whatever Melodia as a whole is using
from Melodia.melodia_settings import HASH_FUNCTION as hash
#Check if there's a hash entry - if there is, the song may not have changed,
#and we can go ahead and return
if self.file_hash != None:
song_file = open(self.url, 'rb')
current_file_hash = hash(song_file.read())
if current_file_hash == self.file_hash:
#The song data hasn't changed at all, we don't need to do anything
return True
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"
#Use mutagen to get the song metadata
import mutagen
try:
#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_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.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
self.bit_rate = track.info.bitrate or _default_int
self.duration = track.info.length or _default_int
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."
if self._file_not_changed():
return
#If we've gotten to here, we do actually need to fully update the metadata
if use_echonest:
self._grab_echonest()
else:
self._grab_metadata_local()
def convert(self, output_location, output_format, progress_func = lambda x, y: None):
"Convert a song to a new format."
pass #Need to get pydub code in place