mirror of
https://github.com/bspeice/Melodia
synced 2024-11-16 04:58:20 -05:00
fdfcecc4a2
Switch to mutagen for tagging, will use pydub for conversion. Change how song is represented in DB to be much more rich.
151 lines
5.6 KiB
Python
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
|