python-musicbrainz2-0.7.4/ 0000755 0001750 0001750 00000000000 11655171144 014476 5 ustar lukas lukas python-musicbrainz2-0.7.4/bin/ 0000755 0001750 0001750 00000000000 11655171140 015242 5 ustar lukas lukas python-musicbrainz2-0.7.4/bin/mb-submit-disc 0000755 0001750 0001750 00000001550 10427117514 020011 0 ustar lukas lukas #! /usr/bin/env python
#
# Helper script to calculate a MusicBrainz DiscID and a disc submission URL.
#
# Usage:
# mb-submit-disc [device]
#
# $Id$
#
import sys
import musicbrainz2.disc as mbdisc
if len(sys.argv) == 1:
device = None
elif len(sys.argv) == 2:
device = sys.argv[1]
if len(sys.argv) > 2:
print >>sys.stderr, 'Usage: %s [device]' % sys.argv[0]
sys.exit(1);
try:
# Read the disc in the given drive. If no device name is given, use
# the default one.
#
if device is None:
disc = mbdisc.readDisc()
else:
disc = mbdisc.readDisc(device)
except mbdisc.DiscError, e:
print "%s: Couldn't read disc: %s" % (sys.argv[0], e)
print >>sys.stderr, "Is there an Audio CD in the drive?"
sys.exit(1)
print 'MusicBrainz DiscID:', disc.id
print
print 'You can use the following URL to submit this disc to MB:'
print ' ', mbdisc.getSubmissionUrl(disc)
# EOF
python-musicbrainz2-0.7.4/examples/ 0000755 0001750 0001750 00000000000 11655171140 016310 5 ustar lukas lukas python-musicbrainz2-0.7.4/examples/getuser.py 0000755 0001750 0001750 00000001414 10411775001 020336 0 ustar lukas lukas #! /usr/bin/env python
#
# Display data about a MusicBrainz user (user name and password required).
#
# Usage:
# python user.py
#
# $Id: getuser.py 201 2006-03-27 14:43:13Z matt $
#
import sys
import logging
import getpass
from musicbrainz2.webservice import WebService, WebServiceError, Query
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
user = raw_input('User name: ')
passwd = getpass.getpass('Password: ')
try:
ws = WebService(host='musicbrainz.org', port=80,
username=user, password=passwd)
q = Query(ws)
user = q.getUserByName(user)
except WebServiceError, e:
print 'Error:', e
sys.exit(1)
print 'Name :', user.name
print 'ShowNag :', user.showNag
print 'Types :', ' '.join(user.types)
# EOF
python-musicbrainz2-0.7.4/examples/findlabel.py 0000755 0001750 0001750 00000002271 10657263546 020624 0 ustar lukas lukas #! /usr/bin/env python
#
# Search for a label by name.
#
# Usage:
# python findlabel.py 'label-name'
#
# $Id: findlabel.py 9316 2007-08-11 07:38:14Z matt $
#
import sys
import logging
from musicbrainz2.webservice import Query, LabelFilter, WebServiceError
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
if len(sys.argv) < 2:
print "Usage: findlabel.py 'label name'"
sys.exit(1)
q = Query()
try:
# Search for all labels matching the given name. Limit the results
# to the 5 best matches. The offset parameter could be used to page
# through the results.
#
f = LabelFilter(name=sys.argv[1], limit=5)
labelResults = q.getLabels(f)
except WebServiceError, e:
print 'Error:', e
sys.exit(1)
# No error occurred, so display the results of the search. It consists of
# LabelResult objects, where each contains a label.
#
for result in labelResults:
label = result.label
print "Score :", result.score
print "Id :", label.id
print "Name :", label.name
print "Sort Name :", label.sortName
print
#
# Now that you have label IDs, you can request a label directly to get more
# detail. See 'getlabel.py' for an example on how to do that.
#
# EOF
python-musicbrainz2-0.7.4/examples/getrelations.py 0000755 0001750 0001750 00000004646 10430144330 021366 0 ustar lukas lukas #! /usr/bin/env python
#
# Retrieving an artist's relations to other artists and URLs.
#
# Usage:
# python getrelations.py artist-id
#
# Interesting Artist IDs for testing:
# http://musicbrainz.org/artist/ea4dfa26-f633-4da6-a52a-f49ea4897b58
# http://musicbrainz.org/artist/172e1f1a-504d-4488-b053-6344ba63e6d0
# http://musicbrainz.org/artist/c0b2500e-0cef-4130-869d-732b23ed9df5
#
# $Id: getrelations.py 7496 2006-05-09 16:52:40Z matt $
#
import sys
import logging
import musicbrainz2.webservice as ws
import musicbrainz2.model as m
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
if len(sys.argv) < 2:
print "Usage: getrelations.py artist-id"
sys.exit(1)
q = ws.Query()
try:
# The result should include all relations to other artists and also
# relations to URLs.
#
inc = ws.ArtistIncludes(artistRelations=True, releaseRelations=True,
urlRelations=True)
artist = q.getArtistById(sys.argv[1], inc)
except ws.WebServiceError, e:
print 'Error:', e
sys.exit(1)
print "Id :", artist.id
print "Name :", artist.name
print
#
# Get the artist's relations to URLs (m.Relation.TO_URL) having the relation
# type 'http://musicbrainz.org/ns/rel-1.0#Wikipedia'. Note that there could
# be more than one relation per type. We just print the first one.
#
urls = artist.getRelationTargets(m.Relation.TO_URL, m.NS_REL_1+'Wikipedia')
if len(urls) > 0:
print 'Wikipedia:', urls[0]
print
#
# List discography pages for an artist.
#
for rel in artist.getRelations(m.Relation.TO_URL, m.NS_REL_1+'Discography'):
print 'Discography:', rel.targetId
print
#
# If the artist is a group, list all members.
#
if artist.type == m.Artist.TYPE_GROUP:
allMembers = artist.getRelations(m.Relation.TO_ARTIST,
m.NS_REL_1+'MemberOfBand')
uri = m.NS_REL_1+'Additional'
coreMembers = [r for r in allMembers if uri not in r.attributes]
additionalMembers = [r for r in allMembers if uri in r.attributes]
print 'Group members:'
for rel in coreMembers:
start = rel.beginDate or 'foundation'
end = rel.endDate or 'end'
print '\t%s (%s to %s)' % (rel.target.name, start, end)
print
print 'Additional members:'
for rel in additionalMembers:
print '\t', rel.target.name
#
# List all releases for which this artist has acted as the producer.
#
releases = artist.getRelationTargets(m.Relation.TO_RELEASE,
m.NS_REL_1+'Producer')
print
print 'Credited as producer for:'
for r in releases:
print '\t', r.title
# EOF
python-musicbrainz2-0.7.4/examples/ripper.py 0000755 0001750 0001750 00000004106 10411775001 020162 0 ustar lukas lukas #! /usr/bin/env python
#
# This shows the web service interaction needed for a typical CD ripper.
#
# Usage:
# python ripper.py
#
# $Id: ripper.py 201 2006-03-27 14:43:13Z matt $
#
import sys
import logging
import musicbrainz2.disc as mbdisc
import musicbrainz2.webservice as mbws
# Activate logging.
#
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# Setup a Query object.
#
service = mbws.WebService()
query = mbws.Query(service)
# Read the disc in the drive
#
try:
disc = mbdisc.readDisc()
except mbdisc.DiscError, e:
print "Error:", e
sys.exit(1)
# Query for all discs matching the given DiscID.
#
try:
filter = mbws.ReleaseFilter(discId=disc.getId())
results = query.getReleases(filter)
except mbws.WebServiceError, e:
print "Error:", e
sys.exit(2)
# No disc matching this DiscID has been found.
#
if len(results) == 0:
print "Disc is not yet in the MusicBrainz database."
print "Consider adding it via", mbdisc.getSubmissionUrl(disc)
sys.exit(0)
# Display the returned results to the user.
#
print 'Matching releases:'
for result in results:
release = result.release
print 'Artist :', release.artist.name
print 'Title :', release.title
print
# Select one of the returned releases. We just pick the first one.
#
selectedRelease = results[0].release
# The returned release object only contains title and artist, but no tracks.
# Query the web service once again to get all data we need.
#
try:
inc = mbws.ReleaseIncludes(artist=True, tracks=True, releaseEvents=True)
release = query.getReleaseById(selectedRelease.getId(), inc)
except mbws.WebServiceError, e:
print "Error:", e
sys.exit(2)
# Now display the returned data.
#
isSingleArtist = release.isSingleArtistRelease()
print "%s - %s" % (release.artist.getUniqueName(), release.title)
i = 1
for t in release.tracks:
if isSingleArtist:
title = t.title
else:
title = t.artist.name + ' - ' + t.title
(minutes, seconds) = t.getDurationSplit()
print " %2d. %s (%d:%02d)" % (i, title, minutes, seconds)
i+=1
# All data has been retrieved, now actually rip the CD :-)
#
# EOF
python-musicbrainz2-0.7.4/examples/getartist.py 0000755 0001750 0001750 00000003565 11231304732 020677 0 ustar lukas lukas #! /usr/bin/env python
#
# Retrieve an artist by ID and display all official albums.
#
# Usage:
# python getartist.py artist-id
#
# $Id: getartist.py 11853 2009-07-21 09:26:50Z luks $
#
import sys
import logging
import musicbrainz2.webservice as ws
import musicbrainz2.model as m
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
if len(sys.argv) < 2:
print "Usage: getartist.py artist-id"
sys.exit(1)
q = ws.Query()
try:
# The result should include all official albums.
#
inc = ws.ArtistIncludes(
releases=(m.Release.TYPE_OFFICIAL, m.Release.TYPE_ALBUM),
tags=True, releaseGroups=True)
artist = q.getArtistById(sys.argv[1], inc)
except ws.WebServiceError, e:
print 'Error:', e
sys.exit(1)
print "Id :", artist.id
print "Name :", artist.name
print "SortName :", artist.sortName
print "UniqueName :", artist.getUniqueName()
print "Type :", artist.type
print "BeginDate :", artist.beginDate
print "EndDate :", artist.endDate
print "Tags :", ', '.join([t.value for t in artist.tags])
print
if len(artist.getReleases()) == 0:
print "No releases found."
else:
print "Releases:"
for release in artist.getReleases():
print
print "Id :", release.id
print "Title :", release.title
print "ASIN :", release.asin
print "Text :", release.textLanguage, '/', release.textScript
print "Types :", release.types
print
if len(artist.getReleaseGroups()) == 0:
print
print "No release groups found."
else:
print
print "Release groups:"
for rg in artist.getReleaseGroups():
print
print "Id :", rg.id
print "Title :", rg.title
print "Type :", rg.type
print
#
# Using the release IDs and Query.getReleaseById(), you could now request
# those releases, including the tracks, release events, the associated
# DiscIDs, and more. The 'getrelease.py' example shows how this works.
#
# EOF
python-musicbrainz2-0.7.4/examples/folksonomytags.py 0000755 0001750 0001750 00000002510 10762216154 021745 0 ustar lukas lukas #! /usr/bin/env python
#
# This queries the tags a user has applied to an entity and submits a changed
# list of tags to the MusicBrainz server.
#
# Usage:
# python tag.py
#
# $Id: folksonomytags.py 9714 2008-03-01 09:05:48Z matt $
#
import getpass
import sys
import logging
import musicbrainz2.webservice as mbws
from musicbrainz2.model import Tag
from musicbrainz2.utils import extractUuid
MB_HOST = 'test.musicbrainz.org'
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# Get the username and password
username = raw_input('Username: ')
password = getpass.getpass('Password: ')
# Ask for a MBID to tag.
mbid = raw_input('Enter an absolute MB ID: ')
# Set the authentication for the webservice (only needed for tag submission).
service = mbws.WebService(host=MB_HOST, username=username, password=password)
# Create a new Query object which will provide
# us an interface to the MusicBrainz web service.
query = mbws.Query(service)
# Read and print the current tags for the given MBID
tags = query.getUserTags(mbid)
print
print 'Current tags: '
print ', '.join([tag.value for tag in tags])
# Ask the user for new tags and submit them
tag_str = raw_input('Enter new tags: ')
new_tags = [Tag(tag.strip()) for tag in tag_str.split(',')]
query.submitUserTags(mbid, new_tags)
print 'Tags submitted.'
# EOF
python-musicbrainz2-0.7.4/examples/findtrack.py 0000755 0001750 0001750 00000001632 10411775001 020627 0 ustar lukas lukas #! /usr/bin/env python
#
# Search for a track by title (and optionally by artist name).
#
# Usage:
# python findtrack.py 'track name' ['artist name']
#
# $Id: findtrack.py 201 2006-03-27 14:43:13Z matt $
#
import sys
import logging
from musicbrainz2.webservice import Query, TrackFilter, WebServiceError
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
if len(sys.argv) < 2:
print "Usage: findtrack.py 'track name' ['artist name']"
sys.exit(1)
if len(sys.argv) > 2:
artistName = sys.argv[2]
else:
artistName = None
q = Query()
try:
f = TrackFilter(title=sys.argv[1], artistName=artistName)
results = q.getTracks(f)
except WebServiceError, e:
print 'Error:', e
sys.exit(1)
for result in results:
track = result.track
print "Score :", result.score
print "Id :", track.id
print "Title :", track.title
print "Artist :", track.artist.name
print
# EOF
python-musicbrainz2-0.7.4/examples/getlabel.py 0000755 0001750 0001750 00000001777 10657263546 020475 0 ustar lukas lukas #! /usr/bin/env python
#
# Retrieve a label by ID.
#
# Usage:
# python getlabel.py label-id
#
# $Id: getlabel.py 9316 2007-08-11 07:38:14Z matt $
#
import sys
import logging
import musicbrainz2.webservice as ws
import musicbrainz2.model as m
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
if len(sys.argv) < 2:
print "Usage: getlabel.py label-id"
sys.exit(1)
q = ws.Query()
try:
# The result should include all aliases.
#
inc = ws.LabelIncludes(aliases=True)
label = q.getLabelById(sys.argv[1], inc)
except ws.WebServiceError, e:
print 'Error:', e
sys.exit(1)
print "Id :", label.id
print "Name :", label.name
print "SortName :", label.sortName
print "UniqueName :", label.getUniqueName()
print "Type :", label.type
print "BeginDate :", label.beginDate
print "EndDate :", label.endDate
print "Country :", label.country
print "Label-Code :", label.code
print "Aliases :"
for alias in label.aliases:
print " ", label.value
# EOF
python-musicbrainz2-0.7.4/examples/getrelease.py 0000755 0001750 0001750 00000004701 11231304732 021002 0 ustar lukas lukas #! /usr/bin/env python
#
# Retrieve a release by ID and display it.
#
# Usage:
# python getrelease.py release-id
#
# Interesting releases IDs for testing:
# http://musicbrainz.org/release/290e10c5-7efc-4f60-ba2c-0dfc0208fbf5
# http://musicbrainz.org/release/fa9f1bdd-495f-41b9-8944-1a766da29120
#
# $Id: getrelease.py 11853 2009-07-21 09:26:50Z luks $
#
import sys
import logging
import musicbrainz2.webservice as ws
import musicbrainz2.utils as u
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
if len(sys.argv) < 2:
print "Usage: getrelease.py release-id"
sys.exit(1)
q = ws.Query()
try:
# Additionally to the release itself, we want the server to include
# the release's artist, all release events, associated discs and
# the track list.
#
inc = ws.ReleaseIncludes(artist=True, releaseEvents=True, labels=True,
discs=True, tracks=True, releaseGroup=True)
release = q.getReleaseById(sys.argv[1], inc)
except ws.WebServiceError, e:
print 'Error:', e
sys.exit(1)
print "Id :", release.id
print "Title :", release.title
print "ASIN :", release.asin
print "Lang/Script :", release.textLanguage, '/', release.textScript
# Print the main artist of this release.
#
if release.artist:
print
print "Artist:"
print " Id :", release.artist.id
print " Name :", release.artist.name
print " SortName :", release.artist.sortName
if release.releaseGroup:
print
print "Release Group:"
print " Id :", release.releaseGroup.id
print " Title :", release.releaseGroup.title
print " Type :", release.releaseGroup.type
# Release events are the dates and times when a release took place.
# We also have the catalog numbers and barcodes for some releases.
#
if len(release.releaseEvents) > 0:
print
print "Released (earliest: %s):" % release.getEarliestReleaseDate()
for event in release.releaseEvents:
print " %s %s" % (u.getCountryName(event.country), event.date),
if event.catalogNumber:
print '#' + event.catalogNumber,
if event.barcode:
print 'EAN=' + event.barcode,
if event.label:
print '(' + event.label.name + ')',
print
if len(release.discs) > 0:
print
print "Discs:"
for disc in release.discs:
print " DiscId: %s (%d sectors)" % (disc.id, disc.sectors)
if len(release.tracks) > 0:
print
print "Tracks:"
for track in release.tracks:
print " Id :", track.id
print " Title :", track.title
print " Duration :", track.duration
print
# EOF
python-musicbrainz2-0.7.4/examples/discid.py 0000755 0001750 0001750 00000001476 10411775001 020127 0 ustar lukas lukas #! /usr/bin/env python
#
# Read a CD in the disc drive and calculate a MusicBrainz DiscID.
#
# Usage:
# python discid.py
#
# $Id: discid.py 201 2006-03-27 14:43:13Z matt $
#
import sys
from musicbrainz2.disc import readDisc, getSubmissionUrl, DiscError
try:
# Read the disc in the default disc drive. If necessary, you can pass
# the 'deviceName' parameter to select a different drive.
#
disc = readDisc()
except DiscError, e:
print "DiscID calculation failed:", str(e)
sys.exit(1)
print 'DiscID :', disc.id
print 'First Track :', disc.firstTrackNum
print 'Last Track :', disc.lastTrackNum
print 'Length :', disc.sectors, 'sectors'
i = disc.firstTrackNum
for (offset, length) in disc.tracks:
print "Track %-2d : %8d %8d" % (i, offset, length)
i += 1
print 'Submit via :', getSubmissionUrl(disc)
# EOF
python-musicbrainz2-0.7.4/examples/findartist.py 0000755 0001750 0001750 00000002401 10550142420 021021 0 ustar lukas lukas #! /usr/bin/env python
#
# Search for an artist by name.
#
# Usage:
# python findartist.py 'artist-name'
#
# $Id: findartist.py 8779 2007-01-07 10:01:52Z matt $
#
import sys
import logging
from musicbrainz2.webservice import Query, ArtistFilter, WebServiceError
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
if len(sys.argv) < 2:
print "Usage: findartist.py 'artist name'"
sys.exit(1)
q = Query()
try:
# Search for all artists matching the given name. Limit the results
# to the 5 best matches. The offset parameter could be used to page
# through the results.
#
f = ArtistFilter(name=sys.argv[1], limit=5)
artistResults = q.getArtists(f)
except WebServiceError, e:
print 'Error:', e
sys.exit(1)
# No error occurred, so display the results of the search. It consists of
# ArtistResult objects, where each contains an artist.
#
for result in artistResults:
artist = result.artist
print "Score :", result.score
print "Id :", artist.id
print "Name :", artist.name
print "Sort Name :", artist.sortName
print
#
# Now that you have artist IDs, you can request an artist in more detail, for
# example to display all official albums by that artist. See the 'getartist.py'
# example on how achieve that.
#
# EOF
python-musicbrainz2-0.7.4/examples/README.txt 0000644 0001750 0001750 00000003452 11231304732 020005 0 ustar lukas lukas Examples for typical use cases
------------------------------
This directory contains example code which should cover the most important
use cases. The following demo scripts are available:
discid.py
Reads the disc in the computers CD-ROM/DVD-ROM drive. It calculates a
MusicBrainz DiscID and displays the TOC as well as a submission URL.
findartist.py
Search MusicBrainz for artists matching the given name.
findtrack.py
Search MusicBrainz for a track matching the given track title and,
optionally, artist name.
findlabel.py
Search MusicBrainz for a label matching the given label name.
findreleasegroup.py
Search MusicBrainz for a release group matching a Lucene query.
folksonomytags.py
Get and submit MusicBrainz folksonomy tags for a given entity.
getartist.py
Retrieve an artist using a MusicBrainz ID and display all officially
released albums.
getrelease.py
Retrieve a release by MusicBrainz ID, including tracks and other
associated data, like release dates or DiscIDs.
getreleasegroup.py
Retrieve a release group by MusicBrainz ID, including releases,
primary artist and release types.
getlabel.py
Retrieve a label by MusicBrainz ID, including aliases.
getrelations.py
Retrieve an artist by MusicBrainz ID and display relations to URLs and
other artists. This demonstrates how to use the more advanced features
of the web service.
getuser.py
Display some information about a MusicBrainz user. You need the user
name and password to use this.
ripper.py
This example shows how to get a MusicBrainz DiscID for a CD in the disc
drive and to get the matching releases from the web service. This is
the typical workflow for a simple CD ripper application.
--
$Id: README.txt 11853 2009-07-21 09:26:50Z luks $
python-musicbrainz2-0.7.4/examples/findreleasegroup.py 0000644 0001750 0001750 00000003024 11231304732 022212 0 ustar lukas lukas #! /usr/bin/env python
#
# Search for a release group by name.
#
# Usage:
# python findreleasegroup.py 'album name/Lucene query'
#
# Examples:
# findreleasegroup.py "Signal morning"
# findreleasegroup.py '"Gimme Fiction" AND artist:"Spoon"'
# findreleasegroup.py '"Skylarking" AND type:"Album"'
#
import sys
import logging
from musicbrainz2.webservice import Query, ReleaseGroupFilter, WebServiceError
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
if len(sys.argv) < 2:
print "Usage: findreleasegroup.py 'release group name/Lucene query'"
sys.exit(1)
q = Query()
try:
# Search for all release groups matching the given query. Limit the results
# to the 5 best matches. The offset parameter could be used to page
# through the results.
#
f = ReleaseGroupFilter(query=sys.argv[1], limit=5)
results = q.getReleaseGroups(f)
except WebServiceError, e:
print 'Error:', e
sys.exit(1)
# No error occurred, so display the results of the search. It consists of
# ReleaseGroupResult objects, where each contains an artist.
#
for result in results:
releaseGroup = result.releaseGroup
print "Score\t\t :", result.score
print "Id :", releaseGroup.id
print "Name :", releaseGroup.title
print "Artist :", releaseGroup.artist.name
print "Type :", releaseGroup.type
print
#
# Now that you have release group IDs, you can request a release group in more detail, for
# example to display all official releases in that group. See the 'getreleasegroup.py'
# example on how achieve that.
#
# EOF
python-musicbrainz2-0.7.4/examples/getreleasegroup.py 0000644 0001750 0001750 00000003277 11231304732 022063 0 ustar lukas lukas #! /usr/bin/env python
#
# Retrieve a release group by ID and display it.
#
# Usage:
# python getreleasegroup.py releasegroup-id
#
# Interesting release group IDs for testing:
# http://musicbrainz.org/release-group/e49e2f8a-94c0-3dcf-8ce6-9bc52a1a7867
# http://musicbrainz.org/release-group/c7a0fc4d-b6a0-3a43-9e25-4052e4fe33b2
# http://musicbrainz.org/release-group/055be730-dcad-31bf-b550-45ba9c202aa3
# http://musicbrainz.org/release-group/963eac15-e3da-3a92-aa5c-2ec23bfb6ec2
#
#
import sys
import logging
import musicbrainz2.webservice as ws
import musicbrainz2.utils as u
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
if len(sys.argv) < 2:
print "Usage: getreleasegroup.py releasegroup-id"
sys.exit(1)
q = ws.Query()
try:
# Additionally to the release itself, we want the server to include
# the release's artist, all release events, associated discs and
# the track list.
#
inc = ws.ReleaseGroupIncludes(artist=True, releases=True, tags=False)
releaseGroup = q.getReleaseGroupById(sys.argv[1], inc)
except ws.WebServiceError, e:
print 'Error:', e
sys.exit(1)
print "Id :", releaseGroup.id
print "Title :", releaseGroup.title
print "Type :", releaseGroup.type
# Print the main artist of this release group.
#
if releaseGroup.artist:
print
print "Artist:"
print " Id :", releaseGroup.artist.id
print " Name :", releaseGroup.artist.name
print " SortName :", releaseGroup.artist.sortName
# Show the releases contained by this release group.
#
for release in releaseGroup.releases:
print
print "Release:"
print " Id :", release.id
print " Title :", release.title
print " Types :", release.types
# EOF
python-musicbrainz2-0.7.4/test-data/ 0000755 0001750 0001750 00000000000 11655171146 016366 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/invalid/ 0000755 0001750 0001750 00000000000 11655171146 020014 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/invalid/release/ 0000755 0001750 0001750 00000000000 11655171146 021434 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/invalid/track/ 0000755 0001750 0001750 00000000000 11655171146 021120 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/invalid/artist/ 0000755 0001750 0001750 00000000000 11655171146 021322 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/invalid/artist/empty_1.xml 0000644 0001750 0001750 00000000000 10367360533 023407 0 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/invalid/artist/tags_1.xml 0000644 0001750 0001750 00000000264 10637777526 023241 0 ustar lukas lukas
foo
python-musicbrainz2-0.7.4/test-data/invalid/artist/empty_3.xml 0000644 0001750 0001750 00000000150 10367360533 023417 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/invalid/artist/empty_2.xml 0000644 0001750 0001750 00000000137 10367360533 023423 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/invalid/artist/ratings_2.xml 0000644 0001750 0001750 00000000244 11104666317 023732 0 ustar lukas lukas
2.5
python-musicbrainz2-0.7.4/test-data/invalid/artist/basic_1.xml 0000644 0001750 0001750 00000000330 10367360533 023340 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/invalid/artist/ratings_1.xml 0000644 0001750 0001750 00000000251 11104666317 023727 0 ustar lukas lukas
2
python-musicbrainz2-0.7.4/test-data/invalid/artist/basic_2.xml 0000644 0001750 0001750 00000000370 10367360533 023345 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/invalid/artist/search_result_1.xml 0000644 0001750 0001750 00000000653 10410272540 025117 0 ustar lukas lukas
Tori AmosAmos, Tori
python-musicbrainz2-0.7.4/test-data/README 0000644 0001750 0001750 00000000666 10367360533 017255 0 ustar lukas lukas MusicBrainz Web Service XML Examples
------------------------------------
This directory contains example XML code for testing an implementation.
Implementations MUST be able to parse the XML in the 'valid' directory.
It is perfect, schema-compliant XML.
The 'invalid' directory contains all kinds of invalid XML. Implementations
are not expected to parse those files. They MAY, however.
--
$Id: README 33 2006-01-30 09:50:19Z matt $
python-musicbrainz2-0.7.4/test-data/valid/ 0000755 0001750 0001750 00000000000 11655171146 017465 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/valid/release/ 0000755 0001750 0001750 00000000000 11655171145 021104 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/valid/release/Mission_Impossible_2.xml 0000644 0001750 0001750 00000016011 10405741741 025652 0 ustar lukas lukas
Mission: Impossible 2Various ArtistsVarious Artists
python-musicbrainz2-0.7.4/test-data/valid/release/Under_the_Pink_3.xml 0000644 0001750 0001750 00000001232 10675465612 024752 0 ustar lukas lukas
Under the PinkTori Amos
python-musicbrainz2-0.7.4/test-data/valid/release/Under_the_Pink_2.xml 0000644 0001750 0001750 00000001246 11226063413 024740 0 ustar lukas lukas
Under the PinkTori AmosUnder the Pink
python-musicbrainz2-0.7.4/test-data/valid/release/Little_Earthquakes_2.xml 0000644 0001750 0001750 00000005742 10371675144 025653 0 ustar lukas lukas
Little EarthquakesB000002IT2Tori AmosAmos, Tori
python-musicbrainz2-0.7.4/test-data/valid/release/Little_Earthquakes_1.xml 0000644 0001750 0001750 00000001740 10371365467 025650 0 ustar lukas lukas
Little EarthquakesB000002IT2Tori AmosAmos, Tori
python-musicbrainz2-0.7.4/test-data/valid/release/Under_the_Pink_1.xml 0000644 0001750 0001750 00000001132 10410300474 024724 0 ustar lukas lukas
Under the PinkB000002IXUTori Amos
python-musicbrainz2-0.7.4/test-data/valid/release/Highway_61_Revisited_2.xml 0000644 0001750 0001750 00000001170 11104666317 025771 0 ustar lukas lukas
Highway 61 Revisitedrockblues rockfolk rockdylanrockfoo4.53
python-musicbrainz2-0.7.4/test-data/valid/release/search_result_1.xml 0000644 0001750 0001750 00000002236 10550166442 024711 0 ustar lukas lukas
Under the PinkB000002IXUTori AmosUnder the Pink Tour 1994Tori Amos
python-musicbrainz2-0.7.4/test-data/valid/release/Highway_61_Revisited_1.xml 0000644 0001750 0001750 00000005750 10405051643 025771 0 ustar lukas lukas
Highway 61 RevisitedB0000C8AVRBob DylanDylan, Bob
python-musicbrainz2-0.7.4/test-data/valid/track/ 0000755 0001750 0001750 00000000000 11655171145 020570 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/valid/track/Silent_All_These_Years_4.xml 0000644 0001750 0001750 00000002353 11215136226 026052 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/track/Silent_All_These_Years_3.xml 0000644 0001750 0001750 00000001214 10405051643 026043 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/track/Silent_All_These_Years_5.xml 0000644 0001750 0001750 00000001427 10405051643 026053 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/track/Silent_All_These_Years_6.xml 0000644 0001750 0001750 00000000733 11201061057 026046 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/track/Silent_All_These_Years_1.xml 0000644 0001750 0001750 00000000360 10367360533 026052 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/track/Silent_All_These_Years_2.xml 0000644 0001750 0001750 00000001363 10406316150 026045 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/track/search_result_1.xml 0000644 0001750 0001750 00000003545 10550166442 024401 0 ustar lukas lukas
Little Earthquakes457760Tori AmosTo Venus and Back (disc 2: Live, Still Orbiting)Little Earthquakes413693Tori AmosLittle EarthquakesLittle Amsterdam270106Tori AmosBoys for Pele
python-musicbrainz2-0.7.4/test-data/valid/artist/ 0000755 0001750 0001750 00000000000 11655171146 020773 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/valid/artist/Tori_Amos_5.xml 0000644 0001750 0001750 00000001022 10477304634 023631 0 ustar lukas lukas
Tori AmosAmos, ToriStrange Little Girls
python-musicbrainz2-0.7.4/test-data/valid/artist/empty_1.xml 0000644 0001750 0001750 00000000217 10367360533 023072 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/artist/Tchaikovsky-1.xml 0000644 0001750 0001750 00000010757 10372357332 024162 0 ustar lukas lukas
Пётр Ильич ЧайковскийTchaikovsky, Pyotr IlyichTchaikovskyPeter Ilyich TchaikovskyPeter TchaikovskyTschaikowskyPeter TschaikowskyPiotr Ilyich TchaikovskyPiotr TchaikovskyPeter Iljitsch TschaikowskyTchaikovsky, Peter IlyichPeter Ilyitch TchaikovskyPjotr Ilyich TchaikovskyPeter I. TschaikowskyPyotr TchaikovskyP. I. TchaikovskyPeter Ilich TchaikovskyTchaikovsky, P.I.TsjaikovskiTchaikovsky, Pyotr IlyichTjajkovskijPiotr Ilyitch TchaikovskyTsjajkovskijPeter Ilyich TchaikovskiPeter I. TchaikovskyTchaikovsky, Peter I.TchaikowskyPeter Ilyich TschaikowskyPeter Iljitsch TschaikowskiPyotr Il'Yich TchaikovskyTchiakovsky, Pyotr Ilich (1840-1893)Tchaikovsky, Peter Ilyich (1840-93)Tchaikovsky, Peter IlyitchPyotr Ilyitch TchaikovskyTsaikovskiPytor Ilyich TchaikovskyPiotr Ilyich TchaikowskyTchaikovsky - Philharmonic OrchestraPeter Iljitsch Tschaikowsky (1840 - 1893)Peter TschaikovskyPeter Ilych TschaikowskyPyotr II'yich TchaikovskyPytor TchaikovskyPyotr Ilyich TchaikovskyЧайковский, Петр ИльичЧайковский, Пётр ИльичПетр Ильич ЧайковскийПётр Ильич ЧайковскийTchaikovsky, Peter Il'yichTchaikovsky, Pjotr Ilyich (1840 - 1893) Piotr Illitch TchaïkovskyPiotr Ilic CiaikovskyPyotr Illyich TchaikovskyTchaikovsky, Piotr Ilich (1840-1893)Peter Ilyich TchaikovshyPyotr Ilyich TchaikovslyPeter Ilych Tchaikovsky차이코프스키Piotr Ilitch TchaïkovskiChaikovsky, P. I.Pjotr Iljitsch TschaikowskyCiaikosvskyTchaikovsky 1841-1893TchaïkovkiPiotr Ilych ChaikovskyPjotr Iljitsj TsjaikovskiPyotor Ilyich TschaikovskyPeter Iljitsj TsjaikovskiP. I. TchaikovskijPiotr Ilich TchaikovskyPeter Iljitsch TchaikovskyTchaikoviskyTchaikovsyTchailoviskyTchaikovskyesTchaikovskysTchaikoskvyPiotr Il'yich TchaikovskyTchaikowskiPiotr Iljič ČajkovskijTchaikivskyPyotor Tchaikovsky
python-musicbrainz2-0.7.4/test-data/valid/artist/empty_2.xml 0000644 0001750 0001750 00000000262 10367360533 023073 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/artist/Tori_Amos_4.xml 0000644 0001750 0001750 00000001055 10406300610 023614 0 ustar lukas lukas
Tori AmosAmos, Toriyes, that oneMyra Ellen AmosMyra AmosTorie Amos
python-musicbrainz2-0.7.4/test-data/valid/artist/Tchaikovsky-2.xml 0000644 0001750 0001750 00000001267 11104666317 024157 0 ustar lukas lukas
Пётр Ильич Чайковскийclassicalrussianromantic eracomposerclassicalrussian4.53
python-musicbrainz2-0.7.4/test-data/valid/artist/Tori_Amos_2.xml 0000644 0001750 0001750 00000006513 11226063413 023626 0 ustar lukas lukas
Tori AmosAmos, ToriStrange Little GirlsB00005NKYQTo Venus and Back (disc 1: Orbiting)B00001IVJSUnder the PinkB000002IXUUnder the PinkTo Venus and BackStrange Little Girls
python-musicbrainz2-0.7.4/test-data/valid/artist/Tori_Amos_3.xml 0000644 0001750 0001750 00000001623 10437077165 023640 0 ustar lukas lukas
Tori AmosAmos, ToriMark HawleyHawley, Mark
python-musicbrainz2-0.7.4/test-data/valid/artist/Tori_Amos_1.xml 0000644 0001750 0001750 00000000455 10367360533 023634 0 ustar lukas lukas
Tori AmosAmos, Tori
python-musicbrainz2-0.7.4/test-data/valid/artist/search_result_1.xml 0000644 0001750 0001750 00000001474 10430176546 024605 0 ustar lukas lukas
Tori AmosAmos, ToriTori SpellingSpelling, ToriLisa And ToriLisa And Tori
python-musicbrainz2-0.7.4/test-data/valid/label/ 0000755 0001750 0001750 00000000000 11655171145 020543 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/valid/label/Atlantic_Records_2.xml 0000644 0001750 0001750 00000000703 10637777526 024744 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/label/Atlantic_Records_3.xml 0000644 0001750 0001750 00000001011 11104666317 024717 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/label/Atlantic_Records_1.xml 0000644 0001750 0001750 00000000521 10607354732 024724 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/label/search_result_1.xml 0000644 0001750 0001750 00000001075 10607354732 024354 0 ustar lukas lukas
python-musicbrainz2-0.7.4/test-data/valid/release-group/ 0000755 0001750 0001750 00000000000 11655171146 022237 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/valid/release-group/The_Cure_1.xml 0000644 0001750 0001750 00000002710 11214414205 024662 0 ustar lukas lukas
The CureThe CureCure, TheThe CureB0002CHGZIThe CureThe CureB0002C9G7OThe CureB00028HOFY
python-musicbrainz2-0.7.4/test-data/valid/release-group/search_result_1.xml 0000644 0001750 0001750 00000002126 11226063413 026034 0 ustar lukas lukas
Signal MorningCirculatory SystemCirculatory SystemCirculatory SystemInside ViewsCirculatory System
python-musicbrainz2-0.7.4/test-data/valid/user/ 0000755 0001750 0001750 00000000000 11655171146 020443 5 ustar lukas lukas python-musicbrainz2-0.7.4/test-data/valid/user/User_1.xml 0000644 0001750 0001750 00000000615 10374153130 022313 0 ustar lukas lukas
matt
python-musicbrainz2-0.7.4/COPYING.txt 0000644 0001750 0001750 00000002737 10434316632 016355 0 ustar lukas lukas Copyright (c) 2006, Matthias Friedrich
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the MusicBrainz project nor the names of the
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
python-musicbrainz2-0.7.4/AUTHORS.txt 0000644 0001750 0001750 00000002141 11347471065 016365 0 ustar lukas lukas Authors:
Matthias Friedrich
- Designed, implemented and documented this package.
Contributors:
Lukáš Lalinský
- Fixed several bugs and tested windows/ctypes compatibility.
- Patch for changing TRMs to PUIDs.
- Created and submitted Debian/Ubuntu packages.
- Lots of other things, including patch reviews.
Alastair Porter
- Added ISRC support.
- Added support for user collections.
- Added CD Stub submission support.
Nikki
- Tested compatibility with python-2.3 on Debian.
- Provided country/language/script lists.
Oliver Charles
- Added initial label support.
Philipp Wolfer
- Added folksonomy tagging support.
Peter Schnebel
- Added rating support.
John J. Jordan
- Added Release Groups support.
--
$Id: AUTHORS.txt 12708 2010-03-15 17:45:25Z matt $
python-musicbrainz2-0.7.4/setup.py 0000755 0001750 0001750 00000005674 11247217206 016224 0 ustar lukas lukas #! /usr/bin/env python
__revision__ = '$Id: setup.py 12028 2009-09-01 13:15:50Z matt $'
import os
import sys
import os.path
import unittest
from distutils.core import setup, Command
sys.path.insert(0, 'src')
import musicbrainz2
class TestCommand(Command):
description = 'run all test cases'
user_options = [ ]
def initialize_options(self): pass
def finalize_options(self): pass
def run(self):
files = [ ]
for f in os.listdir('test'):
elems = os.path.splitext(f)
if f != '__init__.py' and elems[1] == '.py':
files.append('test.' + elems[0])
tests = unittest.defaultTestLoader.loadTestsFromNames(files)
t = unittest.TextTestRunner()
t.run(tests)
class GenerateDocsCommand(Command):
description = 'generate the API documentation'
user_options = [ ]
def initialize_options(self): pass
def finalize_options(self): pass
def run(self):
from distutils.spawn import find_executable, spawn
bin = find_executable('epydoc')
if not bin:
print>>sys.stderr, 'error: epydoc not found'
sys.exit(1)
noPrivate = '--no-private'
verbose = '-v'
cmd = (bin, noPrivate, verbose,
os.path.join('src', 'musicbrainz2'))
spawn(cmd)
long_description = """\
An interface to the MusicBrainz XML web service
===============================================
python-musicbrainz2 provides simple, object oriented access to the
MusicBrainz web service. It is useful for applications like CD rippers,
taggers, media players, and other tools that need music metadata.
The MusicBrainz Project (see http://musicbrainz.org) collects music
metadata and is maintained by its large and constantly growing user
community.
Most of this package works on python-2.3 and later without further
dependencies. If you want to generate DiscIDs from an audio CD in the
drive, you need ctypes (already included in python-2.5) and libdiscid.
"""
trove_classifiers = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Database :: Front-Ends',
'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Ripping',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Text Processing :: Markup :: XML',
]
setup_args = {
'name': 'python-musicbrainz2',
'version': musicbrainz2.__version__,
'description': 'An interface to the MusicBrainz XML web service',
'long_description': long_description,
'author': 'Matthias Friedrich',
'author_email': 'matt@mafr.de',
'url': 'http://musicbrainz.org/products/python-musicbrainz2/',
'download_url': 'http://ftp.musicbrainz.org/pub/musicbrainz/python-musicbrainz2/',
'classifiers': trove_classifiers,
'license': 'BSD',
'packages': [ 'musicbrainz2', 'musicbrainz2.data' ],
'package_dir': { 'musicbrainz2': 'src/musicbrainz2' },
'scripts': [ 'bin/mb-submit-disc' ],
'cmdclass': { 'test': TestCommand, 'docs': GenerateDocsCommand },
}
setup(**setup_args)
# EOF
python-musicbrainz2-0.7.4/src/ 0000755 0001750 0001750 00000000000 11655171136 015266 5 ustar lukas lukas python-musicbrainz2-0.7.4/src/musicbrainz2/ 0000755 0001750 0001750 00000000000 11655171140 017671 5 ustar lukas lukas python-musicbrainz2-0.7.4/src/musicbrainz2/wsxml.py 0000644 0001750 0001750 00000133713 11247217206 021426 0 ustar lukas lukas """A parser for the Music Metadata XML Format (MMD).
This module contains L{MbXmlParser}, which parses the U{Music Metadata XML
Format (MMD) } returned by the
MusicBrainz webservice.
There are also DOM helper functions in this module used by the parser which
probably aren't useful to users.
"""
__revision__ = '$Id: wsxml.py 12028 2009-09-01 13:15:50Z matt $'
import re
import logging
import urlparse
import xml.dom.minidom
import xml.sax.saxutils as saxutils
from xml.parsers.expat import ExpatError
from xml.dom import DOMException
import musicbrainz2.utils as mbutils
import musicbrainz2.model as model
from musicbrainz2.model import NS_MMD_1, NS_REL_1, NS_EXT_1
__all__ = [
'DefaultFactory', 'Metadata', 'ParseError',
'MbXmlParser', 'MbXmlWriter',
'AbstractResult',
'ArtistResult', 'ReleaseResult', 'TrackResult', 'LabelResult',
'ReleaseGroupResult'
]
class DefaultFactory(object):
"""A factory to instantiate classes from the domain model.
This factory may be used to create objects from L{musicbrainz2.model}.
"""
def newArtist(self): return model.Artist()
def newRelease(self): return model.Release()
def newReleaseGroup(self): return model.ReleaseGroup()
def newTrack(self): return model.Track()
def newRelation(self): return model.Relation()
def newReleaseEvent(self): return model.ReleaseEvent()
def newDisc(self): return model.Disc()
def newArtistAlias(self): return model.ArtistAlias()
def newUser(self): return model.User()
def newLabel(self): return model.Label()
def newLabelAlias(self): return model.LabelAlias()
def newTag(self): return model.Tag()
def newRating(self): return model.Rating()
class ParseError(Exception):
"""Exception to be thrown if a parse error occurs.
The C{'msg'} attribute contains a printable error message, C{'reason'}
is the lower level exception that was raised.
"""
def __init__(self, msg='Parse Error', reason=None):
Exception.__init__(self)
self.msg = msg
self.reason = reason
def __str__(self):
return self.msg
class Metadata(object):
"""Represents a parsed Music Metadata XML document.
The Music Metadata XML format is very flexible and may contain a
diverse set of data (e.g. an artist, a release and a list of tracks),
but usually only a small subset is used (either an artist, a release
or a track, or a lists of objects from one class).
@see: L{MbXmlParser} for reading, and L{MbXmlWriter} for writing
Metadata objects
"""
def __init__(self):
self._artist = None
self._release = None
self._track = None
self._label = None
self._releaseGroup = None
self._artistResults = [ ]
self._artistResultsOffset = None
self._artistResultsCount = None
self._releaseResults = [ ]
self._releaseResultsOffset = None
self._releaseResultsCount = None
self._releaseGroupResults = [ ]
self._releaseGroupResultsOffset = None
self._releaseGroupResultsCount = None
self._trackResults = [ ]
self._trackResultsOffset = None
self._trackResultsCount = None
self._labelResults = [ ]
self._labelResultsOffset = None
self._labelResultsCount = None
self._tagList = [ ]
self._rating = None
self._userList = [ ]
def getArtist(self):
return self._artist
def setArtist(self, artist):
self._artist = artist
artist = property(getArtist, setArtist, doc='An Artist object.')
def getLabel(self):
return self._label
def setLabel(self, label):
self._label = label
label = property(getLabel, setLabel, doc='A Label object.')
def getRelease(self):
return self._release
def setRelease(self, release):
self._release = release
release = property(getRelease, setRelease, doc='A Release object.')
def getReleaseGroup(self):
return self._releaseGroup
def setReleaseGroup(self, releaseGroup):
self._releaseGroup = releaseGroup
releaseGroup = property(getReleaseGroup, setReleaseGroup)
def getTrack(self):
return self._track
def setTrack(self, track):
self._track = track
track = property(getTrack, setTrack, doc='A Track object.')
def getArtistResults(self):
"""Returns an artist result list.
@return: a list of L{ArtistResult} objects.
"""
return self._artistResults
artistResults = property(getArtistResults,
doc='A list of ArtistResult objects.')
def getArtistResultsOffset(self):
"""Returns the offset of the artist result list.
The offset is used for paging through the result list. It
is zero-based.
@return: an integer containing the offset, or None
@see: L{getArtistResults}, L{getArtistResultsCount}
"""
return self._artistResultsOffset
def setArtistResultsOffset(self, value):
"""Sets the offset of the artist result list.
@param value: an integer containing the offset, or None
@see: L{getArtistResultsOffset}
"""
self._artistResultsOffset = value
artistResultsOffset = property(
getArtistResultsOffset, setArtistResultsOffset,
doc='The offset of the artist results.')
def getArtistResultsCount(self):
"""Returns the total number of results available.
This may or may not match with the number of elements that
L{getArtistResults} returns. If the count is higher than
the list, it indicates that the list is incomplete.
@return: an integer containing the count, or None
@see: L{setArtistResultsCount}, L{getArtistResultsOffset}
"""
return self._artistResultsCount
def setArtistResultsCount(self, value):
"""Sets the total number of available results.
@param value: an integer containing the count, or None
@see: L{getArtistResults}, L{setArtistResultsOffset}
"""
self._artistResultsCount = value
artistResultsCount = property(
getArtistResultsCount, setArtistResultsCount,
doc='The total number of artists results.')
def getLabelResults(self):
"""Returns a label result list.
@return: a list of L{LabelResult} objects.
"""
return self._labelResults
labelResults = property(getLabelResults,
doc='A list of LabelResult objects')
def getLabelResultsOffset(self):
"""Returns the offset of the label result list.
The offset is used for paging through the result list. It
is zero-based.
@return: an integer containing the offset, or None
@see: L{getLabelResults}, L{getLabelResultsCount}
"""
return self._labelResultsOffset
def setLabelResultsOffset(self, value):
"""Sets the offset of the label result list.
@param value: an integer containing the offset, or None
@see: L{getLabelResultsOffset}
"""
self._labelResultsOffset = value
labelResultsOffset = property(
getLabelResultsOffset, setLabelResultsOffset,
doc='The offset of the label results.')
def getLabelResultsCount(self):
"""Returns the total number of results available.
This may or may not match with the number of elements that
L{getLabelResults} returns. If the count is higher than
the list, it indicates that the list is incomplete.
@return: an integer containing the count, or None
@see: L{setLabelResultsCount}, L{getLabelResultsOffset}
"""
return self._labelResultsCount
def setLabelResultsCount(self, value):
"""Sets the total number of available results.
@param value: an integer containing the count, or None
@see: L{getLabelResults}, L{setLabelResultsOffset}
"""
self._labelResultsCount = value
labelResultsCount = property(
getLabelResultsCount, setLabelResultsCount,
doc='The total number of label results.')
def getReleaseResults(self):
"""Returns a release result list.
@return: a list of L{ReleaseResult} objects.
"""
return self._releaseResults
releaseResults = property(getReleaseResults,
doc='A list of ReleaseResult objects.')
def getReleaseResultsOffset(self):
"""Returns the offset of the release result list.
The offset is used for paging through the result list. It
is zero-based.
@return: an integer containing the offset, or None
@see: L{getReleaseResults}, L{getReleaseResultsCount}
"""
return self._releaseResultsOffset
def setReleaseResultsOffset(self, value):
"""Sets the offset of the release result list.
@param value: an integer containing the offset, or None
@see: L{getReleaseResultsOffset}
"""
self._releaseResultsOffset = value
releaseResultsOffset = property(
getReleaseResultsOffset, setReleaseResultsOffset,
doc='The offset of the release results.')
def getReleaseResultsCount(self):
"""Returns the total number of results available.
This may or may not match with the number of elements that
L{getReleaseResults} returns. If the count is higher than
the list, it indicates that the list is incomplete.
@return: an integer containing the count, or None
@see: L{setReleaseResultsCount}, L{getReleaseResultsOffset}
"""
return self._releaseResultsCount
def setReleaseResultsCount(self, value):
"""Sets the total number of available results.
@param value: an integer containing the count, or None
@see: L{getReleaseResults}, L{setReleaseResultsOffset}
"""
self._releaseResultsCount = value
releaseResultsCount = property(
getReleaseResultsCount, setReleaseResultsCount,
doc='The total number of release results.')
def getReleaseGroupResults(self):
"""Returns a release group result list.
@return: a list of L{ReleaseGroupResult} objects.
"""
return self._releaseGroupResults
releaseGroupResults = property(getReleaseGroupResults,
doc = 'A list of ReleaseGroupResult objects.')
def getReleaseGroupResultsOffset(self):
"""Returns the offset of the release group result list.
The offset is used for paging through the result list. It
is zero-based.
@return: an integer containing the offset, or None.
@see: L{getReleaseGroupResults}, L{getReleaseGroupResultsCount}
"""
return self._releaseGroupResultsOffset
def setReleaseGroupResultsOffset(self, value):
"""Sets the offset of the release group result list.
@param value: an integer containing the offset, or None
@see: L{getReleaseGroupResultsOffset}
"""
self._releaseGroupResultsOffset = value
releaseGroupResultsOffset = property(
getReleaseGroupResultsOffset, setReleaseGroupResultsOffset,
doc='The offset of the release group results.')
def getReleaseGroupResultsCount(self):
"""Returns the total number of results available.
This may or may not match with the number of elements that
L{getReleaseGroupResults} returns. If the count is higher than
the list, it indicates that the list is incomplete.
@return: an integer containing the count, or None
@see: L{setReleaseGroupResultsCount}, L{getReleaseGroupResultsOffset}
"""
return self._releaseGroupResultsCount
def setReleaseGroupResultsCount(self, value):
"""Sets the total number of available results.
@param value: an integer containing the count, or None
@see: L{getReleaseGroupResults}, L{setReleaseGroupResultsOffset}
"""
self._releaseGroupResultsCount = value
releaseGroupResultsCount = property(
getReleaseGroupResultsCount, setReleaseGroupResultsCount,
doc='The total number of release group results.')
def getTrackResults(self):
"""Returns a track result list.
@return: a list of L{TrackResult} objects.
"""
return self._trackResults
trackResults = property(getTrackResults,
doc='A list of TrackResult objects.')
def getTrackResultsOffset(self):
"""Returns the offset of the track result list.
The offset is used for paging through the result list. It
is zero-based.
@return: an integer containing the offset, or None
@see: L{getTrackResults}, L{getTrackResultsCount}
"""
return self._trackResultsOffset
def setTrackResultsOffset(self, value):
"""Sets the offset of the track result list.
@param value: an integer containing the offset, or None
@see: L{getTrackResultsOffset}
"""
self._trackResultsOffset = value
trackResultsOffset = property(
getTrackResultsOffset, setTrackResultsOffset,
doc='The offset of the track results.')
def getTrackResultsCount(self):
"""Returns the total number of results available.
This may or may not match with the number of elements that
L{getTrackResults} returns. If the count is higher than
the list, it indicates that the list is incomplete.
@return: an integer containing the count, or None
@see: L{setTrackResultsCount}, L{getTrackResultsOffset}
"""
return self._trackResultsCount
def setTrackResultsCount(self, value):
"""Sets the total number of available results.
@param value: an integer containing the count, or None
@see: L{getTrackResults}, L{setTrackResultsOffset}
"""
self._trackResultsCount = value
trackResultsCount = property(
getTrackResultsCount, setTrackResultsCount,
doc='The total number of track results.')
def getTagList(self):
"""Returns a list of tags.
@return: a list of L{model.Tag} objects
"""
return self._tagList
tagResults = property(getTagList,
doc='A list of Tag objects.')
def getRating(self):
"""Returns the rating.
@return: rating object
"""
return self._rating
def setRating(self, value):
"""Sets the rating.
@param value: a L{model.Rating} object
"""
self._rating = value
rating = property(getRating, setRating, doc='A Rating object.')
# MusicBrainz extension to the schema
def getUserList(self):
"""Returns a list of users.
@return: a list of L{model.User} objects
@note: This is a MusicBrainz extension.
"""
return self._userList
userResults = property(getUserList,
doc='A list of User objects.')
class AbstractResult(object):
"""The abstract representation of a result.
A result is an instance of some kind (Artist, Release, ...)
associated with a score.
"""
def __init__(self, score):
self._score = score
def getScore(self):
"""Returns the result score.
The score indicates how good this result matches the search
parameters. The higher the value, the better the match.
@return: an int between 0 and 100 (both inclusive), or None
"""
return self._score
def setScore(self, score):
self._score = score
score = property(getScore, setScore, doc='The relevance score.')
class ArtistResult(AbstractResult):
"""Represents an artist result.
An ArtistResult consists of a I{score} and an artist. The score is a
number between 0 and 100, where a higher number indicates a better
match.
"""
def __init__(self, artist, score):
super(ArtistResult, self).__init__(score)
self._artist = artist
def getArtist(self):
"""Returns an Artist object.
@return: a L{musicbrainz2.model.Artist} object
"""
return self._artist
def setArtist(self, artist):
self._artist = artist
artist = property(getArtist, setArtist, doc='An Artist object.')
class ReleaseResult(AbstractResult):
"""Represents a release result.
A ReleaseResult consists of a I{score} and a release. The score is a
number between 0 and 100, where a higher number indicates a better
match.
"""
def __init__(self, release, score):
super(ReleaseResult, self).__init__(score)
self._release = release
def getRelease(self):
"""Returns a Release object.
@return: a L{musicbrainz2.model.Release} object
"""
return self._release
def setRelease(self, release):
self._release = release
release = property(getRelease, setRelease, doc='A Release object.')
class ReleaseGroupResult(AbstractResult):
"""Represents a release group result.
A ReleaseGroupResult consists of a I{score} and a release group. The
score is a number between 0 and 100, where a higher number indicates
a better match.
"""
def __init__(self, releaseGroup, score):
super(ReleaseGroupResult, self).__init__(score)
self._releaseGroup = releaseGroup
def getReleaseGroup(self):
"""Returns a ReleaseGroup object.
@return: a L{musicbrainz2.model.ReleaseGroup} object
"""
return self._releaseGroup
def setReleaseGroup(self, value):
self._releaseGroup = value
releaseGroup = property(getReleaseGroup, setReleaseGroup, doc='A ReleaseGroup object.')
class TrackResult(AbstractResult):
"""Represents a track result.
A TrackResult consists of a I{score} and a track. The score is a
number between 0 and 100, where a higher number indicates a better
match.
"""
def __init__(self, track, score):
super(TrackResult, self).__init__(score)
self._track = track
def getTrack(self):
"""Returns a Track object.
@return: a L{musicbrainz2.model.Track} object
"""
return self._track
def setTrack(self, track):
self._track = track
track = property(getTrack, setTrack, doc='A Track object.')
class LabelResult(AbstractResult):
"""Represents a label result.
An LabelResult consists of a I{score} and a label. The score is a
number between 0 and 100, where a higher number indicates a better
match.
"""
def __init__(self, label, score):
super(LabelResult, self).__init__(score)
self._label = label
def getLabel(self):
"""Returns a Label object.
@return: a L{musicbrainz2.model.Label} object
"""
return self._label
def setLabel(self, label):
self._label = label
label = property(getLabel, setLabel, doc='A Label object.')
class MbXmlParser(object):
"""A parser for the Music Metadata XML format.
This parser supports all basic features and extensions defined by
MusicBrainz, including unlimited document nesting. By default it
reads an XML document from a file-like object (stream) and returns
an object tree representing the document using classes from
L{musicbrainz2.model}.
The implementation tries to be as permissive as possible. Invalid
contents are skipped, but documents have to be well-formed and using
the correct namespace. In case of unrecoverable errors, a L{ParseError}
exception is raised.
@see: U{The Music Metadata XML Format
}
"""
def __init__(self, factory=DefaultFactory()):
"""Constructor.
The C{factory} parameter has be an instance of L{DefaultFactory}
or a subclass of it. It is used by L{parse} to obtain objects
from L{musicbrainz2.model} to build resulting object tree.
If you supply your own factory, you have to make sure all
returned objects have the same interface as their counterparts
from L{musicbrainz2.model}.
@param factory: an object factory
"""
self._log = logging.getLogger(str(self.__class__))
self._factory = factory
def parse(self, inStream):
"""Parses the MusicBrainz web service XML.
Returns a L{Metadata} object representing the parsed XML or
raises a L{ParseError} exception if the data was malformed.
The parser tries to be liberal and skips invalid content if
possible.
Note that an L{IOError} may be raised if there is a problem
reading C{inStream}.
@param inStream: a file-like object
@return: a L{Metadata} object (never None)
@raise ParseError: if the document is not valid
@raise IOError: if reading from the stream failed
"""
try:
doc = xml.dom.minidom.parse(inStream)
# Try to find the root element. If this isn't an mmd
# XML file or the namespace is wrong, this will fail.
elems = doc.getElementsByTagNameNS(NS_MMD_1, 'metadata')
if len(elems) != 0:
md = self._createMetadata(elems[0])
else:
msg = 'cannot find root element mmd:metadata'
self._log.debug('ParseError: ' + msg)
raise ParseError(msg)
doc.unlink()
return md
except ExpatError, e:
self._log.debug('ExpatError: ' + str(e))
raise ParseError(msg=str(e), reason=e)
except DOMException, e:
self._log.debug('DOMException: ' + str(e))
raise ParseError(msg=str(e), reason=e)
def _createMetadata(self, metadata):
md = Metadata()
for node in _getChildElements(metadata):
if _matches(node, 'artist'):
md.artist = self._createArtist(node)
elif _matches(node, 'release'):
md.release = self._createRelease(node)
elif _matches(node, 'release-group'):
md.releaseGroup = self._createReleaseGroup(node)
elif _matches(node, 'track'):
md.track = self._createTrack(node)
elif _matches(node, 'label'):
md.label = self._createLabel(node)
elif _matches(node, 'artist-list'):
(offset, count) = self._getListAttrs(node)
md.artistResultsOffset = offset
md.artistResultsCount = count
self._addArtistResults(node, md.getArtistResults())
elif _matches(node, 'release-list'):
(offset, count) = self._getListAttrs(node)
md.releaseResultsOffset = offset
md.releaseResultsCount = count
self._addReleaseResults(node, md.getReleaseResults())
elif _matches(node, 'release-group-list'):
(offset, count) = self._getListAttrs(node)
md.releaseGroupResultsOffset = offset
md.releaseGroupResultsCount = count
self._addReleaseGroupResults(node, md.getReleaseGroupResults())
elif _matches(node, 'track-list'):
(offset, count) = self._getListAttrs(node)
md.trackResultsOffset = offset
md.trackResultsCount = count
self._addTrackResults(node, md.getTrackResults())
elif _matches(node, 'label-list'):
(offset, count) = self._getListAttrs(node)
md.labelResultsOffset = offset
md.labelResultsCount = count
self._addLabelResults(node, md.getLabelResults())
elif _matches(node, 'tag-list'):
self._addTagsToList(node, md.getTagList())
elif _matches(node, 'user-list', NS_EXT_1):
self._addUsersToList(node, md.getUserList())
return md
def _addArtistResults(self, listNode, resultList):
for c in _getChildElements(listNode):
artist = self._createArtist(c)
score = _getIntAttr(c, 'score', 0, 100, ns=NS_EXT_1)
if artist is not None:
resultList.append(ArtistResult(artist, score))
def _addReleaseResults(self, listNode, resultList):
for c in _getChildElements(listNode):
release = self._createRelease(c)
score = _getIntAttr(c, 'score', 0, 100, ns=NS_EXT_1)
if release is not None:
resultList.append(ReleaseResult(release, score))
def _addReleaseGroupResults(self, listNode, resultList):
for c in _getChildElements(listNode):
releaseGroup = self._createReleaseGroup(c)
score = _getIntAttr(c, 'score', 0, 100, ns=NS_EXT_1)
if releaseGroup is not None:
resultList.append(ReleaseGroupResult(releaseGroup, score))
def _addTrackResults(self, listNode, resultList):
for c in _getChildElements(listNode):
track = self._createTrack(c)
score = _getIntAttr(c, 'score', 0, 100, ns=NS_EXT_1)
if track is not None:
resultList.append(TrackResult(track, score))
def _addLabelResults(self, listNode, resultList):
for c in _getChildElements(listNode):
label = self._createLabel(c)
score = _getIntAttr(c, 'score', 0, 100, ns=NS_EXT_1)
if label is not None:
resultList.append(LabelResult(label, score))
def _addReleasesToList(self, listNode, resultList):
self._addToList(listNode, resultList, self._createRelease)
def _addReleaseGroupsToList(self, listNode, resultList):
self._addToList(listNode, resultList, self._createReleaseGroup)
def _addTracksToList(self, listNode, resultList):
self._addToList(listNode, resultList, self._createTrack)
def _addUsersToList(self, listNode, resultList):
self._addToList(listNode, resultList, self._createUser)
def _addTagsToList(self, listNode, resultList):
self._addToList(listNode, resultList, self._createTag)
def _addTagsToEntity(self, listNode, entity):
for node in _getChildElements(listNode):
tag = self._createTag(node)
entity.addTag(tag)
def _addRatingToEntity(self, attrNode, entity):
rating = self._createRating(attrNode)
entity.setRating(rating)
def _addToList(self, listNode, resultList, creator):
for c in _getChildElements(listNode):
resultList.append(creator(c))
def _getListAttrs(self, listNode):
offset = _getIntAttr(listNode, 'offset')
count = _getIntAttr(listNode, 'count')
return (offset, count)
def _createArtist(self, artistNode):
artist = self._factory.newArtist()
artist.setId(_getIdAttr(artistNode, 'id', 'artist'))
artist.setType(_getUriAttr(artistNode, 'type'))
for node in _getChildElements(artistNode):
if _matches(node, 'name'):
artist.setName(_getText(node))
elif _matches(node, 'sort-name'):
artist.setSortName(_getText(node))
elif _matches(node, 'disambiguation'):
artist.setDisambiguation(_getText(node))
elif _matches(node, 'life-span'):
artist.setBeginDate(_getDateAttr(node, 'begin'))
artist.setEndDate(_getDateAttr(node, 'end'))
elif _matches(node, 'alias-list'):
self._addArtistAliases(node, artist)
elif _matches(node, 'release-list'):
(offset, count) = self._getListAttrs(node)
artist.setReleasesOffset(offset)
artist.setReleasesCount(count)
self._addReleasesToList(node, artist.getReleases())
elif _matches(node, 'release-group-list'):
(offset, count) = self._getListAttrs(node)
artist.setReleaseGroupsOffset(offset)
artist.setReleaseGroupsCount(count)
self._addReleaseGroupsToList(node, artist.getReleaseGroups())
elif _matches(node, 'relation-list'):
self._addRelationsToEntity(node, artist)
elif _matches(node, 'tag-list'):
self._addTagsToEntity(node, artist)
elif _matches(node, 'rating'):
self._addRatingToEntity(node, artist)
return artist
def _createLabel(self, labelNode):
label = self._factory.newLabel()
label.setId(_getIdAttr(labelNode, 'id', 'label'))
label.setType(_getUriAttr(labelNode, 'type'))
for node in _getChildElements(labelNode):
if _matches(node, 'name'):
label.setName(_getText(node))
if _matches(node, 'sort-name'):
label.setSortName(_getText(node))
elif _matches(node, 'disambiguation'):
label.setDisambiguation(_getText(node))
elif _matches(node, 'label-code'):
label.setCode(_getText(node))
elif _matches(node, 'country'):
country = _getText(node, '^[A-Z]{2}$')
label.setCountry(country)
elif _matches(node, 'life-span'):
label.setBeginDate(_getDateAttr(node, 'begin'))
label.setEndDate(_getDateAttr(node, 'end'))
elif _matches(node, 'alias-list'):
self._addLabelAliases(node, label)
elif _matches(node, 'tag-list'):
self._addTagsToEntity(node, label)
elif _matches(node, 'rating'):
self._addRatingToEntity(node, label)
return label
def _createRelease(self, releaseNode):
release = self._factory.newRelease()
release.setId(_getIdAttr(releaseNode, 'id', 'release'))
for t in _getUriListAttr(releaseNode, 'type'):
release.addType(t)
for node in _getChildElements(releaseNode):
if _matches(node, 'title'):
release.setTitle(_getText(node))
elif _matches(node, 'text-representation'):
lang = _getAttr(node, 'language', '^[A-Z]{3}$')
release.setTextLanguage(lang)
script = _getAttr(node, 'script', '^[A-Z][a-z]{3}$')
release.setTextScript(script)
elif _matches(node, 'asin'):
release.setAsin(_getText(node))
elif _matches(node, 'artist'):
release.setArtist(self._createArtist(node))
elif _matches(node, 'release-event-list'):
self._addReleaseEvents(node, release)
elif _matches(node, 'release-group'):
release.setReleaseGroup(self._createReleaseGroup(node))
elif _matches(node, 'disc-list'):
self._addDiscs(node, release)
elif _matches(node, 'track-list'):
(offset, count) = self._getListAttrs(node)
release.setTracksOffset(offset)
release.setTracksCount(count)
self._addTracksToList(node, release.getTracks())
elif _matches(node, 'relation-list'):
self._addRelationsToEntity(node, release)
elif _matches(node, 'tag-list'):
self._addTagsToEntity(node, release)
elif _matches(node, 'rating'):
self._addRatingToEntity(node, release)
return release
def _createReleaseGroup(self, node):
rg = self._factory.newReleaseGroup()
rg.setId(_getIdAttr(node, 'id', 'release-group'))
rg.setType(_getUriAttr(node, 'type'))
for child in _getChildElements(node):
if _matches(child, 'title'):
rg.setTitle(_getText(child))
elif _matches(child, 'artist'):
rg.setArtist(self._createArtist(child))
elif _matches(child, 'release-list'):
(offset, count) = self._getListAttrs(child)
rg.setReleasesOffset(offset)
rg.setReleasesCount(count)
self._addReleasesToList(child, rg.getReleases())
return rg
def _addReleaseEvents(self, releaseListNode, release):
for node in _getChildElements(releaseListNode):
if _matches(node, 'event'):
country = _getAttr(node, 'country', '^[A-Z]{2}$')
date = _getDateAttr(node, 'date')
catalogNumber = _getAttr(node, 'catalog-number')
barcode = _getAttr(node, 'barcode')
format = _getUriAttr(node, 'format')
# The date attribute is mandatory. If it isn't present,
# we don't add anything from this release event.
if date is not None:
event = self._factory.newReleaseEvent()
event.setCountry(country)
event.setDate(date)
event.setCatalogNumber(catalogNumber)
event.setBarcode(barcode)
event.setFormat(format)
for subNode in _getChildElements(node):
if _matches(subNode, 'label'):
event.setLabel(self._createLabel(subNode))
release.addReleaseEvent(event)
def _addDiscs(self, discIdListNode, release):
for node in _getChildElements(discIdListNode):
if _matches(node, 'disc') and node.hasAttribute('id'):
d = self._factory.newDisc()
d.setId(node.getAttribute('id'))
d.setSectors(_getIntAttr(node, 'sectors', 0))
release.addDisc(d)
def _addArtistAliases(self, aliasListNode, artist):
for node in _getChildElements(aliasListNode):
if _matches(node, 'alias'):
alias = self._factory.newArtistAlias()
self._initializeAlias(alias, node)
artist.addAlias(alias)
def _addLabelAliases(self, aliasListNode, label):
for node in _getChildElements(aliasListNode):
if _matches(node, 'alias'):
alias = self._factory.newLabelAlias()
self._initializeAlias(alias, node)
label.addAlias(alias)
def _initializeAlias(self, alias, node):
alias.setValue(_getText(node))
alias.setType(_getUriAttr(node, 'type'))
alias.setScript(_getAttr(node, 'script',
'^[A-Z][a-z]{3}$'))
def _createTrack(self, trackNode):
track = self._factory.newTrack()
track.setId(_getIdAttr(trackNode, 'id', 'track'))
for node in _getChildElements(trackNode):
if _matches(node, 'title'):
track.setTitle(_getText(node))
elif _matches(node, 'artist'):
track.setArtist(self._createArtist(node))
elif _matches(node, 'duration'):
track.setDuration(_getPositiveIntText(node))
elif _matches(node, 'release-list'):
self._addReleasesToList(node, track.getReleases())
elif _matches(node, 'puid-list'):
self._addPuids(node, track)
elif _matches(node, 'isrc-list'):
self._addISRCs(node, track)
elif _matches(node, 'relation-list'):
self._addRelationsToEntity(node, track)
elif _matches(node, 'tag-list'):
self._addTagsToEntity(node, track)
elif _matches(node, 'rating'):
self._addRatingToEntity(node, track)
return track
# MusicBrainz extension
def _createUser(self, userNode):
user = self._factory.newUser()
for t in _getUriListAttr(userNode, 'type', NS_EXT_1):
user.addType(t)
for node in _getChildElements(userNode):
if _matches(node, 'name'):
user.setName(_getText(node))
elif _matches(node, 'nag', NS_EXT_1):
user.setShowNag(_getBooleanAttr(node, 'show'))
return user
def _createRating(self, ratingNode):
rating = self._factory.newRating()
rating.value = _getText(ratingNode)
rating.count = _getIntAttr(ratingNode, 'votes-count')
return rating
def _createTag(self, tagNode):
tag = self._factory.newTag()
tag.value = _getText(tagNode)
tag.count = _getIntAttr(tagNode, 'count')
return tag
def _addPuids(self, puidListNode, track):
for node in _getChildElements(puidListNode):
if _matches(node, 'puid') and node.hasAttribute('id'):
track.addPuid(node.getAttribute('id'))
def _addISRCs(self, isrcListNode, track):
for node in _getChildElements(isrcListNode):
if _matches(node, 'isrc') and node.hasAttribute('id'):
track.addISRC(node.getAttribute('id'))
def _addRelationsToEntity(self, relationListNode, entity):
targetType = _getUriAttr(relationListNode, 'target-type', NS_REL_1)
if targetType is None:
return
for node in _getChildElements(relationListNode):
if _matches(node, 'relation'):
rel = self._createRelation(node, targetType)
if rel is not None:
entity.addRelation(rel)
def _createRelation(self, relationNode, targetType):
relation = self._factory.newRelation()
relation.setType(_getUriAttr(relationNode, 'type', NS_REL_1))
relation.setTargetType(targetType)
resType = _getResourceType(targetType)
relation.setTargetId(_getIdAttr(relationNode, 'target', resType))
if relation.getType() is None \
or relation.getTargetType() is None \
or relation.getTargetId() is None:
return None
relation.setDirection(_getDirectionAttr(relationNode, 'direction'))
relation.setBeginDate(_getDateAttr(relationNode, 'begin'))
relation.setEndDate(_getDateAttr(relationNode, 'end'))
for a in _getUriListAttr(relationNode, 'attributes', NS_REL_1):
relation.addAttribute(a)
target = None
children = _getChildElements(relationNode)
if len(children) > 0:
node = children[0]
if _matches(node, 'artist'):
target = self._createArtist(node)
elif _matches(node, 'release'):
target = self._createRelease(node)
elif _matches(node, 'track'):
target = self._createTrack(node)
relation.setTarget(target)
return relation
#
# XML output
#
class _XmlWriter(object):
def __init__(self, outStream, indentAmount=' ', newline="\n"):
self._out = outStream
self._indentAmount = indentAmount
self._stack = [ ]
self._newline = newline
def prolog(self, encoding='UTF-8', version='1.0'):
pi = '' % (version, encoding)
self._out.write(pi + self._newline)
def start(self, name, attrs={ }):
indent = self._getIndention()
self._stack.append(name)
self._out.write(indent + self._makeTag(name, attrs) + self._newline)
def end(self):
name = self._stack.pop()
indent = self._getIndention()
self._out.write('%s%s>\n' % (indent, name))
def elem(self, name, value, attrs={ }):
# delete attributes with an unset value
for (k, v) in attrs.items():
if v is None or v == '':
del attrs[k]
if value is None or value == '':
if len(attrs) == 0:
return
self._out.write(self._getIndention())
self._out.write(self._makeTag(name, attrs, True) + '\n')
else:
escValue = saxutils.escape(value or '')
self._out.write(self._getIndention())
self._out.write(self._makeTag(name, attrs))
self._out.write(escValue)
self._out.write('%s>\n' % name)
def _getIndention(self):
return self._indentAmount * len(self._stack)
def _makeTag(self, name, attrs={ }, close=False):
ret = '<' + name
for (k, v) in attrs.iteritems():
if v is not None:
v = saxutils.quoteattr(str(v))
ret += ' %s=%s' % (k, v)
if close:
return ret + '/>'
else:
return ret + '>'
class MbXmlWriter(object):
"""Write XML in the Music Metadata XML format."""
def __init__(self, indentAmount=' ', newline="\n"):
"""Constructor.
@param indentAmount: the amount of whitespace to use per level
"""
self._indentAmount = indentAmount
self._newline = newline
def write(self, outStream, metadata):
"""Writes the XML representation of a Metadata object to a file.
@param outStream: an open file-like object
@param metadata: a L{Metadata} object
"""
xml = _XmlWriter(outStream, self._indentAmount, self._newline)
xml.prolog()
xml.start('metadata', {
'xmlns': NS_MMD_1,
'xmlns:ext': NS_EXT_1,
})
self._writeArtist(xml, metadata.getArtist())
self._writeRelease(xml, metadata.getRelease())
self._writeReleaseGroup(xml, metadata.getReleaseGroup())
self._writeTrack(xml, metadata.getTrack())
self._writeLabel(xml, metadata.getLabel())
if len(metadata.getArtistResults()) > 0:
xml.start('artist-list', {
'offset': metadata.artistResultsOffset,
'count': metadata.artistResultsCount,
})
for result in metadata.getArtistResults():
self._writeArtist(xml, result.getArtist(),
result.getScore())
xml.end()
if len(metadata.getReleaseResults()) > 0:
xml.start('release-list', {
'offset': metadata.releaseResultsOffset,
'count': metadata.releaseResultsCount,
})
for result in metadata.getReleaseResults():
self._writeRelease(xml, result.getRelease(),
result.getScore())
xml.end()
if len(metadata.getReleaseGroupResults()) > 0:
xml.start('release-group-list', {
'offset': metadata.releaseGroupResultsOffset,
'count': metadata.releaseGroupResultsCount
})
for result in metadata.getReleaseGroupResults():
self._writeReleaseGroup(xml, result.getReleaseGroup(),
result.getScore())
xml.end()
if len(metadata.getTrackResults()) > 0:
xml.start('track-list', {
'offset': metadata.trackResultsOffset,
'count': metadata.trackResultsCount,
})
for result in metadata.getTrackResults():
self._writeTrack(xml, result.getTrack(),
result.getScore())
xml.end()
if len(metadata.getLabelResults()) > 0:
xml.start('label-list', {
'offset': metadata.labelResultsOffset,
'count': metadata.labelResultsCount,
})
for result in metadata.getLabelResults():
self._writeLabel(xml, result.getLabel(),
result.getScore())
xml.end()
xml.end()
def _writeArtist(self, xml, artist, score=None):
if artist is None:
return
xml.start('artist', {
'id': mbutils.extractUuid(artist.getId()),
'type': mbutils.extractFragment(artist.getType()),
'ext:score': score,
})
xml.elem('name', artist.getName())
xml.elem('sort-name', artist.getSortName())
xml.elem('disambiguation', artist.getDisambiguation())
xml.elem('life-span', None, {
'begin': artist.getBeginDate(),
'end': artist.getEndDate(),
})
if len(artist.getAliases()) > 0:
xml.start('alias-list')
for alias in artist.getAliases():
xml.elem('alias', alias.getValue(), {
'type': alias.getType(),
'script': alias.getScript(),
})
xml.end()
if len(artist.getReleases()) > 0:
xml.start('release-list')
for release in artist.getReleases():
self._writeRelease(xml, release)
xml.end()
if len(artist.getReleaseGroups()) > 0:
xml.start('release-group-list')
for releaseGroup in artist.getReleaseGroups():
self._writeReleaseGroup(xml, releaseGroup)
xml.end()
self._writeRelationList(xml, artist)
# TODO: extensions
xml.end()
def _writeRelease(self, xml, release, score=None):
if release is None:
return
types = [mbutils.extractFragment(t) for t in release.getTypes()]
typesStr = None
if len(types) > 0:
typesStr = ' '.join(types)
xml.start('release', {
'id': mbutils.extractUuid(release.getId()),
'type': typesStr,
'ext:score': score,
})
xml.elem('title', release.getTitle())
xml.elem('text-representation', None, {
'language': release.getTextLanguage(),
'script': release.getTextScript()
})
xml.elem('asin', release.getAsin())
self._writeArtist(xml, release.getArtist())
self._writeReleaseGroup(xml, release.getReleaseGroup())
if len(release.getReleaseEvents()) > 0:
xml.start('release-event-list')
for event in release.getReleaseEvents():
self._writeReleaseEvent(xml, event)
xml.end()
if len(release.getDiscs()) > 0:
xml.start('disc-list')
for disc in release.getDiscs():
xml.elem('disc', None, { 'id': disc.getId() })
xml.end()
if len(release.getTracks()) > 0:
# TODO: count attribute
xml.start('track-list', {
'offset': release.getTracksOffset()
})
for track in release.getTracks():
self._writeTrack(xml, track)
xml.end()
self._writeRelationList(xml, release)
# TODO: extensions
xml.end()
def _writeReleaseGroup(self, xml, rg, score = None):
if rg is None:
return
xml.start('release-group', {
'id': mbutils.extractUuid(rg.getId()),
'type': mbutils.extractFragment(rg.getType()),
'ext:score': score,
})
xml.elem('title', rg.getTitle())
self._writeArtist(xml, rg.getArtist())
if len(rg.getReleases()) > 0:
xml.start('release-list')
for rel in rg.getReleases():
self._writeRelease(xml, rel)
xml.end()
xml.end()
def _writeReleaseEvent(self, xml, event):
xml.start('event', {
'country': event.getCountry(),
'date': event.getDate(),
'catalog-number': event.getCatalogNumber(),
'barcode': event.getBarcode(),
'format': event.getFormat()
})
self._writeLabel(xml, event.getLabel())
xml.end()
def _writeTrack(self, xml, track, score=None):
if track is None:
return
xml.start('track', {
'id': mbutils.extractUuid(track.getId()),
'ext:score': score,
})
xml.elem('title', track.getTitle())
xml.elem('duration', str(track.getDuration()))
self._writeArtist(xml, track.getArtist())
if len(track.getReleases()) > 0:
# TODO: offset + count
xml.start('release-list')
for release in track.getReleases():
self._writeRelease(xml, release)
xml.end()
if len(track.getPuids()) > 0:
xml.start('puid-list')
for puid in track.getPuids():
xml.elem('puid', None, { 'id': puid })
xml.end()
self._writeRelationList(xml, track)
# TODO: extensions
xml.end()
def _writeLabel(self, xml, label, score=None):
if label is None:
return
xml.start('label', {
'id': mbutils.extractUuid(label.getId()),
'type': mbutils.extractFragment(label.getType()),
'ext:score': score,
})
xml.elem('name', label.getName())
xml.elem('sort-name', label.getSortName())
xml.elem('disambiguation', label.getDisambiguation())
xml.elem('life-span', None, {
'begin': label.getBeginDate(),
'end': label.getEndDate(),
})
if len(label.getAliases()) > 0:
xml.start('alias-list')
for alias in label.getAliases():
xml.elem('alias', alias.getValue(), {
'type': alias.getType(),
'script': alias.getScript(),
})
xml.end()
# TODO: releases, artists
self._writeRelationList(xml, label)
# TODO: extensions
xml.end()
def _writeRelationList(self, xml, entity):
for tt in entity.getRelationTargetTypes():
xml.start('relation-list', {
'target-type': mbutils.extractFragment(tt),
})
for rel in entity.getRelations(targetType=tt):
self._writeRelation(xml, rel, tt)
xml.end()
def _writeRelation(self, xml, rel, targetType):
relAttrs = ' '.join([mbutils.extractFragment(a)
for a in rel.getAttributes()])
if relAttrs == '':
relAttrs = None
attrs = {
'type': mbutils.extractFragment(rel.getType()),
'target': rel.getTargetId(),
'direction': rel.getDirection(),
'begin': rel.getBeginDate(),
'end': rel.getBeginDate(),
'attributes': relAttrs,
}
if rel.getTarget() is None:
xml.elem('relation', None, attrs)
else:
xml.start('relation', attrs)
if targetType == NS_REL_1 + 'Artist':
self._writeArtist(xml, rel.getTarget())
elif targetType == NS_REL_1 + 'Release':
self._writeRelease(xml, rel.getTarget())
elif targetType == NS_REL_1 + 'Track':
self._writeTrack(xml, rel.getTarget())
xml.end()
#
# DOM Utilities
#
def _matches(node, name, namespace=NS_MMD_1):
"""Checks if an xml.dom.Node and a given name and namespace match."""
if node.localName == name and node.namespaceURI == namespace:
return True
else:
return False
def _getChildElements(parentNode):
"""Returns all direct child elements of the given xml.dom.Node."""
children = [ ]
for node in parentNode.childNodes:
if node.nodeType == node.ELEMENT_NODE:
children.append(node)
return children
def _getText(element, regex=None, default=None):
"""Returns the text content of the given xml.dom.Element.
This function simply fetches all contained text nodes, so the element
should not contain child elements.
"""
res = ''
for node in element.childNodes:
if node.nodeType == node.TEXT_NODE:
res += node.data
if regex is None or re.match(regex, res):
return res
else:
return default
def _getPositiveIntText(element):
"""Returns the text content of the given xml.dom.Element as an int."""
res = _getText(element)
if res is None:
return None
try:
return int(res)
except ValueError:
return None
def _getAttr(element, attrName, regex=None, default=None, ns=None):
"""Returns an attribute of the given element.
If there is no attribute with that name or the attribute doesn't
match the regular expression, default is returned.
"""
if element.hasAttributeNS(ns, attrName):
content = element.getAttributeNS(ns, attrName)
if regex is None or re.match(regex, content):
return content
else:
return default
else:
return default
def _getDateAttr(element, attrName):
"""Gets an incomplete date from an attribute."""
return _getAttr(element, attrName, '^\d+(-\d\d)?(-\d\d)?$')
def _getIdAttr(element, attrName, typeName):
"""Gets an ID from an attribute and turns it into an absolute URI."""
value = _getAttr(element, attrName)
return _makeAbsoluteUri('http://musicbrainz.org/' + typeName + '/', value)
def _getIntAttr(element, attrName, min=0, max=None, ns=None):
"""Gets an int from an attribute, or None."""
try:
val = int(_getAttr(element, attrName, ns=ns))
if max is None:
max = val
if min <= val <= max:
return val
else:
return None
except ValueError:
return None # raised if conversion to int fails
except TypeError:
return None # raised if no such attribute exists
def _getUriListAttr(element, attrName, prefix=NS_MMD_1):
"""Gets a list of URIs from an attribute."""
if not element.hasAttribute(attrName):
return [ ]
f = lambda x: x != ''
uris = filter(f, re.split('\s+', element.getAttribute(attrName)))
m = lambda x: _makeAbsoluteUri(prefix, x)
uris = map(m, uris)
return uris
def _getUriAttr(element, attrName, prefix=NS_MMD_1):
"""Gets a URI from an attribute.
This also works for space-separated URI lists. In this case, the
first URI is returned.
"""
uris = _getUriListAttr(element, attrName, prefix)
if len(uris) > 0:
return uris[0]
else:
return None
def _getBooleanAttr(element, attrName):
"""Gets a boolean value from an attribute."""
value = _getAttr(element, attrName)
if value == 'true':
return True
elif value == 'false':
return False
else:
return None
def _getDirectionAttr(element, attrName):
"""Gets the Relation reading direction from an attribute."""
regex = '^\s*(' + '|'.join((
model.Relation.DIR_FORWARD,
model.Relation.DIR_BACKWARD)) + ')\s*$'
return _getAttr(element, 'direction', regex, model.Relation.DIR_NONE)
def _makeAbsoluteUri(prefix, uriStr):
"""Creates an absolute URI adding prefix, if necessary."""
if uriStr is None:
return None
(scheme, netloc, path, params, query, frag) = urlparse.urlparse(uriStr)
if scheme == '' and netloc == '':
return prefix + uriStr
else:
return uriStr
def _getResourceType(uri):
"""Gets the resource type from a URI.
The resource type is the basename of the URI's path.
"""
m = re.match('^' + NS_REL_1 + '(.*)$', uri)
if m:
return m.group(1).lower()
else:
return None
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/disc.py 0000644 0001750 0001750 00000015322 11243756477 021207 0 ustar lukas lukas """Utilities for working with Audio CDs.
This module contains utilities for working with Audio CDs.
The functions in this module need both a working ctypes package (already
included in python-2.5) and an installed libdiscid. If you don't have
libdiscid, it can't be loaded, or your platform isn't supported by either
ctypes or this module, a C{NotImplementedError} is raised when using the
L{readDisc()} function.
@author: Matthias Friedrich
"""
__revision__ = '$Id: disc.py 11987 2009-08-22 11:57:51Z matt $'
import sys
import urllib
import urlparse
import ctypes
import ctypes.util
from musicbrainz2.model import Disc
__all__ = [ 'DiscError', 'readDisc', 'getSubmissionUrl' ]
class DiscError(IOError):
"""The Audio CD could not be read.
This may be simply because no disc was in the drive, the device name
was wrong or the disc can't be read. Reading errors can occur in case
of a damaged disc or a copy protection mechanism, for example.
"""
pass
def _openLibrary():
"""Tries to open libdiscid.
@return: a C{ctypes.CDLL} object, representing the opened library
@raise NotImplementedError: if the library can't be opened
"""
# This only works for ctypes >= 0.9.9.3. Any libdiscid is found,
# no matter how it's called on this platform.
try:
if hasattr(ctypes.cdll, 'find'):
libDiscId = ctypes.cdll.find('discid')
_setPrototypes(libDiscId)
return libDiscId
except OSError, e:
raise NotImplementedError('Error opening library: ' + str(e))
# Try to find the library using ctypes.util
libName = ctypes.util.find_library('discid')
if libName != None:
try:
libDiscId = ctypes.cdll.LoadLibrary(libName)
_setPrototypes(libDiscId)
return libDiscId
except OSError, e:
raise NotImplementedError('Error opening library: ' +
str(e))
# For compatibility with ctypes < 0.9.9.3 try to figure out the library
# name without the help of ctypes. We use cdll.LoadLibrary() below,
# which isn't available for ctypes == 0.9.9.3.
#
if sys.platform == 'linux2':
libName = 'libdiscid.so.0'
elif sys.platform == 'darwin':
libName = 'libdiscid.0.dylib'
elif sys.platform == 'win32':
libName = 'discid.dll'
else:
# This should at least work for Un*x-style operating systems
libName = 'libdiscid.so.0'
try:
libDiscId = ctypes.cdll.LoadLibrary(libName)
_setPrototypes(libDiscId)
return libDiscId
except OSError, e:
raise NotImplementedError('Error opening library: ' + str(e))
assert False # not reached
def _setPrototypes(libDiscId):
ct = ctypes
libDiscId.discid_new.argtypes = ( )
libDiscId.discid_new.restype = ct.c_void_p
libDiscId.discid_free.argtypes = (ct.c_void_p, )
libDiscId.discid_read.argtypes = (ct.c_void_p, ct.c_char_p)
libDiscId.discid_get_error_msg.argtypes = (ct.c_void_p, )
libDiscId.discid_get_error_msg.restype = ct.c_char_p
libDiscId.discid_get_id.argtypes = (ct.c_void_p, )
libDiscId.discid_get_id.restype = ct.c_char_p
libDiscId.discid_get_first_track_num.argtypes = (ct.c_void_p, )
libDiscId.discid_get_first_track_num.restype = ct.c_int
libDiscId.discid_get_last_track_num.argtypes = (ct.c_void_p, )
libDiscId.discid_get_last_track_num.restype = ct.c_int
libDiscId.discid_get_sectors.argtypes = (ct.c_void_p, )
libDiscId.discid_get_sectors.restype = ct.c_int
libDiscId.discid_get_track_offset.argtypes = (ct.c_void_p, ct.c_int)
libDiscId.discid_get_track_offset.restype = ct.c_int
libDiscId.discid_get_track_length.argtypes = (ct.c_void_p, ct.c_int)
libDiscId.discid_get_track_length.restype = ct.c_int
def getSubmissionUrl(disc, host='mm.musicbrainz.org', port=80):
"""Returns a URL for adding a disc to the MusicBrainz database.
A fully initialized L{musicbrainz2.model.Disc} object is needed, as
returned by L{readDisc}. A disc object returned by the web service
doesn't provide the necessary information.
Note that the created URL is intended for interactive use and points
to the MusicBrainz disc submission wizard by default. This method
just returns a URL, no network connection is needed. The disc drive
isn't used.
@param disc: a fully initialized L{musicbrainz2.model.Disc} object
@param host: a string containing a host name
@param port: an integer containing a port number
@return: a string containing the submission URL
@see: L{readDisc}
"""
assert isinstance(disc, Disc), 'musicbrainz2.model.Disc expected'
discid = disc.getId()
first = disc.getFirstTrackNum()
last = disc.getLastTrackNum()
sectors = disc.getSectors()
assert None not in (discid, first, last, sectors)
tracks = last - first + 1
toc = "%d %d %d " % (first, last, sectors)
toc = toc + ' '.join( map(lambda x: str(x[0]), disc.getTracks()) )
query = urllib.urlencode({ 'id': discid, 'toc': toc, 'tracks': tracks })
if port == 80:
netloc = host
else:
netloc = host + ':' + str(port)
url = ('http', netloc, '/bare/cdlookup.html', '', query, '')
return urlparse.urlunparse(url)
def readDisc(deviceName=None):
"""Reads an Audio CD in the disc drive.
This reads a CD's table of contents (TOC) and calculates the MusicBrainz
DiscID, which is a 28 character ASCII string. This DiscID can be used
to retrieve a list of matching releases from the web service (see
L{musicbrainz2.webservice.Query}).
Note that an Audio CD has to be in drive for this to work. The
C{deviceName} argument may be used to set the device. The default
depends on the operating system (on linux, it's C{'/dev/cdrom'}).
No network connection is needed for this function.
If the device doesn't exist or there's no valid Audio CD in the drive,
a L{DiscError} exception is raised.
@param deviceName: a string containing the CD drive's device name
@return: a L{musicbrainz2.model.Disc} object
@raise DiscError: if there was a problem reading the disc
@raise NotImplementedError: if DiscID generation isn't supported
"""
libDiscId = _openLibrary()
handle = libDiscId.discid_new()
assert handle != 0, "libdiscid: discid_new() returned NULL"
# Access the CD drive. This also works if deviceName is None because
# ctypes passes a NULL pointer in this case.
#
res = libDiscId.discid_read(handle, deviceName)
if res == 0:
raise DiscError(libDiscId.discid_get_error_msg(handle))
# Now extract the data from the result.
#
disc = Disc()
disc.setId( libDiscId.discid_get_id(handle) )
firstTrackNum = libDiscId.discid_get_first_track_num(handle)
lastTrackNum = libDiscId.discid_get_last_track_num(handle)
disc.setSectors(libDiscId.discid_get_sectors(handle))
for i in range(firstTrackNum, lastTrackNum+1):
trackOffset = libDiscId.discid_get_track_offset(handle, i)
trackSectors = libDiscId.discid_get_track_length(handle, i)
disc.addTrack( (trackOffset, trackSectors) )
disc.setFirstTrackNum(firstTrackNum)
disc.setLastTrackNum(lastTrackNum)
libDiscId.discid_free(handle)
return disc
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/__init__.py 0000644 0001750 0001750 00000001473 11655170705 022015 0 ustar lukas lukas """A collection of classes for MusicBrainz.
To get started quickly, have a look at L{webservice.Query} and the examples
there. The source distribution also contains example code you might find
interesting.
This package contains the following modules:
1. L{model}: The MusicBrainz domain model, containing classes like
L{Artist }, L{Release }, or
L{Track }
2. L{webservice}: An interface to the MusicBrainz XML web service.
3. L{wsxml}: A parser for the web service XML format (MMD).
4. L{disc}: Functions for creating and submitting DiscIDs.
5. L{utils}: Utilities for working with URIs and other commonly needed tools.
@author: Matthias Friedrich
"""
__revision__ = '$Id: __init__.py 13329 2011-11-05 08:20:21Z luks $'
__version__ = '0.7.4'
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/data/ 0000755 0001750 0001750 00000000000 11655171137 020610 5 ustar lukas lukas python-musicbrainz2-0.7.4/src/musicbrainz2/data/releasetypenames.py 0000644 0001750 0001750 00000002077 10541352746 024536 0 ustar lukas lukas # -*- coding: utf-8 -*-
__revision__ = '$Id: releasetypenames.py 8728 2006-12-17 23:42:30Z luks $'
releaseTypeNames = {
u'http://musicbrainz.org/ns/mmd-1.0#None': u'None',
u'http://musicbrainz.org/ns/mmd-1.0#Album': u'Album',
u'http://musicbrainz.org/ns/mmd-1.0#Single': u'Single',
u'http://musicbrainz.org/ns/mmd-1.0#EP': u'EP',
u'http://musicbrainz.org/ns/mmd-1.0#Compilation': u'Compilation',
u'http://musicbrainz.org/ns/mmd-1.0#Soundtrack': u'Soundtrack',
u'http://musicbrainz.org/ns/mmd-1.0#Spokenword': u'Spokenword',
u'http://musicbrainz.org/ns/mmd-1.0#Interview': u'Interview',
u'http://musicbrainz.org/ns/mmd-1.0#Audiobook': u'Audiobook',
u'http://musicbrainz.org/ns/mmd-1.0#Live': u'Live',
u'http://musicbrainz.org/ns/mmd-1.0#Remix': u'Remix',
u'http://musicbrainz.org/ns/mmd-1.0#Other': u'Other',
u'http://musicbrainz.org/ns/mmd-1.0#Official': u'Official',
u'http://musicbrainz.org/ns/mmd-1.0#Promotion': u'Promotion',
u'http://musicbrainz.org/ns/mmd-1.0#Bootleg': u'Bootleg',
u'http://musicbrainz.org/ns/mmd-1.0#Pseudo-Release': u'Pseudo-Release',
}
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/data/countrynames.py 0000644 0001750 0001750 00000013666 10425115667 023725 0 ustar lukas lukas # -*- coding: utf-8 -*-
__revision__ = '$Id: countrynames.py 7386 2006-04-30 11:12:55Z matt $'
countryNames = {
u'BD': u'Bangladesh',
u'BE': u'Belgium',
u'BF': u'Burkina Faso',
u'BG': u'Bulgaria',
u'BB': u'Barbados',
u'WF': u'Wallis and Futuna Islands',
u'BM': u'Bermuda',
u'BN': u'Brunei Darussalam',
u'BO': u'Bolivia',
u'BH': u'Bahrain',
u'BI': u'Burundi',
u'BJ': u'Benin',
u'BT': u'Bhutan',
u'JM': u'Jamaica',
u'BV': u'Bouvet Island',
u'BW': u'Botswana',
u'WS': u'Samoa',
u'BR': u'Brazil',
u'BS': u'Bahamas',
u'BY': u'Belarus',
u'BZ': u'Belize',
u'RU': u'Russian Federation',
u'RW': u'Rwanda',
u'RE': u'Reunion',
u'TM': u'Turkmenistan',
u'TJ': u'Tajikistan',
u'RO': u'Romania',
u'TK': u'Tokelau',
u'GW': u'Guinea-Bissau',
u'GU': u'Guam',
u'GT': u'Guatemala',
u'GR': u'Greece',
u'GQ': u'Equatorial Guinea',
u'GP': u'Guadeloupe',
u'JP': u'Japan',
u'GY': u'Guyana',
u'GF': u'French Guiana',
u'GE': u'Georgia',
u'GD': u'Grenada',
u'GB': u'United Kingdom',
u'GA': u'Gabon',
u'SV': u'El Salvador',
u'GN': u'Guinea',
u'GM': u'Gambia',
u'GL': u'Greenland',
u'GI': u'Gibraltar',
u'GH': u'Ghana',
u'OM': u'Oman',
u'TN': u'Tunisia',
u'JO': u'Jordan',
u'HT': u'Haiti',
u'HU': u'Hungary',
u'HK': u'Hong Kong',
u'HN': u'Honduras',
u'HM': u'Heard and Mc Donald Islands',
u'VE': u'Venezuela',
u'PR': u'Puerto Rico',
u'PW': u'Palau',
u'PT': u'Portugal',
u'SJ': u'Svalbard and Jan Mayen Islands',
u'PY': u'Paraguay',
u'IQ': u'Iraq',
u'PA': u'Panama',
u'PF': u'French Polynesia',
u'PG': u'Papua New Guinea',
u'PE': u'Peru',
u'PK': u'Pakistan',
u'PH': u'Philippines',
u'PN': u'Pitcairn',
u'PL': u'Poland',
u'PM': u'St. Pierre and Miquelon',
u'ZM': u'Zambia',
u'EH': u'Western Sahara',
u'EE': u'Estonia',
u'EG': u'Egypt',
u'ZA': u'South Africa',
u'EC': u'Ecuador',
u'IT': u'Italy',
u'VN': u'Viet Nam',
u'SB': u'Solomon Islands',
u'ET': u'Ethiopia',
u'SO': u'Somalia',
u'ZW': u'Zimbabwe',
u'SA': u'Saudi Arabia',
u'ES': u'Spain',
u'ER': u'Eritrea',
u'MD': u'Moldova, Republic of',
u'MG': u'Madagascar',
u'MA': u'Morocco',
u'MC': u'Monaco',
u'UZ': u'Uzbekistan',
u'MM': u'Myanmar',
u'ML': u'Mali',
u'MO': u'Macau',
u'MN': u'Mongolia',
u'MH': u'Marshall Islands',
u'MK': u'Macedonia, The Former Yugoslav Republic of',
u'MU': u'Mauritius',
u'MT': u'Malta',
u'MW': u'Malawi',
u'MV': u'Maldives',
u'MQ': u'Martinique',
u'MP': u'Northern Mariana Islands',
u'MS': u'Montserrat',
u'MR': u'Mauritania',
u'UG': u'Uganda',
u'MY': u'Malaysia',
u'MX': u'Mexico',
u'IL': u'Israel',
u'FR': u'France',
u'IO': u'British Indian Ocean Territory',
u'SH': u'St. Helena',
u'FI': u'Finland',
u'FJ': u'Fiji',
u'FK': u'Falkland Islands (Malvinas)',
u'FM': u'Micronesia, Federated States of',
u'FO': u'Faroe Islands',
u'NI': u'Nicaragua',
u'NL': u'Netherlands',
u'NO': u'Norway',
u'NA': u'Namibia',
u'VU': u'Vanuatu',
u'NC': u'New Caledonia',
u'NE': u'Niger',
u'NF': u'Norfolk Island',
u'NG': u'Nigeria',
u'NZ': u'New Zealand',
u'ZR': u'Zaire',
u'NP': u'Nepal',
u'NR': u'Nauru',
u'NU': u'Niue',
u'CK': u'Cook Islands',
u'CI': u'Cote d\'Ivoire',
u'CH': u'Switzerland',
u'CO': u'Colombia',
u'CN': u'China',
u'CM': u'Cameroon',
u'CL': u'Chile',
u'CC': u'Cocos (Keeling) Islands',
u'CA': u'Canada',
u'CG': u'Congo',
u'CF': u'Central African Republic',
u'CZ': u'Czech Republic',
u'CY': u'Cyprus',
u'CX': u'Christmas Island',
u'CR': u'Costa Rica',
u'CV': u'Cape Verde',
u'CU': u'Cuba',
u'SZ': u'Swaziland',
u'SY': u'Syrian Arab Republic',
u'KG': u'Kyrgyzstan',
u'KE': u'Kenya',
u'SR': u'Suriname',
u'KI': u'Kiribati',
u'KH': u'Cambodia',
u'KN': u'Saint Kitts and Nevis',
u'KM': u'Comoros',
u'ST': u'Sao Tome and Principe',
u'SI': u'Slovenia',
u'KW': u'Kuwait',
u'SN': u'Senegal',
u'SM': u'San Marino',
u'SL': u'Sierra Leone',
u'SC': u'Seychelles',
u'KZ': u'Kazakhstan',
u'KY': u'Cayman Islands',
u'SG': u'Singapore',
u'SE': u'Sweden',
u'SD': u'Sudan',
u'DO': u'Dominican Republic',
u'DM': u'Dominica',
u'DJ': u'Djibouti',
u'DK': u'Denmark',
u'VG': u'Virgin Islands (British)',
u'DE': u'Germany',
u'YE': u'Yemen',
u'DZ': u'Algeria',
u'US': u'United States',
u'UY': u'Uruguay',
u'YT': u'Mayotte',
u'UM': u'United States Minor Outlying Islands',
u'LB': u'Lebanon',
u'LC': u'Saint Lucia',
u'LA': u'Lao People\'s Democratic Republic',
u'TV': u'Tuvalu',
u'TW': u'Taiwan',
u'TT': u'Trinidad and Tobago',
u'TR': u'Turkey',
u'LK': u'Sri Lanka',
u'LI': u'Liechtenstein',
u'LV': u'Latvia',
u'TO': u'Tonga',
u'LT': u'Lithuania',
u'LU': u'Luxembourg',
u'LR': u'Liberia',
u'LS': u'Lesotho',
u'TH': u'Thailand',
u'TF': u'French Southern Territories',
u'TG': u'Togo',
u'TD': u'Chad',
u'TC': u'Turks and Caicos Islands',
u'LY': u'Libyan Arab Jamahiriya',
u'VA': u'Vatican City State (Holy See)',
u'VC': u'Saint Vincent and The Grenadines',
u'AE': u'United Arab Emirates',
u'AD': u'Andorra',
u'AG': u'Antigua and Barbuda',
u'AF': u'Afghanistan',
u'AI': u'Anguilla',
u'VI': u'Virgin Islands (U.S.)',
u'IS': u'Iceland',
u'IR': u'Iran (Islamic Republic of)',
u'AM': u'Armenia',
u'AL': u'Albania',
u'AO': u'Angola',
u'AN': u'Netherlands Antilles',
u'AQ': u'Antarctica',
u'AS': u'American Samoa',
u'AR': u'Argentina',
u'AU': u'Australia',
u'AT': u'Austria',
u'AW': u'Aruba',
u'IN': u'India',
u'TZ': u'Tanzania, United Republic of',
u'AZ': u'Azerbaijan',
u'IE': u'Ireland',
u'ID': u'Indonesia',
u'UA': u'Ukraine',
u'QA': u'Qatar',
u'MZ': u'Mozambique',
u'BA': u'Bosnia and Herzegovina',
u'CD': u'Congo, The Democratic Republic of the',
u'CS': u'Serbia and Montenegro',
u'HR': u'Croatia',
u'KP': u'Korea (North), Democratic People\'s Republic of',
u'KR': u'Korea (South), Republic of',
u'SK': u'Slovakia',
u'SU': u'Soviet Union (historical, 1922-1991)',
u'TL': u'East Timor',
u'XC': u'Czechoslovakia (historical, 1918-1992)',
u'XE': u'Europe',
u'XG': u'East Germany (historical, 1949-1990)',
u'XU': u'[Unknown Country]',
u'XW': u'[Worldwide]',
u'YU': u'Yugoslavia (historical, 1918-1992)',
}
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/data/__init__.py 0000644 0001750 0001750 00000000477 10425115667 022731 0 ustar lukas lukas """Support data for the musicbrainz2 package.
This package is I{not} part of the public API, it has been added to work
around shortcomings in python and may thus be removed at any time.
Please use the L{musicbrainz2.utils} module instead.
"""
__revision__ = '$Id: __init__.py 7386 2006-04-30 11:12:55Z matt $'
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/data/languagenames.py 0000644 0001750 0001750 00000021532 10541343413 023763 0 ustar lukas lukas # -*- coding: utf-8 -*-
__revision__ = '$Id: languagenames.py 8725 2006-12-17 22:39:07Z luks $'
languageNames = {
u'ART': u'Artificial (Other)',
u'ROH': u'Raeto-Romance',
u'SCO': u'Scots',
u'SCN': u'Sicilian',
u'ROM': u'Romany',
u'RON': u'Romanian',
u'OSS': u'Ossetian; Ossetic',
u'ALE': u'Aleut',
u'MNI': u'Manipuri',
u'NWC': u'Classical Newari; Old Newari; Classical Nepal Bhasa',
u'OSA': u'Osage',
u'MNC': u'Manchu',
u'MWR': u'Marwari',
u'VEN': u'Venda',
u'MWL': u'Mirandese',
u'FAS': u'Persian',
u'FAT': u'Fanti',
u'FAN': u'Fang',
u'FAO': u'Faroese',
u'DIN': u'Dinka',
u'HYE': u'Armenian',
u'DSB': u'Lower Sorbian',
u'CAR': u'Carib',
u'DIV': u'Divehi',
u'TEL': u'Telugu',
u'TEM': u'Timne',
u'NBL': u'Ndebele, South; South Ndebele',
u'TER': u'Tereno',
u'TET': u'Tetum',
u'SUN': u'Sundanese',
u'KUT': u'Kutenai',
u'SUK': u'Sukuma',
u'KUR': u'Kurdish',
u'KUM': u'Kumyk',
u'SUS': u'Susu',
u'NEW': u'Newari; Nepal Bhasa',
u'KUA': u'Kuanyama; Kwanyama',
u'MEN': u'Mende',
u'LEZ': u'Lezghian',
u'GLA': u'Gaelic; Scottish Gaelic',
u'BOS': u'Bosnian',
u'GLE': u'Irish',
u'EKA': u'Ekajuk',
u'GLG': u'Gallegan',
u'AKA': u'Akan',
u'BOD': u'Tibetan',
u'GLV': u'Manx',
u'JRB': u'Judeo-Arabic',
u'VIE': u'Vietnamese',
u'IPK': u'Inupiaq',
u'UZB': u'Uzbek',
u'BRE': u'Breton',
u'BRA': u'Braj',
u'AYM': u'Aymara',
u'CHA': u'Chamorro',
u'CHB': u'Chibcha',
u'CHE': u'Chechen',
u'CHG': u'Chagatai',
u'CHK': u'Chuukese',
u'CHM': u'Mari',
u'CHN': u'Chinook jargon',
u'CHO': u'Choctaw',
u'CHP': u'Chipewyan',
u'CHR': u'Cherokee',
u'CHU': u'Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic',
u'CHV': u'Chuvash',
u'CHY': u'Cheyenne',
u'MSA': u'Malay',
u'III': u'Sichuan Yi',
u'ACE': u'Achinese',
u'IBO': u'Igbo',
u'IBA': u'Iban',
u'XHO': u'Xhosa',
u'DEU': u'German',
u'CAT': u'Catalan; Valencian',
u'DEL': u'Delaware',
u'DEN': u'Slave (Athapascan)',
u'CAD': u'Caddo',
u'TAT': u'Tatar',
u'RAJ': u'Rajasthani',
u'SPA': u'Spanish; Castilian',
u'TAM': u'Tamil',
u'TAH': u'Tahitian',
u'AFH': u'Afrihili',
u'ENG': u'English',
u'CSB': u'Kashubian',
u'NYN': u'Nyankole',
u'NYO': u'Nyoro',
u'SID': u'Sidamo',
u'NYA': u'Chichewa; Chewa; Nyanja',
u'SIN': u'Sinhala; Sinhalese',
u'AFR': u'Afrikaans',
u'LAM': u'Lamba',
u'SND': u'Sindhi',
u'MAR': u'Marathi',
u'LAH': u'Lahnda',
u'NYM': u'Nyamwezi',
u'SNA': u'Shona',
u'LAD': u'Ladino',
u'SNK': u'Soninke',
u'MAD': u'Madurese',
u'MAG': u'Magahi',
u'MAI': u'Maithili',
u'MAH': u'Marshallese',
u'LAV': u'Latvian',
u'MAL': u'Malayalam',
u'MAN': u'Mandingo',
u'ZND': u'Zande',
u'ZEN': u'Zenaga',
u'KBD': u'Kabardian',
u'ITA': u'Italian',
u'VAI': u'Vai',
u'TSN': u'Tswana',
u'TSO': u'Tsonga',
u'TSI': u'Tsimshian',
u'BYN': u'Blin; Bilin',
u'FIJ': u'Fijian',
u'FIN': u'Finnish',
u'EUS': u'Basque',
u'CEB': u'Cebuano',
u'DAN': u'Danish',
u'NOG': u'Nogai',
u'NOB': u'Norwegian Bokmål; Bokmål, Norwegian',
u'DAK': u'Dakota',
u'CES': u'Czech',
u'DAR': u'Dargwa',
u'DAY': u'Dayak',
u'NOR': u'Norwegian',
u'KPE': u'Kpelle',
u'GUJ': u'Gujarati',
u'MDF': u'Moksha',
u'MAS': u'Masai',
u'LAO': u'Lao',
u'MDR': u'Mandar',
u'GON': u'Gondi',
u'SMS': u'Skolt Sami',
u'SMO': u'Samoan',
u'SMN': u'Inari Sami',
u'SMJ': u'Lule Sami',
u'GOT': u'Gothic',
u'SME': u'Northern Sami',
u'BLA': u'Siksika',
u'SMA': u'Southern Sami',
u'GOR': u'Gorontalo',
u'AST': u'Asturian; Bable',
u'ORM': u'Oromo',
u'QUE': u'Quechua',
u'ORI': u'Oriya',
u'CRH': u'Crimean Tatar; Crimean Turkish',
u'ASM': u'Assamese',
u'PUS': u'Pushto',
u'DGR': u'Dogrib',
u'LTZ': u'Luxembourgish; Letzeburgesch',
u'NDO': u'Ndonga',
u'GEZ': u'Geez',
u'ISL': u'Icelandic',
u'LAT': u'Latin',
u'MAK': u'Makasar',
u'ZAP': u'Zapotec',
u'YID': u'Yiddish',
u'KOK': u'Konkani',
u'KOM': u'Komi',
u'KON': u'Kongo',
u'UKR': u'Ukrainian',
u'TON': u'Tonga (Tonga Islands)',
u'KOS': u'Kosraean',
u'KOR': u'Korean',
u'TOG': u'Tonga (Nyasa)',
u'HUN': u'Hungarian',
u'HUP': u'Hupa',
u'CYM': u'Welsh',
u'UDM': u'Udmurt',
u'BEJ': u'Beja',
u'BEN': u'Bengali',
u'BEL': u'Belarusian',
u'BEM': u'Bemba',
u'AAR': u'Afar',
u'NZI': u'Nzima',
u'SAH': u'Yakut',
u'SAN': u'Sanskrit',
u'SAM': u'Samaritan Aramaic',
u'SAG': u'Sango',
u'SAD': u'Sandawe',
u'RAR': u'Rarotongan',
u'RAP': u'Rapanui',
u'SAS': u'Sasak',
u'SAT': u'Santali',
u'MIN': u'Minangkabau',
u'LIM': u'Limburgan; Limburger; Limburgish',
u'LIN': u'Lingala',
u'LIT': u'Lithuanian',
u'EFI': u'Efik',
u'BTK': u'Batak (Indonesia)',
u'KAC': u'Kachin',
u'KAB': u'Kabyle',
u'KAA': u'Kara-Kalpak',
u'KAN': u'Kannada',
u'KAM': u'Kamba',
u'KAL': u'Kalaallisut; Greenlandic',
u'KAS': u'Kashmiri',
u'KAR': u'Karen',
u'KAU': u'Kanuri',
u'KAT': u'Georgian',
u'KAZ': u'Kazakh',
u'TYV': u'Tuvinian',
u'AWA': u'Awadhi',
u'URD': u'Urdu',
u'DOI': u'Dogri',
u'TPI': u'Tok Pisin',
u'MRI': u'Maori',
u'ABK': u'Abkhazian',
u'TKL': u'Tokelau',
u'NLD': u'Dutch; Flemish',
u'OJI': u'Ojibwa',
u'OCI': u'Occitan (post 1500); Provençal',
u'WOL': u'Wolof',
u'JAV': u'Javanese',
u'HRV': u'Croatian',
u'DYU': u'Dyula',
u'SSW': u'Swati',
u'MUL': u'Multiple languages',
u'HIL': u'Hiligaynon',
u'HIM': u'Himachali',
u'HIN': u'Hindi',
u'BAS': u'Basa',
u'GBA': u'Gbaya',
u'WLN': u'Walloon',
u'BAD': u'Banda',
u'NEP': u'Nepali',
u'CRE': u'Cree',
u'BAN': u'Balinese',
u'BAL': u'Baluchi',
u'BAM': u'Bambara',
u'BAK': u'Bashkir',
u'SHN': u'Shan',
u'ARP': u'Arapaho',
u'ARW': u'Arawak',
u'ARA': u'Arabic',
u'ARC': u'Aramaic',
u'ARG': u'Aragonese',
u'SEL': u'Selkup',
u'ARN': u'Araucanian',
u'LUS': u'Lushai',
u'MUS': u'Creek',
u'LUA': u'Luba-Lulua',
u'LUB': u'Luba-Katanga',
u'LUG': u'Ganda',
u'LUI': u'Luiseno',
u'LUN': u'Lunda',
u'LUO': u'Luo (Kenya and Tanzania)',
u'IKU': u'Inuktitut',
u'TUR': u'Turkish',
u'TUK': u'Turkmen',
u'TUM': u'Tumbuka',
u'COP': u'Coptic',
u'COS': u'Corsican',
u'COR': u'Cornish',
u'ILO': u'Iloko',
u'GWI': u'Gwich´in',
u'TLI': u'Tlingit',
u'TLH': u'Klingon; tlhIngan-Hol',
u'POR': u'Portuguese',
u'PON': u'Pohnpeian',
u'POL': u'Polish',
u'TGK': u'Tajik',
u'TGL': u'Tagalog',
u'FRA': u'French',
u'BHO': u'Bhojpuri',
u'SWA': u'Swahili',
u'DUA': u'Duala',
u'SWE': u'Swedish',
u'YAP': u'Yapese',
u'TIV': u'Tiv',
u'YAO': u'Yao',
u'XAL': u'Kalmyk',
u'FRY': u'Frisian',
u'GAY': u'Gayo',
u'OTA': u'Turkish, Ottoman (1500-1928)',
u'HMN': u'Hmong',
u'HMO': u'Hiri Motu',
u'GAA': u'Ga',
u'FUR': u'Friulian',
u'MLG': u'Malagasy',
u'SLV': u'Slovenian',
u'FIL': u'Filipino; Pilipino',
u'MLT': u'Maltese',
u'SLK': u'Slovak',
u'FUL': u'Fulah',
u'JPN': u'Japanese',
u'VOL': u'Volapük',
u'VOT': u'Votic',
u'IND': u'Indonesian',
u'AVE': u'Avestan',
u'JPR': u'Judeo-Persian',
u'AVA': u'Avaric',
u'PAP': u'Papiamento',
u'EWO': u'Ewondo',
u'PAU': u'Palauan',
u'EWE': u'Ewe',
u'PAG': u'Pangasinan',
u'PAM': u'Pampanga',
u'PAN': u'Panjabi; Punjabi',
u'KIR': u'Kirghiz',
u'NIA': u'Nias',
u'KIK': u'Kikuyu; Gikuyu',
u'SYR': u'Syriac',
u'KIN': u'Kinyarwanda',
u'NIU': u'Niuean',
u'EPO': u'Esperanto',
u'JBO': u'Lojban',
u'MIC': u'Mi\'kmaq; Micmac',
u'THA': u'Thai',
u'HAI': u'Haida',
u'ELL': u'Greek, Modern (1453-)',
u'ADY': u'Adyghe; Adygei',
u'ELX': u'Elamite',
u'ADA': u'Adangme',
u'GRB': u'Grebo',
u'HAT': u'Haitian; Haitian Creole',
u'HAU': u'Hausa',
u'HAW': u'Hawaiian',
u'BIN': u'Bini',
u'AMH': u'Amharic',
u'BIK': u'Bikol',
u'BIH': u'Bihari',
u'MOS': u'Mossi',
u'MOH': u'Mohawk',
u'MON': u'Mongolian',
u'MOL': u'Moldavian',
u'BIS': u'Bislama',
u'TVL': u'Tuvalu',
u'IJO': u'Ijo',
u'EST': u'Estonian',
u'KMB': u'Kimbundu',
u'UMB': u'Umbundu',
u'TMH': u'Tamashek',
u'FON': u'Fon',
u'HSB': u'Upper Sorbian',
u'RUN': u'Rundi',
u'RUS': u'Russian',
u'PLI': u'Pali',
u'SRD': u'Sardinian',
u'ACH': u'Acoli',
u'NDE': u'Ndebele, North; North Ndebele',
u'DZO': u'Dzongkha',
u'KRU': u'Kurukh',
u'SRR': u'Serer',
u'IDO': u'Ido',
u'SRP': u'Serbian',
u'KRO': u'Kru',
u'KRC': u'Karachay-Balkar',
u'NDS': u'Low German; Low Saxon; German, Low; Saxon, Low',
u'ZUN': u'Zuni',
u'ZUL': u'Zulu',
u'TWI': u'Twi',
u'NSO': u'Northern Sotho, Pedi; Sepedi',
u'SOM': u'Somali',
u'SON': u'Songhai',
u'SOT': u'Sotho, Southern',
u'MKD': u'Macedonian',
u'HER': u'Herero',
u'LOL': u'Mongo',
u'HEB': u'Hebrew',
u'LOZ': u'Lozi',
u'GIL': u'Gilbertese',
u'WAS': u'Washo',
u'WAR': u'Waray',
u'BUL': u'Bulgarian',
u'WAL': u'Walamo',
u'BUA': u'Buriat',
u'BUG': u'Buginese',
u'AZE': u'Azerbaijani',
u'ZHA': u'Zhuang; Chuang',
u'ZHO': u'Chinese',
u'NNO': u'Norwegian Nynorsk; Nynorsk, Norwegian',
u'UIG': u'Uighur; Uyghur',
u'MYV': u'Erzya',
u'INH': u'Ingush',
u'KHM': u'Khmer',
u'MYA': u'Burmese',
u'KHA': u'Khasi',
u'INA': u'Interlingua (International Auxiliary Language Association)',
u'NAH': u'Nahuatl',
u'TIR': u'Tigrinya',
u'NAP': u'Neapolitan',
u'NAV': u'Navajo; Navaho',
u'NAU': u'Nauru',
u'GRN': u'Guarani',
u'TIG': u'Tigre',
u'YOR': u'Yoruba',
u'ILE': u'Interlingue',
u'SQI': u'Albanian',
}
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/data/scriptnames.py 0000644 0001750 0001750 00000002557 10425115667 023523 0 ustar lukas lukas # -*- coding: utf-8 -*-
__revision__ = '$Id: scriptnames.py 7386 2006-04-30 11:12:55Z matt $'
scriptNames = {
u'Yiii': u'Yi',
u'Telu': u'Telugu',
u'Taml': u'Tamil',
u'Guru': u'Gurmukhi',
u'Hebr': u'Hebrew',
u'Geor': u'Georgian (Mkhedruli)',
u'Ugar': u'Ugaritic',
u'Cyrl': u'Cyrillic',
u'Hrkt': u'Kanji & Kana',
u'Armn': u'Armenian',
u'Runr': u'Runic',
u'Khmr': u'Khmer',
u'Latn': u'Latin',
u'Hani': u'Han (Hanzi, Kanji, Hanja)',
u'Ital': u'Old Italic (Etruscan, Oscan, etc.)',
u'Hano': u'Hanunoo (Hanunóo)',
u'Ethi': u'Ethiopic (Ge\'ez)',
u'Gujr': u'Gujarati',
u'Hang': u'Hangul',
u'Arab': u'Arabic',
u'Thaa': u'Thaana',
u'Buhd': u'Buhid',
u'Sinh': u'Sinhala',
u'Orya': u'Oriya',
u'Hans': u'Han (Simplified variant)',
u'Thai': u'Thai',
u'Cprt': u'Cypriot',
u'Linb': u'Linear B',
u'Hant': u'Han (Traditional variant)',
u'Osma': u'Osmanya',
u'Mong': u'Mongolian',
u'Deva': u'Devanagari (Nagari)',
u'Laoo': u'Lao',
u'Tagb': u'Tagbanwa',
u'Hira': u'Hiragana',
u'Bopo': u'Bopomofo',
u'Goth': u'Gothic',
u'Tale': u'Tai Le',
u'Mymr': u'Myanmar (Burmese)',
u'Tglg': u'Tagalog',
u'Grek': u'Greek',
u'Mlym': u'Malayalam',
u'Cher': u'Cherokee',
u'Tibt': u'Tibetan',
u'Kana': u'Katakana',
u'Syrc': u'Syriac',
u'Cans': u'Unified Canadian Aboriginal Syllabics',
u'Beng': u'Bengali',
u'Limb': u'Limbu',
u'Ogam': u'Ogham',
u'Knda': u'Kannada',
}
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/webservice.py 0000644 0001750 0001750 00000141511 11654514634 022414 0 ustar lukas lukas """Classes for interacting with the MusicBrainz XML web service.
The L{WebService} class talks to a server implementing the MusicBrainz XML
web service. It mainly handles URL generation and network I/O. Use this
if maximum control is needed.
The L{Query} class provides a convenient interface to the most commonly
used features of the web service. By default it uses L{WebService} to
retrieve data and the L{XML parser } to parse the
responses. The results are object trees using the L{MusicBrainz domain
model }.
@author: Matthias Friedrich
"""
__revision__ = '$Id: webservice.py 13325 2011-11-03 13:39:40Z luks $'
import urllib
import urllib2
import urlparse
import logging
import musicbrainz2
from musicbrainz2.model import Release
from musicbrainz2.wsxml import MbXmlParser, ParseError
import musicbrainz2.utils as mbutils
__all__ = [
'WebServiceError', 'AuthenticationError', 'ConnectionError',
'RequestError', 'ResourceNotFoundError', 'ResponseError',
'IIncludes', 'ArtistIncludes', 'ReleaseIncludes', 'TrackIncludes',
'LabelIncludes', 'ReleaseGroupIncludes',
'IFilter', 'ArtistFilter', 'ReleaseFilter', 'TrackFilter',
'UserFilter', 'LabelFilter', 'ReleaseGroupFilter',
'IWebService', 'WebService', 'Query',
]
class IWebService(object):
"""An interface all concrete web service classes have to implement.
All web service classes have to implement this and follow the
method specifications.
"""
def get(self, entity, id_, include, filter, version):
"""Query the web service.
Using this method, you can either get a resource by id (using
the C{id_} parameter, or perform a query on all resources of
a type.
The C{filter} and the C{id_} parameter exclude each other. If
you are using a filter, you may not set C{id_} and vice versa.
Returns a file-like object containing the result or raises a
L{WebServiceError} or one of its subclasses in case of an
error. Which one is used depends on the implementing class.
@param entity: a string containing the entity's name
@param id_: a string containing a UUID, or the empty string
@param include: a tuple containing values for the 'inc' parameter
@param filter: parameters, depending on the entity
@param version: a string containing the web service version to use
@return: a file-like object
@raise WebServiceError: in case of errors
"""
raise NotImplementedError()
def post(self, entity, id_, data, version):
"""Submit data to the web service.
@param entity: a string containing the entity's name
@param id_: a string containing a UUID, or the empty string
@param data: A string containing the data to post
@param version: a string containing the web service version to use
@return: a file-like object
@raise WebServiceError: in case of errors
"""
raise NotImplementedError()
class WebServiceError(Exception):
"""A web service error has occurred.
This is the base class for several other web service related
exceptions.
"""
def __init__(self, msg='Webservice Error', reason=None):
"""Constructor.
Set C{msg} to an error message which explains why this
exception was raised. The C{reason} parameter should be the
original exception which caused this L{WebService} exception
to be raised. If given, it has to be an instance of
C{Exception} or one of its child classes.
@param msg: a string containing an error message
@param reason: another exception instance, or None
"""
Exception.__init__(self)
self.msg = msg
self.reason = reason
def __str__(self):
"""Makes this class printable.
@return: a string containing an error message
"""
return self.msg
class ConnectionError(WebServiceError):
"""Getting a server connection failed.
This exception is mostly used if the client couldn't connect to
the server because of an invalid host name or port. It doesn't
make sense if the web service in question doesn't use the network.
"""
pass
class RequestError(WebServiceError):
"""An invalid request was made.
This exception is raised if the client made an invalid request.
That could be syntactically invalid identifiers or unknown or
invalid parameter values.
"""
pass
class ResourceNotFoundError(WebServiceError):
"""No resource with the given ID exists.
This is usually a wrapper around IOError (which is superclass of
HTTPError).
"""
pass
class AuthenticationError(WebServiceError):
"""Authentication failed.
This is thrown if user name, password or realm were invalid while
trying to access a protected resource.
"""
pass
class ResponseError(WebServiceError):
"""The returned resource was invalid.
This may be due to a malformed XML document or if the requested
data wasn't part of the response. It can only occur in case of
bugs in the web service itself.
"""
pass
class DigestAuthHandler(urllib2.HTTPDigestAuthHandler):
"""Patched DigestAuthHandler to correctly handle Digest Auth according to RFC 2617.
This will allow multiple qop values in the WWW-Authenticate header (e.g. "auth,auth-int").
The only supported qop value is still auth, though.
See http://bugs.python.org/issue9714
@author: Kuno Woudt
"""
def get_authorization(self, req, chal):
qop = chal.get('qop')
if qop and ',' in qop and 'auth' in qop.split(','):
chal['qop'] = 'auth'
return urllib2.HTTPDigestAuthHandler.get_authorization(self, req, chal)
class WebService(IWebService):
"""An interface to the MusicBrainz XML web service via HTTP.
By default, this class uses the MusicBrainz server but may be
configured for accessing other servers as well using the
L{constructor <__init__>}. This implements L{IWebService}, so
additional documentation on method parameters can be found there.
"""
def __init__(self, host='musicbrainz.org', port=80, pathPrefix='/ws',
username=None, password=None, realm='musicbrainz.org',
opener=None, userAgent=None):
"""Constructor.
This can be used without parameters. In this case, the
MusicBrainz server will be used.
@param host: a string containing a host name
@param port: an integer containing a port number
@param pathPrefix: a string prepended to all URLs
@param username: a string containing a MusicBrainz user name
@param password: a string containing the user's password
@param realm: a string containing the realm used for authentication
@param opener: an C{urllib2.OpenerDirector} object used for queries
@param userAgent: a string containing the user agent
"""
self._host = host
self._port = port
self._username = username
self._password = password
self._realm = realm
self._pathPrefix = pathPrefix
self._log = logging.getLogger(str(self.__class__))
if opener is None:
self._opener = urllib2.build_opener()
else:
self._opener = opener
if userAgent is None:
self._userAgent = "python-musicbrainz/" + musicbrainz2.__version__
else:
self._userAgent = userAgent.replace("-", "/") \
+ " python-musicbrainz/" \
+ musicbrainz2.__version__
passwordMgr = self._RedirectPasswordMgr()
authHandler = DigestAuthHandler(passwordMgr)
authHandler.add_password(self._realm, (), # no host set
self._username, self._password)
self._opener.add_handler(authHandler)
def _makeUrl(self, entity, id_, include=( ), filter={ },
version='1', type_='xml'):
params = dict(filter)
if type_ is not None:
params['type'] = type_
if len(include) > 0:
params['inc'] = ' '.join(include)
netloc = self._host
if self._port != 80:
netloc += ':' + str(self._port)
path = '/'.join((self._pathPrefix, version, entity, id_))
query = urllib.urlencode(params)
url = urlparse.urlunparse(('http', netloc, path, '', query,''))
return url
def _openUrl(self, url, data=None):
req = urllib2.Request(url)
req.add_header('User-Agent', self._userAgent)
return self._opener.open(req, data)
def get(self, entity, id_, include=( ), filter={ }, version='1'):
"""Query the web service via HTTP-GET.
Returns a file-like object containing the result or raises a
L{WebServiceError}. Conditions leading to errors may be
invalid entities, IDs, C{include} or C{filter} parameters
and unsupported version numbers.
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid IDs or parameters
@raise AuthenticationError: invalid user name and/or password
@raise ResourceNotFoundError: resource doesn't exist
@see: L{IWebService.get}
"""
url = self._makeUrl(entity, id_, include, filter, version)
self._log.debug('GET ' + url)
try:
return self._openUrl(url)
except urllib2.HTTPError, e:
self._log.debug("GET failed: " + str(e))
if e.code == 400: # in python 2.4: httplib.BAD_REQUEST
raise RequestError(str(e), e)
elif e.code == 401: # httplib.UNAUTHORIZED
raise AuthenticationError(str(e), e)
elif e.code == 404: # httplib.NOT_FOUND
raise ResourceNotFoundError(str(e), e)
else:
raise WebServiceError(str(e), e)
except urllib2.URLError, e:
self._log.debug("GET failed: " + str(e))
raise ConnectionError(str(e), e)
def post(self, entity, id_, data, version='1'):
"""Send data to the web service via HTTP-POST.
Note that this may require authentication. You can set
user name, password and realm in the L{constructor <__init__>}.
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid IDs or parameters
@raise AuthenticationError: invalid user name and/or password
@raise ResourceNotFoundError: resource doesn't exist
@see: L{IWebService.post}
"""
url = self._makeUrl(entity, id_, version=version, type_=None)
self._log.debug('POST ' + url)
self._log.debug('POST-BODY: ' + data)
try:
return self._openUrl(url, data)
except urllib2.HTTPError, e:
self._log.debug("POST failed: " + str(e))
if e.code == 400: # in python 2.4: httplib.BAD_REQUEST
raise RequestError(str(e), e)
elif e.code == 401: # httplib.UNAUTHORIZED
raise AuthenticationError(str(e), e)
elif e.code == 404: # httplib.NOT_FOUND
raise ResourceNotFoundError(str(e), e)
else:
raise WebServiceError(str(e), e)
except urllib2.URLError, e:
self._log.debug("POST failed: " + str(e))
raise ConnectionError(str(e), e)
# Special password manager which also works with redirects by simply
# ignoring the URI. As a consequence, only *ONE* (username, password)
# tuple per realm can be used for all URIs.
#
class _RedirectPasswordMgr(urllib2.HTTPPasswordMgr):
def __init__(self):
self._realms = { }
def find_user_password(self, realm, uri):
# ignoring the uri parameter intentionally
try:
return self._realms[realm]
except KeyError:
return (None, None)
def add_password(self, realm, uri, username, password):
# ignoring the uri parameter intentionally
self._realms[realm] = (username, password)
class IFilter(object):
"""A filter for collections.
This is the interface all filters have to implement. Filter classes
are initialized with a set of criteria and are then applied to
collections of items. The criteria are usually strings or integer
values, depending on the filter.
Note that all strings passed to filters should be unicode strings
(python type C{unicode}). Standard strings are converted to unicode
internally, but have a limitation: Only 7 Bit pure ASCII characters
may be used, otherwise a C{UnicodeDecodeError} is raised.
"""
def createParameters(self):
"""Create a list of query parameters.
This method creates a list of (C{parameter}, C{value}) tuples,
based on the contents of the implementing subclass.
C{parameter} is a string containing a parameter name
and C{value} an arbitrary string. No escaping of those strings
is required.
@return: a sequence of (key, value) pairs
"""
raise NotImplementedError()
class ArtistFilter(IFilter):
"""A filter for the artist collection."""
def __init__(self, name=None, limit=None, offset=None, query=None):
"""Constructor.
The C{query} parameter may contain a query in U{Lucene syntax
}.
Note that the C{name} and C{query} may not be used together.
@param name: a unicode string containing the artist's name
@param limit: the maximum number of artists to return
@param offset: start results at this zero-based offset
@param query: a string containing a query in Lucene syntax
"""
self._params = [
('name', name),
('limit', limit),
('offset', offset),
('query', query),
]
if not _paramsValid(self._params):
raise ValueError('invalid combination of parameters')
def createParameters(self):
return _createParameters(self._params)
class LabelFilter(IFilter):
"""A filter for the label collection."""
def __init__(self, name=None, limit=None, offset=None, query=None):
"""Constructor.
The C{query} parameter may contain a query in U{Lucene syntax
}.
Note that the C{name} and C{query} may not be used together.
@param name: a unicode string containing the label's name
@param limit: the maximum number of labels to return
@param offset: start results at this zero-based offset
@param query: a string containing a query in Lucene syntax
"""
self._params = [
('name', name),
('limit', limit),
('offset', offset),
('query', query),
]
if not _paramsValid(self._params):
raise ValueError('invalid combination of parameters')
def createParameters(self):
return _createParameters(self._params)
class ReleaseGroupFilter(IFilter):
"""A filter for the release group collection."""
def __init__(self, title=None, releaseTypes=None, artistName=None,
artistId=None, limit=None, offset=None, query=None):
"""Constructor.
If C{artistId} is set, only releases matching those IDs are
returned. The C{releaseTypes} parameter allows you to limit
the types of the release groups returned. You can set it to
C{(Release.TYPE_ALBUM, Release.TYPE_OFFICIAL)}, for example,
to only get officially released albums. Note that those values
are connected using the I{AND} operator. MusicBrainz' support
is currently very limited, so C{Release.TYPE_LIVE} and
C{Release.TYPE_COMPILATION} exclude each other (see U{the
documentation on release attributes
} for more
information and all valid values).
If both the C{artistName} and the C{artistId} parameter are
given, the server will ignore C{artistName}.
The C{query} parameter may contain a query in U{Lucene syntax
}.
Note that C{query} may not be used together with the other
parameters except for C{limit} and C{offset}.
@param title: a unicode string containing the release group's title
@param releaseTypes: a sequence of release type URIs
@param artistName: a unicode string containing the artist's name
@param artistId: a unicode string containing the artist's ID
@param limit: the maximum number of release groups to return
@param offset: start results at this zero-based offset
@param query: a string containing a query in Lucene syntax
@see: the constants in L{musicbrainz2.model.Release}
"""
if releaseTypes is None or len(releaseTypes) == 0:
releaseTypesStr = None
else:
releaseTypesStr = ' '.join(map(mbutils.extractFragment, releaseTypes))
self._params = [
('title', title),
('releasetypes', releaseTypesStr),
('artist', artistName),
('artistid', mbutils.extractUuid(artistId)),
('limit', limit),
('offset', offset),
('query', query),
]
if not _paramsValid(self._params):
raise ValueError('invalid combination of parameters')
def createParameters(self):
return _createParameters(self._params)
class ReleaseFilter(IFilter):
"""A filter for the release collection."""
def __init__(self, title=None, discId=None, releaseTypes=None,
artistName=None, artistId=None, limit=None,
offset=None, query=None, trackCount=None):
"""Constructor.
If C{discId} or C{artistId} are set, only releases matching
those IDs are returned. The C{releaseTypes} parameter allows
to limit the types of the releases returned. You can set it to
C{(Release.TYPE_ALBUM, Release.TYPE_OFFICIAL)}, for example,
to only get officially released albums. Note that those values
are connected using the I{AND} operator. MusicBrainz' support
is currently very limited, so C{Release.TYPE_LIVE} and
C{Release.TYPE_COMPILATION} exclude each other (see U{the
documentation on release attributes
} for more
information and all valid values).
If both the C{artistName} and the C{artistId} parameter are
given, the server will ignore C{artistName}.
The C{query} parameter may contain a query in U{Lucene syntax
}.
Note that C{query} may not be used together with the other
parameters except for C{limit} and C{offset}.
@param title: a unicode string containing the release's title
@param discId: a unicode string containing the DiscID
@param releaseTypes: a sequence of release type URIs
@param artistName: a unicode string containing the artist's name
@param artistId: a unicode string containing the artist's ID
@param limit: the maximum number of releases to return
@param offset: start results at this zero-based offset
@param query: a string containing a query in Lucene syntax
@param trackCount: the number of tracks in the release
@see: the constants in L{musicbrainz2.model.Release}
"""
if releaseTypes is None or len(releaseTypes) == 0:
releaseTypesStr = None
else:
tmp = [ mbutils.extractFragment(x) for x in releaseTypes ]
releaseTypesStr = ' '.join(tmp)
self._params = [
('title', title),
('discid', discId),
('releasetypes', releaseTypesStr),
('artist', artistName),
('artistid', mbutils.extractUuid(artistId)),
('limit', limit),
('offset', offset),
('query', query),
('count', trackCount),
]
if not _paramsValid(self._params):
raise ValueError('invalid combination of parameters')
def createParameters(self):
return _createParameters(self._params)
class TrackFilter(IFilter):
"""A filter for the track collection."""
def __init__(self, title=None, artistName=None, artistId=None,
releaseTitle=None, releaseId=None,
duration=None, puid=None, limit=None, offset=None,
query=None):
"""Constructor.
If C{artistId}, C{releaseId} or C{puid} are set, only tracks
matching those IDs are returned.
The server will ignore C{artistName} and C{releaseTitle} if
C{artistId} or ${releaseId} are set respectively.
The C{query} parameter may contain a query in U{Lucene syntax
}.
Note that C{query} may not be used together with the other
parameters except for C{limit} and C{offset}.
@param title: a unicode string containing the track's title
@param artistName: a unicode string containing the artist's name
@param artistId: a string containing the artist's ID
@param releaseTitle: a unicode string containing the release's title
@param releaseId: a string containing the release's title
@param duration: the track's length in milliseconds
@param puid: a string containing a PUID
@param limit: the maximum number of releases to return
@param offset: start results at this zero-based offset
@param query: a string containing a query in Lucene syntax
"""
self._params = [
('title', title),
('artist', artistName),
('artistid', mbutils.extractUuid(artistId)),
('release', releaseTitle),
('releaseid', mbutils.extractUuid(releaseId)),
('duration', duration),
('puid', puid),
('limit', limit),
('offset', offset),
('query', query),
]
if not _paramsValid(self._params):
raise ValueError('invalid combination of parameters')
def createParameters(self):
return _createParameters(self._params)
class UserFilter(IFilter):
"""A filter for the user collection."""
def __init__(self, name=None):
"""Constructor.
@param name: a unicode string containing a MusicBrainz user name
"""
self._name = name
def createParameters(self):
if self._name is not None:
return [ ('name', self._name.encode('utf-8')) ]
else:
return [ ]
class IIncludes(object):
"""An interface implemented by include tag generators."""
def createIncludeTags(self):
raise NotImplementedError()
class ArtistIncludes(IIncludes):
"""A specification on how much data to return with an artist.
Example:
>>> from musicbrainz2.model import Release
>>> from musicbrainz2.webservice import ArtistIncludes
>>> inc = ArtistIncludes(artistRelations=True, releaseRelations=True,
... releases=(Release.TYPE_ALBUM, Release.TYPE_OFFICIAL))
>>>
The MusicBrainz server only supports some combinations of release
types for the C{releases} and C{vaReleases} include tags. At the
moment, not more than two release types should be selected, while
one of them has to be C{Release.TYPE_OFFICIAL},
C{Release.TYPE_PROMOTION} or C{Release.TYPE_BOOTLEG}.
@note: Only one of C{releases} and C{vaReleases} may be given.
"""
def __init__(self, aliases=False, releases=(), vaReleases=(),
artistRelations=False, releaseRelations=False,
trackRelations=False, urlRelations=False, tags=False,
ratings=False, releaseGroups=False):
assert not isinstance(releases, basestring)
assert not isinstance(vaReleases, basestring)
assert len(releases) == 0 or len(vaReleases) == 0
self._includes = {
'aliases': aliases,
'artist-rels': artistRelations,
'release-groups': releaseGroups,
'release-rels': releaseRelations,
'track-rels': trackRelations,
'url-rels': urlRelations,
'tags': tags,
'ratings': ratings,
}
for elem in releases:
self._includes['sa-' + mbutils.extractFragment(elem)] = True
for elem in vaReleases:
self._includes['va-' + mbutils.extractFragment(elem)] = True
def createIncludeTags(self):
return _createIncludes(self._includes)
class ReleaseIncludes(IIncludes):
"""A specification on how much data to return with a release."""
def __init__(self, artist=False, counts=False, releaseEvents=False,
discs=False, tracks=False,
artistRelations=False, releaseRelations=False,
trackRelations=False, urlRelations=False,
labels=False, tags=False, ratings=False, isrcs=False,
releaseGroup=False):
self._includes = {
'artist': artist,
'counts': counts,
'labels': labels,
'release-groups': releaseGroup,
'release-events': releaseEvents,
'discs': discs,
'tracks': tracks,
'artist-rels': artistRelations,
'release-rels': releaseRelations,
'track-rels': trackRelations,
'url-rels': urlRelations,
'tags': tags,
'ratings': ratings,
'isrcs': isrcs,
}
# Requesting labels without releaseEvents makes no sense,
# so we pull in releaseEvents, if necessary.
if labels and not releaseEvents:
self._includes['release-events'] = True
# Ditto for isrcs with no tracks
if isrcs and not tracks:
self._includes['tracks'] = True
def createIncludeTags(self):
return _createIncludes(self._includes)
class ReleaseGroupIncludes(IIncludes):
"""A specification on how much data to return with a release group."""
def __init__(self, artist=False, releases=False, tags=False):
"""Constructor.
@param artist: Whether to include the release group's main artist info.
@param releases: Whether to include the release group's releases.
"""
self._includes = {
'artist': artist,
'releases': releases,
}
def createIncludeTags(self):
return _createIncludes(self._includes)
class TrackIncludes(IIncludes):
"""A specification on how much data to return with a track."""
def __init__(self, artist=False, releases=False, puids=False,
artistRelations=False, releaseRelations=False,
trackRelations=False, urlRelations=False, tags=False,
ratings=False, isrcs=False):
self._includes = {
'artist': artist,
'releases': releases,
'puids': puids,
'artist-rels': artistRelations,
'release-rels': releaseRelations,
'track-rels': trackRelations,
'url-rels': urlRelations,
'tags': tags,
'ratings': ratings,
'isrcs': isrcs,
}
def createIncludeTags(self):
return _createIncludes(self._includes)
class LabelIncludes(IIncludes):
"""A specification on how much data to return with a label."""
def __init__(self, aliases=False, tags=False, ratings=False):
self._includes = {
'aliases': aliases,
'tags': tags,
'ratings': ratings,
}
def createIncludeTags(self):
return _createIncludes(self._includes)
class Query(object):
"""A simple interface to the MusicBrainz web service.
This is a facade which provides a simple interface to the MusicBrainz
web service. It hides all the details like fetching data from a server,
parsing the XML and creating an object tree. Using this class, you can
request data by ID or search the I{collection} of all resources
(artists, releases, or tracks) to retrieve those matching given
criteria. This document contains examples to get you started.
Working with Identifiers
========================
MusicBrainz uses absolute URIs as identifiers. For example, the artist
'Tori Amos' is identified using the following URI::
http://musicbrainz.org/artist/c0b2500e-0cef-4130-869d-732b23ed9df5
In some situations it is obvious from the context what type of
resource an ID refers to. In these cases, abbreviated identifiers may
be used, which are just the I{UUID} part of the URI. Thus the ID above
may also be written like this::
c0b2500e-0cef-4130-869d-732b23ed9df5
All methods in this class which require IDs accept both the absolute
URI and the abbreviated form (aka the relative URI).
Creating a Query Object
=======================
In most cases, creating a L{Query} object is as simple as this:
>>> import musicbrainz2.webservice as ws
>>> q = ws.Query()
>>>
The instantiated object uses the standard L{WebService} class to
access the MusicBrainz web service. If you want to use a different
server or you have to pass user name and password because one of
your queries requires authentication, you have to create the
L{WebService} object yourself and configure it appropriately.
This example uses the MusicBrainz test server and also sets
authentication data:
>>> import musicbrainz2.webservice as ws
>>> service = ws.WebService(host='test.musicbrainz.org',
... username='whatever', password='secret')
>>> q = ws.Query(service)
>>>
Querying for Individual Resources
=================================
If the MusicBrainz ID of a resource is known, then the L{getArtistById},
L{getReleaseById}, or L{getTrackById} method can be used to retrieve
it. Example:
>>> import musicbrainz2.webservice as ws
>>> q = ws.Query()
>>> artist = q.getArtistById('c0b2500e-0cef-4130-869d-732b23ed9df5')
>>> artist.name
u'Tori Amos'
>>> artist.sortName
u'Amos, Tori'
>>> print artist.type
http://musicbrainz.org/ns/mmd-1.0#Person
>>>
This returned just the basic artist data, however. To get more detail
about a resource, the C{include} parameters may be used which expect
an L{ArtistIncludes}, L{ReleaseIncludes}, or L{TrackIncludes} object,
depending on the resource type.
To get data about a release which also includes the main artist
and all tracks, for example, the following query can be used:
>>> import musicbrainz2.webservice as ws
>>> q = ws.Query()
>>> releaseId = '33dbcf02-25b9-4a35-bdb7-729455f33ad7'
>>> include = ws.ReleaseIncludes(artist=True, tracks=True)
>>> release = q.getReleaseById(releaseId, include)
>>> release.title
u'Tales of a Librarian'
>>> release.artist.name
u'Tori Amos'
>>> release.tracks[0].title
u'Precious Things'
>>>
Note that the query gets more expensive for the server the more
data you request, so please be nice.
Searching in Collections
========================
For each resource type (artist, release, and track), there is one
collection which contains all resources of a type. You can search
these collections using the L{getArtists}, L{getReleases}, and
L{getTracks} methods. The collections are huge, so you have to
use filters (L{ArtistFilter}, L{ReleaseFilter}, or L{TrackFilter})
to retrieve only resources matching given criteria.
For example, If you want to search the release collection for
releases with a specified DiscID, you would use L{getReleases}
and a L{ReleaseFilter} object:
>>> import musicbrainz2.webservice as ws
>>> q = ws.Query()
>>> filter = ws.ReleaseFilter(discId='8jJklE258v6GofIqDIrE.c5ejBE-')
>>> results = q.getReleases(filter=filter)
>>> results[0].score
100
>>> results[0].release.title
u'Under the Pink'
>>>
The query returns a list of results (L{wsxml.ReleaseResult} objects
in this case), which are ordered by score, with a higher score
indicating a better match. Note that those results don't contain
all the data about a resource. If you need more detail, you can then
use the L{getArtistById}, L{getReleaseById}, or L{getTrackById}
methods to request the resource.
All filters support the C{limit} argument to limit the number of
results returned. This defaults to 25, but the server won't send
more than 100 results to save bandwidth and processing power. Using
C{limit} and the C{offset} parameter, you can page through the
results.
Error Handling
==============
All methods in this class raise a L{WebServiceError} exception in case
of errors. Depending on the method, a subclass of L{WebServiceError} may
be raised which allows an application to handle errors more precisely.
The following example handles connection errors (invalid host name
etc.) separately and all other web service errors in a combined
catch clause:
>>> try:
... artist = q.getArtistById('c0b2500e-0cef-4130-869d-732b23ed9df5')
... except ws.ConnectionError, e:
... pass # implement your error handling here
... except ws.WebServiceError, e:
... pass # catches all other web service errors
...
>>>
"""
def __init__(self, ws=None, wsFactory=WebService, clientId=None):
"""Constructor.
The C{ws} parameter has to be a subclass of L{IWebService}.
If it isn't given, the C{wsFactory} parameter is used to
create an L{IWebService} subclass.
If the constructor is called without arguments, an instance
of L{WebService} is used, preconfigured to use the MusicBrainz
server. This should be enough for most users.
If you want to use queries which require authentication you
have to pass a L{WebService} instance where user name and
password have been set.
The C{clientId} parameter is required for data submission.
The format is C{'application-version'}, where C{application}
is your application's name and C{version} is a version
number which may not include a '-' character.
Even if you don't plan to submit data, setting this parameter is
encouraged because it will set the user agent used to make requests if
you don't supply the C{ws} parameter.
@param ws: a subclass instance of L{IWebService}, or None
@param wsFactory: a callable object which creates an object
@param clientId: a unicode string containing the application's ID
"""
if ws is None:
self._ws = wsFactory(userAgent=clientId)
else:
self._ws = ws
self._clientId = clientId
self._log = logging.getLogger(str(self.__class__))
def getArtistById(self, id_, include=None):
"""Returns an artist.
If no artist with that ID can be found, C{include} contains
invalid tags or there's a server problem, an exception is
raised.
@param id_: a string containing the artist's ID
@param include: an L{ArtistIncludes} object, or None
@return: an L{Artist } object, or None
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise ResourceNotFoundError: artist doesn't exist
@raise ResponseError: server returned invalid data
"""
uuid = mbutils.extractUuid(id_, 'artist')
result = self._getFromWebService('artist', uuid, include)
artist = result.getArtist()
if artist is not None:
return artist
else:
raise ResponseError("server didn't return artist")
def getArtists(self, filter):
"""Returns artists matching given criteria.
@param filter: an L{ArtistFilter} object
@return: a list of L{musicbrainz2.wsxml.ArtistResult} objects
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise ResponseError: server returned invalid data
"""
result = self._getFromWebService('artist', '', filter=filter)
return result.getArtistResults()
def getLabelById(self, id_, include=None):
"""Returns a L{model.Label}
If no label with that ID can be found, or there is a server problem,
an exception is raised.
@param id_: a string containing the label's ID.
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise ResourceNotFoundError: release doesn't exist
@raise ResponseError: server returned invalid data
"""
uuid = mbutils.extractUuid(id_, 'label')
result = self._getFromWebService('label', uuid, include)
label = result.getLabel()
if label is not None:
return label
else:
raise ResponseError("server didn't return a label")
def getLabels(self, filter):
result = self._getFromWebService('label', '', filter=filter)
return result.getLabelResults()
def getReleaseById(self, id_, include=None):
"""Returns a release.
If no release with that ID can be found, C{include} contains
invalid tags or there's a server problem, and exception is
raised.
@param id_: a string containing the release's ID
@param include: a L{ReleaseIncludes} object, or None
@return: a L{Release } object, or None
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise ResourceNotFoundError: release doesn't exist
@raise ResponseError: server returned invalid data
"""
uuid = mbutils.extractUuid(id_, 'release')
result = self._getFromWebService('release', uuid, include)
release = result.getRelease()
if release is not None:
return release
else:
raise ResponseError("server didn't return release")
def getReleases(self, filter):
"""Returns releases matching given criteria.
@param filter: a L{ReleaseFilter} object
@return: a list of L{musicbrainz2.wsxml.ReleaseResult} objects
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise ResponseError: server returned invalid data
"""
result = self._getFromWebService('release', '', filter=filter)
return result.getReleaseResults()
def getReleaseGroupById(self, id_, include=None):
"""Returns a release group.
If no release group with that ID can be found, C{include}
contains invalid tags, or there's a server problem, an
exception is raised.
@param id_: a string containing the release group's ID
@param include: a L{ReleaseGroupIncludes} object, or None
@return: a L{ReleaseGroup } object, or None
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise ResourceNotFoundError: release doesn't exist
@raise ResponseError: server returned invalid data
"""
uuid = mbutils.extractUuid(id_, 'release-group')
result = self._getFromWebService('release-group', uuid, include)
releaseGroup = result.getReleaseGroup()
if releaseGroup is not None:
return releaseGroup
else:
raise ResponseError("server didn't return releaseGroup")
def getReleaseGroups(self, filter):
"""Returns release groups matching the given criteria.
@param filter: a L{ReleaseGroupFilter} object
@return: a list of L{musicbrainz2.wsxml.ReleaseGroupResult} objects
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise ResponseError: server returned invalid data
"""
result = self._getFromWebService('release-group', '', filter=filter)
return result.getReleaseGroupResults()
def getTrackById(self, id_, include=None):
"""Returns a track.
If no track with that ID can be found, C{include} contains
invalid tags or there's a server problem, an exception is
raised.
@param id_: a string containing the track's ID
@param include: a L{TrackIncludes} object, or None
@return: a L{Track } object, or None
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise ResourceNotFoundError: track doesn't exist
@raise ResponseError: server returned invalid data
"""
uuid = mbutils.extractUuid(id_, 'track')
result = self._getFromWebService('track', uuid, include)
track = result.getTrack()
if track is not None:
return track
else:
raise ResponseError("server didn't return track")
def getTracks(self, filter):
"""Returns tracks matching given criteria.
@param filter: a L{TrackFilter} object
@return: a list of L{musicbrainz2.wsxml.TrackResult} objects
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise ResponseError: server returned invalid data
"""
result = self._getFromWebService('track', '', filter=filter)
return result.getTrackResults()
def getUserByName(self, name):
"""Returns information about a MusicBrainz user.
You can only request user data if you know the user name and
password for that account. If username and/or password are
incorrect, an L{AuthenticationError} is raised.
See the example in L{Query} on how to supply user name and
password.
@param name: a unicode string containing the user's name
@return: a L{User } object
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or include tags
@raise AuthenticationError: invalid user name and/or password
@raise ResourceNotFoundError: track doesn't exist
@raise ResponseError: server returned invalid data
"""
filter = UserFilter(name=name)
result = self._getFromWebService('user', '', None, filter)
if len(result.getUserList()) > 0:
return result.getUserList()[0]
else:
raise ResponseError("response didn't contain user data")
def _getFromWebService(self, entity, id_, include=None, filter=None):
if filter is None:
filterParams = [ ]
else:
filterParams = filter.createParameters()
if include is None:
includeParams = [ ]
else:
includeParams = include.createIncludeTags()
stream = self._ws.get(entity, id_, includeParams, filterParams)
try:
parser = MbXmlParser()
return parser.parse(stream)
except ParseError, e:
raise ResponseError(str(e), e)
def submitPuids(self, tracks2puids):
"""Submit track to PUID mappings.
The C{tracks2puids} parameter has to be a dictionary, with the
keys being MusicBrainz track IDs (either as absolute URIs or
in their 36 character ASCII representation) and the values
being PUIDs (ASCII, 36 characters).
Note that this method only works if a valid user name and
password have been set. See the example in L{Query} on how
to supply authentication data.
@param tracks2puids: a dictionary mapping track IDs to PUIDs
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid track or PUIDs
@raise AuthenticationError: invalid user name and/or password
"""
assert self._clientId is not None, 'Please supply a client ID'
params = [ ]
params.append( ('client', self._clientId.encode('utf-8')) )
for (trackId, puid) in tracks2puids.iteritems():
trackId = mbutils.extractUuid(trackId, 'track')
params.append( ('puid', trackId + ' ' + puid) )
encodedStr = urllib.urlencode(params, True)
self._ws.post('track', '', encodedStr)
def submitISRCs(self, tracks2isrcs):
"""Submit track to ISRC mappings.
The C{tracks2isrcs} parameter has to be a dictionary, with the
keys being MusicBrainz track IDs (either as absolute URIs or
in their 36 character ASCII representation) and the values
being ISRCs (ASCII, 12 characters).
Note that this method only works if a valid user name and
password have been set. See the example in L{Query} on how
to supply authentication data.
@param tracks2isrcs: a dictionary mapping track IDs to ISRCs
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid track or ISRCs
@raise AuthenticationError: invalid user name and/or password
"""
params = [ ]
for (trackId, isrc) in tracks2isrcs.iteritems():
trackId = mbutils.extractUuid(trackId, 'track')
params.append( ('isrc', trackId + ' ' + isrc) )
encodedStr = urllib.urlencode(params, True)
self._ws.post('track', '', encodedStr)
def addToUserCollection(self, releases):
"""Add releases to a user's collection.
The releases parameter must be a list. It can contain either L{Release}
objects or a string representing a MusicBrainz release ID (either as
absolute URIs or in their 36 character ASCII representation).
Adding a release that is already in the collection has no effect.
@param releases: a list of releases to add to the user collection
@raise ConnectionError: couldn't connect to server
@raise AuthenticationError: invalid user name and/or password
"""
rels = []
for release in releases:
if isinstance(release, Release):
rels.append(mbutils.extractUuid(release.id))
else:
rels.append(mbutils.extractUuid(release))
encodedStr = urllib.urlencode({'add': ",".join(rels)}, True)
self._ws.post('collection', '', encodedStr)
def removeFromUserCollection(self, releases):
"""Remove releases from a user's collection.
The releases parameter must be a list. It can contain either L{Release}
objects or a string representing a MusicBrainz release ID (either as
absolute URIs or in their 36 character ASCII representation).
Removing a release that is not in the collection has no effect.
@param releases: a list of releases to remove from the user collection
@raise ConnectionError: couldn't connect to server
@raise AuthenticationError: invalid user name and/or password
"""
rels = []
for release in releases:
if isinstance(release, Release):
rels.append(mbutils.extractUuid(release.id))
else:
rels.append(mbutils.extractUuid(release))
encodedStr = urllib.urlencode({'remove': ",".join(rels)}, True)
self._ws.post('collection', '', encodedStr)
def getUserCollection(self, offset=0, maxitems=100):
"""Get the releases that are in a user's collection
A maximum of 100 items will be returned for any one call
to this method. To fetch more than 100 items, use the offset
parameter.
@param offset: the offset to start fetching results from
@param maxitems: the upper limit on items to return
@return: a list of L{musicbrainz2.wsxml.ReleaseResult} objects
@raise ConnectionError: couldn't connect to server
@raise AuthenticationError: invalid user name and/or password
"""
params = { 'offset': offset, 'maxitems': maxitems }
stream = self._ws.get('collection', '', filter=params)
try:
parser = MbXmlParser()
result = parser.parse(stream)
except ParseError, e:
raise ResponseError(str(e), e)
return result.getReleaseResults()
def submitUserTags(self, entityUri, tags):
"""Submit folksonomy tags for an entity.
Note that all previously existing tags from the authenticated
user are replaced with the ones given to this method. Other
users' tags are not affected.
@param entityUri: a string containing an absolute MB ID
@param tags: A list of either L{Tag } objects
or strings
@raise ValueError: invalid entityUri
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID, entity or tags
@raise AuthenticationError: invalid user name and/or password
"""
entity = mbutils.extractEntityType(entityUri)
uuid = mbutils.extractUuid(entityUri, entity)
params = (
('type', 'xml'),
('entity', entity),
('id', uuid),
('tags', ','.join([unicode(tag).encode('utf-8') for tag in tags]))
)
encodedStr = urllib.urlencode(params)
self._ws.post('tag', '', encodedStr)
def getUserTags(self, entityUri):
"""Returns a list of folksonomy tags a user has applied to an entity.
The given parameter has to be a fully qualified MusicBrainz ID, as
returned by other library functions.
Note that this method only works if a valid user name and
password have been set. Only the tags the authenticated user
applied to the entity will be returned. If username and/or
password are incorrect, an AuthenticationError is raised.
This method will return a list of L{Tag }
objects.
@param entityUri: a string containing an absolute MB ID
@raise ValueError: invalid entityUri
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or entity
@raise AuthenticationError: invalid user name and/or password
"""
entity = mbutils.extractEntityType(entityUri)
uuid = mbutils.extractUuid(entityUri, entity)
params = { 'entity': entity, 'id': uuid }
stream = self._ws.get('tag', '', filter=params)
try:
parser = MbXmlParser()
result = parser.parse(stream)
except ParseError, e:
raise ResponseError(str(e), e)
return result.getTagList()
def submitUserRating(self, entityUri, rating):
"""Submit rating for an entity.
Note that all previously existing rating from the authenticated
user are replaced with the one given to this method. Other
users' ratings are not affected.
@param entityUri: a string containing an absolute MB ID
@param rating: A L{Rating } object
or integer
@raise ValueError: invalid entityUri
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID, entity or tags
@raise AuthenticationError: invalid user name and/or password
"""
entity = mbutils.extractEntityType(entityUri)
uuid = mbutils.extractUuid(entityUri, entity)
params = (
('type', 'xml'),
('entity', entity),
('id', uuid),
('rating', unicode(rating).encode('utf-8'))
)
encodedStr = urllib.urlencode(params)
self._ws.post('rating', '', encodedStr)
def getUserRating(self, entityUri):
"""Return the rating a user has applied to an entity.
The given parameter has to be a fully qualified MusicBrainz
ID, as returned by other library functions.
Note that this method only works if a valid user name and
password have been set. Only the rating the authenticated user
applied to the entity will be returned. If username and/or
password are incorrect, an AuthenticationError is raised.
This method will return a L{Rating }
object.
@param entityUri: a string containing an absolute MB ID
@raise ValueError: invalid entityUri
@raise ConnectionError: couldn't connect to server
@raise RequestError: invalid ID or entity
@raise AuthenticationError: invalid user name and/or password
"""
entity = mbutils.extractEntityType(entityUri)
uuid = mbutils.extractUuid(entityUri, entity)
params = { 'entity': entity, 'id': uuid }
stream = self._ws.get('rating', '', filter=params)
try:
parser = MbXmlParser()
result = parser.parse(stream)
except ParseError, e:
raise ResponseError(str(e), e)
return result.getRating()
def submitCDStub(self, cdstub):
"""Submit a CD Stub to the database.
The number of tracks added to the CD Stub must match the TOC and DiscID
otherwise the submission wil fail. The submission will also fail if
the Disc ID is already in the MusicBrainz database.
This method will only work if no user name and password are set.
@param cdstub: a L{CDStub} object to submit
@raise RequestError: Missmatching TOC/Track information or the
the CD Stub already exists or the Disc ID already exists
"""
assert self._clientId is not None, 'Please supply a client ID'
disc = cdstub._disc
params = [ ]
params.append( ('client', self._clientId.encode('utf-8')) )
params.append( ('discid', disc.id) )
params.append( ('title', cdstub.title) )
params.append( ('artist', cdstub.artist) )
if cdstub.barcode != "":
params.append( ('barcode', cdstub.barcode) )
if cdstub.comment != "":
params.append( ('comment', cdstub.comment) )
trackind = 0
for track,artist in cdstub.tracks:
params.append( ('track%d' % trackind, track) )
if artist != "":
params.append( ('artist%d' % trackind, artist) )
trackind += 1
toc = "%d %d %d " % (disc.firstTrackNum, disc.lastTrackNum, disc.sectors)
toc = toc + ' '.join( map(lambda x: str(x[0]), disc.getTracks()) )
params.append( ('toc', toc) )
encodedStr = urllib.urlencode(params)
self._ws.post('release', '', encodedStr)
def _createIncludes(tagMap):
selected = filter(lambda x: x[1] == True, tagMap.items())
return map(lambda x: x[0], selected)
def _createParameters(params):
"""Remove (x, None) tuples and encode (x, str/unicode) to utf-8."""
ret = [ ]
for p in params:
if isinstance(p[1], (str, unicode)):
ret.append( (p[0], p[1].encode('utf-8')) )
elif p[1] is not None:
ret.append(p)
return ret
def _paramsValid(params):
"""Check if the query parameter collides with other parameters."""
tmp = [ ]
for name, value in params:
if value is not None and name not in ('offset', 'limit'):
tmp.append(name)
if 'query' in tmp and len(tmp) > 1:
return False
else:
return True
if __name__ == '__main__':
import doctest
doctest.testmod()
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/model.py 0000644 0001750 0001750 00000200027 11444132513 021341 0 ustar lukas lukas """The MusicBrainz domain model.
These classes are part of the MusicBrainz domain model. They may be used
by other modules and don't contain any network or other I/O code. If you
want to request data from the web service, please have a look at
L{musicbrainz2.webservice}.
The most important classes, usually acting as entry points, are
L{Artist}, L{Release}, and L{Track}.
@var VARIOUS_ARTISTS_ID: The ID of the special 'Various Artists' artist.
@var NS_MMD_1: Default namespace prefix for all MusicBrainz metadata.
@var NS_REL_1: Namespace prefix for relations.
@var NS_EXT_1: Namespace prefix for MusicBrainz extensions.
@see: L{musicbrainz2.webservice}
@author: Matthias Friedrich
"""
try:
set
except NameError:
from sets import Set as set
__revision__ = '$Id: model.py 12829 2010-09-15 12:00:11Z luks $'
__all__ = [
'VARIOUS_ARTISTS_ID', 'NS_MMD_1', 'NS_REL_1', 'NS_EXT_1',
'Entity', 'Artist', 'Release', 'Track', 'User', 'ReleaseGroup',
'Relation', 'Disc', 'ReleaseEvent', 'Label', 'Tag', 'Rating',
'AbstractAlias', 'ArtistAlias', 'LabelAlias',
]
VARIOUS_ARTISTS_ID = 'http://musicbrainz.org/artist/89ad4ac3-39f7-470e-963a-56509c546377'
# Namespace URI prefixes
#
NS_MMD_1 = 'http://musicbrainz.org/ns/mmd-1.0#'
NS_REL_1 = 'http://musicbrainz.org/ns/rel-1.0#'
NS_EXT_1 = 'http://musicbrainz.org/ns/ext-1.0#'
class Entity(object):
"""A first-level MusicBrainz class.
All entities in MusicBrainz have unique IDs (which are absolute URIs)
as well as any number of L{relations } to other entities
and free text tags. This class is abstract and should not be
instantiated.
Relations are differentiated by their I{target type}, that means,
where they link to. MusicBrainz currently supports four target types
(artists, releases, tracks, and URLs) each identified using a URI.
To get all relations with a specific target type, you can use
L{getRelations} and pass one of the following constants as the
parameter:
- L{Relation.TO_ARTIST}
- L{Relation.TO_RELEASE}
- L{Relation.TO_TRACK}
- L{Relation.TO_URL}
@see: L{Relation}
"""
def __init__(self, id_=None):
"""Constructor.
This should only used by derived classes.
@param id_: a string containing an absolute URI
"""
self._id = id_
self._relations = { }
self._tags = { }
self._rating = Rating()
def getId(self):
"""Returns a MusicBrainz ID.
@return: a string containing a URI, or None
"""
return self._id
def setId(self, value):
"""Sets a MusicBrainz ID.
@param value: a string containing an absolute URI
"""
self._id = value
id = property(getId, setId, doc='The MusicBrainz ID.')
def getRelations(self, targetType=None, relationType=None,
requiredAttributes=(), direction=None):
"""Returns a list of relations.
If C{targetType} is given, only relations of that target
type are returned. For MusicBrainz, the following target
types are defined:
- L{Relation.TO_ARTIST}
- L{Relation.TO_RELEASE}
- L{Relation.TO_TRACK}
- L{Relation.TO_URL}
If C{targetType} is L{Relation.TO_ARTIST}, for example,
this method returns all relations between this Entity and
artists.
You may use the C{relationType} parameter to further restrict
the selection. If it is set, only relations with the given
relation type are returned. The C{requiredAttributes} sequence
lists attributes that have to be part of all returned relations.
If C{direction} is set, only relations with the given reading
direction are returned. You can use the L{Relation.DIR_FORWARD},
L{Relation.DIR_BACKWARD}, and L{Relation.DIR_NONE} constants
for this.
@param targetType: a string containing an absolute URI, or None
@param relationType: a string containing an absolute URI, or None
@param requiredAttributes: a sequence containing absolute URIs
@param direction: one of L{Relation}'s direction constants
@return: a list of L{Relation} objects
@see: L{Entity}
"""
allRels = [ ]
if targetType is not None:
allRels = self._relations.setdefault(targetType, [ ])
else:
for (k, relList) in self._relations.items():
for rel in relList:
allRels.append(rel)
# Filter for direction.
#
if direction is not None:
allRels = [r for r in allRels if r.getDirection() == direction]
# Filter for relation type.
#
if relationType is None:
return allRels
else:
allRels = [r for r in allRels if r.getType() == relationType]
# Now filer for attribute type.
#
tmp = []
required = set(iter(requiredAttributes))
for r in allRels:
attrs = set(iter(r.getAttributes()))
if required.issubset(attrs):
tmp.append(r)
return tmp
def getRelationTargets(self, targetType=None, relationType=None,
requiredAttributes=(), direction=None):
"""Returns a list of relation targets.
The arguments work exactly like in L{getRelations}, but
instead of L{Relation} objects, the matching relation
targets are returned. This can be L{Artist}, L{Release},
or L{Track} objects, depending on the relations.
As a special case, URL strings are returned if the target
is an URL.
@param targetType: a string containing an absolute URI, or None
@param relationType: a string containing an absolute URI, or None
@param requiredAttributes: a sequence containing absolute URIs
@param direction: one of L{Relation}'s direction constants
@return: a list of objects, depending on the relation
@see: L{getRelations}
"""
ret = [ ]
rels = self.getRelations(targetType, relationType,
requiredAttributes, direction)
for r in rels:
if r.getTargetType() == Relation.TO_URL:
ret.append(r.getTargetId())
else:
ret.append(r.getTarget())
return ret
def addRelation(self, relation):
"""Adds a relation.
This method adds C{relation} to the list of relations. The
given relation has to be initialized, at least the target
type has to be set.
@param relation: the L{Relation} object to add
@see: L{Entity}
"""
assert relation.getType is not None
assert relation.getTargetType is not None
assert relation.getTargetId is not None
l = self._relations.setdefault(relation.getTargetType(), [ ])
l.append(relation)
def getRelationTargetTypes(self):
"""Returns a list of target types available for this entity.
Use this to find out to which types of targets this entity
has relations. If the entity only has relations to tracks and
artists, for example, then a list containg the strings
L{Relation.TO_TRACK} and L{Relation.TO_ARTIST} is returned.
@return: a list of strings containing URIs
@see: L{getRelations}
"""
return self._relations.keys()
def getTag(self, value):
"""Return the tag with the given value (aka the tag's name).
@return: the L{Tag} with the given name or raises a KeyError
"""
return self._tags[value]
def getTags(self):
"""Return all tags attached to this Entity.
@return: a list of L{Tag} objects
"""
return self._tags.values()
tags = property(getTags, doc='The tags for this entity.')
def addTag(self, tag):
"""Add a new tag.
This merges an existing tag with the same name.
@param tag: the L{Tag} object to add
@see: L{getTags}
"""
if self._tags.has_key(tag.value):
existing = self._tags[tag.value]
existing.count += tag.count
else:
self._tags[tag.value] = tag
def getRating(self):
"""Return the rating of this Entity.
0 = Unrated
1 - 5 = Rating
@return: rating
"""
return self._rating
rating = property(getRating, doc='The rating for this entity.')
def setRating(self, value):
self._rating = value
class Artist(Entity):
"""Represents an artist.
Artists in MusicBrainz can have a type. Currently, this type can
be either Person or Group for which the following URIs are assigned:
- C{http://musicbrainz.org/ns/mmd-1.0#Person}
- C{http://musicbrainz.org/ns/mmd-1.0#Group}
Use the L{TYPE_PERSON} and L{TYPE_GROUP} constants for comparison.
"""
TYPE_PERSON = NS_MMD_1 + 'Person'
TYPE_GROUP = NS_MMD_1 + 'Group'
def __init__(self, id_=None, type_=None, name=None, sortName=None):
"""Constructor.
@param id_: a string containing an absolute URI
@param type_: a string containing an absolute URI
@param name: a string containing the artist's name
@param sortName: a string containing the artist's sort name
"""
Entity.__init__(self, id_)
self._type = type_
self._name = name
self._sortName = sortName
self._disambiguation = None
self._beginDate = None
self._endDate = None
self._aliases = [ ]
self._releases = [ ]
self._releasesCount = None
self._releasesOffset = None
self._releaseGroups = [ ]
self._releaseGroupsCount = None
self._releaseGroupsOffset = None
def getType(self):
"""Returns the artist's type.
@return: a string containing an absolute URI, or None
"""
return self._type
def setType(self, type_):
"""Sets the artist's type.
@param type_: a string containing an absolute URI
"""
self._type = type_
type = property(getType, setType, doc="The artist's type.")
def getName(self):
"""Returns the artist's name.
@return: a string containing the artist's name, or None
"""
return self._name
def setName(self, name):
"""Sets the artist's name.
@param name: a string containing the artist's name
"""
self._name = name
name = property(getName, setName, doc="The artist's name.")
def getSortName(self):
"""Returns the artist's sort name.
The sort name is the artist's name in a special format which
is better suited for lexicographic sorting. The MusicBrainz
style guide specifies this format.
@see: U{The MusicBrainz Style Guidelines
}
"""
return self._sortName
def setSortName(self, sortName):
"""Sets the artist's sort name.
@param sortName: a string containing the artist's sort name
@see: L{getSortName}
"""
self._sortName = sortName
sortName = property(getSortName, setSortName,
doc="The artist's sort name.")
def getDisambiguation(self):
"""Returns the disambiguation attribute.
This attribute may be used if there is more than one artist
with the same name. In this case, disambiguation attributes
are added to the artists' names to keep them apart.
For example, there are at least three bands named 'Vixen'.
Each band has a different disambiguation in the MusicBrainz
database, like 'Hip-hop' or 'all-female rock/glam band'.
@return: a disambiguation string, or None
@see: L{getUniqueName}
"""
return self._disambiguation
def setDisambiguation(self, disambiguation):
"""Sets the disambiguation attribute.
@param disambiguation: a disambiguation string
@see: L{getDisambiguation}, L{getUniqueName}
"""
self._disambiguation = disambiguation
disambiguation = property(getDisambiguation, setDisambiguation,
doc="The disambiguation comment.")
def getUniqueName(self):
"""Returns a unique artist name (using disambiguation).
This method returns the artist name together with the
disambiguation attribute in parenthesis if it exists.
Example: 'Vixen (Hip-hop)'.
@return: a string containing the unique name
@see: L{getDisambiguation}
"""
d = self.getDisambiguation()
if d is not None and d.strip() != '':
return '%s (%s)' % (self.getName(), d)
else:
return self.getName()
def getBeginDate(self):
"""Returns the birth/foundation date.
The definition of the I{begin date} depends on the artist's
type. For persons, this is the day of birth, for groups it
is the day the group was founded.
The returned date has the format 'YYYY', 'YYYY-MM', or
'YYYY-MM-DD', depending on how much detail is known.
@return: a string containing the date, or None
@see: L{getType}
"""
return self._beginDate
def setBeginDate(self, dateStr):
"""Sets the begin/foundation date.
@param dateStr: a date string
@see: L{getBeginDate}
"""
self._beginDate = dateStr
beginDate = property(getBeginDate, setBeginDate,
doc="The begin/foundation date.")
def getEndDate(self):
"""Returns the death/dissolving date.
The definition of the I{end date} depends on the artist's
type. For persons, this is the day of death, for groups it
is the day the group was dissolved.
@return: a string containing a date, or None
@see: L{getBeginDate}
"""
return self._endDate
def setEndDate(self, dateStr):
"""Sets the death/dissolving date.
@param dateStr: a string containing a date
@see: L{setEndDate}, L{getBeginDate}
"""
self._endDate = dateStr
endDate = property(getEndDate, setEndDate,
doc="The death/dissolving date.")
def getAliases(self):
"""Returns the list of aliases for this artist.
@return: a list of L{ArtistAlias} objects
"""
return self._aliases
aliases = property(getAliases, doc='The list of aliases.')
def addAlias(self, alias):
"""Adds an alias for this artist.
@param alias: an L{ArtistAlias} object
"""
self._aliases.append(alias)
def getReleases(self):
"""Returns a list of releases from this artist.
This may also include releases where this artist isn't the
I{main} artist but has just contributed one or more tracks
(aka VA-Releases).
@return: a list of L{Release} objects
"""
return self._releases
releases = property(getReleases, doc='The list of releases')
def addRelease(self, release):
"""Adds a release to this artist's list of releases.
@param release: a L{Release} object
"""
self._releases.append(release)
def getReleasesOffset(self):
"""Returns the offset of the release list.
This is used if the release list is incomplete (ie. the web
service only returned part of the release for this artist).
Note that the offset value is zero-based, which means release
C{0} is the first release.
@return: an integer containing the offset, or None
@see: L{getReleases}, L{getReleasesCount}
"""
return self._releasesOffset
def setReleasesOffset(self, offset):
"""Sets the offset of the release list.
@param offset: an integer containing the offset, or None
@see: L{getReleasesOffset}
"""
self._releasesOffset = offset
releasesOffset = property(getReleasesOffset, setReleasesOffset,
doc='The offset of the release list.')
def getReleasesCount(self):
"""Returns the number of existing releases.
This may or may not match with the number of elements that
L{getReleases} returns. If the count is higher than
the list, it indicates that the list is incomplete.
@return: an integer containing the count, or None
@see: L{setReleasesCount}, L{getReleasesOffset}
"""
return self._releasesCount
def setReleasesCount(self, value):
"""Sets the number of existing releases.
@param value: an integer containing the count, or None
@see: L{getReleasesCount}, L{setReleasesOffset}
"""
self._releasesCount = value
releasesCount = property(getReleasesCount, setReleasesCount,
doc='The total number of releases')
def getReleaseGroups(self):
"""Returns a list of release groups from this artist.
@return: a list of L{ReleaseGroup} objects
"""
return self._releaseGroups
releaseGroups = property(getReleaseGroups, doc='The list of release groups')
def addReleaseGroup(self, releaseGroup):
"""Adds a release group to this artist's list of release groups.
@param releaseGroup: a L{ReleaseGroup} object
"""
self._releaseGroups.append(releaseGroup)
def getReleaseGroupsOffset(self):
"""Returns the offset of the release group list.
This is used if the release group list is incomplete (ie. the
web service only returned part of the result for this artist).
Note that the offset value is zero-based, which means release
group C{0} is the first release group.
@return: an integer containing the offset, or None
@see: L{getReleaseGroups}, L{getReleaseGroupsCount}
"""
return self._releaseGroupsOffset
def setReleaseGroupsOffset(self, offset):
"""Sets the offset of the release group list.
@param offset: an integer containing the offset, or None
@see: L{getReleaseGroupsOffset}
"""
self._releaseGroupsOffset = offset
releaseGroupsOffset = property(getReleaseGroupsOffset, setReleaseGroupsOffset,
doc='The offset of the release group list.')
def getReleaseGroupsCount(self):
"""Returns the number of existing release groups.
This may or may not match with the number of elements that
L{getReleaseGroups} returns. If the count is higher than
the list, it indicates that the list is incomplete.
@return: an integer containing the count, or None
@see: L{setReleaseGroupsCount}, L{getReleaseGroupsOffset}
"""
return self._releaseGroupsCount
def setReleaseGroupsCount(self, value):
"""Sets the number of existing release groups.
@param value: an integer containing the count, or None
@see: L{getReleaseGroupsCount}, L{setReleaseGroupsOffset}
"""
self._releaseGroupsCount = value
releasesCount = property(getReleaseGroupsCount, setReleaseGroupsCount,
doc='The total number of release groups')
class Rating(object):
"""The representation of a MusicBrain rating.
The rating can have the following values:
0 = Unrated
[1..5] = Rating
"""
def __init__(self, value=None, count=None):
"""Constructor.
@param value: a string containing the tag's value
@param count: the number of users who added this tag
"""
self._value = value
self._count = count
def getValue(self):
"""Returns a string with the tag's value.
@return: an integer containing the rating's value, or None
"""
return self._value
def setValue(self, value):
""" Set the value of this rating.
0 or None = Clear your rating
1 - 5 = Rating
@param value: the rating to apply
@raise ValueError: if value is not a double or not in the
range 0 - 5 or None.
"""
if value == None:
value = 0
try:
value = float(value)
except ValueError, e:
raise ValueError("Value for rating needs to be an" \
"float.")
if value < 0.0 or value > 5.0:
raise ValueError("Value needs to be in the range [0..5]")
self._value = value
value = property(getValue, setValue, doc='The value of the rating.')
def getCount(self):
"""Returns an integer containing the rating's frequency count.
@return: an integer containing the rating's frequency count,
or None
"""
return self._count
def setCount(self, count):
"""Sets the frequency count of this rating.
@param count: an integer containing the tag's frequency count
"""
self._count = count
count = property(getCount, setCount, doc="This tag's frequency count.")
def __str__(self):
return str(self._value)
def __unicode__(self):
return unicode(self._value)
class Tag(object):
"""The representation of a MusicBrainz folksonomy tag.
The tag's value is the text that's displayed in the tag cloud.
The count attribute keeps track of how many users added the tag
to its owning entity.
"""
def __init__(self, value=None, count=None):
"""Constructor.
@param value: a string containing the tag's value
@param count: the number of users who added this tag
"""
self._value = value
self._count = count
def getValue(self):
"""Returns a string with the tag's value.
@return: a string containing the tags's value, or None
"""
return self._value
def setValue(self, value):
"""Sets the value of this tag.
@param value: A string containing the value of the tag
"""
self._value = value
value = property(getValue, setValue, doc='The value of the text.')
def getCount(self):
"""Returns an integer containing the tag's frequency count.
@return: an integer containing the tags's frequency count, or None
"""
return self._count
def setCount(self, count):
"""Sets the frequency count of this tag.
@param count: an integer containing the tag's frequency count
"""
self._count = count
count = property(getCount, setCount, doc="This tag's frequency count.")
def __str__(self):
return str(self._value)
def __unicode__(self):
return unicode(self._value)
class Label(Entity):
"""Represents a record label.
A label within MusicBrainz is an L{Entity}. It contains information
about the label like when it was established, its name, label code and
other relationships. All release events may be assigned a label.
"""
TYPE_UNKNOWN = NS_MMD_1 + 'Unknown'
TYPE_DISTRIBUTOR = NS_MMD_1 + 'Distributor'
TYPE_HOLDING = NS_MMD_1 + 'Holding'
TYPE_PRODUCTION = NS_MMD_1 + 'Production'
TYPE_ORIGINAL = NS_MMD_1 + 'OriginalProduction'
TYPE_BOOTLEG = NS_MMD_1 + 'BootlegProduction'
TYPE_REISSUE = NS_MMD_1 + 'ReissueProduction'
def __init__(self, id_=None):
"""Constructor.
@param id_: a string containing an absolute URI
"""
Entity.__init__(self, id_)
self._type = None
self._name = None
self._sortName = None
self._disambiguation = None
self._countryId = None
self._code = None
self._beginDate = None
self._endDate = None
self._aliases = [ ]
def getType(self):
"""Returns the type of this label.
@return: a string containing an absolute URI
"""
return self._type
def setType(self, type_):
"""Sets the type of this label.
@param type_: A string containing the absolute URI of the type of label.
"""
self._type = type_
type = property(getType, setType, doc='The type of label')
def getName(self):
"""Returns a string with the name of the label.
@return: a string containing the label's name, or None
"""
return self._name
def setName(self, name):
"""Sets the name of this label.
@param name: A string containing the name of the label
"""
self._name = name
name = property(getName, setName, doc='The name of the label.')
def getSortName(self):
"""Returns the label's sort name.
The sort name is the label's name in a special format which
is better suited for lexicographic sorting. The MusicBrainz
style guide specifies this format.
@see: U{The MusicBrainz Style Guidelines
}
"""
return self._sortName
def setSortName(self, sortName):
"""Sets the label's sort name.
@param sortName: a string containing the label's sort name
@see: L{getSortName}
"""
self._sortName = sortName
sortName = property(getSortName, setSortName,
doc="The label's sort name.")
def getDisambiguation(self):
"""Returns the disambiguation attribute.
This attribute may be used if there is more than one label
with the same name. In this case, disambiguation attributes
are added to the labels' names to keep them apart.
@return: a disambiguation string, or None
@see: L{getUniqueName}
"""
return self._disambiguation
def setDisambiguation(self, disambiguation):
"""Sets the disambiguation attribute.
@param disambiguation: a disambiguation string
@see: L{getDisambiguation}, L{getUniqueName}
"""
self._disambiguation = disambiguation
disambiguation = property(getDisambiguation, setDisambiguation,
doc="The disambiguation comment.")
def getUniqueName(self):
"""Returns a unique label name (using disambiguation).
This method returns the label's name together with the
disambiguation attribute in parenthesis if it exists.
@return: a string containing the unique name
@see: L{getDisambiguation}
"""
d = self.getDisambiguation()
if d is not None and d.strip() != '':
return '%s (%s)' % (self.getName(), d)
else:
return self.getName()
def getBeginDate(self):
"""Returns the date this label was established.
@return: A string contained the start date, or None
"""
return self._beginDate
def setBeginDate(self, date):
"""Set the date this label was established.
@param date: A string in the format of YYYY-MM-DD
"""
self._beginDate = date
beginDate = property(getBeginDate, setBeginDate,
doc='The date this label was established.')
def getEndDate(self):
"""Returns the date this label closed.
The returned date has the format 'YYYY', 'YYYY-MM', or
'YYYY-MM-DD', depending on how much detail is known.
@return: A string containing the date, or None
"""
return self._endDate
def setEndDate(self, date):
"""Set the date this label closed.
The date may have the format 'YYYY', 'YYYY-MM', or
'YYYY-MM-DD', depending on how much detail is known.
@param date: A string containing the date, or None
"""
self._endDate = date
endDate = property(getEndDate, setEndDate,
doc='The date this label closed.')
def getCountry(self):
"""Returns the country the label is located.
@return: a string containing an ISO-3166 country code, or None
@see: L{musicbrainz2.utils.getCountryName}
"""
return self._countryId
def setCountry(self, country):
"""Sets the country the label is located.
@param country: a string containing an ISO-3166 country code
"""
self._countryId = country
country = property(getCountry, setCountry,
doc='The country the label is located.')
def getCode(self):
"""Returns the label code.
Label codes have been introduced by the IFPI (International
Federation of Phonogram and Videogram Industries) to uniquely
identify record labels. The label code consists of 'LC-' and 4
figures (currently being extended to 5 figures).
@return: a string containing the label code, or None
"""
return self._code
def setCode(self, code):
"""Sets the label code.
@param code: a string containing the label code
"""
self._code = code
code = property(getCode, setCode,
doc='The label code.')
def getAliases(self):
"""Returns the list of aliases for this label.
@return: a list of L{LabelAlias} objects
"""
return self._aliases
aliases = property(getAliases, doc='The list of aliases.')
def addAlias(self, alias):
"""Adds an alias for this label.
@param alias: a L{LabelAlias} object
"""
self._aliases.append(alias)
class Release(Entity):
"""Represents a Release.
A release within MusicBrainz is an L{Entity} which contains L{Track}
objects. Releases may be of more than one type: There can be albums,
singles, compilations, live recordings, official releases, bootlegs
etc.
@note: The current MusicBrainz server implementation supports only a
limited set of types.
"""
TYPE_NONE = NS_MMD_1 + 'None'
TYPE_NON_ALBUM_TRACKS = NS_MMD_1 + "NonAlbum Track"
TYPE_ALBUM = NS_MMD_1 + 'Album'
TYPE_SINGLE = NS_MMD_1 + 'Single'
TYPE_EP = NS_MMD_1 + 'EP'
TYPE_COMPILATION = NS_MMD_1 + 'Compilation'
TYPE_SOUNDTRACK = NS_MMD_1 + 'Soundtrack'
TYPE_SPOKENWORD = NS_MMD_1 + 'Spokenword'
TYPE_INTERVIEW = NS_MMD_1 + 'Interview'
TYPE_AUDIOBOOK = NS_MMD_1 + 'Audiobook'
TYPE_LIVE = NS_MMD_1 + 'Live'
TYPE_REMIX = NS_MMD_1 + 'Remix'
TYPE_OTHER = NS_MMD_1 + 'Other'
TYPE_OFFICIAL = NS_MMD_1 + 'Official'
TYPE_PROMOTION = NS_MMD_1 + 'Promotion'
TYPE_BOOTLEG = NS_MMD_1 + 'Bootleg'
TYPE_PSEUDO_RELEASE = NS_MMD_1 + 'Pseudo-Release'
def __init__(self, id_=None, title=None):
"""Constructor.
@param id_: a string containing an absolute URI
@param title: a string containing the title
"""
Entity.__init__(self, id_)
self._types = [ ]
self._title = title
self._textLanguage = None
self._textScript = None
self._asin = None
self._artist = None
self._releaseEvents = [ ]
#self._releaseEventsCount = None
self._releaseGroup = None
self._discs = [ ]
#self._discIdsCount = None
self._tracks = [ ]
self._tracksOffset = None
self._tracksCount = None
def getTypes(self):
"""Returns the types of this release.
To test for release types, you can use the constants
L{TYPE_ALBUM}, L{TYPE_SINGLE}, etc.
@return: a list of strings containing absolute URIs
@see: L{musicbrainz2.utils.getReleaseTypeName}
"""
return self._types
types = property(getTypes, doc='The list of types for this release.')
def addType(self, type_):
"""Add a type to the list of types.
@param type_: a string containing absolute URIs
@see: L{getTypes}
"""
self._types.append(type_)
def getTitle(self):
"""Returns the release's title.
@return: a string containing the release's title
"""
return self._title
def setTitle(self, title):
"""Sets the release's title.
@param title: a string containing the release's title, or None
"""
self._title = title
title = property(getTitle, setTitle, doc='The title of this release.')
def getTextLanguage(self):
"""Returns the language used in release and track titles.
To represent the language, the ISO-639-2/T standard is used,
which provides three-letter terminological language codes like
'ENG', 'DEU', 'JPN', 'KOR', 'ZHO' or 'YID'.
Note that this refers to release and track I{titles}, not
lyrics.
@return: a string containing the language code, or None
@see: L{musicbrainz2.utils.getLanguageName}
"""
return self._textLanguage
def setTextLanguage(self, language):
"""Sets the language used in releaes and track titles.
@param language: a string containing a language code
@see: L{getTextLanguage}
"""
self._textLanguage = language
textLanguage = property(getTextLanguage, setTextLanguage,
doc='The language used in release and track titles.')
def getTextScript(self):
"""Returns the script used in release and track titles.
To represent the script, ISO-15924 script codes are used.
Valid codes are, among others: 'Latn', 'Cyrl', 'Hans', 'Hebr'
Note that this refers to release and track I{titles}, not
lyrics.
@return: a string containing the script code, or None
@see: L{musicbrainz2.utils.getScriptName}
"""
return self._textScript
def setTextScript(self, script):
"""Sets the script used in releaes and track titles.
@param script: a string containing a script code
@see: L{getTextScript}
"""
self._textScript = script
textScript = property(getTextScript, setTextScript,
doc='The script used in release and track titles.')
def getAsin(self):
"""Returns the amazon shop identifier (ASIN).
The ASIN is a 10-letter code (except for books) assigned
by Amazon, which looks like 'B000002IT2' or 'B00006I4YD'.
@return: a string containing the ASIN, or None
"""
return self._asin
def setAsin(self, asin):
"""Sets the amazon shop identifier (ASIN).
@param asin: a string containing the ASIN
@see: L{getAsin}
"""
self._asin = asin
asin = property(getAsin, setAsin, doc='The amazon shop identifier.')
def getArtist(self):
"""Returns the main artist of this release.
@return: an L{Artist} object, or None
"""
return self._artist
def setArtist(self, artist):
"""Sets this release's main artist.
@param artist: an L{Artist} object
"""
self._artist = artist
artist = property(getArtist, setArtist,
doc='The main artist of this release.')
def getReleaseGroup(self):
"""Returns the release group to which this release belongs.
@return: a L{ReleaseGroup} object, or None.
"""
return self._releaseGroup
def setReleaseGroup(self, releaseGroup):
"""Sets the release's release group.
@param releaseGroup: a L{ReleaseGroup} object, or None.
"""
self._releaseGroup = releaseGroup
releaseGroup = property(getReleaseGroup, setReleaseGroup,
doc='The release group this release belongs to.')
def isSingleArtistRelease(self):
"""Checks if this is a single artist's release.
Returns C{True} if the release's main artist (L{getArtist}) is
also the main artist for all of the tracks. This is checked by
comparing the artist IDs.
Note that the release's artist has to be set (see L{setArtist})
for this. The track artists may be unset.
@return: True, if this is a single artist's release
"""
releaseArtist = self.getArtist()
assert releaseArtist is not None, 'Release Artist may not be None!'
for track in self.getTracks():
if track.getArtist() is None:
continue
if track.getArtist().getId() != releaseArtist.getId():
return False
return True
def getTracks(self):
"""Returns the tracks this release contains.
@return: a list containing L{Track} objects
@see: L{getTracksOffset}, L{getTracksCount}
"""
return self._tracks
tracks = property(getTracks, doc='The list of tracks.')
def addTrack(self, track):
"""Adds a track to this release.
This appends a track at the end of this release's track list.
@param track: a L{Track} object
"""
self._tracks.append(track)
def getTracksOffset(self):
"""Returns the offset of the track list.
This is used if the track list is incomplete (ie. the web
service only returned part of the tracks on this release).
Note that the offset value is zero-based, which means track
C{0} is the first track.
@return: an integer containing the offset, or None
@see: L{getTracks}, L{getTracksCount}
"""
return self._tracksOffset
def setTracksOffset(self, offset):
"""Sets the offset of the track list.
@param offset: an integer containing the offset, or None
@see: L{getTracksOffset}, L{setTracksCount}
"""
self._tracksOffset = offset
tracksOffset = property(getTracksOffset, setTracksOffset,
doc='The offset of the track list.')
def getTracksCount(self):
"""Returns the number of tracks on this release.
This may or may not match with the number of elements that
L{getTracks} returns. If the count is higher than
the list, it indicates that the list is incomplete.
@return: an integer containing the count, or None
@see: L{setTracksCount}, L{getTracks}, L{getTracksOffset}
"""
return self._tracksCount
def setTracksCount(self, value):
"""Sets the number of tracks on this release.
@param value: an integer containing the count, or None
@see: L{getTracksCount}, L{setTracksOffset}
"""
self._tracksCount = value
tracksCount = property(getTracksCount, setTracksCount,
doc='The total number of releases')
def getReleaseEvents(self):
"""Returns the list of release events.
A L{Release} may contain a list of so-called release events,
each represented using a L{ReleaseEvent} object. Release
evens specify where and when this release was, well, released.
@return: a list of L{ReleaseEvent} objects
@see: L{getReleaseEventsAsDict}
"""
return self._releaseEvents
releaseEvents = property(getReleaseEvents,
doc='The list of release events.')
def addReleaseEvent(self, event):
"""Adds a release event to this release.
@param event: a L{ReleaseEvent} object
@see: L{getReleaseEvents}
"""
self._releaseEvents.append(event)
def getReleaseEventsAsDict(self):
"""Returns the release events represented as a dict.
Keys are ISO-3166 country codes like 'DE', 'UK', 'FR' etc.
Values are dates in 'YYYY', 'YYYY-MM' or 'YYYY-MM-DD' format.
@return: a dict containing (countryCode, date) entries
@see: L{getReleaseEvents}, L{musicbrainz2.utils.getCountryName}
"""
d = { }
for event in self.getReleaseEvents():
d[event.getCountry()] = event.getDate()
return d
def getEarliestReleaseDate(self):
"""Returns the earliest release date.
This favours complete dates. For example, '2006-09' is
returned if there is '2000', too. If there is no release
event associated with this release, None is returned.
@return: a string containing the date, or None
@see: L{getReleaseEvents}, L{getReleaseEventsAsDict}
"""
event = self.getEarliestReleaseEvent()
if event is None:
return None
else:
return event.getDate()
def getEarliestReleaseEvent(self):
"""Returns the earliest release event.
This works like L{getEarliestReleaseDate}, but instead of
just the date, this returns a L{ReleaseEvent} object.
@return: a L{ReleaseEvent} object, or None
@see: L{getReleaseEvents}, L{getEarliestReleaseDate}
"""
dates = [ ]
for event in self.getReleaseEvents():
date = event.getDate()
if len(date) == 10: # 'YYYY-MM-DD'
dates.append( (date, event) )
elif len(date) == 7: # 'YYYY-MM'
dates.append( (date + '-99', event) )
else:
dates.append( (date + '-99-99', event) )
dates.sort(lambda x, y: cmp(x[0], y[0]))
if len(dates) > 0:
return dates[0][1]
else:
return None
#def getReleaseEventsCount(self):
# """Returns the number of release events.
#
# This may or may not match with the number of elements that
# getReleaseEvents() returns. If the count is higher than
# the list, it indicates that the list is incomplete.
# """
# return self._releaseEventsCount
#def setReleaseEventsCount(self, value):
# self._releaseEventsCount = value
def getDiscs(self):
"""Returns the discs associated with this release.
Discs are currently containers for MusicBrainz DiscIDs.
Note that under rare circumstances (identical TOCs), a
DiscID could be associated with more than one release.
@return: a list of L{Disc} objects
"""
return self._discs
discs = property(getDiscs, doc='The list of associated discs.')
def addDisc(self, disc):
"""Adds a disc to this release.
@param disc: a L{Disc} object
"""
self._discs.append(disc)
#def getDiscIdsCount(self):
# return self._discIdsCount
#def setDiscIdsCount(self, value):
# self._discIdsCount = value
class ReleaseGroup(Entity):
"""Represents a ReleaseGroup.
A ReleaseGroup in MusicBrainz is an L{Entity} which groups several different
versions of L{Release} objects (e.g., different editions of the same album).
@see: L{Release}
@see: L{Entity}
"""
def __init__(self, id_=None, title=None):
"""Constructor.
@param id_: a string containing an absolute URI
@param title: a string containing the title
"""
Entity.__init__(self, id_)
self._title = title
self._id = id_
self._type = None
self._releases = [ ]
self._artist = None
self._releasesOffset = 0
self._releasesCount = 0
def getType(self):
"""Returns the type of this release group.
To test for release types, you can use the constants
L{Release.TYPE_ALBUM}, L{Release.TYPE_SINGLE}, etc.
@return: a string containing an absolute URI, or None.
@see: L{musicbrainz2.utils.getReleaseTypeName}
"""
return self._type
def setType(self, type_):
"""Sets the type of this release group.
Use a constant from the L{Release} class, such as
L{Release.TYPE_ALBUM} or L{Release.TYPE_SINGLE} to
set the value.
@param type_: a string containing an absolute URI, or None.
@see: L{musicbrainz2.utils.getReleaseTypeName}
"""
self._type = type_
type = property(getType, setType,
doc = 'The type of this release group.')
def getReleases(self):
"""Gets the releases in this release group.
@return: a list of L{Release} objects
@see: L{Release}
"""
return self._releases
releases = property(getReleases,
doc = 'The list of releases in this release group.')
def addRelease(self, release):
"""Adds a L{Release} to this release group.
@param release: a L{Release} object
"""
self._releases.append(release)
def getReleasesOffset(self):
"""Returns the offset of the release list.
This is used if the release list is incomplete (i.e., the web
service only returned a portion of the releases in this release
group).
@return: an integer containing the offset, or None.
@see: L{getReleases}, L{getReleasesCount}
"""
return self._releasesOffset
def setReleasesOffset(self, offset):
"""Sets the offset of the release list.
@param offset: an integer containing the offset, or None.
@see: L{getReleases}, L{getReleasesOffset}
"""
self._releasesOffset = offset
releasesOffset = property(getReleasesOffset, setReleasesOffset,
doc='The offset of the release list.')
def getReleasesCount(self):
"""Returns the number of releases in this release group.
This may or may not match the number of elements returned by
L{getReleases}. If the count is higher than the length of that
list, then the list is incomplete.
@return: an integer containing the count, or None
@see: L{getReleases}, L{setReleasesCount}, L{getReleasesOffset}
"""
return self._releasesCount
def setReleasesCount(self, value):
"""Sets the number of releases in this release group.
@param value: an integer containing the count, or None.
@see: L{getReleases}, L{getReleasesCount}, L{getReleasesOffset}
"""
self._releasesCount = value
releasesCount = property(getReleasesCount, setReleasesCount,
doc = 'The total number of releases')
def getTitle(self):
"""Returns this release group's title.
@return: a string containing the release group's title
"""
return self._title
def setTitle(self, title):
"""Sets the release group's title.
@param title: a string containing the release group's title.
"""
self._title = title
title = property(getTitle, setTitle,
doc = 'The title of this release group.')
def getArtist(self):
"""Returns the main artist of this release group.
@return: an L{Artist} object, or None
"""
return self._artist
def setArtist(self, artist):
"""Sets the release group's main artist.
@param artist: an L{Artist} object
"""
self._artist = artist
artist = property(getArtist, setArtist,
doc = 'The main artist of this release group')
class Track(Entity):
"""Represents a track.
This class represents a track which may appear on one or more releases.
A track may be associated with exactly one artist (the I{main} artist).
Using L{getReleases}, you can find out on which releases this track
appears. To get the track number, too, use the
L{Release.getTracksOffset} method.
@note: Currently, the MusicBrainz server doesn't support tracks to
be on more than one release.
@see: L{Release}, L{Artist}
"""
def __init__(self, id_=None, title=None):
"""Constructor.
@param id_: a string containing an absolute URI
@param title: a string containing the title
"""
Entity.__init__(self, id_)
self._title = title
self._artist = None
self._duration = None
self._puids = [ ]
self._releases = [ ]
self._isrcs = [ ]
def getTitle(self):
"""Returns the track's title.
The style and format of this attribute is specified by the
style guide.
@return: a string containing the title, or None
@see: U{The MusicBrainz Style Guidelines
}
"""
return self._title
def setTitle(self, title):
"""Sets the track's title.
@param title: a string containing the title
@see: L{getTitle}
"""
self._title = title
title = property(getTitle, setTitle, doc="The track's title.")
def getArtist(self):
"""Returns the main artist of this track.
@return: an L{Artist} object, or None
"""
return self._artist
def setArtist(self, artist):
"""Sets this track's main artist.
@param artist: an L{Artist} object
"""
self._artist = artist
artist = property(getArtist, setArtist, doc="The track's main artist.")
def getDuration(self):
"""Returns the duration of this track in milliseconds.
@return: an int containing the duration in milliseconds, or None
"""
return self._duration
def setDuration(self, duration):
"""Sets the duration of this track in milliseconds.
@param duration: an int containing the duration in milliseconds
"""
self._duration = duration
duration = property(getDuration, setDuration,
doc='The duration in milliseconds.')
def getDurationSplit(self):
"""Returns the duration as a (minutes, seconds) tuple.
If no duration is set, (0, 0) is returned. Seconds are
rounded towards the ceiling if at least 500 milliseconds
are left.
@return: a (minutes, seconds) tuple, both entries being ints
"""
duration = self.getDuration()
if duration is None:
return (0, 0)
else:
seconds = int( round(duration / 1000.0) )
return (seconds / 60, seconds % 60)
def getPuids(self):
"""Returns the PUIDs associated with this track.
Please note that a PUID may be associated with more than one
track.
@return: a list of strings, each containing one PUID
"""
return self._puids
puids = property(getPuids, doc='The list of associated PUIDs.')
def addPuid(self, puid):
"""Add a PUID to this track.
@param puid: a string containing a PUID
"""
self._puids.append(puid)
def getISRCs(self):
"""Returns the ISRCs associated with this track.
@return: a list of strings, each containing one ISRC
"""
return self._isrcs
isrcs = property(getISRCs, doc='The list of associated ISRCs')
def addISRC(self, isrc):
"""Add a ISRC to this track.
@param isrc: a string containing an ISRC
"""
self._isrcs.append(isrc)
def getReleases(self):
"""Returns the list of releases this track appears on.
@return: a list of L{Release} objects
"""
return self._releases
releases = property(getReleases,
doc='The releases on which this track appears.')
def addRelease(self, release):
"""Add a release on which this track appears.
@param release: a L{Release} object
"""
self._releases.append(release)
class Relation(object):
"""Represents a relation between two Entities.
There may be an arbitrary number of relations between all first
class objects in MusicBrainz. The Relation itself has multiple
attributes, which may or may not be used for a given relation
type.
Note that a L{Relation} object only contains the target but not
the source end of the relation.
@todo: Add some examples.
@cvar TO_ARTIST: Identifies relations linking to an artist.
@cvar TO_RELEASE: Identifies relations linking to a release.
@cvar TO_TRACK: Identifies relations linking to a track.
@cvar TO_URL: Identifies relations linking to an URL.
@cvar DIR_NONE: Relation reading direction doesn't matter.
@cvar DIR_FORWARD: Relation reading direction is from source to target.
@cvar DIR_BACKWARD: Relation reading direction is from target to source.
@cvar DIR_BOTH: Relation reading direction doesn't matter (no longer used!).
"""
# Relation target types
#
TO_ARTIST = NS_REL_1 + 'Artist'
TO_RELEASE = NS_REL_1 + 'Release'
TO_TRACK = NS_REL_1 + 'Track'
TO_URL = NS_REL_1 + 'Url'
# Relation reading directions
#
DIR_BOTH = 'both'
DIR_FORWARD = 'forward'
DIR_BACKWARD = 'backward'
DIR_NONE = 'none'
def __init__(self, relationType=None, targetType=None, targetId=None,
direction=DIR_NONE, attributes=None,
beginDate=None, endDate=None, target=None):
"""Constructor.
@param relationType: a string containing an absolute URI
@param targetType: a string containing an absolute URI
@param targetId: a string containing an absolute URI
@param direction: one of C{Relation.DIR_FORWARD},
C{Relation.DIR_BACKWARD}, or C{Relation.DIR_NONE}
@param attributes: a list of strings containing absolute URIs
@param beginDate: a string containing a date
@param endDate: a string containing a date
@param target: an instance of a subclass of L{Entity}
"""
self._relationType = relationType
self._targetType = targetType
self._targetId = targetId
self._direction = direction
self._beginDate = beginDate
self._endDate = endDate
self._target = target
self._attributes = attributes
if self._attributes is None:
self._attributes = [ ]
def getType(self):
"""Returns this relation's type.
@return: a string containing an absolute URI, or None
"""
return self._relationType
def setType(self, type_):
"""Sets this relation's type.
@param type_: a string containing an absolute URI
"""
self._relationType = type_
type = property(getType, setType, doc="The relation's type.")
def getTargetId(self):
"""Returns the target's ID.
This is the ID the relation points to. It is an absolute
URI, and in case of an URL relation, it is a URL.
@return: a string containing an absolute URI
"""
return self._targetId
def setTargetId(self, targetId):
"""Sets the target's ID.
@param targetId: a string containing an absolute URI
@see: L{getTargetId}
"""
self._targetId = targetId
targetId = property(getTargetId, setTargetId, doc="The target's ID.")
def getTargetType(self):
"""Returns the target's type.
For MusicBrainz data, the following target types are defined:
- artists: L{Relation.TO_ARTIST}
- releases: L{Relation.TO_RELEASE}
- tracks: L{Relation.TO_TRACK}
- urls: L{Relation.TO_URL}
@return: a string containing an absolute URI
"""
return self._targetType
def setTargetType(self, targetType):
"""Sets the target's type.
@param targetType: a string containing an absolute URI
@see: L{getTargetType}
"""
self._targetType = targetType
targetId = property(getTargetId, setTargetId,
doc="The type of target this relation points to.")
def getAttributes(self):
"""Returns a list of attributes describing this relation.
The attributes permitted depend on the relation type.
@return: a list of strings containing absolute URIs
"""
return self._attributes
attributes = property(getAttributes,
doc='The list of attributes describing this relation.')
def addAttribute(self, attribute):
"""Adds an attribute to the list.
@param attribute: a string containing an absolute URI
"""
self._attributes.append(attribute)
def getBeginDate(self):
"""Returns the begin date.
The definition depends on the relation's type. It may for
example be the day of a marriage or the year an artist
joined a band. For other relation types this may be
undefined.
@return: a string containing a date
"""
return self._beginDate
def setBeginDate(self, dateStr):
"""Sets the begin date.
@param dateStr: a string containing a date
@see: L{getBeginDate}
"""
self._beginDate = dateStr
beginDate = property(getBeginDate, setBeginDate, doc="The begin date.")
def getEndDate(self):
"""Returns the end date.
As with the begin date, the definition depends on the
relation's type. Depending on the relation type, this may
or may not be defined.
@return: a string containing a date
@see: L{getBeginDate}
"""
return self._endDate
def setEndDate(self, dateStr):
"""Sets the end date.
@param dateStr: a string containing a date
@see: L{getBeginDate}
"""
self._endDate = dateStr
endDate = property(getEndDate, setEndDate, doc="The end date.")
def getDirection(self):
"""Returns the reading direction.
The direction may be one of L{Relation.DIR_FORWARD},
L{Relation.DIR_BACKWARD}, or L{Relation.DIR_NONE},
depending on how the relation should be read. For example,
if direction is L{Relation.DIR_FORWARD} for a cover relation,
it is read as "X is a cover of Y". For some relations there is
no reading direction (like marriages) and the web service doesn't
send a direction. In these cases, the direction is set to
L{Relation.DIR_NONE}.
@return: L{Relation.DIR_FORWARD}, L{Relation.DIR_BACKWARD},
or L{Relation.DIR_NONE}
"""
return self._direction
def setDirection(self, direction):
"""Sets the reading direction.
@param direction: L{Relation.DIR_FORWARD},
L{Relation.DIR_BACKWARD}, or L{Relation.DIR_NONE}
@see: L{getDirection}
"""
self._direction = direction
direction = property(getDirection, setDirection,
doc="The reading direction.")
def getTarget(self):
"""Returns this relation's target object.
Note that URL relations never have a target object. Use the
L{getTargetId} method to get the URL.
@return: a subclass of L{Entity}, or None
"""
return self._target
def setTarget(self, target):
"""Sets this relation's target object.
Note that URL relations never have a target object, they
are set using L{setTargetId}.
@param target: a subclass of L{Entity}
"""
self._target = target
target = property(getTarget, setTarget,
doc="The relation's target object.")
class ReleaseEvent(object):
"""A release event, indicating where and when a release took place.
All country codes used must be valid ISO-3166 country codes (i.e. 'DE',
'UK' or 'FR'). The dates are strings and must have the format 'YYYY',
'YYYY-MM' or 'YYYY-MM-DD'.
The format of the release medium is a URI that can be compared to the
constants on this class (L{FORMAT_CD}, L{FORMAT_DVD} and others).
"""
FORMAT_CD = NS_MMD_1 + 'CD'
FORMAT_DVD = NS_MMD_1 + 'DVD'
FORMAT_SACD = NS_MMD_1 + 'SACD'
FORMAT_DUALDISC = NS_MMD_1 + 'DualDisc'
FORMAT_LASERDISC = NS_MMD_1 + 'LaserDisc'
FORMAT_MINIDISC = NS_MMD_1 + 'MiniDisc'
FORMAT_VINYL = NS_MMD_1 + 'Vinyl'
FORMAT_CASSETTE = NS_MMD_1 + 'Cassette'
FORMAT_CARTRIDGE = NS_MMD_1 + 'Cartridge'
FORMAT_REEL_TO_REEL = NS_MMD_1 + 'ReelToReel'
FORMAT_DAT = NS_MMD_1 + 'DAT'
FORMAT_DIGITAL = NS_MMD_1 + 'Digital'
FORMAT_WAX_CYLINDER = NS_MMD_1 + 'WaxCylinder'
FORMAT_PIANO_ROLL = NS_MMD_1 + 'PianoRoll'
FORMAT_OTHER = NS_MMD_1 + 'Other'
def __init__(self, country=None, dateStr=None):
"""Constructor.
@param country: a string containing an ISO-3166 country code
@param dateStr: a string containing a date string
"""
self._countryId = country
self._dateStr = dateStr
self._catalogNumber = None
self._barcode = None
self._label = None
self._format = None
def getCountry(self):
"""Returns the country a release took place.
@note: Due to a server limitation, the web service does not
return country IDs for release collection queries. This only
affects the L{musicbrainz2.webservice.Query.getReleases} query.
@return: a string containing an ISO-3166 country code, or None
@see: L{musicbrainz2.utils.getCountryName}
"""
return self._countryId
def setCountry(self, country):
"""Sets the country a release took place.
@param country: a string containing an ISO-3166 country code
"""
self._countryId = country
country = property(getCountry, setCountry,
doc='The country a release took place.')
def getCatalogNumber(self):
"""Returns the catalog number of this release event.
@return: A string containing the catalog number, or None
"""
return self._catalogNumber
def setCatalogNumber(self, catalogNumber):
"""Sets the catalog number of this release event.
@param catalogNumber: A string containing the catalog number
"""
self._catalogNumber = catalogNumber
catalogNumber = property(getCatalogNumber, setCatalogNumber,
doc='The catalog number of the release event')
def getBarcode(self):
"""Returns the barcode of this release event.
@return: A string containing the barcode, or None
"""
return self._barcode
def setBarcode(self, barcode):
"""Sets the barcode of this release event.
@param barcode: A string containing the barcode
"""
self._barcode = barcode
barcode = property(getBarcode, setBarcode,
doc='The barcode of the release event')
def getLabel(self):
"""Returns a L{Label} object for the label associated with this release.
@return: a L{Label} object, or None
"""
return self._label
def setLabel(self, label):
"""Sets the label of this release event.
@param label: A L{Label} object
"""
self._label = label
label = property(getLabel, setLabel, doc='The label of the release')
def getDate(self):
"""Returns the date a release took place.
@return: a string containing a date
"""
return self._dateStr
def setDate(self, dateStr):
"""Sets the date a release took place.
@param dateStr: a string containing a date
"""
self._dateStr = dateStr
date = property(getDate, setDate, doc='The date a release took place.')
def getFormat(self):
"""Returns the format of the release medium.
@return: a string containing a URI, or None
"""
return self._format
def setFormat(self, format):
"""Sets the format of the release medium.
@param format: a string containing a URI
"""
self._format = format
format = property(getFormat, setFormat,
doc='The format of the release medium.')
class CDStub(object):
"""Represents a CD Stub"""
def __init__(self, disc):
"""Constructor.
@param disc: a L{Disc} object to create this CD Stub from
"""
assert isinstance(disc, Disc), 'musicbrainz2.model.Disc expected'
self._disc = disc
self._tracks = [ ]
self._title = ""
self._artist = ""
self._barcode = ""
self._comment = ""
def setTitle(self, title):
"""Sets the title of this release.
@param title: a string containing the title
"""
self._title = title
def getTitle(self):
"""Returns the title of this release.
@return: a string containing the title
"""
return self._title
title = property(getTitle, setTitle,
doc='The title of the release')
def setArtist(self, artist):
"""Sets the artist of this release.
@param artist: a string containing the artist
"""
self._artist = artist
def getArtist(self):
"""Returns the artist of this release.
@return: a string containing the artist
"""
return self._artist
artist = property(getArtist, setArtist,
doc='The artist of the release')
def setComment(self, comment):
"""Sets the comment for this release.
@param comment: a string containing the comment
"""
self._comment = comment
def getComment(self):
"""Returns the comment for this release.
@return: a string containing the comment
"""
return self._comment
comment = property(getComment, setComment,
doc='Comment for the release (optional)')
def setBarcode(self, barcode):
"""Sets the barcode of this release.
@param barcode: a string containing the barcode
"""
self._barcode = barcode
def getBarcode(self):
"""Returns the barcode of this release.
@return: a string containing the barcode
"""
return self._barcode
barcode = property(getBarcode, setBarcode,
doc='Barcode for the release (optional)')
def addTrack(self, title, artist=''):
"""Add a track to this release
@param title: a string containing the title of the track
@param artist: a string containing the artist of the track,
if different to the album artist
"""
self._tracks.append((title, artist))
def getTracks(self):
"""Return all the tracks on the release.
@return: a list of tuples containing (title, artist) pairs
for each track
"""
return self._tracks
tracks = property(getTracks, doc='The tracks of the release.')
class Disc(object):
"""Represents an Audio CD.
This class represents an Audio CD. A disc can have an ID (the
MusicBrainz DiscID), which is calculated from the CD's table of
contents (TOC). There may also be data from the TOC like the length
of the disc in sectors, as well as position and length of the tracks.
Note that different TOCs, maybe due to different pressings, lead to
different DiscIDs. Conversely, if two different discs have the same
TOC, they also have the same DiscID (which is unlikely but not
impossible). DiscIDs are always 28 characters long and look like this:
C{'J68I_CDcUFdCRCIbHSEbTBCbooA-'}. Sometimes they are also referred
to as CDIndex IDs.
The L{MusicBrainz web service } only returns
the DiscID and the number of sectors. The DiscID calculation function
L{musicbrainz2.disc.readDisc}, however, can retrieve the other
attributes of L{Disc} from an Audio CD in the disc drive.
"""
def __init__(self, id_=None):
"""Constructor.
@param id_: a string containing a 28-character DiscID
"""
self._id = id_
self._sectors = None
self._firstTrackNum = None
self._lastTrackNum = None
self._tracks = [ ]
def getId(self):
"""Returns the MusicBrainz DiscID.
@return: a string containing a 28-character DiscID
"""
return self._id
def setId(self, id_):
"""Sets the MusicBrainz DiscId.
@param id_: a string containing a 28-character DiscID
"""
self._id = id_
id = property(getId, setId, doc="The MusicBrainz DiscID.")
def getSectors(self):
"""Returns the length of the disc in sectors.
@return: the length in sectors as an integer, or None
"""
return self._sectors
def setSectors(self, sectors):
"""Sets the length of the disc in sectors.
@param sectors: the length in sectors as an integer
"""
self._sectors = sectors
sectors = property(getSectors, setSectors,
doc="The length of the disc in sectors.")
def getFirstTrackNum(self):
"""Returns the number of the first track on this disc.
@return: an int containing the track number, or None
"""
return self._firstTrackNum
def setFirstTrackNum(self, trackNum):
"""Sets the number of the first track on this disc.
@param trackNum: an int containing the track number, or None
"""
self._firstTrackNum = trackNum
firstTrackNum = property(getFirstTrackNum, setFirstTrackNum,
doc="The number of the first track on this disc.")
def getLastTrackNum(self):
"""Returns the number of the last track on this disc.
@return: an int containing the track number, or None
"""
return self._lastTrackNum
def setLastTrackNum(self, trackNum):
"""Sets the number of the last track on this disc.
@param trackNum: an int containing the track number, or None
"""
self._lastTrackNum = trackNum
lastTrackNum = property(getLastTrackNum, setLastTrackNum,
doc="The number of the last track on this disc.")
def getTracks(self):
"""Returns the sector offset and length of this disc.
This method returns a list of tuples containing the track
offset and length in sectors for all tracks on this disc.
The track offset is measured from the beginning of the disc,
the length is relative to the track's offset. Note that the
leadout track is I{not} included.
@return: a list of (offset, length) tuples (values are ints)
"""
return self._tracks
tracks = property(getTracks,
doc='Sector offset and length of all tracks.')
def addTrack(self, track):
"""Adds a track to the list.
This method adds an (offset, length) tuple to the list of
tracks. The leadout track must I{not} be added. The total
length of the disc can be set using L{setSectors}.
@param track: an (offset, length) tuple (values are ints)
@see: L{getTracks}
"""
self._tracks.append(track)
class AbstractAlias(object):
"""An abstract super class for all alias classes."""
def __init__(self, value=None, type_=None, script=None):
"""Constructor.
@param value: a string containing the alias
@param type_: a string containing an absolute URI
@param script: a string containing an ISO-15924 script code
"""
self._value = value
self._type = type_
self._script = script
def getValue(self):
"""Returns the alias.
@return: a string containing the alias
"""
return self._value
def setValue(self, value):
"""Sets the alias.
@param value: a string containing the alias
"""
self._value = value
value = property(getValue, setValue, doc='The alias value.')
def getType(self):
"""Returns the alias type.
@return: a string containing an absolute URI, or None
"""
return self._type
def setType(self, type_):
"""Sets the alias type.
@param type_: a string containing an absolute URI, or None
"""
self._type = type_
type = property(getType, setType, doc='The alias type.')
def getScript(self):
"""Returns the alias script.
@return: a string containing an ISO-15924 script code
"""
return self._script
def setScript(self, script):
"""Sets the alias script.
@param script: a string containing an ISO-15924 script code
"""
self._script = script
script = property(getScript, setScript, doc='The alias script.')
class ArtistAlias(AbstractAlias):
"""Represents an artist alias.
An alias (the I{alias value}) is a different representation of an
artist's name. This may be a common misspelling or a transliteration
(the I{alias type}).
The I{alias script} is interesting mostly for transliterations and
indicates which script is used for the alias value. To represent the
script, ISO-15924 script codes like 'Latn', 'Cyrl', or 'Hebr' are used.
"""
pass
class LabelAlias(AbstractAlias):
"""Represents a label alias.
An alias (the I{alias value}) is a different representation of a
label's name. This may be a common misspelling or a transliteration
(the I{alias type}).
The I{alias script} is interesting mostly for transliterations and
indicates which script is used for the alias value. To represent the
script, ISO-15924 script codes like 'Latn', 'Cyrl', or 'Hebr' are used.
"""
pass
class User(object):
"""Represents a MusicBrainz user."""
def __init__(self):
"""Constructor."""
self._name = None
self._types = [ ]
self._showNag = None
def getName(self):
"""Returns the user name.
@return: a string containing the user name
"""
return self._name
def setName(self, name):
"""Sets the user name.
@param name: a string containing the user name
"""
self._name = name
name = property(getName, setName, doc='The MusicBrainz user name.')
def getTypes(self):
"""Returns the types of this user.
Most users' type list is empty. Currently, the following types
are defined:
- 'http://musicbrainz.org/ns/ext-1.0#AutoEditor'
- 'http://musicbrainz.org/ns/ext-1.0#RelationshipEditor'
- 'http://musicbrainz.org/ns/ext-1.0#Bot'
- 'http://musicbrainz.org/ns/ext-1.0#NotNaggable'
@return: a list of strings containing absolute URIs
"""
return self._types
types = property(getTypes, doc="The user's types.")
def addType(self, type_):
"""Add a type to the list of types.
@param type_: a string containing absolute URIs
@see: L{getTypes}
"""
self._types.append(type_)
def getShowNag(self):
"""Returns true if a nag screen should be displayed to the user.
@return: C{True}, C{False}, or None
"""
return self._showNag
def setShowNag(self, value):
"""Sets the value of the nag screen flag.
If set to C{True},
@param value: C{True} or C{False}
@see: L{getShowNag}
"""
self._showNag = value
showNag = property(getShowNag, setShowNag,
doc='The value of the nag screen flag.')
# EOF
python-musicbrainz2-0.7.4/src/musicbrainz2/utils.py 0000644 0001750 0001750 00000013441 11654514476 021422 0 ustar lukas lukas """Various utilities to simplify common tasks.
This module contains helper functions to make common tasks easier.
@author: Matthias Friedrich
"""
__revision__ = '$Id: utils.py 13322 2011-11-03 13:38:06Z luks $'
import re
import urlparse
__all__ = [
'extractUuid', 'extractFragment', 'extractEntityType',
'getReleaseTypeName', 'getCountryName', 'getLanguageName',
'getScriptName',
]
# A pattern to split the path part of an absolute MB URI.
PATH_PATTERN = '^/(artist|release|track|label|release-group)/([^/]*)$'
def extractUuid(uriStr, resType=None):
"""Extract the UUID part from a MusicBrainz identifier.
This function takes a MusicBrainz ID (an absolute URI) as the input
and returns the UUID part of the URI, thus turning it into a relative
URI. If C{uriStr} is None or a relative URI, then it is returned
unchanged.
The C{resType} parameter can be used for error checking. Set it to
'artist', 'release', or 'track' to make sure C{uriStr} is a
syntactically valid MusicBrainz identifier of the given resource
type. If it isn't, a C{ValueError} exception is raised.
This error checking only works if C{uriStr} is an absolute URI, of
course.
Example:
>>> from musicbrainz2.utils import extractUuid
>>> extractUuid('http://musicbrainz.org/artist/c0b2500e-0cef-4130-869d-732b23ed9df5', 'artist')
'c0b2500e-0cef-4130-869d-732b23ed9df5'
>>>
@param uriStr: a string containing a MusicBrainz ID (an URI), or None
@param resType: a string containing a resource type
@return: a string containing a relative URI, or None
@raise ValueError: the given URI is no valid MusicBrainz ID
"""
if uriStr is None:
return None
(scheme, netloc, path) = urlparse.urlparse(uriStr)[:3]
if scheme == '':
return uriStr # no URI, probably already the UUID
if scheme != 'http' or netloc != 'musicbrainz.org':
raise ValueError('%s is no MB ID.' % uriStr)
m = re.match(PATH_PATTERN, path)
if m:
if resType is None:
return m.group(2)
else:
if m.group(1) == resType:
return m.group(2)
else:
raise ValueError('expected "%s" Id' % resType)
else:
raise ValueError('%s is no valid MB ID.' % uriStr)
def extractFragment(uriStr, uriPrefix=None):
"""Extract the fragment part from a URI.
If C{uriStr} is None or no absolute URI, then it is returned unchanged.
The C{uriPrefix} parameter can be used for error checking. If C{uriStr}
is an absolute URI, then the function checks if it starts with
C{uriPrefix}. If it doesn't, a C{ValueError} exception is raised.
@param uriStr: a string containing an absolute URI
@param uriPrefix: a string containing an URI prefix
@return: a string containing the fragment, or None
@raise ValueError: the given URI doesn't start with C{uriPrefix}
"""
if uriStr is None:
return None
(scheme, netloc, path, params, query, frag) = urlparse.urlparse(uriStr)
if scheme == '':
return uriStr # this is no URI
if uriPrefix is None or uriStr.startswith(uriPrefix):
return frag
else:
raise ValueError("prefix doesn't match URI %s" % uriStr)
def extractEntityType(uriStr):
"""Returns the entity type an entity URI is referring to.
@param uriStr: a string containing an absolute entity URI
@return: a string containing 'artist', 'release', 'track', or 'label'
@raise ValueError: if the given URI is no valid MusicBrainz ID
"""
if uriStr is None:
raise ValueError('None is no valid entity URI')
(scheme, netloc, path) = urlparse.urlparse(uriStr)[:3]
if scheme == '':
raise ValueError('%s is no absolute MB ID.' % uriStr)
if scheme != 'http' or netloc != 'musicbrainz.org':
raise ValueError('%s is no MB ID.' % uriStr)
m = re.match(PATH_PATTERN, path)
if m:
return m.group(1)
else:
raise ValueError('%s is no valid MB ID.' % uriStr)
def getReleaseTypeName(releaseType):
"""Returns the name of a release type URI.
@param releaseType: a string containing a release type URI
@return: a string containing a printable name for the release type
@see: L{musicbrainz2.model.Release}
"""
from musicbrainz2.data.releasetypenames import releaseTypeNames
return releaseTypeNames.get(releaseType)
def getCountryName(id_):
"""Returns a country's name based on an ISO-3166 country code.
The country table this function is based on has been modified for
MusicBrainz purposes by using the extension mechanism defined in
ISO-3166. All IDs are still valid ISO-3166 country codes, but some
IDs have been added to include historic countries and some of the
country names have been modified to make them better suited for
display purposes.
If the country ID is not found, None is returned. This may happen
for example, when new countries are added to the MusicBrainz web
service which aren't known to this library yet.
@param id_: a two-letter upper case string containing an ISO-3166 code
@return: a string containing the country's name, or None
@see: L{musicbrainz2.model}
"""
from musicbrainz2.data.countrynames import countryNames
return countryNames.get(id_)
def getLanguageName(id_):
"""Returns a language name based on an ISO-639-2/T code.
This function uses a subset of the ISO-639-2/T code table to map
language IDs (terminologic, not bibliographic ones!) to names.
@param id_: a three-letter upper case string containing an ISO-639-2/T code
@return: a string containing the language's name, or None
@see: L{musicbrainz2.model}
"""
from musicbrainz2.data.languagenames import languageNames
return languageNames.get(id_)
def getScriptName(id_):
"""Returns a script name based on an ISO-15924 code.
This function uses a subset of the ISO-15924 code table to map
script IDs to names.
@param id_: a four-letter string containing an ISO-15924 script code
@return: a string containing the script's name, or None
@see: L{musicbrainz2.model}
"""
from musicbrainz2.data.scriptnames import scriptNames
return scriptNames.get(id_)
# EOF
python-musicbrainz2-0.7.4/CHANGES.txt 0000644 0001750 0001750 00000013475 11655171107 016320 0 ustar lukas lukas Changes in 0.7.4:
* Support for setting custom User-Agent string
Changes in 0.7.3:
* Fixed typo in Artist.addRelease (#4076)
* Fixed HTTP Digest Authentication with the NGS web service (#5905)
Changes in 0.7.2:
* Added support for CD stub submission (patch by Alastair Porter).
Changes in 0.7.1:
* Added release group support (patch by John J. Jordan, review by Luks).
* Added support for user collections (patch by Alastair Porter).
* Fixed libdiscid detection on OpenBSD (patch by Anton Yabchinskiy).
* Export model.Rating.
* Minor documentation cleanups.
Changes in 0.7.0:
* Added ISRC support (patch by Alastair Porter).
* Added rating support (patch by Peter Schnebel).
* Added a trackCount parameter to ReleaseFilter (#3781).
* Changed ReleaseFilter and TrackFilter to accept URIs for artistId
and releaseId (#4746).
* Fixed serialization of relations in MbXmlWriter (#4394).
* Fixed a crash on 64-bit systems in libdiscid bindings (Luks).
* Fixed a deprecation warning: use builtin set if available (#4956).
Changes in 0.6.0:
* Added folksonomy tagging support (mostly by Philipp Wolfer, #3285):
- Added getTag(), getTags(), and addTag() methods to model.Entity.
- Added getUserTags() and submitUserTags() to webservice.Query.
- Added an example script: examples/folksonomytags.py.
* Relation.DIR_BOTH is no longer used since the web service doesn't
know bidirectional relations. If no direction is returned, the client
library maps it to Relation.DIR_NONE. Note that this is *not* entirely
backwards compatible (#3034).
* Added "WaxCylinder" and "PianoRoll" to ReleaseEvent (#3231).
* Changed download URL to HTTP for installation via setuptools (#3202).
* Warn if epydoc isn't found when installing from source (by intgr, #3124).
Changes in 0.5.0:
* Added label support (based on Oliver's patch in #2671):
- Added the model.Label and model.LabelAlias classes.
- Added a label property to model.Release.
- Updated the webservice.Query class.
- Added webservice.LabelFilter and webservice.LabelIncludes.
- Updated wsxml.Metadata, wsxml.MbXmlParser, and wsxml.MbXmlWriter.
* Added catalog number and barcode properties to model.ReleaseEvent.
* Changes to IFilter:
- Added a query parameter to pass in Lucene queries.
- Added an offset parameter to support paging through results.
* Added count and offset properties to all Result classes in wsxml.
Changes in 0.4.1:
* Access to a CD drive other than the default drive works again (Luks).
* IWebService.post() now returns a file-like object instead of None (Luks).
* Added the "Artificial" language and the "Pseudo-Release" type (Luks).
Changes in 0.4.0:
* DiscID calculation now uses libdiscid instead of libmusicbrainz2.
* Fixed the IFilter subclasses to properly encode strings (#2148)
* Added several count and offset related methods in the model package.
* Added Entity.getRelationTargets() for easier relation handling.
* Added two (optional) parameters for Entity.getRelations().
* Added mb-submit-disc, a tool for submitting DiscIDs to MusicBrainz.
Changes in 0.3.2:
* Made the package work inside a zip file for py2exe (#1411, Luks).
* Added the mb-submit-disc tool.
Changes in 0.3.1:
* Fixed webservice.WebService.post(), to make PUID submission work (Luks).
* Added and updated package metadata in setup.py.
Changes in 0.3.0:
* API changes in model.Query to support relevance scores (#1175):
- Changed getArtists() to return a list of ArtistResult objects.
- Changed getReleases() to return a list of ReleaseResult objects.
- Changed getTracks() to return a list of TrackResult objects.
* API changes in model.ReleaseEvent:
- Renamed getCountryId() to getCountry()
- Renamed __init__()'s 'countryId' parameter to 'country'.
* API changes in model.Artist:
- Renamed setType()'s 'typeUri' parameter to 'type_'.
* API changes in model.ArtistAlias:
- Renamed __init__()'s and setType()'s 'typeUri' parameter to 'type_'.
* API changes to wsxml.Metadata:
- Removed getArtistList(), getReleaseList(), getTrackList()
- Added getArtistResults(), getReleaseResults(), and getTrackResults(),
returning ArtistResult, ReleaseResult, and TrackResult objects,
respectively.
* General API changes:
- Classes are now derived from 'object' (new-style classes).
- Marked all supposedly private attributes as private ('attr' -> '_attr').
- Marked the DOM utility functions in wsxml.py as private.
* All getter and setter methods are now exposed as properties, too.
* Added the class wsxml.MbXmlWriter which generates MMD XML.
* Fixed model.Release.TYPE_EP ('Ep' -> 'EP').
* Added TYPE_PERSON and TYPE_GROUP constans to model.Artist.
* Added functions to the utils module to map country, language, script
and release type IDs to printable strings.
* Modules now have __all__, which makes 'import *' possible (but don't do it).
* The country code in model.ReleaseEvent is now optional. This is due to
the server which doesn't provide country codes for release collection
queries (and just the earliest date).
* Added model.Release.getEarliestReleaseEvent() (#1260).
* Added more example code and improved the documentation.
Changes in 0.2.2:
* Added the musicbrainz2.utils module (#1192).
Changes in 0.2.1:
* Release date parsing has been fixed (#1181).
* ctypes 0.9.9.3 is now supported.
* Authentication now works even after HTTP redirects (#1166).
* Added type and script support to ArtistAlias.
Changes in 0.2.0:
* API change: Switched from TRM to PUID (Luks).
All interfaces with 'Trm' or 'trm' in the name are affected.
* python 2.3 compatibility fixes (#1160)
Changes in 0.1.0:
* Initial release.
--
$Id: CHANGES.txt 13330 2011-11-05 08:22:31Z luks $
python-musicbrainz2-0.7.4/README.txt 0000644 0001750 0001750 00000002242 11655170567 016204 0 ustar lukas lukas Python Bindings for the MusicBrainz XML Web Service
---------------------------------------------------
This python package contains various modules for accessing the MusicBrainz
web service, as well as parsing the MusicBrainz Metadata XML (MMD), or
calculating DiscIDs from Audio CDs.
Except for the DiscID generation, everything should work with standard
python 2.3 or later. However, for DiscID calculation, libdiscid and the
ctypes package are required. See the installation instructions for details.
To get started quickly have a look at the examples directory which
contains various sample scripts. API documentation can be generated
using epydoc (http://epydoc.sourceforge.net).
Please report all bugs you find via the MusicBrainz bug tracker:
http://tickets.musicbrainz.org/
Questions about this package may be posted to the MusicBrainz
development mailing list (mb-devel):
http://musicbrainz.org/list.html
More information can be found at the package's official homepage. It
also contains an FAQ section which could be of help in case you run into
problems:
http://musicbrainz.org/products/python-musicbrainz2/
--
$Id: README.txt 13328 2011-11-05 08:19:03Z luks $
python-musicbrainz2-0.7.4/test/ 0000755 0001750 0001750 00000000000 11655171135 015455 5 ustar lukas lukas python-musicbrainz2-0.7.4/test/test_wsxml_releasegroup.py 0000644 0001750 0001750 00000007567 11231304732 023023 0 ustar lukas lukas """Tests for parsing release groups using MbXmlParser."""
import unittest
from musicbrainz2.wsxml import MbXmlParser, MbXmlWriter, ParseError
from musicbrainz2.model import ReleaseGroup, NS_MMD_1
from StringIO import StringIO
import os.path
VALID_DATA_DIR = os.path.join('test-data', 'valid')
# Only have tests for valid data, ATM.
RELEASEGROUP_DIR = os.path.join(VALID_DATA_DIR, 'release-group')
def makeId(relativeUri):
return 'http://musicbrainz.org/release-group/' + relativeUri
class ParseReleaseGroupTest(unittest.TestCase):
def testReleaseGroupBasic(self):
f = os.path.join(RELEASEGROUP_DIR, 'The_Cure_1.xml')
md = MbXmlParser().parse(f)
releaseGroup = md.getReleaseGroup()
self.failIf(releaseGroup is None)
self.assertEquals(releaseGroup.getId(),
makeId('c6a62b78-70f7-44f7-b159-064f6b7ba03a'))
self.assertEquals(releaseGroup.getTitle(), 'The Cure')
self.assertEquals(releaseGroup.getType(), NS_MMD_1 + 'Album')
def testReleaseGroupFull(self):
f = os.path.join(RELEASEGROUP_DIR, 'The_Cure_1.xml')
md = MbXmlParser().parse(f)
releaseGroup = md.getReleaseGroup()
self.failIf(releaseGroup is None)
releases = releaseGroup.getReleases()
self.failIf(releases is None)
self.assertEquals(len(releases), 4)
# Check releases, which are in no particular order.
expectedIds = [
'd984e1a3-7281-46bb-ad8b-1478a00f2fbf',
'c100a398-3132-48a8-a5fc-c3e908ac17dc',
'24bec892-b21d-47d8-a288-dc6450152574',
'61a4ec51-fa34-4757-85d7-83231776ed14']
actualIds = [release.id[-36:] for release in releases]
for expectedId in expectedIds:
self.assert_(expectedId in actualIds)
# Check artist
self.assertEquals(releaseGroup.getArtist().getName(), 'The Cure')
self.assertEquals(releaseGroup.getArtist().id[-36:],
'69ee3720-a7cb-4402-b48d-a02c366f2bcf')
def testSearchResults(self):
f = os.path.join(RELEASEGROUP_DIR, 'search_result_1.xml')
md = MbXmlParser().parse(f)
releaseGroups = md.getReleaseGroupResults()
self.failIf(releaseGroups is None)
self.assertEquals(md.getReleaseGroupResultsOffset(), 0)
self.assertEquals(md.getReleaseGroupResultsCount(), 3)
expectedEntries = {
'963eac15-e3da-3a92-aa5c-2ec23bfb6ec2': ['Signal Morning', 100],
'0bd324a3-1c90-3bdb-8ca4-4101a580c62c': ['Circulatory System', 98],
'ea7d8352-7751-30be-8490-bb6df737f47c': ['Inside Views', 90]}
for result in releaseGroups:
releaseGroup = result.releaseGroup
self.failIf(releaseGroup is None)
releaseGroupId = releaseGroup.id[-36:]
self.assert_(releaseGroupId in expectedEntries)
expectedTitle, expectedScore = expectedEntries[releaseGroupId]
self.assertEquals(releaseGroup.title, expectedTitle)
self.assertEquals(result.score, expectedScore)
del expectedEntries[releaseGroupId]
def testSerialize(self):
# Get initial.
f = os.path.join(RELEASEGROUP_DIR, 'The_Cure_1.xml')
md1 = MbXmlParser().parse(f)
# Serialize.
outbuffer = StringIO()
MbXmlWriter().write(outbuffer, md1)
# Deserialize.
inbuffer = StringIO(outbuffer.getvalue().encode('utf-8'))
md2 = MbXmlParser().parse(inbuffer)
# Check
releaseGroup = md2.getReleaseGroup()
self.failIf(releaseGroup is None)
self.assertEquals(len(releaseGroup.getReleases()), 4)
self.assertEquals(releaseGroup.getId(),
makeId('c6a62b78-70f7-44f7-b159-064f6b7ba03a'))
self.assertEquals(releaseGroup.getTitle(), 'The Cure')
self.assertEquals(releaseGroup.getType(), NS_MMD_1 + 'Album')
def testEmptyType(self):
f = os.path.join(RELEASEGROUP_DIR, 'The_Cure_1.xml')
md1 = MbXmlParser().parse(f)
releaseGroup1 = md1.getReleaseGroup()
releaseGroup1.setType(None)
# Serialize.
outbuffer = StringIO()
MbXmlWriter().write(outbuffer, md1)
inbuffer = StringIO(outbuffer.getvalue().encode('utf-8'))
md2 = MbXmlParser().parse(inbuffer)
# Check
releaseGroup2 = md2.getReleaseGroup()
self.failIf(releaseGroup2 is None)
self.assertEquals(releaseGroup2.getType(), None)
python-musicbrainz2-0.7.4/test/__init__.py 0000644 0001750 0001750 00000000065 10367417216 017571 0 ustar lukas lukas """This package contains modules with unit tests."""
python-musicbrainz2-0.7.4/test/test_model.py 0000644 0001750 0001750 00000006231 11444132513 020161 0 ustar lukas lukas """Tests for various model classes."""
import unittest
from musicbrainz2.model import Artist, Release, Track, Relation, Tag, NS_REL_1
class MiscModelTest(unittest.TestCase):
def __init__(self, name):
unittest.TestCase.__init__(self, name)
def testAddRelation(self):
rel = Relation(NS_REL_1+'Producer', Relation.TO_RELEASE, 'a_id', attributes=[NS_REL_1+'Co'])
artist = Artist('ar_id', 'Tori Amos', 'Person')
artist.addRelation(rel)
rel2 = artist.getRelations(Relation.TO_RELEASE)[0]
self.assertEquals(rel.getType(), rel2.getType())
self.assertEquals(rel.getTargetType(), rel2.getTargetType())
self.assertEquals(rel.getTargetId(), rel2.getTargetId())
self.assertEquals(rel.getAttributes(), rel2.getAttributes())
self.assertEquals(rel.getBeginDate(), rel2.getBeginDate())
self.assertEquals(rel.getEndDate(), rel2.getEndDate())
self.assertEquals(artist.getRelationTargetTypes(),
[ Relation.TO_RELEASE ])
# works because we only have one relation
self.assertEquals(artist.getRelations(),
artist.getRelations(Relation.TO_RELEASE))
rel3 = artist.getRelations(Relation.TO_RELEASE,
NS_REL_1 + 'Producer')
self.assertEquals(len(rel3), 1)
rel4 = artist.getRelations(Relation.TO_RELEASE,
NS_REL_1 + 'Producer', [NS_REL_1 + 'Co'])
self.assertEquals(len(rel4), 1)
rel5 = artist.getRelations(Relation.TO_RELEASE,
NS_REL_1 + 'Producer', [NS_REL_1 + 'NotThere'])
self.assertEquals(len(rel5), 0)
rel6 = artist.getRelations(Relation.TO_RELEASE,
NS_REL_1 + 'Producer', [NS_REL_1 + 'Co'], 'none')
self.assertEquals(len(rel6), 1)
rel6 = artist.getRelations(Relation.TO_RELEASE,
NS_REL_1 + 'Producer', [NS_REL_1 + 'Co'], 'forward')
self.assertEquals(len(rel6), 0)
def testTrackDuration(self):
t = Track()
self.assert_( t.getDuration() is None )
self.assert_( t.getDurationSplit() == (0, 0) )
t.setDuration(0)
self.assert_( t.getDurationSplit() == (0, 0) )
t.setDuration(218666)
self.assert_( t.getDurationSplit() == (3, 39) )
def testReleaseIsSingleArtist(self):
r = Release()
r.setArtist(Artist(id_=1))
r.addTrack(Track())
r.addTrack(Track())
self.assert_( r.isSingleArtistRelease() )
r.getTracks()[0].setArtist(Artist(id_=7))
r.getTracks()[1].setArtist(Artist(id_=8))
self.assert_( r.isSingleArtistRelease() == False )
r.getTracks()[0].setArtist(Artist(id_=1))
r.getTracks()[1].setArtist(Artist(id_=1))
self.assert_( r.isSingleArtistRelease() )
def testTags(self):
a = Artist()
a.addTag(Tag('foo', 1))
a.addTag(Tag('bar', 2))
a.addTag(Tag('bar', 5))
self.assertEquals(len(a.tags), 2)
self.assertEquals(a.getTag('foo').count, 1)
self.assertEquals(a.getTag('bar').count, 7)
class TagTest(unittest.TestCase):
def test__str__(self):
self.assertEquals("foo", str(Tag(u"foo")))
self.assertRaises(UnicodeEncodeError, str, Tag(u"f\u014do"))
def test__unicode__(self):
self.assertEquals(u"foo", unicode(Tag(u"foo")))
self.assertEquals(u"f\u014do", unicode(Tag(u"f\u014do")))
class ArtistTest(unittest.TestCase):
def testAddRelease(self):
release = Release()
artist = Artist()
artist.addRelease(release)
self.assertEquals(artist.releases, [release])
# EOF
python-musicbrainz2-0.7.4/test/test_first.py 0000644 0001750 0001750 00000000234 10367417216 020216 0 ustar lukas lukas import unittest
class First(unittest.TestCase):
def testRun(self):
self.assert_( True )
def suite():
suite = unittest.makeSuite(First)
return suite
python-musicbrainz2-0.7.4/test/test_wsxml_user.py 0000644 0001750 0001750 00000001721 10375126170 021274 0 ustar lukas lukas """Tests for parsing user elements using MbXmlParser."""
import unittest
from musicbrainz2.wsxml import MbXmlParser, ParseError
from musicbrainz2.model import NS_MMD_1, NS_EXT_1
import StringIO
import os.path
VALID_DATA_DIR = os.path.join('test-data', 'valid')
INVALID_DATA_DIR = os.path.join('test-data', 'invalid')
VALID_USER_DIR = os.path.join(VALID_DATA_DIR, 'user')
class ParseUserTest(unittest.TestCase):
def __init__(self, name):
unittest.TestCase.__init__(self, name)
def testUser(self):
f = os.path.join(VALID_USER_DIR, 'User_1.xml')
md = MbXmlParser().parse(f)
userList = md.getUserList()
self.assertEquals(len(userList), 1)
user = userList[0]
self.failIf( user is None )
self.assertEquals(user.getName(), 'matt')
self.assertEquals(user.getShowNag(), False)
types = user.getTypes()
self.assertEquals(len(types), 2)
self.assert_( NS_EXT_1 + 'AutoEditor' in types )
self.assert_( NS_EXT_1 + 'RelationshipEditor' in types )
# EOF
python-musicbrainz2-0.7.4/test/test_ws_query.py 0000644 0001750 0001750 00000014261 11347471065 020753 0 ustar lukas lukas """Tests for webservice.Query."""
import unittest
from musicbrainz2.model import Tag
from musicbrainz2.model import Rating
from musicbrainz2.model import Release
from musicbrainz2.webservice import Query, IWebService, AuthenticationError, RequestError
class FakeWebService(IWebService):
def __init__(self):
self.data = []
def post(self, entity, id_, data, version='1'):
self.data.append((entity, id_, data, version))
class FakeBadAuthWebService(IWebService):
def post(self, entity, id_, data, version='1'):
raise AuthenticationError()
class FakeBadRequestWebService(IWebService):
def post(self, entity, id_, data, version='1'):
raise RequestError()
class QueryTest(unittest.TestCase):
def testAddToUserCollection(self):
ws = FakeWebService()
q = Query(ws)
r1 = "9e186398-9ae2-45bf-a9f6-d26bc350221e"
r2 = "http://musicbrainz.org/release/6b050dcf-7ab1-456d-9e1b-c3c41c18eed2"
r3 = Release("d3cc336e-1010-4252-9091-7923f0429824")
q.addToUserCollection([r1, r2, r3])
self.assertEquals(len(ws.data), 1)
res = ws.data[0]
self.assertEquals(res[0], "collection")
self.assertEquals(res[2], "add=9e186398-9ae2-45bf-a9f6-d26bc350221e%2C6b050dcf-7ab1-456d-9e1b-c3c41c18eed2%2Cd3cc336e-1010-4252-9091-7923f0429824")
def testRemoveFromUserCollection(self):
ws = FakeWebService()
q = Query(ws)
r1 = "9e186398-9ae2-45bf-a9f6-d26bc350221e"
r2 = "http://musicbrainz.org/release/6b050dcf-7ab1-456d-9e1b-c3c41c18eed2"
r3 = Release("d3cc336e-1010-4252-9091-7923f0429824")
q.removeFromUserCollection([r1, r2, r3])
self.assertEquals(len(ws.data), 1)
res = ws.data[0]
self.assertEquals(res[0], "collection")
self.assertEquals(res[2], "remove=9e186398-9ae2-45bf-a9f6-d26bc350221e%2C6b050dcf-7ab1-456d-9e1b-c3c41c18eed2%2Cd3cc336e-1010-4252-9091-7923f0429824")
def testSubmitUserTags(self):
ws = FakeWebService()
q = Query(ws)
t1 = [u"foo", u"bar", u"f\u014do"]
t2 = [Tag(u"foo"), Tag(u"bar"), Tag(u"f\u014do")]
prefix = 'http://musicbrainz.org/artist/'
uri = prefix + 'c0b2500e-0cef-4130-869d-732b23ed9df5'
q.submitUserTags(uri, t1)
q.submitUserTags(uri, t2)
self.assertEquals(len(ws.data), 2)
self.assertEquals(ws.data[0], ws.data[1])
q.submitUserRating(uri, Rating(5))
q.submitUserRating(uri, 5)
self.assertEquals(len(ws.data), 4)
self.assertEquals(ws.data[2], ws.data[3])
def testSubmitIsrc(self):
tracks2isrcs = {
'6a47088b-d9e0-4088-868a-394ee3c6cd33':'NZABC0800001',
'b547acbc-58c6-4a31-9806-e2348db3a167':'NZABC0800002'
}
ws = FakeWebService()
q = Query(ws)
q.submitISRCs(tracks2isrcs)
self.assertEquals(len(ws.data), 1)
req = ws.data[0]
qstring = 'isrc=6a47088b-d9e0-4088-868a-394ee3c6cd33+NZABC0800001&isrc=b547acbc-58c6-4a31-9806-e2348db3a167+NZABC0800002'
self.assertEquals(req[0], 'track')
self.assertEquals(req[2], qstring)
def testSubmitIsrcBadUser(self):
ws = FakeBadAuthWebService()
q = Query(ws)
self.assertRaises(AuthenticationError, q.submitISRCs, {})
def testSubmitIsrcBadTrack(self):
ws = FakeBadRequestWebService()
q = Query(ws)
self.assertRaises(RequestError, q.submitISRCs, {})
def testSubmitPuid(self):
tracks2puids = {
'6a47088b-d9e0-4088-868a-394ee3c6cd33':'c2a2cee5-a8ca-4f89-a092-c3e1e65ab7e6',
'b547acbc-58c6-4a31-9806-e2348db3a167':'c2a2cee5-a8ca-4f89-a092-c3e1e65ab7e6'
}
ws = FakeWebService()
q = Query(ws, clientId='test-1')
q.submitPuids(tracks2puids)
self.assertEquals(len(ws.data), 1)
req = ws.data[0]
qstring = 'client=test-1&puid=6a47088b-d9e0-4088-868a-394ee3c6cd33+c2a2cee5-a8ca-4f89-a092-c3e1e65ab7e6&puid=b547acbc-58c6-4a31-9806-e2348db3a167+c2a2cee5-a8ca-4f89-a092-c3e1e65ab7e6'
self.assertEquals(req[0], 'track')
self.assertEquals(req[2], qstring)
def testSubmitPuidBadUser(self):
ws = FakeBadAuthWebService()
q = Query(ws, clientId='test-1')
self.assertRaises(AuthenticationError, q.submitPuids, {})
def testSubmitPuidBadTrack(self):
ws = FakeBadRequestWebService()
q = Query(ws, clientId='test-1')
self.assertRaises(RequestError, q.submitPuids, {})
def testSubmitPuidNoClient(self):
ws = FakeWebService()
q = Query(ws)
self.assertRaises(AssertionError, q.submitPuids, None)
def testSubmitCDStubNoClient(self):
ws = FakeWebService()
q = Query(ws)
self.assertRaises(AssertionError, q.submitCDStub, None)
def testSubmitCdStub(self):
from musicbrainz2.model import Disc, CDStub
ws = FakeWebService()
q = Query(ws, clientId='test-1')
discid = "6EmGGSLhuDYz2lNXtqrCiCCqO0o-"
disc = Disc(discid)
disc.firstTrackNum = 1
disc.lastTrackNum = 4
disc.sectors = 89150
disc.addTrack( (150, 20551) )
disc.addTrack( (20701, 26074) )
disc.addTrack( (46775, 19438) )
disc.addTrack( (66213, 22937) )
cdstub = CDStub(disc)
cdstub.artist = "artist"
cdstub.title = "title"
cdstub.addTrack("trackname1")
cdstub.addTrack("trackname2")
cdstub.addTrack("trackname3")
cdstub.addTrack("trackname4")
q.submitCDStub(cdstub)
cdstub.barcode = "12345"
cdstub.comment = "acomment"
q.submitCDStub(cdstub)
self.assertEquals(len(ws.data), 2)
req = ws.data[0]
qstring = 'client=test-1&discid=6EmGGSLhuDYz2lNXtqrCiCCqO0o-&title=title&artist=artist&track0=trackname1&track1=trackname2&track2=trackname3&track3=trackname4&toc=1+4+89150+150+20701+46775+66213'
self.assertEquals(req[0], 'release')
self.assertEquals(req[2], qstring)
req = ws.data[1]
qstring = 'client=test-1&discid=6EmGGSLhuDYz2lNXtqrCiCCqO0o-&title=title&artist=artist&barcode=12345&comment=acomment&track0=trackname1&track1=trackname2&track2=trackname3&track3=trackname4&toc=1+4+89150+150+20701+46775+66213'
self.assertEquals(req[2], qstring)
cdstub._tracks = []
cdstub.addTrack("tname1", "artist1")
cdstub.addTrack("tname2", "artist2")
cdstub.addTrack("tname3", "artist3")
cdstub.addTrack("tname4", "artist4")
q.submitCDStub(cdstub)
self.assertEquals(len(ws.data), 3)
req = ws.data[2]
qstring = 'client=test-1&discid=6EmGGSLhuDYz2lNXtqrCiCCqO0o-&title=title&artist=artist&barcode=12345&comment=acomment&track0=tname1&artist0=artist1&track1=tname2&artist1=artist2&track2=tname3&artist2=artist3&track3=tname4&artist3=artist4&toc=1+4+89150+150+20701+46775+66213'
self.assertEquals(req[2], qstring)
# EOF
python-musicbrainz2-0.7.4/test/test_ws_filters.py 0000644 0001750 0001750 00000001772 10550142420 021242 0 ustar lukas lukas """Tests for subclasses of IFilter."""
import unittest
from musicbrainz2.model import Release
from musicbrainz2.webservice import ReleaseFilter
class ReleaseFilterTest(unittest.TestCase):
def testBasic(self):
f = ReleaseFilter(title='Under the Pink')
params = f.createParameters()
self.assert_( ('title', 'Under the Pink') in params )
def testReleaseTypes(self):
f = ReleaseFilter(artistName='Tori Amos', releaseTypes=(
Release.TYPE_ALBUM, Release.TYPE_OFFICIAL))
params = f.createParameters()
self.assertEquals(len(params), 2)
self.assert_( ('artist', 'Tori Amos') )
self.assert_( ('releasetypes', 'Album Official') )
def testQuery(self):
try:
f1 = ReleaseFilter(title='Pink', query='xyz')
self.fail('title and query are mutually exclusive')
except ValueError:
pass
except:
self.fail('invalid exception')
# the following shouldn't throw exceptions:
f2 = ReleaseFilter(title='Pink', limit=10, offset=20)
f3 = ReleaseFilter(query='Pink', limit=10, offset=20)
# EOF
python-musicbrainz2-0.7.4/test/test_ws_includes.py 0000644 0001750 0001750 00000004253 11231304732 021400 0 ustar lukas lukas """Tests for subclasses of IIncludes."""
import unittest
from musicbrainz2.model import Release
from musicbrainz2.webservice import (
ArtistIncludes, ReleaseIncludes, TrackIncludes, LabelIncludes)
class ArtistIncludesTest(unittest.TestCase):
def testReleases(self):
inc1 = ArtistIncludes(aliases=True, releaseRelations=True)
tags1 = inc1.createIncludeTags()
tags1.sort()
self.assertEqual(tags1, ['aliases', 'release-rels'])
inc2 = ArtistIncludes(releaseRelations=True)
tags2 = inc2.createIncludeTags()
tags2.sort()
self.assertNotEqual(tags2, ['aliases', 'release-rels'])
inc3 = ArtistIncludes(aliases=True,
releases=(Release.TYPE_ALBUM, Release.TYPE_OFFICIAL))
tags3 = inc3.createIncludeTags()
tags3.sort()
self.assertEqual(tags3, ['aliases', 'sa-Album', 'sa-Official'])
inc4 = ArtistIncludes(aliases=True, vaReleases=('Bootleg',))
tags4 = inc4.createIncludeTags()
tags4.sort()
self.assertEqual(tags4, ['aliases', 'va-Bootleg'])
inc5 = ArtistIncludes(aliases=True,
vaReleases=(Release.TYPE_BOOTLEG,))
tags5 = inc5.createIncludeTags()
tags5.sort()
self.assertEqual(tags5, ['aliases', 'va-Bootleg'])
def testReleaseGroups(self):
inc = ArtistIncludes(releaseGroups=True)
self.assertEqual(inc.createIncludeTags(), ['release-groups'])
def testTags(self):
def check(includes_class):
inc1 = includes_class(tags=True)
tags1 = inc1.createIncludeTags()
tags1.sort()
self.assertEqual(tags1, ['tags'])
check(ArtistIncludes)
check(ReleaseIncludes)
check(TrackIncludes)
check(LabelIncludes)
class ReleaseIncludesTest(unittest.TestCase):
# Test that including isrcs in release also pulls in tracks
def testIsrcs(self):
inc = ReleaseIncludes(isrcs=True)
tags = inc.createIncludeTags()
tags.sort()
self.assertEqual(tags, ['isrcs', 'tracks'])
# Test that including labels in release also pulls in release events
def testReleaseEvents(self):
inc = ReleaseIncludes(labels=True)
tags = inc.createIncludeTags()
tags.sort()
self.assertEqual(tags, ['labels', 'release-events'])
def testReleaseGroup(self):
inc = ReleaseIncludes(releaseGroup=True)
tags = inc.createIncludeTags()
self.assertEqual(tags, ['release-groups'])
# EOF
python-musicbrainz2-0.7.4/test/test_wsxml_artist.py 0000644 0001750 0001750 00000012221 11231304732 021613 0 ustar lukas lukas """Tests for parsing artists using MbXmlParser."""
import unittest
from musicbrainz2.wsxml import MbXmlParser, ParseError
from musicbrainz2.model import NS_MMD_1
import StringIO
import os.path
VALID_DATA_DIR = os.path.join('test-data', 'valid')
INVALID_DATA_DIR = os.path.join('test-data', 'invalid')
VALID_ARTIST_DIR = os.path.join(VALID_DATA_DIR, 'artist')
def makeId(relativeUri, resType='artist'):
return 'http://musicbrainz.org/%s/%s' % (resType, relativeUri)
class ParseArtistTest(unittest.TestCase):
def __init__(self, name):
unittest.TestCase.__init__(self, name)
def testArtistBasic(self):
f = os.path.join(VALID_ARTIST_DIR, 'Tori_Amos_1.xml')
md = MbXmlParser().parse(f)
artist = md.getArtist()
self.failIf( artist is None )
self.assertEquals(artist.getName(), 'Tori Amos')
self.assertEquals(artist.getSortName(), 'Amos, Tori')
self.assertEquals(artist.getBeginDate(), '1963-08-22')
self.assertEquals(len(artist.getReleases()), 0)
def testArtistFull(self):
f = os.path.join(VALID_ARTIST_DIR, 'Tori_Amos_2.xml')
md = MbXmlParser().parse(f)
artist = md.getArtist()
self.failIf( artist is None )
self.assertEquals(artist.getId(),
makeId('c0b2500e-0cef-4130-869d-732b23ed9df5'))
self.assertEquals(artist.getName(), 'Tori Amos')
self.assertEquals(artist.getSortName(), 'Amos, Tori')
self.assert_(artist.getDisambiguation() is None)
self.assertEquals(artist.getUniqueName(), artist.getName())
self.assertEquals(artist.getBeginDate(), '1963-08-22')
self.assertEquals(len(artist.getReleases()), 3)
self.assertEquals(len(artist.getReleaseGroups()), 3)
release1 = artist.getReleases()[0]
self.assertEquals(release1.getTitle(), 'Strange Little Girls')
self.assertEquals(release1.getAsin(), 'B00005NKYQ')
# Check last release in more detail.
#
release3 = artist.getReleases()[2]
self.assertEquals(release3.getId(),
makeId('290e10c5-7efc-4f60-ba2c-0dfc0208fbf5', 'release'))
self.assertEquals(len(release3.getTypes()), 2)
self.assert_(NS_MMD_1 + 'Album' in release3.getTypes())
self.assert_(NS_MMD_1 + 'Official' in release3.getTypes())
self.assertEquals(release3.getTitle(), 'Under the Pink')
self.assertEquals(release3.getAsin(), 'B000002IXU')
self.assertEquals(release3.getArtist().getId(),
makeId('c0b2500e-0cef-4130-869d-732b23ed9df5'))
self.failIf(release3.getReleaseGroup() is None)
self.assertEquals(release3.getReleaseGroup().id[-36:],
'ef2b891f-ca73-3e14-b38b-a68699dab8c4')
events = release3.getReleaseEvents()
self.assertEquals(len(events), 5)
self.assertEquals(events[0].getCountry(), 'DE')
self.assertEquals(events[0].getDate(), '1994-01-28')
self.assertEquals(events[4].getCountry(), 'AU')
self.assertEquals(events[4].getDate(), '1994-11')
self.assertEquals(release3.getEarliestReleaseDate(), '1994-01-28')
#self.assertEquals(release3.getDiscCount(), 4)
def testAliases(self):
f = os.path.join(VALID_ARTIST_DIR, 'Tori_Amos_4.xml')
md = MbXmlParser().parse(f)
artist = md.getArtist()
self.failIf( artist is None )
self.assertEquals(artist.getDisambiguation(), 'yes, that one')
self.assertEquals(artist.getName(), 'Tori Amos')
self.assertEquals(artist.getUniqueName(),
'Tori Amos (yes, that one)')
aliases = artist.getAliases()
self.assertEquals(len(aliases), 3)
self.assertEquals(aliases[0].getValue(), 'Myra Ellen Amos')
self.assertEquals(aliases[0].getScript(), 'Latn')
self.assertEquals(aliases[1].getValue(), 'Myra Amos')
self.assertEquals(aliases[2].getValue(), 'Torie Amos')
self.assertEquals(aliases[2].getType(), NS_MMD_1 + 'Misspelling')
def testTags(self):
f = os.path.join(VALID_ARTIST_DIR, 'Tchaikovsky-2.xml')
md = MbXmlParser().parse(f)
artist = md.getArtist()
self.failIf( artist is None )
self.assertEquals(artist.getTag('classical').count, 100)
self.assertEquals(artist.getTag('russian').count, 60)
self.assertEquals(artist.getTag('romantic era').count, 40)
self.assertEquals(artist.getTag('composer').count, 120)
def testReleaseGroups(self):
f = os.path.join(VALID_ARTIST_DIR, 'Tori_Amos_2.xml')
md = MbXmlParser().parse(f)
artist = md.getArtist()
self.failIf(artist is None)
releaseGroups = artist.getReleaseGroups()
self.failIf(releaseGroups is None)
self.assertEquals(len(releaseGroups), 3)
expectedEntries = {
'ef2b891f-ca73-3e14-b38b-a68699dab8c4': 'Under the Pink',
'1fd43909-8056-3805-b2f9-c663ce7e71e6': 'To Venus and Back',
'a69a1574-dfe3-3e2a-b499-d26d5e916041': 'Strange Little Girls'}
for releaseGroup in releaseGroups:
self.assertEquals(releaseGroup.getType(), NS_MMD_1 + 'Album')
releaseGroupId = releaseGroup.id[-36:]
self.assert_(releaseGroupId in expectedEntries)
self.assertEquals(releaseGroup.getTitle(), expectedEntries[releaseGroupId])
del expectedEntries[releaseGroupId]
def testSearchResults(self):
f = os.path.join(VALID_ARTIST_DIR, 'search_result_1.xml')
md = MbXmlParser().parse(f)
self.assertEquals(md.artistResultsOffset, 0)
self.assertEquals(md.artistResultsCount, 47)
results = md.artistResults
self.assertEquals(len(results), 3)
self.assertEquals(results[0].score, 100)
artist1 = results[0].artist
self.assertEquals(artist1.name, 'Tori Amos')
# EOF
python-musicbrainz2-0.7.4/test/test_utils.py 0000644 0001750 0001750 00000005467 10762216154 020241 0 ustar lukas lukas """Tests for the utils module."""
import unittest
import musicbrainz2.model as m
import musicbrainz2.utils as u
class UtilsTest(unittest.TestCase):
def __init__(self, name):
unittest.TestCase.__init__(self, name)
def testExtractUuid(self):
artistPrefix = 'http://musicbrainz.org/artist/'
uuid = 'c0b2500e-0cef-4130-869d-732b23ed9df5'
mbid = artistPrefix + uuid
self.assertEquals(u.extractUuid(None), None)
self.assertEquals(u.extractUuid(uuid), uuid)
self.assertEquals(u.extractUuid(mbid), uuid)
self.assertEquals(u.extractUuid(mbid, 'artist'), uuid)
# not correct, but not enough data to catch this
self.assertEquals(u.extractUuid(uuid, 'release'), uuid)
self.assertRaises(ValueError, u.extractUuid, mbid, 'release')
self.assertRaises(ValueError, u.extractUuid, mbid, 'track')
self.assertRaises(ValueError, u.extractUuid, mbid+'/xy', 'artist')
invalidId = 'http://example.invalid/' + uuid
self.assertRaises(ValueError, u.extractUuid, invalidId)
def testExtractFragment(self):
fragment = 'Album'
uri = m.NS_MMD_1 + fragment
self.assertEquals(u.extractFragment(None), None)
self.assertEquals(u.extractFragment(fragment), fragment)
self.assertEquals(u.extractFragment(uri), fragment)
self.assertEquals(u.extractFragment(uri, m.NS_MMD_1), fragment)
prefix = 'http://example.invalid/'
self.assertRaises(ValueError, u.extractFragment, uri, prefix)
def testExtractEntityType(self):
prefix = 'http://musicbrainz.org'
uuid = 'c0b2500e-0cef-4130-869d-732b23ed9df5'
mbid1 = prefix + '/artist/' + uuid
self.assertEquals(u.extractEntityType(mbid1), 'artist')
mbid2 = prefix + '/release/' + uuid
self.assertEquals(u.extractEntityType(mbid2), 'release')
mbid3 = prefix + '/track/' + uuid
self.assertEquals(u.extractEntityType(mbid3), 'track')
mbid4 = prefix + '/label/' + uuid
self.assertEquals(u.extractEntityType(mbid4), 'label')
mbid5 = prefix + '/invalid/' + uuid
self.assertRaises(ValueError, u.extractEntityType, mbid5)
self.assertRaises(ValueError, u.extractEntityType, None)
self.assertRaises(ValueError, u.extractEntityType, uuid)
invalidUri = 'http://example.invalid/foo'
self.assertRaises(ValueError, u.extractEntityType, invalidUri)
def testGetCountryName(self):
self.assertEquals(u.getCountryName('DE'), 'Germany')
self.assertEquals(u.getCountryName('FR'), 'France')
def testGetLanguageName(self):
self.assertEquals(u.getLanguageName('DEU'), 'German')
self.assertEquals(u.getLanguageName('ENG'), 'English')
def testGetScriptName(self):
self.assertEquals(u.getScriptName('Latn'), 'Latin')
self.assertEquals(u.getScriptName('Cyrl'), 'Cyrillic')
def testGetReleaseTypeName(self):
self.assertEquals(u.getReleaseTypeName(m.Release.TYPE_ALBUM),
'Album')
self.assertEquals(u.getReleaseTypeName(m.Release.TYPE_COMPILATION), 'Compilation')
# EOF
python-musicbrainz2-0.7.4/test/test_wsxml_release.py 0000644 0001750 0001750 00000013216 11231304732 021732 0 ustar lukas lukas """Tests for parsing releases using MbXmlParser."""
import unittest
from musicbrainz2.wsxml import MbXmlParser, ParseError
from musicbrainz2.model import VARIOUS_ARTISTS_ID, NS_MMD_1, \
Relation, ReleaseEvent
import StringIO
import os.path
VALID_DATA_DIR = os.path.join('test-data', 'valid')
INVALID_DATA_DIR = os.path.join('test-data', 'invalid')
VALID_RELEASE_DIR = os.path.join(VALID_DATA_DIR, 'release')
def makeId(relativeUri):
return 'http://musicbrainz.org/release/' + relativeUri
class ParseReleaseTest(unittest.TestCase):
def __init__(self, name):
unittest.TestCase.__init__(self, name)
def testReleaseBasic(self):
f = os.path.join(VALID_RELEASE_DIR, 'Little_Earthquakes_1.xml')
md = MbXmlParser().parse(f)
release = md.getRelease()
self.failIf( release is None )
self.assertEquals(release.getId(),
makeId('02232360-337e-4a3f-ad20-6cdd4c34288c'))
self.assertEquals(release.getTitle(), 'Little Earthquakes')
self.assertEquals(release.getTextLanguage(), 'ENG')
self.assertEquals(release.getTextScript(), 'Latn')
self.assertEquals(len(release.getTypes()), 2)
self.assert_(NS_MMD_1 + 'Album' in release.getTypes())
self.assert_(NS_MMD_1 + 'Official' in release.getTypes())
def testReleaseFull(self):
f = os.path.join(VALID_RELEASE_DIR, 'Little_Earthquakes_2.xml')
md = MbXmlParser().parse(f)
release = md.getRelease()
self.failIf( release is None )
self.assertEquals(release.getId(),
makeId('02232360-337e-4a3f-ad20-6cdd4c34288c'))
self.assertEquals(release.getArtist().getName(), 'Tori Amos')
events = release.getReleaseEventsAsDict()
self.assertEquals(len(events), 3)
self.assertEquals(events['GB'], '1992-01-13')
self.assertEquals(events['DE'], '1992-01-17')
self.assertEquals(events['US'], '1992-02-25')
date = release.getEarliestReleaseDate()
self.assertEquals(date, '1992-01-13')
event = release.getEarliestReleaseEvent()
self.assertEquals(event.date, date)
self.assertEquals(event.country, 'GB')
discs = release.getDiscs()
self.assertEquals(len(discs), 3)
self.assertEquals(discs[0].getId(), 'ILKp3.bZmvoMO7wSrq1cw7WatfA-')
self.assertEquals(discs[1].getId(), 'ejdrdtX1ZyvCb0g6vfJejVaLIK8-')
self.assertEquals(discs[2].getId(), 'Y96eDQZbF4Z26Y5.Sxdbh3wGypo-')
tracks = release.getTracks()
self.assertEquals(len(tracks), 12)
self.assertEquals(tracks[0].getTitle(), 'Crucify')
self.assertEquals(tracks[4].getTitle(), 'Winter')
def testReleaseRelations(self):
f = os.path.join(VALID_RELEASE_DIR, 'Highway_61_Revisited_1.xml')
md = MbXmlParser().parse(f)
release = md.getRelease()
self.failIf( release is None )
self.assertEquals(release.getId(),
makeId('d61a2bd9-81ac-4023-bd22-1c884d4a176c'))
(rel1, rel2) = release.getRelations(Relation.TO_URL)
self.assertEquals(rel1.getTargetId(),
'http://en.wikipedia.org/wiki/Highway_61_Revisited')
self.assertEquals(rel1.getDirection(), Relation.DIR_NONE)
self.assertEquals(rel2.getTargetId(),
'http://www.amazon.com/gp/product/B0000024SI')
def testVariousArtistsRelease(self):
f = os.path.join(VALID_RELEASE_DIR, 'Mission_Impossible_2.xml')
md = MbXmlParser().parse(f)
release = md.getRelease()
self.failIf( release is None )
artistId = release.getArtist().getId()
self.assertEquals(artistId, VARIOUS_ARTISTS_ID)
events = release.getReleaseEventsAsDict()
self.assertEquals(len(events), 1)
self.assertEquals(events['EU'], '2000')
track14 = release.getTracks()[14]
self.assertEquals(track14.getTitle(), 'Carnival')
self.assertEquals(track14.getArtist().getName(), 'Tori Amos')
def testIncompleteReleaseEvent(self):
f = os.path.join(VALID_RELEASE_DIR, 'Under_the_Pink_1.xml')
md = MbXmlParser().parse(f)
release = md.getRelease()
self.failIf( release is None )
self.assertEquals(release.getTitle(), 'Under the Pink')
events = release.getReleaseEvents()
self.assertEquals(len(events), 1)
self.assertEquals(events[0].getDate(), '1994-01-28')
def testReleaseEvents(self):
f = os.path.join(VALID_RELEASE_DIR, 'Under_the_Pink_3.xml')
md = MbXmlParser().parse(f)
release = md.getRelease()
self.failIf( release is None )
self.assertEquals(release.getTitle(), 'Under the Pink')
events = release.getReleaseEvents()
self.assertEquals(len(events), 1)
e1 = events[0]
self.assertEquals(e1.date, '1994-01-31')
self.assertEquals(e1.catalogNumber, '82567-2')
self.assertEquals(e1.barcode, '07567825672')
self.assertEquals(e1.format, ReleaseEvent.FORMAT_CD)
self.failIf( e1.label is None )
self.assertEquals(e1.label.name, 'Atlantic Records')
def testReleaseGroup(self):
f = os.path.join(VALID_RELEASE_DIR, 'Under_the_Pink_2.xml')
md = MbXmlParser().parse(f)
release = md.getRelease()
self.failIf(release is None)
self.assertEquals(release.getTitle(), 'Under the Pink')
releaseGroup = release.getReleaseGroup()
self.failIf(releaseGroup is None)
self.assertEquals(releaseGroup.id[-36:],
'ef2b891f-ca73-3e14-b38b-a68699dab8c4')
self.assertEquals(releaseGroup.getTitle(), 'Under the Pink')
self.assertEquals(releaseGroup.getType(), NS_MMD_1 + 'Album')
def testTags(self):
f = os.path.join(VALID_RELEASE_DIR, 'Highway_61_Revisited_2.xml')
md = MbXmlParser().parse(f)
release = md.getRelease()
self.failIf( release is None )
self.assertEquals(release.getTag('rock').count, 100)
self.assertEquals(release.getTag('blues rock').count, 40)
self.assertEquals(release.getTag('folk rock').count, 40)
self.assertEquals(release.getTag('dylan').count, 4)
def testResultAttributes(self):
f = os.path.join(VALID_RELEASE_DIR, 'search_result_1.xml')
md = MbXmlParser().parse(f)
self.assertEquals(md.releaseResultsOffset, 0)
self.assertEquals(md.releaseResultsCount, 234)
# EOF
python-musicbrainz2-0.7.4/test/test_wsxml_track.py 0000644 0001750 0001750 00000006550 11215137423 021424 0 ustar lukas lukas """Tests for parsing tracks using MbXmlParser."""
import unittest
from musicbrainz2.wsxml import MbXmlParser, ParseError
from musicbrainz2.model import NS_MMD_1, NS_REL_1, Relation
import StringIO
import os.path
VALID_DATA_DIR = os.path.join('test-data', 'valid')
INVALID_DATA_DIR = os.path.join('test-data', 'invalid')
VALID_TRACK_DIR = os.path.join(VALID_DATA_DIR, 'track')
def makeId(relativeUri, resType='track'):
return 'http://musicbrainz.org/%s/%s' % (resType, relativeUri)
class ParseTrackTest(unittest.TestCase):
def __init__(self, name):
unittest.TestCase.__init__(self, name)
def testTrackBasic(self):
f = os.path.join(VALID_TRACK_DIR, 'Silent_All_These_Years_1.xml')
md = MbXmlParser().parse(f)
track = md.getTrack()
self.failIf( track is None )
self.assertEquals(track.getTitle(), 'Silent All These Years')
self.assertEquals(track.getDuration(), 253466)
def testTrackRelations(self):
f = os.path.join(VALID_TRACK_DIR, 'Silent_All_These_Years_2.xml')
md = MbXmlParser().parse(f)
track = md.getTrack()
self.failIf( track is None )
self.assertEquals(track.getTitle(), 'Silent All These Years')
self.assertEquals(track.getDuration(), 253466)
trackRels = track.getRelations(Relation.TO_TRACK)
self.assertEquals(len(trackRels), 1)
rel1 = trackRels[0]
self.assertEquals(rel1.getType(), NS_REL_1 + 'Cover')
self.assertEquals(rel1.getDirection(), Relation.DIR_BACKWARD)
self.assertEquals(rel1.getTargetId(),
makeId('31e1c0c4-967f-435e-b09a-35ee079ee234', 'track'))
self.assert_( rel1.getBeginDate() is None )
self.assert_( rel1.getEndDate() is None )
self.failIf( rel1.getTarget() is None )
self.assertEquals(rel1.getTarget().getId(),
makeId('31e1c0c4-967f-435e-b09a-35ee079ee234'))
self.assertEquals(rel1.getTarget().getArtist().getId(),
makeId('5bcd4eaa-fae7-465f-9f03-d005b959ed02', 'artist'))
def testTrackFull(self):
f = os.path.join(VALID_TRACK_DIR, 'Silent_All_These_Years_4.xml')
md = MbXmlParser().parse(f)
track = md.getTrack()
self.failIf( track is None )
self.assertEquals(track.getTitle(), 'Silent All These Years')
self.assertEquals(track.getDuration(), 253466)
artist = track.getArtist()
self.failIf( artist is None )
self.assertEquals(artist.getId(),
makeId('c0b2500e-0cef-4130-869d-732b23ed9df5', 'artist'))
self.assertEquals(artist.getType(), NS_MMD_1 + 'Person')
self.assertEquals(artist.getName(), 'Tori Amos')
self.assertEquals(artist.getSortName(), 'Amos, Tori')
puids = track.getPuids()
self.assertEquals(len(puids), 7)
self.assertEquals(puids[0], 'c2a2cee5-a8ca-4f89-a092-c3e1e65ab7e6')
self.assertEquals(puids[6], '42ab76ea-5d42-4259-85d7-e7f2c69e4485')
isrcs = track.isrcs
self.assertEquals(len(isrcs), 1)
self.assertEquals(isrcs[0], 'USPR37300012')
releases = track.getReleases()
self.assertEquals(len(releases), 1)
self.assertEquals(releases[0].getTitle(), 'Little Earthquakes')
self.assertEquals(releases[0].getTracksOffset(), 2)
def testSearchResults(self):
f = os.path.join(VALID_TRACK_DIR, 'search_result_1.xml')
md = MbXmlParser().parse(f)
self.assertEquals(md.trackResultsOffset, 7)
self.assertEquals(md.trackResultsCount, 100)
results = md.getTrackResults()
self.assertEquals(len(results), 3)
self.assertEquals(results[0].getScore(), 100)
track1 = results[0].getTrack()
self.assertEquals(track1.getTitle(), 'Little Earthquakes')
# EOF
python-musicbrainz2-0.7.4/test/test_wsxml.py 0000644 0001750 0001750 00000003420 10411775001 020226 0 ustar lukas lukas """Tests for the MbXmlParser class."""
import unittest
from musicbrainz2.wsxml import MbXmlParser, ParseError, _makeAbsoluteUri
import StringIO
import os.path
VALID_DATA_DIR = os.path.join('test-data', 'valid')
INVALID_DATA_DIR = os.path.join('test-data', 'invalid')
VALID_ARTIST_DIR = os.path.join(VALID_DATA_DIR, 'artist')
INVALID_ARTIST_DIR = os.path.join(INVALID_DATA_DIR, 'artist')
class BaseParserTest(unittest.TestCase):
"""Test the most basic cases: empty and invalid documents."""
def __init__(self, name):
unittest.TestCase.__init__(self, name)
def testEmptyValid(self):
tests = ('empty_1.xml', 'empty_2.xml')
for f in self._makeFiles(VALID_ARTIST_DIR, tests):
try:
MbXmlParser().parse(f)
except ParseError, e:
self.fail(f.name + ' ' + e.msg)
def testEmptyInvalid(self):
tests = ('empty_1.xml', 'empty_2.xml', 'empty_3.xml')
for f in self._makeFiles(INVALID_ARTIST_DIR, tests):
p = MbXmlParser()
try:
MbXmlParser().parse(f)
except ParseError:
pass
else:
self.fail(f.name + ' ' + f.name)
def testMakeAbsoluteUri(self):
self.assert_(_makeAbsoluteUri('http://mb.org/artist/', None) is None)
self.assertEquals('http://mb.org/artist/some_id',
_makeAbsoluteUri('http://mb.org/artist/', 'some_id'))
self.assertEquals('http://mb.org/artist/some_id',
_makeAbsoluteUri('http://mb.org/artist/',
'http://mb.org/artist/some_id'))
self.assertEquals('http://mb.org/ns/mmd-1.0#name',
_makeAbsoluteUri('http://mb.org/ns/mmd-1.0#', 'name'))
self.assertEquals('http://mb.org/ns/mmd-1.0#name',
_makeAbsoluteUri('http://mb.org/ns/mmd-1.0#',
'http://mb.org/ns/mmd-1.0#name'))
def _makeFiles(self, dir, basenames):
files = [ ]
for b in basenames:
files.append( file(os.path.join(dir, b)) )
return files
# EOF
python-musicbrainz2-0.7.4/test/test_wsxml_label.py 0000644 0001750 0001750 00000006110 10761050360 021366 0 ustar lukas lukas """Tests for parsing artists using MbXmlParser."""
import unittest
from musicbrainz2.wsxml import MbXmlParser, ParseError
from musicbrainz2.model import NS_MMD_1
import StringIO
import os.path
VALID_DATA_DIR = os.path.join('test-data', 'valid')
INVALID_DATA_DIR = os.path.join('test-data', 'invalid')
VALID_LABEL_DIR = os.path.join(VALID_DATA_DIR, 'label')
def makeId(relativeUri, resType='label'):
return 'http://musicbrainz.org/%s/%s' % (resType, relativeUri)
class ParseLabelTest(unittest.TestCase):
def __init__(self, name):
unittest.TestCase.__init__(self, name)
def testLabelBasic(self):
f = os.path.join(VALID_LABEL_DIR, 'Atlantic_Records_1.xml')
md = MbXmlParser().parse(f)
label = md.getLabel()
self.failIf( label is None )
self.assertEquals(label.id,
makeId('50c384a2-0b44-401b-b893-8181173339c7'))
self.assertEquals(label.type, NS_MMD_1 + 'OriginalProduction')
self.assertEquals(label.name, 'Atlantic Records')
self.assertEquals(label.beginDate, '1947')
self.assertEquals(label.endDate, None)
self.assertEquals(label.country, 'US')
self.assertEquals(label.code, '121')
def testIncomplete(self):
f = os.path.join(VALID_LABEL_DIR, 'Atlantic_Records_3.xml')
md = MbXmlParser().parse(f)
label = md.getLabel()
self.failIf( label is None )
self.assertEquals(label.id,
makeId('50c384a2-0b44-401b-b893-8181173339c7'))
self.assertEquals(label.code, None)
def testLabelSubElements(self):
f = os.path.join(VALID_LABEL_DIR, 'Atlantic_Records_2.xml')
md = MbXmlParser().parse(f)
label = md.getLabel()
self.failIf( label is None )
self.assertEquals(label.type, NS_MMD_1 + 'Distributor')
self.assertEquals(label.name, 'Atlantic Records')
self.assertEquals(label.sortName, 'AR SortName')
self.assertEquals(label.disambiguation, 'fake')
self.assertEquals(label.beginDate, '1947')
self.assertEquals(label.endDate, '2047')
self.assertEquals(label.country, 'US')
self.assertEquals(label.code, '121')
self.assertEquals(len(label.aliases), 1)
alias = label.aliases[0]
self.assertEquals(alias.value, 'Atlantic Rec.')
self.assertEquals(label.getUniqueName(),
'Atlantic Records (fake)')
def testTags(self):
f = os.path.join(VALID_LABEL_DIR, 'Atlantic_Records_3.xml')
md = MbXmlParser().parse(f)
label = md.getLabel()
self.failIf( label is None )
self.assertEquals(label.getTag('american').count, None)
self.assertEquals(label.getTag('jazz').count, None)
self.assertEquals(label.getTag('blues').count, None)
def testSearchResults(self):
f = os.path.join(VALID_LABEL_DIR, 'search_result_1.xml')
md = MbXmlParser().parse(f)
self.assertEquals(md.labelResultsOffset, 0)
self.assertEquals(md.labelResultsCount, 2)
self.assertEquals(md.getLabelResultsOffset(), 0)
self.assertEquals(md.getLabelResultsCount(), 2)
results = md.labelResults
self.assertEquals(len(results), 2)
self.assertEquals(results[0].score, 100)
label1 = results[0].label
self.assertEquals(label1.name, 'Atlantic Records')
self.assertEquals(results[1].score, 46)
label2 = results[1].label
self.assertEquals(label2.name, 'DRO Atlantic')
# EOF
python-musicbrainz2-0.7.4/INSTALL.txt 0000644 0001750 0001750 00000002232 11245512572 016343 0 ustar lukas lukas Installation Instructions
-------------------------
First of all, install the following dependencies:
1. python (tested with 2.4)
Standard python 2.3 or later should work.
-> http://www.python.org/
2. libdiscid (tested with 0.1.0)
Any version above 0.1.0 should do.
-> http://musicbrainz.org/products/libdiscid/
3. ctypes (tested with 0.9.6 and 0.9.9.3)
Version 0.9.0 or later should work, which is included in recent
linux distributions. This is required for DiscID calculation.
Note that python 2.5 already comes with ctypes.
-> http://starship.python.net/crew/theller/ctypes/
Installation works using python's standard distutils (on most systems,
root permissions are required):
python setup.py install
Note that on Debian-based systems like Ubuntu, you have to add the
--install-layout=deb command line switch or it won't work on python 2.6
and later.
Optionally, API documentation can be generated using epydoc. This command
runs epydoc (http://epydoc.sf.net) which creates a 'html' directory containing
the documentation:
python setup.py docs
--
$Id: INSTALL.txt 12005 2009-08-27 14:17:30Z matt $
python-musicbrainz2-0.7.4/MANIFEST.in 0000644 0001750 0001750 00000000151 10541343413 016222 0 ustar lukas lukas include *.txt
recursive-include examples *
recursive-include test *.py
recursive-include test-data *.xml