* morituri/test/release.c7d919f4-3ea0-4c4b-a230-b3605f069440.xml:

Add release data for Bettie Serveert, Lamprey
	* morituri/test/test_common_program.py:
	  Add a test for parsing and getting the whole duration.
	* morituri/common/common.py:
	  Add a method to format time.
	* morituri/common/program.py:
	  Add duration to tracks and release metadatas.
	  When there are multiple matches, look up the closest in duration.
	  Make sure that multiple matches closest in duration contain same
	  artist and title.
	  Complain about the other ones.
	* morituri/image/table.py:
	  Add a method to calculate a duration from the table.
	* morituri/test/test_image_table.py:
	  Add a test for it.
This commit is contained in:
Thomas Vander Stichele
2011-08-14 12:18:51 +00:00
parent e95d7a159b
commit d43860bdfb
7 changed files with 183 additions and 3 deletions

View File

@@ -1,3 +1,22 @@
2011-08-14 Thomas Vander Stichele <thomas at apestaart dot org>
* morituri/test/release.c7d919f4-3ea0-4c4b-a230-b3605f069440.xml:
Add release data for Bettie Serveert, Lamprey
* morituri/test/test_common_program.py:
Add a test for parsing and getting the whole duration.
* morituri/common/common.py:
Add a method to format time.
* morituri/common/program.py:
Add duration to tracks and release metadatas.
When there are multiple matches, look up the closest in duration.
Make sure that multiple matches closest in duration contain same
artist and title.
Complain about the other ones.
* morituri/image/table.py:
Add a method to calculate a duration from the table.
* morituri/test/test_image_table.py:
Add a test for it.
2011-08-13 Thomas Vander Stichele <thomas at apestaart dot org>
* morituri/rip/cd.py:

View File

@@ -67,6 +67,46 @@ def framesToHMSF(frames):
return "%02d:%02d:%02d.%02d" % (h, m, s, f)
def formatTime(seconds, fractional=3):
"""
Nicely format time in a human-readable format, like
HH:MM:SS.mmm
If fractional is zero, no seconds will be shown.
If it is greater than 0, we will show seconds and fractions of seconds.
As a side consequence, there is no way to show seconds without fractions.
@param seconds: the time in seconds to format.
@type seconds: int or float
@param fractional: how many digits to show for the fractional part of
seconds.
@type fractional: int
@rtype: string
@returns: a nicely formatted time string.
"""
chunks = []
if seconds < 0:
chunks.append(('-'))
seconds = -seconds
hour = 60 * 60
hours = seconds / hour
seconds %= hour
minute = 60
minutes = seconds / minute
seconds %= minute
chunk = '%02d:%02d' % (hours, minutes)
if fractional > 0:
chunk += ':%0*.*f' % (fractional + 3, fractional, seconds)
chunks.append(chunk)
return " ".join(chunks)
class Persister(object):
"""
I wrap an optional pickle to persist an object to disk.

View File

@@ -26,6 +26,7 @@ Common functionality and class for all programs using morituri.
import os
import urlparse
import time
from morituri.common import common, log
from morituri.result import result
@@ -40,6 +41,7 @@ class MusicBrainzException(Exception):
class TrackMetadata(object):
artist = None
title = None
duration = None # in ms
class DiscMetadata(object):
"""
@@ -90,8 +92,12 @@ def getMetadata(release):
metadata.mbidArtist = urlparse.urlparse(release.artist.id)[2].split("/")[-1]
duration = 0
for t in release.tracks:
track = TrackMetadata()
track.duration = t.duration
duration += t.duration
if isSingleArtist or t.artist == None:
track.artist = metadata.artist
track.sortName = metadata.sortName
@@ -106,6 +112,8 @@ def getMetadata(release):
track.mbid = urlparse.urlparse(t.id)[2].split("/")[-1]
metadata.tracks.append(track)
metadata.duration = duration
return metadata
@@ -159,8 +167,10 @@ def musicbrainz(discid):
md = getMetadata(release)
if md:
log.debug('program', 'duration %r', md.duration)
ret.append(md)
return ret
class Program(log.Loggable):
@@ -345,18 +355,54 @@ class Program(log.Loggable):
ret = None
metadatas = None
try:
metadatas = musicbrainz(mbdiscid)
except MusicBrainzException, e:
for i in range(0, 4):
try:
metadatas = musicbrainz(mbdiscid)
except MusicBrainzException, e:
print "Warning:", e
time.sleep(5)
continue
if not metadatas:
print "Error:", e
print 'Continuing without metadata'
if metadatas:
print 'Disc duration: %s' % common.formatTime(
ittoc.duration() / 1000.0)
print
print 'Matching releases:'
deltas = {}
for metadata in metadatas:
print 'Artist : %s' % metadata.artist.encode('utf-8')
print 'Title : %s' % metadata.title.encode('utf-8')
print 'Duration: %s' % common.formatTime(
metadata.duration / 1000.0)
delta = abs(metadata.duration - ittoc.duration())
if not delta in deltas:
deltas[delta] = []
deltas[delta].append(metadata)
# Select the release that most closely matches the duration.
lowest = min(deltas.keys())
# If we have multiple, make sure they match
metadatas = deltas[lowest]
if len(metadatas) > 1:
artist = metadatas[0].artist
title = metadatas[0].title
for metadata in metadatas:
assert artist == metadata.artist
assert title == metadata.title
if (len(deltas.keys()) > 1):
print
print 'Picked closest match in duration.'
print 'Others may be wrong in musicbrainz, please correct.'
print 'Artist : %s' % artist
print 'Title : %s' % title
# Select one of the returned releases. We just pick the first one.
ret = metadatas[0]

