mirror of
https://github.com/bspeice/Melodia
synced 2025-09-04 22:04:50 -04:00
Commit initial archive code and test data
This commit is contained in:
118
archiver/archive.py
Normal file
118
archiver/archive.py
Normal file
@ -0,0 +1,118 @@
|
||||
from django.db import models
|
||||
|
||||
from song import Song
|
||||
|
||||
"""
|
||||
This is the archive model for the archiving backend of Melodia.
|
||||
It's purpose is to control the high-level functionality of managing
|
||||
multiple archives of music. It is different from a playlist both conceptually
|
||||
and practically - an archive describes a group of files, while a playlist
|
||||
describes a group of songs.
|
||||
In this way, you back up archives of music - you don't back up the songs in a
|
||||
playlist. Additionally, you may want to re-organize your music to use a
|
||||
cleaner directory structure - a playlist doesn't care about this.
|
||||
Note that archives are different from collections:
|
||||
-Archives are physical organizations of songs. These are used in the backend.
|
||||
-Collections are logical organizations of songs. These are intended to be used
|
||||
on the frontend.
|
||||
The difference is intended to separate the difference between logical and physical
|
||||
operations. For example, you don't need to re-organize the directory structure of
|
||||
a collection of songs. However, you may want to prevent kids from accessing explicit
|
||||
songs even if they are part of the same archive folder as clean songs.
|
||||
"""
|
||||
|
||||
class Archive (models.Model):
|
||||
|
||||
"""
|
||||
The archive model itself, and all functions used to interact with it.
|
||||
The archive is built up from a grouping of songs, and the functions
|
||||
that are used to interact with many songs at a single time. The archive
|
||||
for example allows you to re-organize a specific set of music files into
|
||||
a cleaner directory structure.
|
||||
The archive is given a folder to use as its root directory - it finds all
|
||||
music files under there, and takes control of them from there.
|
||||
"""
|
||||
|
||||
name = models.CharField(max_length = 64)
|
||||
|
||||
#Note that we're not using FilePathField since this is actually a folder
|
||||
root_folder = models.CharField(max_length = 255)
|
||||
|
||||
#And a reference to the songs in this archive
|
||||
songs = models.ManyToManyField(Song)
|
||||
|
||||
def _scan_filesystem(self):
|
||||
"Scan the archive's root filesystem and add any new songs"
|
||||
#This method is implemented since the other scan methods all need to use the same code
|
||||
#DRY FTW
|
||||
import re, os
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from Melodia.melodia_settings import SUPPORTED_AUDIO_EXTENSIONS
|
||||
from Melodia.melodia_settings import HASH_FUNCTION as hash
|
||||
|
||||
_regex = '|'.join(( '.*' + ext + '$' for ext in SUPPORTED_AUDIO_EXTENSIONS))
|
||||
regex = re.compile(_regex, re.IGNORECASE)
|
||||
|
||||
for dirname, dirnames, filenames in os.walk(self.root_folder):
|
||||
#For each filename
|
||||
for filename in filenames:
|
||||
#If the filename is a supported audio extension
|
||||
if re.match(regex, filename):
|
||||
#Make sure that `filename` is in the database
|
||||
try:
|
||||
self.songs.get(url = filename)
|
||||
|
||||
except ObjectDoesNotExist, e:
|
||||
#Song needs to be added to database
|
||||
print "Adding song: " + filename
|
||||
|
||||
full_url = os.path.join(dirname, filename)
|
||||
new_song = Song(url = full_url)
|
||||
|
||||
f = open(full_url)
|
||||
new_song.file_hash = hash(f.read())
|
||||
new_song.populate_metadata()
|
||||
|
||||
new_song.save()
|
||||
|
||||
self.songs.add(new_song)
|
||||
|
||||
def scan(self):
|
||||
"Scan this archive's root folder and make sure that all songs are in the database."
|
||||
|
||||
from os.path import isfile
|
||||
|
||||
#Validate existing database results
|
||||
for song in self.songs.all():
|
||||
if not isfile(song.song_url):
|
||||
song.delete()
|
||||
|
||||
#Scan the root folder, and find if we need to add any new songs
|
||||
self._scan_filesystem
|
||||
|
||||
def deep_scan(self):
|
||||
"Scan this archive's root folder and make sure that all songs are in the database, and update metadata as necessary"
|
||||
|
||||
#Overload the regular hash function with whatever Melodia as a whole is using
|
||||
from Melodia.melodia_settings import HASH_FUNCTION as hash
|
||||
import os.path
|
||||
|
||||
for song in self.songs.all():
|
||||
|
||||
if not os.path.isfile(song.song_url):
|
||||
song.delete()
|
||||
continue
|
||||
|
||||
#The song exists, check that the hash is the same
|
||||
db_hash = song.file_hash
|
||||
|
||||
f = open(song_url)
|
||||
file_hash = hash(f.read())
|
||||
|
||||
if file_hash != db_hash:
|
||||
#Something about the song has changed, rescan the metadata
|
||||
song.populate_metadata()
|
||||
|
||||
#Make sure to add any new songs as well
|
||||
self._scan_filesystem()
|
||||
|
@ -1,3 +1,5 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
from archive import Archive
|
||||
from song import Song
|
||||
|
47
archiver/song.py
Normal file
47
archiver/song.py
Normal file
@ -0,0 +1,47 @@
|
||||
from django.db import models
|
||||
from Melodia import melodia_settings
|
||||
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
class Song (models.Model):
|
||||
|
||||
"""
|
||||
This class defines the fields and functions related to controlling
|
||||
individual music files.
|
||||
"""
|
||||
|
||||
#Standard user-populated metadata
|
||||
title = models.CharField(max_length = 64)
|
||||
artist = models.CharField(max_length = 64)
|
||||
album = models.CharField(max_length = 64)
|
||||
release_date = models.DateField()
|
||||
genre = models.CharField(max_length = 64)
|
||||
bpm = models.IntegerField()
|
||||
|
||||
|
||||
#File metadata
|
||||
bit_rate = models.IntegerField()
|
||||
duration = models.IntegerField()
|
||||
echonest_song_id = models.CharField(max_length = 64)
|
||||
url = models.CharField(max_length = 64)
|
||||
file_hash = melodia_settings.HASH_RESULT_DB_TYPE
|
||||
|
||||
def populate_metadata(self):
|
||||
"Populate the metadata of this song"
|
||||
#Will eventually use EchoNest to power this. For now, just use defaults.
|
||||
import datetime
|
||||
self.title = "_"
|
||||
self.artist = "_"
|
||||
self.album = "_"
|
||||
self.release_date = datetime.datetime.now()
|
||||
self.genre = "_"
|
||||
self.bpm = 0
|
||||
|
||||
self.bit_rate = 0
|
||||
self.duration = 0
|
||||
self.echonest_song_id = "_"
|
@ -7,10 +7,47 @@ Replace this with more appropriate tests for your application.
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
class FilesystemScanTest(TestCase):
|
||||
def test_filesystem_scan(self):
|
||||
"Tests that we can scan a filesystem correctly."
|
||||
import os
|
||||
from archiver.archive import Archive
|
||||
from Melodia.settings import PROJECT_FOLDER
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.assertEqual(1 + 1, 2)
|
||||
TEST_DATA_FOLDER = os.path.join(PROJECT_FOLDER, "test_data")
|
||||
new_archive = Archive(root_folder = TEST_DATA_FOLDER)
|
||||
|
||||
#We must save the archive before we can start adding songs to it
|
||||
new_archive.save()
|
||||
|
||||
new_archive._scan_filesystem()
|
||||
|
||||
class ScanTest(TestCase):
|
||||
def test_archive_scan(self):
|
||||
"Tests that we can scan an archive correctly."
|
||||
import os
|
||||
from archiver.archive import Archive
|
||||
from Melodia.settings import PROJECT_FOLDER
|
||||
|
||||
TEST_DATA_FOLDER = os.path.join(PROJECT_FOLDER, "test_data")
|
||||
new_archive = Archive(root_folder = TEST_DATA_FOLDER)
|
||||
|
||||
#We must save the archive before we can start adding songs to it
|
||||
new_archive.save()
|
||||
|
||||
new_archive.scan()
|
||||
|
||||
class DeepScanTest(TestCase):
|
||||
def test_archive_deep_scan(self):
|
||||
"Tests that we can deep scan an archive correctly."
|
||||
import os
|
||||
from archiver.archive import Archive
|
||||
from Melodia.settings import PROJECT_FOLDER
|
||||
|
||||
TEST_DATA_FOLDER = os.path.join(PROJECT_FOLDER, "test_data")
|
||||
new_archive = Archive(root_folder = TEST_DATA_FOLDER)
|
||||
|
||||
#We must save the archive before we can start adding songs to it
|
||||
new_archive.save()
|
||||
|
||||
new_archive.deep_scan()
|
||||
|
Reference in New Issue
Block a user