Merge branch 'ngs'

This commit is contained in:
rembo10
2012-05-29 19:17:33 +05:30
17 changed files with 1165 additions and 7887 deletions

View File

@@ -3,16 +3,13 @@ from __future__ import with_statement
import time
import threading
import lib.musicbrainz2.webservice as ws
import lib.musicbrainz2.model as m
import lib.musicbrainz2.utils as u
from lib.musicbrainz2.webservice import WebServiceError
import headphones
from headphones import logger, db
from headphones.helpers import multikeysort, replace_all
import lib.musicbrainzngs as musicbrainzngs
from lib.musicbrainzngs import WebServiceError
mb_lock = threading.Lock()
@@ -46,8 +43,22 @@ def startmb(forcemb=False):
mbport = 5000
sleepytime = 0
service = ws.WebService(host=mbhost, port=mbport, username=mbuser, password=mbpass, mirror=headphones.MIRROR)
q = ws.Query(service)
musicbrainzngs.set_useragent("headphones","0.0","https://github.com/rembo10/headphones")
musicbrainzngs.set_hostname(mbhost + ":" + str(mbport))
if sleepytime == 0:
musicbrainzngs.set_rate_limit(False)
else:
musicbrainzngs.set_rate_limit(True)
# Add headphones credentials
if headphones.MIRROR == "headphones":
if not mbuser and mbpass:
logger.warn("No username or password set for VIP server")
else:
musicbrainzngs.hpauth(mbuser,mbpass)
#q = musicbrainzngs
q = musicbrainzngs
logger.debug('Using the following server values:\nMBHost: %s ; MBPort: %i ; Sleep Interval: %i ' % (mbhost, mbport, sleepytime))
@@ -56,9 +67,7 @@ def startmb(forcemb=False):
def findArtist(name, limit=1):
with mb_lock:
artistlist = []
attempt = 0
artistResults = None
chars = set('!?*')
@@ -67,59 +76,50 @@ def findArtist(name, limit=1):
q, sleepytime = startmb(forcemb=True)
while attempt < 5:
try:
artistResults = q.getArtists(ws.ArtistFilter(query=name, limit=limit))
break
artistResults = musicbrainzngs.search_artists(query=name,limit=limit)['artist-list']
except WebServiceError, e:
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
attempt += 1
time.sleep(10)
time.sleep(5)
time.sleep(sleepytime)
if not artistResults:
return False
for result in artistResults:
if result.artist.name != result.artist.getUniqueName() and limit == 1:
if 'disambiguation' in result:
uniquename = unicode(result['sort-name'] + " (" + result['disambiguation'] + ")")
else:
uniquename = unicode(result['sort-name'])
if result['name'] != uniquename and limit == 1:
logger.debug('Found an artist with a disambiguation: %s - doing an album based search' % name)
artistdict = findArtistbyAlbum(name)
if not artistdict:
logger.debug('Cannot determine the best match from an artist/album search. Using top match instead')
artistlist.append({
'name': result.artist.name,
'uniquename': result.artist.getUniqueName(),
'id': u.extractUuid(result.artist.id),
'url': result.artist.id,
'score': result.score
'name': unicode(result['sort-name']),
'uniquename': uniquename,
'id': unicode(result['id']),
'url': unicode("http://musicbrainz.org/artist/" + result['id']),#probably needs to be changed
'score': int(result['ext:score'])
})
else:
artistlist.append(artistdict)
else:
artistlist.append({
'name': result.artist.name,
'uniquename': result.artist.getUniqueName(),
'id': u.extractUuid(result.artist.id),
'url': result.artist.id,
'score': result.score
'name': unicode(result['sort-name']),
'uniquename': uniquename,
'id': unicode(result['id']),
'url': unicode("http://musicbrainz.org/artist/" + result['id']),#probably needs to be changed
'score': int(result['ext:score'])
})
return artistlist
def findRelease(name, limit=1):
with mb_lock:
releaselist = []
attempt = 0
releaseResults = None
releaselistngs = []
releaseResultsngs = None
chars = set('!?')
if any((c in chars) for c in name):
@@ -127,81 +127,86 @@ def findRelease(name, limit=1):
q, sleepytime = startmb(forcemb=True)
while attempt < 5:
try:
releaseResults = q.getReleases(ws.ReleaseFilter(query=name, limit=limit))
break
except WebServiceError, e:
releaseResultsngs = musicbrainzngs.search_releases(query=name,limit=limit)['release-list']
except WebServiceError, e: #need to update exceptions
logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e)))
attempt += 1
time.sleep(10)
time.sleep(5)
time.sleep(sleepytime)
if not releaseResults:
if not releaseResultsngs:
return False
for result in releaseResults:
releaselist.append({
'uniquename': result.release.artist.name,
'title': result.release.title,
'id': u.extractUuid(result.release.artist.id),
'albumid': u.extractUuid(result.release.id),
'url': result.release.artist.id,
'albumurl': result.release.id,
'score': result.score
for result in releaseResultsngs:
releaselistngs.append({
'uniquename': unicode(result['artist-credit'][0]['artist']['name']),
'title': unicode(result['title']),
'id': unicode(result['artist-credit'][0]['artist']['id']),
'albumid': unicode(result['id']),
'url': unicode("http://musicbrainz.org/artist/" + result['artist-credit'][0]['artist']['id']),#probably needs to be changed
'albumurl': unicode("http://musicbrainz.org/release/" + result['id']),#probably needs to be changed
'score': int(result['ext:score'])
})
return releaselist
return releaselistngs
def getArtist(artistid, extrasonly=False):
with mb_lock:
artist_dict = {}
#Get all official release groups
inc = ws.ArtistIncludes(releases=(m.Release.TYPE_OFFICIAL, m.Release.TYPE_ALBUM), releaseGroups=True)
artist = None
attempt = 0
q, sleepytime = startmb()
while attempt < 5:
try:
artist = q.getArtistById(artistid, inc)
break
limit = 100
artist = musicbrainzngs.get_artist_by_id(artistid)['artist']
newRgs = None
artist['release-group-list'] = []
while newRgs == None or len(newRgs) >= limit:
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type="album",offset=len(artist['release-group-list']),limit=limit)['release-group-list']
artist['release-group-list'] += newRgs
except WebServiceError, e:
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
attempt += 1
time.sleep(5)
except Exception,e:
pass
if not artist:
return False
time.sleep(sleepytime)
artist_dict['artist_name'] = artist.name
artist_dict['artist_sortname'] = artist.sortName
artist_dict['artist_uniquename'] = artist.getUniqueName()
artist_dict['artist_type'] = u.extractFragment(artist.type)
artist_dict['artist_begindate'] = artist.beginDate
artist_dict['artist_enddate'] = artist.endDate
if 'disambiguation' in artist:
uniquename = unicode(artist['sort-name'] + " (" + artist['disambiguation'] + ")")
else:
uniquename = unicode(artist['sort-name'])
artist_dict['artist_name'] = unicode(artist['name'])
artist_dict['artist_sortname'] = unicode(artist['sort-name'])
artist_dict['artist_uniquename'] = uniquename
artist_dict['artist_type'] = unicode(artist['type'])
artist_dict['artist_begindate'] = None
artist_dict['artist_enddate'] = None
if 'life-span' in artist:
if 'begin' in artist['life-span']:
artist_dict['artist_begindate'] = unicode(artist['life-span']['begin'])
if 'end' in artist['life-span']:
artist_dict['artist_enddate'] = unicode(artist['life-span']['end'])
releasegroups = []
if not extrasonly:
for rg in artist.getReleaseGroups():
for rg in artist['release-group-list']:
if rg['type'] != 'Album': #only add releases without a secondary type
continue
releasegroups.append({
'title': rg.title,
'id': u.extractUuid(rg.id),
'url': rg.id,
'type': u.getReleaseTypeName(rg.type)
'title': unicode(rg['title']),
'id': unicode(rg['id']),
'url': u"http://musicbrainz.org/release-group/" + rg['id'],
'type': unicode(rg['type'])
})
# See if we need to grab extras
@@ -213,33 +218,25 @@ def getArtist(artistid, extrasonly=False):
includeExtras = False
if includeExtras or headphones.INCLUDE_EXTRAS:
includes = [m.Release.TYPE_COMPILATION, m.Release.TYPE_REMIX, m.Release.TYPE_SINGLE, m.Release.TYPE_LIVE, m.Release.TYPE_EP, m.Release.TYPE_SOUNDTRACK]
includes = ["single", "ep", "compilation", "soundtrack", "live", "remix"]
for include in includes:
inc = ws.ArtistIncludes(releases=(m.Release.TYPE_OFFICIAL, include), releaseGroups=True)
artist = None
attempt = 0
while attempt < 5:
try:
artist = q.getArtistById(artistid, inc)
break
except WebServiceError, e:
artist = musicbrainzngs.get_artist_by_id(artistid,includes=["releases","release-groups"],release_status=['official'],release_type=include)['artist']
except WebServiceError, e:#update exceptions
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
attempt += 1
time.sleep(5)
if not artist:
continue
for rg in artist.getReleaseGroups():
for rg in artist['release-group-list']:
releasegroups.append({
'title': rg.title,
'id': u.extractUuid(rg.id),
'url': rg.id,
'type': u.getReleaseTypeName(rg.type)
'title': unicode(rg['title']),
'id': unicode(rg['id']),
'url': u"http://musicbrainz.org/release-group/" + rg['id'],
'type': unicode(rg['type'])
})
artist_dict['releasegroups'] = releasegroups
@@ -254,51 +251,37 @@ def getReleaseGroup(rgid):
releaselist = []
inc = ws.ReleaseGroupIncludes(releases=True, artist=True)
releaseGroup = None
attempt = 0
q, sleepytime = startmb()
while attempt < 5:
try:
releaseGroup = q.getReleaseGroupById(rgid, inc)
break
releaseGroup = musicbrainzngs.get_release_group_by_id(rgid,["artists","releases","media","discids",])['release-group']
except WebServiceError, e:
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
attempt += 1
time.sleep(5)
if not releaseGroup:
return False
time.sleep(sleepytime)
# I think for now we have to make separate queries for each release, in order
# to get more detailed release info (ASIN, track count, etc.)
for release in releaseGroup.releases:
inc = ws.ReleaseIncludes(tracks=True, releaseEvents=True)
for release in releaseGroup['release-list']:
releaseResult = None
attempt = 0
while attempt < 5:
try:
releaseResult = q.getReleaseById(release.id, inc)
break
releaseResult = musicbrainzngs.get_release_by_id(release['id'],["recordings","media"])['release']
except WebServiceError, e:
logger.warn('Attempt to retrieve release information for %s from MusicBrainz failed (%s)' % (releaseResult.title, str(e)))
attempt += 1
time.sleep(5)
if not releaseResult:
continue
# Release filter for non-official live albums
types = releaseResult.getTypes()
if any('Live' in type for type in types):
if not any('Official' in type for type in types):
if releaseGroup['type'] == 'live' and releaseResult['status'] != 'Official':
logger.debug('%s is not an official live album. Skipping' % releaseResult.name)
continue
@@ -313,68 +296,72 @@ def getReleaseGroup(rgid):
'Digital Media': '0'
}
country = {
countries = {
'US': '0',
'GB': '1',
'JP': '2',
}
try:
format = int(replace_all(u.extractFragment(releaseResult.releaseEvents[0].format), formats))
format = int(formats[releaseResult['medium-list'][0]['format']])
except:
format = 3
try:
country = int(replace_all(releaseResult.releaseEvents[0].country, country))
country = int(countries[releaseResult['country']])
except:
country = 3
totalTracks = 0
tracks = []
for medium in releaseResult['medium-list']:
for track in medium['track-list']:
tracks.append({
'number': totalTracks + 1,
'title': unicode(track['recording']['title']),
'id': unicode(track['recording']['id']),
'url': u"http://musicbrainz.org/track/" + track['recording']['id'],
'duration': int(track['recording']['length'] if 'length' in track['recording'] else track['length'] if 'length' in track else 0)
})
totalTracks += 1
release_dict = {
'hasasin': bool(releaseResult.asin),
'asin': releaseResult.asin,
'trackscount': len(releaseResult.getTracks()),
'releaseid': u.extractUuid(releaseResult.id),
'releasedate': releaseResult.getEarliestReleaseDate(),
'hasasin': bool(releaseResult.get('asin')),
'asin': unicode(releaseResult.get('asin')) if 'asin' in releaseResult else None,
'trackscount': totalTracks,
'releaseid': unicode(releaseResult.get('id')),
'releasedate': unicode(releaseResult.get('date')),
'format': format,
'country': country
}
tracks = []
i = 1
for track in releaseResult.tracks:
tracks.append({
'number': i,
'title': track.title,
'id': u.extractUuid(track.id),
'url': track.id,
'duration': track.duration
})
i += 1
release_dict['tracks'] = tracks
releaselist.append(release_dict)
#necessary to make dates that miss the month and/or day show up after full dates
def getSortableReleaseDate(releaseDate):
if releaseDate.count('-') == 2:
return releaseDate
elif releaseDate.count('-') == 1:
return releaseDate + '32'
else:
return releaseDate + '13-32'
releaselist.sort(key=lambda x:getSortableReleaseDate(x['releasedate']))
average_tracks = sum(x['trackscount'] for x in releaselist) / float(len(releaselist))
for item in releaselist:
item['trackscount_delta'] = abs(average_tracks - item['trackscount'])
a = multikeysort(releaselist, ['-hasasin', 'country', 'format', 'trackscount_delta'])
release_dict = {'releaseid' :a[0]['releaseid'],
'releasedate' : releaselist[0]['releasedate'],
'releasedate' : unicode(releaselist[0]['releasedate']),
'trackcount' : a[0]['trackscount'],
'tracks' : a[0]['tracks'],
'asin' : a[0]['asin'],
'releaselist' : releaselist,
'artist_name' : releaseGroup.artist.name,
'artist_id' : u.extractUuid(releaseGroup.artist.id),
'title' : releaseGroup.title,
'type' : u.extractFragment(releaseGroup.type)
'artist_name' : unicode(releaseGroup['artist-credit'][0]['artist']['name']),
'artist_id' : unicode(releaseGroup['artist-credit'][0]['artist']['id']),
'title' : unicode(releaseGroup['title']),
'type' : unicode(releaseGroup['type'])
}
return release_dict
@@ -386,21 +373,14 @@ def getRelease(releaseid):
with mb_lock:
release = {}
inc = ws.ReleaseIncludes(tracks=True, releaseEvents=True, releaseGroup=True, artist=True)
results = None
attempt = 0
q, sleepytime = startmb()
while attempt < 5:
try:
results = q.getReleaseById(releaseid, inc)
break
results = musicbrainzngs.get_release_by_id(releaseid,["artists","release-groups","media","recordings"]).get('release')
except WebServiceError, e:
logger.warn('Attempt to retrieve information from MusicBrainz for release "%s" failed (%s)' % (releaseid, str(e)))
attempt += 1
time.sleep(5)
if not results:
@@ -408,36 +388,34 @@ def getRelease(releaseid):
time.sleep(sleepytime)
release['title'] = results.title
release['id'] = u.extractUuid(results.id)
release['asin'] = results.asin
release['date'] = results.getEarliestReleaseDate()
release['title'] = unicode(results['title'])
release['id'] = unicode(results['id'])
release['asin'] = unicode(results['asin']) if 'asin' in results else None
release['date'] = unicode(results['date'])
rg = results.getReleaseGroup()
if rg:
release['rgid'] = u.extractUuid(rg.id)
release['rg_title'] = rg.title
release['rg_type'] = u.extractFragment(rg.type)
if 'release-group' in results:
release['rgid'] = unicode(results['release-group']['id'])
release['rg_title'] = unicode(results['release-group']['title'])
release['rg_type'] = unicode(results['release-group']['type'])
else:
logger.warn("Release " + releaseid + "had no ReleaseGroup associated")
#so we can start with a releaseID from anywhere and get the artist info
#it looks like MB api v1 only returns 1 artist object - 2.0 returns more...
release['artist_name'] = results.artist.name
release['artist_id'] = u.extractUuid(results.artist.id)
release['artist_name'] = unicode(results['artist-credit'][0]['artist']['name'])
release['artist_id'] = unicode(results['artist-credit'][0]['artist']['id'])
totalTracks = 0
tracks = []
i = 1
for track in results.tracks:
for medium in results['medium-list']:
for track in medium['track-list']:
tracks.append({
'number': i,
'title': track.title,
'id': u.extractUuid(track.id),
'url': track.id,
'duration': track.duration
'number': totalTracks + 1,
'title': unicode(track['recording']['title']),
'id': unicode(track['recording']['id']),
'url': u"http://musicbrainz.org/track/" + track['recording']['id'],
'duration': int(track['length']) if 'length' in track else 0
})
i += 1
totalTracks += 1
release['tracks'] = tracks
@@ -459,21 +437,15 @@ def findArtistbyAlbum(name):
term = '"'+artist['AlbumTitle']+'" AND artist:"'+name+'"'
f = ws.ReleaseGroupFilter(query=term, limit=1)
results = None
attempt = 0
q, sleepytime = startmb(forcemb=True)
while attempt < 5:
try:
results = q.getReleaseGroups(f)
break
results = musicbrainzngs.search_release_groups(term).get('release-group-list')
except WebServiceError, e:
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
attempt += 1
time.sleep(10)
time.sleep(5)
time.sleep(sleepytime)
@@ -481,39 +453,41 @@ def findArtistbyAlbum(name):
return False
artist_dict = {}
for releaseGroup in results:
newArtist = releaseGroup['artist-credit'][0]['artist']
if 'disambiguation' in newArtist:
uniquename = unicode(newArtist['sort-name'] + " (" + newArtist['disambiguation'] + ")")
else:
uniquename = unicode(newArtist['sort-name'])
artist_dict['name'] = unicode(newArtist['sort-name'])
artist_dict['uniquename'] = uniquename
artist_dict['id'] = unicode(newArtist['id'])
artist_dict['url'] = u'http://musicbrainz.org/artist/' + newArtist['id']
artist_dict['score'] = int(releaseGroup['ext:score'])
for result in results:
releaseGroup = result.releaseGroup
artist_dict['name'] = releaseGroup.artist.name
artist_dict['uniquename'] = releaseGroup.artist.getUniqueName()
artist_dict['id'] = u.extractUuid(releaseGroup.artist.id)
artist_dict['url'] = releaseGroup.artist.id
artist_dict['score'] = result.score
return artist_dict
def findAlbumID(artist=None, album=None):
f = ws.ReleaseGroupFilter(title=album, artistName=artist, limit=1)
results = None
attempt = 0
results_ngs = None
q, sleepytime = startmb(forcemb=True)
while attempt < 5:
try:
results = q.getReleaseGroups(f)
break
term = '"'+album+'" AND artist:"'+artist+'"'
results_ngs = musicbrainzngs.search_release_groups(term,1).get('release-group-list')
except WebServiceError, e:
logger.warn('Attempt to query MusicBrainz for %s - %s failed (%s)' % (artist, album, str(e)))
attempt += 1
time.sleep(10)
time.sleep(5)
time.sleep(sleepytime)
if not results:
if not results_ngs:
return False
rgid = u.extractUuid(results[0].releaseGroup.id)
return rgid
if len(results_ngs) < 1:
return False
rgid_ngs = unicode(results_ngs[0]['id'])
return rgid_ngs

View File

@@ -1,26 +0,0 @@
"""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 <model.Artist>}, L{Release <model.Release>}, or
L{Track <model.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 <matt@mafr.de>
"""
__revision__ = '$Id: __init__.py 12974 2011-05-01 08:43:54Z luks $'
__version__ = '0.7.3'
# EOF

View File

@@ -1,10 +0,0 @@
"""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

View File

@@ -1,253 +0,0 @@
# -*- 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

View File

@@ -1,400 +0,0 @@
# -*- 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

View File

@@ -1,24 +0,0 @@
# -*- 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

View File

@@ -1,59 +0,0 @@
# -*- 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

View File

@@ -1,221 +0,0 @@
"""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 <matt@mafr.de>
"""
__revision__ = '$Id: disc.py 11987 2009-08-22 11:57:51Z matt $'
import sys
import urllib
import urlparse
import ctypes
import ctypes.util
from lib.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

File diff suppressed because it is too large Load Diff

View File

@@ -1,204 +0,0 @@
"""Various utilities to simplify common tasks.
This module contains helper functions to make common tasks easier.
@author: Matthias Friedrich <matt@mafr.de>
"""
__revision__ = '$Id: utils.py 11853 2009-07-21 09:26:50Z luks $'
import re
import urlparse
import os.path
__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 lib.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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
from musicbrainz import *
from lib.musicbrainzngs.musicbrainz import *

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2012 Kenneth Reitz.
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""
pythoncompat
"""
import sys
# -------
# Pythons
# -------
# Syntax sugar.
_ver = sys.version_info
#: Python 2.x?
is_py2 = (_ver[0] == 2)
#: Python 3.x?
is_py3 = (_ver[0] == 3)
# ---------
# Specifics
# ---------
if is_py2:
from StringIO import StringIO
from urllib2 import HTTPPasswordMgr, HTTPDigestAuthHandler, Request,\
HTTPHandler, build_opener, HTTPError, URLError,\
build_opener
from httplib import BadStatusLine, HTTPException
from urlparse import urlunparse
from urllib import urlencode
bytes = str
unicode = unicode
basestring = basestring
elif is_py3:
from io import StringIO
from urllib.request import HTTPPasswordMgr, HTTPDigestAuthHandler, Request,\
HTTPHandler, build_opener
from urllib.error import HTTPError, URLError
from http.client import HTTPException, BadStatusLine
from urllib.parse import urlunparse, urlencode
unicode = str
bytes = bytes
basestring = (str,bytes)

View File

@@ -4,9 +4,11 @@
# See the COPYING file for more information.
import xml.etree.ElementTree as ET
import string
import StringIO
import logging
from lib.musicbrainzngs import compat
from lib.musicbrainzngs import util
try:
from ET import fixtag
except:
@@ -16,7 +18,7 @@ except:
# tag and namespace declaration, if any
if isinstance(tag, ET.QName):
tag = tag.text
namespace_uri, tag = string.split(tag[1:], "}", 1)
namespace_uri, tag = tag[1:].split("}", 1)
prefix = namespaces.get(namespace_uri)
if prefix is None:
prefix = "ns%d" % len(namespaces)
@@ -29,6 +31,7 @@ except:
xmlns = None
return "%s:%s" % (prefix, tag), xmlns
NS_MAP = {"http://musicbrainz.org/ns/mmd-2.0#": "ws2",
"http://musicbrainz.org/ns/ext#-2.0": "ext"}
_log = logging.getLogger("python-musicbrainz-ngs")
@@ -113,9 +116,7 @@ def parse_inner(inner_els, element):
return result
def parse_message(message):
s = message.read()
f = StringIO.StringIO(s)
tree = ET.ElementTree(file=f)
tree = util.bytes_to_elementtree(message)
root = tree.getroot()
result = {}
valid_elements = {"artist": parse_artist,
@@ -176,7 +177,8 @@ def parse_artist_list(al):
def parse_artist(artist):
result = {}
attribs = ["id", "type", "ext:score"]
elements = ["name", "sort-name", "country", "user-rating", "disambiguation"]
elements = ["name", "sort-name", "country", "user-rating",
"disambiguation", "gender", "ipi"]
inner_els = {"life-span": parse_artist_lifespan,
"recording-list": parse_recording_list,
"release-list": parse_release_list,
@@ -199,7 +201,8 @@ def parse_label_list(ll):
def parse_label(label):
result = {}
attribs = ["id", "type", "ext:score"]
elements = ["name", "sort-name", "country", "label-code", "user-rating"]
elements = ["name", "sort-name", "country", "label-code", "user-rating",
"ipi", "disambiguation"]
inner_els = {"life-span": parse_artist_lifespan,
"release-list": parse_release_list,
"tag-list": parse_tag_list,
@@ -427,7 +430,7 @@ def parse_track_list(tl):
def parse_track(track):
result = {}
elements = ["position", "title"]
elements = ["position", "title","length"] #CHANGED!!!
inner_els = {"recording": parse_recording}
result.update(parse_elements(elements, track))

View File

@@ -3,20 +3,20 @@
# This file is distributed under a BSD-2-Clause type license.
# See the COPYING file for more information.
import urlparse
import urllib2
import urllib
import mbxml
import re
import threading
import time
import logging
import httplib
import socket
import xml.etree.ElementTree as etree
from xml.parsers import expat
import base64
_version = "0.3dev"
from lib.musicbrainzngs import mbxml
from lib.musicbrainzngs import util
from lib.musicbrainzngs import compat
_version = "0.3devMODIFIED"
_log = logging.getLogger("musicbrainzngs")
@@ -74,6 +74,7 @@ VALID_INCLUDES = {
'puid': ["artists", "releases", "puids", "echoprints", "isrcs"],
'isrc': ["artists", "releases", "puids", "echoprints", "isrcs"],
'iswc': ["artists"],
'collection': ['releases'],
}
VALID_RELEASE_TYPES = [
"nat", "album", "single", "ep", "compilation", "soundtrack", "spokenword",
@@ -83,29 +84,33 @@ VALID_RELEASE_STATUSES = ["official", "promotion", "bootleg", "pseudo-release"]
VALID_SEARCH_FIELDS = {
'artist': [
'arid', 'artist', 'sortname', 'type', 'begin', 'end', 'comment',
'alias', 'country', 'gender', 'tag'
'alias', 'country', 'gender', 'tag', 'ipi', 'artistaccent'
],
'release-group': [
'rgid', 'releasegroup', 'reid', 'release', 'arid', 'artist',
'artistname', 'creditname', 'type', 'tag'
'artistname', 'creditname', 'type', 'tag', 'releasegroupaccent',
'releases', 'comment'
],
'release': [
'reid', 'release', 'arid', 'artist', 'artistname', 'creditname',
'type', 'status', 'tracks', 'tracksmedium', 'discids',
'discidsmedium', 'mediums', 'date', 'asin', 'lang', 'script',
'country', 'date', 'label', 'catno', 'barcode', 'puid'
'country', 'date', 'label', 'catno', 'barcode', 'puid', 'comment',
'format', 'releaseaccent', 'rgid'
],
'recording': [
'rid', 'recording', 'isrc', 'arid', 'artist', 'artistname',
'creditname', 'reid', 'release', 'type', 'status', 'tracks',
'tracksrelease', 'dur', 'qdur', 'tnum', 'position', 'tag'
'tracksrelease', 'dur', 'qdur', 'tnum', 'position', 'tag', 'comment',
'country', 'date' 'format', 'recordingaccent'
],
'label': [
'laid', 'label', 'sortname', 'type', 'code', 'country', 'begin',
'end', 'comment', 'alias', 'tag'
'end', 'comment', 'alias', 'tag', 'ipi', 'labelaccent'
],
'work': [
'wid', 'work', 'iswc', 'type', 'arid', 'artist', 'alias', 'tag'
'wid', 'work', 'iswc', 'type', 'arid', 'artist', 'alias', 'tag',
'comment', 'workaccent'
],
}
@@ -186,9 +191,9 @@ def _check_filter_and_make_params(entity, includes, release_status=[], release_t
the filters can be used with the given includes. Return a params
dict that can be passed to _do_mb_query.
"""
if isinstance(release_status, basestring):
if isinstance(release_status, compat.basestring):
release_status = [release_status]
if isinstance(release_type, basestring):
if isinstance(release_type, compat.basestring):
release_type = [release_type]
_check_filter(release_status, VALID_RELEASE_STATUSES)
_check_filter(release_type, VALID_RELEASE_TYPES)
@@ -225,9 +230,17 @@ def auth(u, p):
user = u
password = p
def hpauth(u, p):
"""Set the username and password to be used in subsequent queries to
the MusicBrainz XML API that require authentication.
"""
global hpuser, hppassword
hpuser = u
hppassword = p
def set_useragent(app, version, contact=None):
""" Set the User-Agent to be used for requests to the MusicBrainz webservice.
This should be set before requests are made."""
"""Set the User-Agent to be used for requests to the MusicBrainz webservice.
This must be set before requests are made."""
global _useragent, _client
if contact is not None:
_useragent = "%s/%s python-musicbrainz-ngs/%s ( %s )" % (app, version, _version, contact)
@@ -237,6 +250,8 @@ def set_useragent(app, version, contact=None):
_log.debug("set user-agent to %s" % _useragent)
def set_hostname(new_hostname):
"""Set the base hostname for MusicBrainz webservice requests.
Defaults to 'musicbrainz.org'."""
global hostname
hostname = new_hostname
@@ -244,17 +259,26 @@ def set_hostname(new_hostname):
limit_interval = 1.0
limit_requests = 1
do_rate_limit = True
def set_rate_limit(new_interval=1.0, new_requests=1):
def set_rate_limit(rate_limit=True, new_interval=1.0, new_requests=1):
"""Sets the rate limiting behavior of the module. Must be invoked
before the first Web service call. Specify the number of requests
(`new_requests`) that may be made per given interval
(`new_interval`).
before the first Web service call.
If the `rate_limit` parameter is set to True, then only a set number
of requests (`new_requests`) will be made per given interval
(`new_interval`). If `rate_limit` is False, then no rate limiting
will occur.
"""
global limit_interval
global limit_requests
global do_rate_limit
if new_interval <= 0.0:
raise ValueError("new_interval can't be less than 0")
if new_requests <= 0:
raise ValueError("new_requests can't be less than 0")
limit_interval = new_interval
limit_requests = new_requests
do_rate_limit = rate_limit
class _rate_limit(object):
"""A decorator that limits the rate at which the function may be
@@ -290,6 +314,7 @@ class _rate_limit(object):
def __call__(self, *args, **kwargs):
with self.lock:
if do_rate_limit:
self._update_remaining()
# Delay if necessary.
@@ -302,11 +327,8 @@ class _rate_limit(object):
self.remaining_requests -= 1.0
return self.fun(*args, **kwargs)
# Generic support for making HTTP requests.
# From pymb2
class _RedirectPasswordMgr(urllib2.HTTPPasswordMgr):
class _RedirectPasswordMgr(compat.HTTPPasswordMgr):
def __init__(self):
self._realms = { }
@@ -321,18 +343,18 @@ class _RedirectPasswordMgr(urllib2.HTTPPasswordMgr):
# ignoring the uri parameter intentionally
self._realms[realm] = (username, password)
class _DigestAuthHandler(urllib2.HTTPDigestAuthHandler):
class _DigestAuthHandler(compat.HTTPDigestAuthHandler):
def get_authorization (self, req, chal):
qop = chal.get ('qop', None)
if qop and ',' in qop and 'auth' in qop.split (','):
chal['qop'] = 'auth'
return urllib2.HTTPDigestAuthHandler.get_authorization (self, req, chal)
return compat.HTTPDigestAuthHandler.get_authorization (self, req, chal)
class _MusicbrainzHttpRequest(urllib2.Request):
class _MusicbrainzHttpRequest(compat.Request):
""" A custom request handler that allows DELETE and PUT"""
def __init__(self, method, url, data=None):
urllib2.Request.__init__(self, url, data)
compat.Request.__init__(self, url, data)
allowed_m = ["GET", "POST", "DELETE", "PUT"]
if method not in allowed_m:
raise ValueError("invalid method: %s" % method)
@@ -344,7 +366,7 @@ class _MusicbrainzHttpRequest(urllib2.Request):
# Core (internal) functions for calling the MB API.
def _safe_open(opener, req, body=None, max_retries=8, retry_delay_delta=2.0):
def _safe_open(opener, req, body=None, max_retries=3, retry_delay_delta=2.0):
"""Open an HTTP request with a given URL opener and (optionally) a
request body. Transient errors lead to retries. Permanent errors
and repeated errors are translated into a small set of handleable
@@ -362,8 +384,8 @@ def _safe_open(opener, req, body=None, max_retries=8, retry_delay_delta=2.0):
else:
f = opener.open(req)
except urllib2.HTTPError, exc:
if exc.code in (400, 404):
except compat.HTTPError as exc:
if exc.code in (400, 404, 411):
# Bad request, not found, etc.
raise ResponseError(cause=exc)
elif exc.code in (503, 502, 500):
@@ -374,19 +396,23 @@ def _safe_open(opener, req, body=None, max_retries=8, retry_delay_delta=2.0):
# retrying for now.
_log.debug("unknown HTTP error %i" % exc.code)
last_exc = exc
except httplib.BadStatusLine, exc:
except compat.BadStatusLine as exc:
_log.debug("bad status line")
last_exc = exc
except httplib.HTTPException, exc:
except compat.HTTPException as exc:
_log.debug("miscellaneous HTTP exception: %s" % str(exc))
last_exc = exc
except urllib2.URLError, exc:
except compat.URLError as exc:
if isinstance(exc.reason, socket.error):
code = exc.reason.errno
if code == 104: # "Connection reset by peer."
continue
raise NetworkError(cause=exc)
except IOError, exc:
except socket.error as exc:
if exc.errno == 104:
continue
raise NetworkError(cause=exc)
except IOError as exc:
raise NetworkError(cause=exc)
else:
# No exception! Yay!
@@ -425,23 +451,23 @@ def _mb_request(path, method='GET', auth_required=False, client_required=False,
# Encode Unicode arguments using UTF-8.
for key, value in args.items():
if isinstance(value, unicode):
if isinstance(value, compat.unicode):
args[key] = value.encode('utf8')
# Construct the full URL for the request, including hostname and
# query string.
url = urlparse.urlunparse((
url = compat.urlunparse((
'http',
hostname,
'/ws/2/%s' % path,
'',
urllib.urlencode(args),
compat.urlencode(args),
''
))
_log.debug("%s request for %s" % (method, url))
# Set up HTTP request handler and URL opener.
httpHandler = urllib2.HTTPHandler(debuglevel=0)
httpHandler = compat.HTTPHandler(debuglevel=0)
handlers = [httpHandler]
# Add credentials if required.
@@ -455,14 +481,24 @@ def _mb_request(path, method='GET', auth_required=False, client_required=False,
authHandler.add_password("musicbrainz.org", (), user, password)
handlers.append(authHandler)
opener = urllib2.build_opener(*handlers)
opener = compat.build_opener(*handlers)
# Make request.
req = _MusicbrainzHttpRequest(method, url, data)
req.add_header('User-Agent', _useragent)
# Add headphones credentials
if hostname == '178.63.142.150:8181':
base64string = base64.encodestring('%s:%s' % (hpuser, hppassword)).replace('\n', '')
req.add_header("Authorization", "Basic %s" % base64string)
_log.debug("requesting with UA %s" % _useragent)
if body:
req.add_header('Content-Type', 'application/xml; charset=UTF-8')
elif not data and not req.has_header('Content-Length'):
# Explicitly indicate zero content length if no request data
# will be sent (avoids HTTP 411 error).
req.add_header('Content-Length', '0')
f = _safe_open(opener, req, body)
# Parse the response.
@@ -496,6 +532,8 @@ def _do_mb_query(entity, id, includes=[], params={}):
response is parsed and returned.
"""
# Build arguments.
if not isinstance(includes, list):
includes = [includes]
_check_includes(entity, includes)
auth_required = _is_auth_required(entity, includes)
args = dict(params)
@@ -507,15 +545,28 @@ def _do_mb_query(entity, id, includes=[], params={}):
path = '%s/%s' % (entity, id)
return _mb_request(path, 'GET', auth_required, args=args)
def _do_mb_search(entity, query='', fields={}, limit=None, offset=None):
def _do_mb_search(entity, query='', fields={},
limit=None, offset=None, strict=False):
"""Perform a full-text search on the MusicBrainz search server.
`query` is a free-form query string and `fields` is a dictionary
`query` is a lucene query string when no fields are set,
but is escaped when any fields are given. `fields` is a dictionary
of key/value query parameters. They keys in `fields` must be valid
for the given entity type.
"""
# Encode the query terms as a Lucene query string.
query_parts = [query.replace('\x00', '').strip()]
for key, value in fields.iteritems():
query_parts = []
if query:
clean_query = util._unicode(query)
if fields:
clean_query = re.sub(r'([+\-&|!(){}\[\]\^"~*?:\\])',
r'\\\1', clean_query)
if strict:
query_parts.append('"%s"' % clean_query)
else:
query_parts.append(clean_query.lower())
else:
query_parts.append(clean_query)
for key, value in fields.items():
# Ensure this is a valid search field.
if key not in VALID_SEARCH_FIELDS[entity]:
raise InvalidSearchFieldError(
@@ -523,12 +574,19 @@ def _do_mb_search(entity, query='', fields={}, limit=None, offset=None):
)
# Escape Lucene's special characters.
value = util._unicode(value)
value = re.sub(r'([+\-&|!(){}\[\]\^"~*?:\\])', r'\\\1', value)
value = value.replace('\x00', '').strip()
value = value.lower() # Avoid binary operators like OR.
if value:
query_parts.append(u'%s:(%s)' % (key, value))
full_query = u' '.join(query_parts).strip()
if strict:
query_parts.append('%s:"%s"' % (key, value))
else:
value = value.lower() # avoid AND / OR
query_parts.append('%s:(%s)' % (key, value))
if strict:
full_query = ' AND '.join(query_parts).strip()
else:
full_query = ' '.join(query_parts).strip()
if not full_query:
raise ValueError('at least one query term is required')
@@ -562,23 +620,23 @@ def _do_mb_post(path, body):
# Single entity by ID
def get_artist_by_id(id, includes=[], release_status=[], release_type=[]):
params = _check_filter_and_make_params(includes, release_status, release_type)
params = _check_filter_and_make_params("artist", includes, release_status, release_type)
return _do_mb_query("artist", id, includes, params)
def get_label_by_id(id, includes=[], release_status=[], release_type=[]):
params = _check_filter_and_make_params(includes, release_status, release_type)
params = _check_filter_and_make_params("label", includes, release_status, release_type)
return _do_mb_query("label", id, includes, params)
def get_recording_by_id(id, includes=[], release_status=[], release_type=[]):
params = _check_filter_and_make_params(includes, release_status, release_type)
params = _check_filter_and_make_params("recording", includes, release_status, release_type)
return _do_mb_query("recording", id, includes, params)
def get_release_by_id(id, includes=[], release_status=[], release_type=[]):
params = _check_filter_and_make_params(includes, release_status, release_type)
params = _check_filter_and_make_params("release", includes, release_status, release_type)
return _do_mb_query("release", id, includes, params)
def get_release_group_by_id(id, includes=[], release_status=[], release_type=[]):
params = _check_filter_and_make_params(includes, release_status, release_type)
params = _check_filter_and_make_params("release-group", includes, release_status, release_type)
return _do_mb_query("release-group", id, includes, params)
def get_work_by_id(id, includes=[]):
@@ -587,54 +645,68 @@ def get_work_by_id(id, includes=[]):
# Searching
def search_artists(query='', limit=None, offset=None, **fields):
"""Search for artists by a free-form `query` string and/or any of
def search_artists(query='', limit=None, offset=None, strict=False, **fields):
"""Search for artists by a free-form `query` string or any of
the following keyword arguments specifying field queries:
arid, artist, sortname, type, begin, end, comment, alias, country,
gender, tag
When `fields` are set, special lucene characters are escaped
in the `query`.
"""
return _do_mb_search('artist', query, fields, limit, offset)
return _do_mb_search('artist', query, fields, limit, offset, strict)
def search_labels(query='', limit=None, offset=None, **fields):
"""Search for labels by a free-form `query` string and/or any of
def search_labels(query='', limit=None, offset=None, strict=False, **fields):
"""Search for labels by a free-form `query` string or any of
the following keyword arguments specifying field queries:
laid, label, sortname, type, code, country, begin, end, comment,
alias, tag
When `fields` are set, special lucene characters are escaped
in the `query`.
"""
return _do_mb_search('label', query, fields, limit, offset)
return _do_mb_search('label', query, fields, limit, offset, strict)
def search_recordings(query='', limit=None, offset=None, **fields):
"""Search for recordings by a free-form `query` string and/or any of
def search_recordings(query='', limit=None, offset=None, strict=False, **fields):
"""Search for recordings by a free-form `query` string or any of
the following keyword arguments specifying field queries:
rid, recording, isrc, arid, artist, artistname, creditname, reid,
release, type, status, tracks, tracksrelease, dur, qdur, tnum,
position, tag
When `fields` are set, special lucene characters are escaped
in the `query`.
"""
return _do_mb_search('recording', query, fields, limit, offset)
return _do_mb_search('recording', query, fields, limit, offset, strict)
def search_releases(query='', limit=None, offset=None, **fields):
"""Search for releases by a free-form `query` string and/or any of
def search_releases(query='', limit=None, offset=None, strict=False, **fields):
"""Search for releases by a free-form `query` string or any of
the following keyword arguments specifying field queries:
reid, release, arid, artist, artistname, creditname, type, status,
tracks, tracksmedium, discids, discidsmedium, mediums, date, asin,
lang, script, country, date, label, catno, barcode, puid
When `fields` are set, special lucene characters are escaped
in the `query`.
"""
return _do_mb_search('release', query, fields, limit, offset)
return _do_mb_search('release', query, fields, limit, offset, strict)
def search_release_groups(query='', limit=None, offset=None, **fields):
"""Search for release groups by a free-form `query` string and/or
def search_release_groups(query='', limit=None, offset=None,
strict=False, **fields):
"""Search for release groups by a free-form `query` string or
any of the following keyword arguments specifying field queries:
rgid, releasegroup, reid, release, arid, artist, artistname,
creditname, type, tag
When `fields` are set, special lucene characters are escaped
in the `query`.
"""
return _do_mb_search('release-group', query, fields, limit, offset)
return _do_mb_search('release-group', query, fields,
limit, offset, strict)
def search_works(query='', limit=None, offset=None, **fields):
"""Search for works by a free-form `query` string and/or any of
def search_works(query='', limit=None, offset=None, strict=False, **fields):
"""Search for works by a free-form `query` string or any of
the following keyword arguments specifying field queries:
wid, work, iswc, type, arid, artist, alias, tag
When `fields` are set, special lucene characters are escaped
in the `query`.
"""
return _do_mb_search('work', query, fields, limit, offset)
return _do_mb_search('work', query, fields, limit, offset, strict)
# Lists of entities
@@ -721,35 +793,40 @@ def get_releases_in_collection(collection):
# Submission methods
def submit_barcodes(barcodes):
"""
Submits a set of {release1: barcode1, release2:barcode2}
Must call auth(user, pass) first
"""
"""Submits a set of {release1: barcode1, release2:barcode2}
Must call auth(user, pass) first"""
query = mbxml.make_barcode_request(barcodes)
return _do_mb_post("release", query)
def submit_puids(puids):
"""Submit PUIDs.
Must call auth(user, pass) first"""
query = mbxml.make_puid_request(puids)
return _do_mb_post("recording", query)
def submit_echoprints(echoprints):
"""Submit echoprints.
Must call auth(user, pass) first"""
query = mbxml.make_echoprint_request(echoprints)
return _do_mb_post("recording", query)
def submit_isrcs(recordings_isrcs):
"""
Submit ISRCs.
Submits a set of {recording-id: [isrc1, irc1]}
Must call auth(user, pass) first
"""
"""Submit ISRCs.
Submits a set of {recording-id: [isrc1, isrc1, ...]}
Must call auth(user, pass) first"""
query = mbxml.make_isrc_request(recordings_isrcs=recordings_isrcs)
return _do_mb_post("recording", query)
def submit_tags(artist_tags={}, recording_tags={}):
""" Submit user tags.
"""Submit user tags.
Artist or recording parameters are of the form:
{'entityid': [taglist]}
"""
Must call auth(user, pass) first"""
query = mbxml.make_tag_request(artist_tags, recording_tags)
return _do_mb_post("tag", query)
@@ -757,15 +834,24 @@ def submit_ratings(artist_ratings={}, recording_ratings={}):
""" Submit user ratings.
Artist or recording parameters are of the form:
{'entityid': rating}
"""
Must call auth(user, pass) first"""
query = mbxml.make_rating_request(artist_ratings, recording_ratings)
return _do_mb_post("rating", query)
def add_releases_to_collection(collection, releases=[]):
"""Add releases to a collection.
Collection and releases should be identified by their MBIDs
Must call auth(user, pass) first"""
# XXX: Maximum URI length of 16kb means we should only allow ~400 releases
releaselist = ";".join(releases)
_do_mb_put("collection/%s/releases/%s" % (collection, releaselist))
def remove_releases_from_collection(collection, releases=[]):
"""Remove releases from a collection.
Collection and releases should be identified by their MBIDs
Must call auth(user, pass) first"""
releaselist = ";".join(releases)
_do_mb_delete("collection/%s/releases/%s" % (collection, releaselist))

View File

@@ -0,0 +1,37 @@
# This file is part of the musicbrainzngs library
# Copyright (C) Alastair Porter, Adrian Sampson, and others
# This file is distributed under a BSD-2-Clause type license.
# See the COPYING file for more information.
import sys
import locale
import xml.etree.ElementTree as ET
from . import compat
def _unicode(string, encoding=None):
"""Try to decode byte strings to unicode.
This can only be a guess, but this might be better than failing.
It is safe to use this on numbers or strings that are already unicode.
"""
if isinstance(string, compat.unicode):
unicode_string = string
elif isinstance(string, compat.bytes):
# use given encoding, stdin, preferred until something != None is found
if encoding is None:
encoding = sys.stdin.encoding
if encoding is None:
encoding = locale.getpreferredencoding()
unicode_string = string.decode(encoding, "ignore")
else:
unicode_string = compat.unicode(string)
return unicode_string.replace('\x00', '').strip()
def bytes_to_elementtree(_bytes):
if compat.is_py3:
s = _unicode(_bytes.read(), "utf-8")
else:
s = _bytes.read()
f = compat.StringIO(s)
tree = ET.ElementTree(file=f)
return tree