View File

@@ -427,6 +427,14 @@ class Table(object, log.Loggable):
return urlparse.urlunparse((
'http', host, '/bare/cdlookup.html', '', query, ''))
def duration(self):
"""
Get an estimate of the duration in ms.
"""
values = self._getMusicBrainzValues()
leadout = values[2]
first = values[3]
return ((leadout - first) * 1000) / common.FRAMES_PER_SECOND
def _getMusicBrainzValues(self):
"""

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- gotten from get "http://www.musicbrainz.org/ws/1/release/c7d919f4-3ea0-4c4b-a230-b3605f069440?type=xml&inc=tracks+release-events+artist"
-->
<metadata xmlns="http://musicbrainz.org/ns/mmd-1.0#" >
<release type="Album Official" id="c7d919f4-3ea0-4c4b-a230-b3605f069440">
<title>Lamprey</title><text-representation script="Latn" language="ENG" />
<asin>B00000581T</asin>
<artist id="89e4aade-fc77-4a18-8d0c-3554cc9b8c54">
<name>Bettie Serveert</name><sort-name>Bettie Serveert</sort-name>
</artist>
<track-list>
<track id="a89c2320-1eae-4f2d-8b0e-6dfac4ae1451">
<title>Keepsake</title><duration>378693</duration>
</track>
<track id="730df656-f504-4866-b5d0-c59ee01524a3">
<title>Ray Ray Rain</title><duration>262106</duration>
</track>
<track id="62ff731e-3cec-4f7c-a614-72adb062fd6c">
<title>D. Feathers</title><duration>332626</duration>
</track>
<track id="e2702c6e-1dae-4a5d-ab16-aed1be105f94">
<title>Re-Feel-It</title><duration>238240</duration>
</track>
<track id="8bc5865e-6286-4cf2-83c8-94061cf4e25f">
<title>21 Days</title><duration>203826</duration>
</track>
<track id="484f7bc6-51bd-44dd-bac6-b8218602d3f0">
<title>Cybor*D</title><duration>241800</duration>
</track>
<track id="e0f2ce29-392e-499d-8ff9-f573e01fb3dc">
<title>Tell Me, Sad</title><duration>318333</duration>
</track>
<track id="a6d4d047-fef8-41f8-88c0-ed077de875a8">
<title>Crutches</title><duration>292373</duration>
</track>
<track id="8a05d376-b09f-4f92-b0d4-10cf177df0f3">
<title>Something So Wild</title><duration>171466</duration>
</track>
<track id="8b3402bd-aa83-4c01-961b-8fd01cf47228">
<title>Totally Freaked Out</title><duration>250893</duration>
</track>
<track id="b5628295-f5be-438e-ae89-6213666fd552">
<title>Silent Spring</title><duration>272533</duration>
</track>
</track-list>
<release-event-list>
<event country="US" format="CD" date="1995-04-16" barcode="075679250421" catalog-number="OLE-121-2" />
</release-event-list>
</release>
</metadata>

View File

@@ -110,3 +110,17 @@ class PathTestCase(unittest.TestCase):
self.assertEquals(path,
u'/tmp/Jeff Buckley/Grace')
class MetadataLengthTestCase(unittest.TestCase):
def testLamprey(self):
from musicbrainz2 import wsxml
path = os.path.join(os.path.dirname(__file__),
'release.c7d919f4-3ea0-4c4b-a230-b3605f069440.xml')
handle = open(path, "rb")
reader = wsxml.MbXmlParser()
wsMetadata = reader.parse(handle)
release = wsMetadata.getRelease()
metadata = progam.getMetadata(release)
self.assertEquals(metadata.duration, 2962889)

View File

@@ -52,6 +52,9 @@ class LadyhawkeTestCase(tcommon.TestCase):
self.assertEquals(self.table.getAccurateRipURL(),
"http://www.accuraterip.com/accuraterip/a/5/d/dBAR-012-0013bd5a-00b8d489-c60af50d.bin")
def testDuration(self):
self.assertEquals(self.table.duration(), 2609413)
class MusicBrainzTestCase(tcommon.TestCase):
# example taken from http://musicbrainz.org/doc/DiscIDCalculation
# disc is Ettella Diamant