Merge from trunk

This commit is contained in:
Chad Wilson
2011-07-30 16:11:07 +08:00
19 changed files with 500 additions and 311 deletions

View File

@@ -1,3 +1,16 @@
Version 0.15 - 2011-07-17
* Added options for using standardized track, release, and artist metadata.
* Added preferred release format support.
* Expanded preferred release country support to allow multiple countries.
* Added support for tagging non-album tracks (standalone recordings).
* Plugins can now be installed via drag and drop, or a file browser.
* Added several new tags: %_originaldate%, %_recordingcomment%, and %_releasecomment%
* Changes to request queuing: added separate high and low priority queues for each host.
* Tagger scripts now run after metadata plugins finish (#5850)
* The "compilation" tag can now be $unset or modified via tagger script.
* Added a shortcut (Ctrl+I) for Edit->Details.
* Miscellaneous bug fixes.
Version 0.15beta1 - 2011-05-29
* Support for the NGS web service
@@ -118,7 +131,7 @@ Version 0.10 - 2008-07-27
* Fixed crash when reading CD TOC on 64-bit systems
* Fixed handling of MP4 files with no metadata
* Change the hotkey for help to the right key for OS X
* Replace special characters after tagger script evalutaion to allow
* Replace special characters after tagger script evalutaion to allow
special characters being replaced by tagger script
* Actually ignore 'ignored (folksonomy) tags'
* Remove dependency on Mutagen 1.13, version 1.11 is enough now
@@ -218,7 +231,7 @@ Version 0.9.0alpha12 - 2007-07-29
environment variable "PICARD_DEBUG".
* For plugins:
- metadata["~#length"] is now metadata.length
- metadata["~#artwork"] is now metadata.images
- metadata["~#artwork"] is now metadata.images
* New Features:
* Save embedded images to MP4 files.
* Added option to select release events for albums.

View File

@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
PLUGIN_NAME = u'No release'
PLUGIN_AUTHOR = u'Johannes Weißl'
PLUGIN_DESCRIPTION = '''Do not store specific release information in releases of unknown origin.'''
PLUGIN_VERSION = '0.1'
PLUGIN_API_VERSIONS = ['0.15']
from PyQt4 import QtCore, QtGui
from picard.album import Album
from picard.metadata import register_album_metadata_processor, register_track_metadata_processor
from picard.ui.options import register_options_page, OptionsPage
from picard.ui.itemviews import BaseAction, register_album_action
from picard.config import BoolOption, TextOption
class Ui_NoReleaseOptionsPage(object):
def setupUi(self, NoReleaseOptionsPage):
NoReleaseOptionsPage.setObjectName('NoReleaseOptionsPage')
NoReleaseOptionsPage.resize(394, 300)
self.verticalLayout = QtGui.QVBoxLayout(NoReleaseOptionsPage)
self.verticalLayout.setObjectName('verticalLayout')
self.groupBox = QtGui.QGroupBox(NoReleaseOptionsPage)
self.groupBox.setObjectName('groupBox')
self.vboxlayout = QtGui.QVBoxLayout(self.groupBox)
self.vboxlayout.setObjectName('vboxlayout')
self.norelease_enable = QtGui.QCheckBox(self.groupBox)
self.norelease_enable.setObjectName('norelease_enable')
self.vboxlayout.addWidget(self.norelease_enable)
self.label = QtGui.QLabel(self.groupBox)
self.label.setObjectName('label')
self.vboxlayout.addWidget(self.label)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName('horizontalLayout')
self.norelease_strip_tags = QtGui.QLineEdit(self.groupBox)
self.norelease_strip_tags.setObjectName('norelease_strip_tags')
self.horizontalLayout.addWidget(self.norelease_strip_tags)
self.vboxlayout.addLayout(self.horizontalLayout)
self.verticalLayout.addWidget(self.groupBox)
spacerItem = QtGui.QSpacerItem(368, 187, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.retranslateUi(NoReleaseOptionsPage)
QtCore.QMetaObject.connectSlotsByName(NoReleaseOptionsPage)
def retranslateUi(self, NoReleaseOptionsPage):
self.groupBox.setTitle(QtGui.QApplication.translate('NoReleaseOptionsPage', 'No release', None, QtGui.QApplication.UnicodeUTF8))
self.norelease_enable.setText(QtGui.QApplication.translate('NoReleaseOptionsPage', _('Enable plugin for all releases by default'), None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate('NoReleaseOptionsPage', _('Tags to strip (comma-separated)'), None, QtGui.QApplication.UnicodeUTF8))
def strip_release_specific_metadata(tagger, metadata):
strip_tags = tagger.config.setting['norelease_strip_tags']
strip_tags = [tag.strip() for tag in strip_tags.split(',')]
for tag in strip_tags:
if tag in metadata:
del metadata[tag]
class NoReleaseAction(BaseAction):
NAME = _('Remove specific release information...')
def callback(self, objs):
for album in objs:
if isinstance(album, Album):
strip_release_specific_metadata(self.tagger, album.metadata)
for track in album.tracks:
strip_release_specific_metadata(self.tagger, track.metadata)
for file in track.linked_files:
track.update_file_metadata(file)
album.update()
class NoReleaseOptionsPage(OptionsPage):
NAME = 'norelease'
TITLE = 'No release'
PARENT = 'plugins'
options = [
BoolOption('setting', 'norelease_enable', False),
TextOption('setting', 'norelease_strip_tags', 'asin,barcode,catalognumber,date,label,media,releasecountry,releasestatus'),
]
def __init__(self, parent=None):
super(NoReleaseOptionsPage, self).__init__(parent)
self.ui = Ui_NoReleaseOptionsPage()
self.ui.setupUi(self)
def load(self):
self.ui.norelease_strip_tags.setText(self.config.setting['norelease_strip_tags'])
self.ui.norelease_enable.setChecked(self.config.setting['norelease_enable'])
def save(self):
self.config.setting['norelease_strip_tags'] = unicode(self.ui.norelease_strip_tags.text())
self.config.setting['norelease_enable'] = self.ui.norelease_enable.isChecked()
def NoReleaseAlbumProcessor(tagger, metadata, release):
if tagger.config.setting['norelease_enable']:
strip_release_specific_metadata(tagger, metadata)
def NoReleaseTrackProcessor(tagger, metadata, track, release):
if tagger.config.setting['norelease_enable']:
strip_release_specific_metadata(tagger, metadata)
register_album_metadata_processor(NoReleaseAlbumProcessor)
register_track_metadata_processor(NoReleaseTrackProcessor)
register_album_action(NoReleaseAction())
register_options_page(NoReleaseOptionsPage)

View File

@@ -17,7 +17,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
version_info = (0, 15, 0, 'beta', 2)
version_info = (0, 15, 0, 'final', 0)
if version_info[3] == 'final':
if version_info[2] == 0:

View File

@@ -19,8 +19,9 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import traceback
from collections import deque
from PyQt4 import QtCore
from picard.metadata import Metadata, run_album_metadata_processors
from picard.metadata import Metadata, run_album_metadata_processors, run_track_metadata_processors
from picard.dataobj import DataObject
from picard.file import File
from picard.track import Track
@@ -28,8 +29,8 @@ from picard.script import ScriptParser
from picard.ui.item import Item
from picard.util import format_time, partial, translate_artist, queue, mbid_validate
from picard.cluster import Cluster
from picard.mbxml import release_to_metadata, track_to_metadata
from picard.const import RELEASE_FORMATS, VARIOUS_ARTISTS_ID
from picard.mbxml import release_to_metadata, medium_to_metadata, track_to_metadata, media_formats_from_node, label_info_from_node
from picard.const import VARIOUS_ARTISTS_ID
class Album(DataObject, Item):
@@ -38,12 +39,17 @@ class Album(DataObject, Item):
DataObject.__init__(self, id)
self.metadata = Metadata()
self.tracks = []
self.format_str = ""
self.tracks_str = ""
self.loaded = False
self.load_task = None
self.rgloaded = False
self.rgid = None
self._files = 0
self._requests = 0
self._discid = discid
self._after_load_callbacks = queue.Queue()
self._metadata_plugins = deque()
self.other_versions = []
self.unmatched_files = Cluster(_("Unmatched Files"), special=True, related_album=self, hide_if_empty=True)
@@ -78,13 +84,11 @@ class Album(DataObject, Item):
m.length = 0
release_to_metadata(release_node, m, config=self.config, album=self)
self.format_str = media_formats_from_node(release_node.medium_list[0])
self.rgid = release_node.release_group[0].id
if self._discid:
m['musicbrainz_discid'] = self._discid
if not self.rgloaded:
releasegroupid = release_node.release_group[0].id
self.tagger.xmlws.get_release_group_by_id(releasegroupid, self._release_group_request_finished)
# 'Translate' artist name
if self.config.setting['translate_artist_names']:
m['albumartist'] = m['artist'] = translate_artist(m['artist'], m['artistsort'])
@@ -93,88 +97,55 @@ class Album(DataObject, Item):
if m['musicbrainz_artistid'] == VARIOUS_ARTISTS_ID:
m['albumartistsort'] = m['artistsort'] = m['albumartist'] = m['artist'] = self.config.setting['va_name']
# Album metadata plugins
try:
run_album_metadata_processors(self, m, release_node)
except:
self.log.error(traceback.format_exc())
# Prepare parser for user's script
if self.config.setting["enable_tagger_script"]:
script = self.config.setting["tagger_script"]
parser = ScriptParser()
else:
script = parser = None
# Strip leading/trailing whitespace
m.strip_whitespace()
ignore_tags = [s.strip() for s in self.config.setting['ignore_tags'].split(',')]
artists = set()
first_artist = None
compilation = False
track_counts = []
m['totaldiscs'] = release_node.medium_list[0].count
for medium in release_node.medium_list[0].medium:
discnumber = medium.position[0].text
track_list = medium.track_list[0]
totaltracks = track_list.count
discsubtitle = medium.title[0].text if "title" in medium.children else ""
format = medium.format[0].text if "format" in medium.children else ""
plugins = partial(run_album_metadata_processors, self, m, release_node)
self._metadata_plugins.append(plugins)
for node in track_list.track:
t = Track(node.recording[0].id, self)
for medium_node in release_node.medium_list[0].medium:
mm = Metadata()
mm.copy(m)
medium_to_metadata(medium_node, mm)
track_counts.append(mm['totaltracks'])
for track_node in medium_node.track_list[0].track:
t = Track(track_node.recording[0].id, self)
self._new_tracks.append(t)
# Get track metadata
tm = t.metadata
tm.copy(m)
tm['discnumber'] = discnumber
tm['discsubtitle'] = discsubtitle
tm['totaltracks'] = totaltracks
if format: tm['media'] = format
track_to_metadata(node, config=self.config, track=t)
t._customize_metadata(node, release_node, script, parser, ignore_tags)
artists.add(tm['musicbrainz_artistid'])
tm.copy(mm)
track_to_metadata(track_node, t, self.config)
m.length += tm.length
if len(artists) > 1:
for t in self._new_tracks:
t.metadata['compilation'] = '1'
artist_id = tm['musicbrainz_artistid']
if compilation is False:
if first_artist is None:
first_artist = artist_id
if first_artist != artist_id:
compilation = True
for track in self._new_tracks:
track.metadata['compilation'] = '1'
else:
tm['compilation'] = '1'
if script:
# Run tagger script for the album itself
try:
parser.eval(script, m)
except:
self.log.error(traceback.format_exc())
t._customize_metadata(ignore_tags)
plugins = partial(run_track_metadata_processors, self, tm, release_node, track_node)
self._metadata_plugins.append(plugins)
self.tracks_str = " + ".join(track_counts)
return True
def _parse_release_group(self, document):
releases = document.metadata[0].release_group[0].release_list[0].release
for release in releases:
version = {}
version["mbid"] = release.id
if "date" in release.children:
version["date"] = release.date[0].text
if "country" in release.children:
version["country"] = release.country[0].text
version["totaltracks"] = [int(m.track_list[0].count) for m in release.medium_list[0].medium]
formats = {}
for medium in release.medium_list[0].medium:
if "format" in medium.children:
f = medium.format[0].text
if f in formats: formats[f] += 1
else: formats[f] = 1
if formats:
version["media"] = " + ".join(["%s%s" % (str(j)+u"×" if j>1 else "", RELEASE_FORMATS[i])
for i, j in formats.items()])
self.other_versions.append(version)
self.other_versions.sort(key=lambda x: x["date"])
def _release_request_finished(self, document, http, error):
if self.load_task is None:
return
self.load_task = None
parsed = False
try:
if error:
@@ -200,6 +171,20 @@ class Album(DataObject, Item):
if parsed or error:
self._finalize_loading(error)
def _parse_release_group(self, document):
for node in document.metadata[0].release_list[0].release:
v = {}
v["mbid"] = node.id
v["date"] = node.date[0].text if "date" in node.children else ""
v["country"] = node.country[0].text if "country" in node.children else ""
labels, catnums = label_info_from_node(node.label_info_list[0])
v["labels"] = ", ".join(set(labels))
v["catnums"] = ", ".join(set(catnums))
v["tracks"] = " + ".join([m.track_list[0].count for m in node.medium_list[0].medium])
v["format"] = media_formats_from_node(node.medium_list[0])
self.other_versions.append(v)
self.other_versions.sort(key=lambda x: x["date"])
def _release_group_request_finished(self, document, http, error):
try:
if error:
@@ -212,6 +197,7 @@ class Album(DataObject, Item):
self.log.error(traceback.format_exc())
finally:
self.rgloaded = True
self.emit(QtCore.SIGNAL("release_group_loaded"))
def _finalize_loading(self, error):
if error:
@@ -220,22 +206,58 @@ class Album(DataObject, Item):
del self._new_metadata
del self._new_tracks
self.update()
else:
if not self._requests:
for track in self.tracks:
for file in list(track.linked_files):
file.move(self.unmatched_files)
self.metadata = self._new_metadata
self.tracks = self._new_tracks
del self._new_metadata
del self._new_tracks
self.loaded = True
self.match_files(self.unmatched_files.files)
self.update()
self.tagger.window.set_statusbar_message('Album %s loaded', self.id, timeout=3000)
while self._after_load_callbacks.qsize() > 0:
func = self._after_load_callbacks.get()
func()
return
# Run metadata plugins
while self._metadata_plugins:
try:
self._metadata_plugins.popleft()()
except:
self.log.error(traceback.format_exc())
if not self._requests:
# Prepare parser for user's script
script = None
if self.config.setting["enable_tagger_script"]:
script = self.config.setting["tagger_script"]
parser = ScriptParser()
for track in self._new_tracks:
# Update the track with new album metadata, in case it
# was modified by plugins.
for key, values in self._new_metadata.rawitems():
track.metadata[key] = values[:]
# Run tagger script for each track
if script:
try:
parser.eval(script, track.metadata)
except:
self.log.error(traceback.format_exc())
# Strip leading/trailing whitespace
track.metadata.strip_whitespace()
# Run tagger script for the album itself
if script:
try:
parser.eval(script, self._new_metadata)
except:
self.log.error(traceback.format_exc())
self._new_metadata.strip_whitespace()
for track in self.tracks:
for file in list(track.linked_files):
file.move(self.unmatched_files)
self.metadata = self._new_metadata
self.tracks = self._new_tracks
del self._new_metadata
del self._new_tracks
self.loaded = True
self.match_files(self.unmatched_files.files)
self.update()
self.tagger.window.set_statusbar_message('Album %s loaded', self.id, timeout=3000)
while self._after_load_callbacks.qsize() > 0:
func = self._after_load_callbacks.get()
func()
def load(self):
if self._requests:
@@ -264,8 +286,9 @@ class Album(DataObject, Item):
if self.config.setting['enable_ratings']:
require_authentication = True
inc += ['user-ratings']
self.tagger.xmlws.get_release_by_id(self.id, self._release_request_finished, inc=inc,
mblogin=require_authentication)
self.load_task = self.tagger.xmlws.get_release_by_id(
self.id, self._release_request_finished, inc=inc,
mblogin=require_authentication)
def run_when_loaded(self, func):
if self.loaded:
@@ -273,6 +296,11 @@ class Album(DataObject, Item):
else:
self._after_load_callbacks.put(func)
def stop_loading(self):
if self.load_task:
self.tagger.xmlws.remove_task(self.load_task)
self.load_task = None
def update(self, update_tracks=True):
self.tagger.emit(QtCore.SIGNAL("album_updated"), self, update_tracks)
@@ -361,7 +389,7 @@ class Album(DataObject, Item):
if not self.tracks:
return False
for track in self.tracks:
if len(track.linked_files) != 1:
if track.num_linked_files != 1:
return False
else:
return True
@@ -421,3 +449,6 @@ class NatAlbum(Album):
for file in track.linked_files:
track.update_file_metadata(file)
super(NatAlbum, self).update(update_tracks)
def _finalize_loading(self, error):
self.update()

View File

@@ -44,6 +44,7 @@ VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377'
# Release formats
RELEASE_FORMATS = {
u'CD': N_('CD'),
u'CD-R': N_('CD-R'),
u'HDCD': N_('HDCD'),
u'Vinyl': N_('Vinyl'),
u'7" Vinyl': N_('7" Vinyl'),

View File

@@ -82,7 +82,7 @@ def _openLibrary():
# Check to see if we're running in a Mac OS X bundle.
if sys.platform == 'darwin':
try:
libDiscId = ctypes.cdll.LoadLibrary('../Frameworks/libdiscid.1.dylib')
libDiscId = ctypes.cdll.LoadLibrary('../Frameworks/libdiscid.0.dylib')
_setPrototypes(libDiscId)
return libDiscId
except OSError, e:
@@ -105,7 +105,7 @@ def _openLibrary():
if sys.platform == 'linux2':
libName = 'libdiscid.so.0'
elif sys.platform == 'darwin':
libName = 'libdiscid.1.dylib'
libName = 'libdiscid.0.dylib'
elif sys.platform == 'win32':
libName = 'discid.dll'
else:

View File

@@ -329,6 +329,7 @@ class File(LockableObject, Item):
if parent != self.parent:
self.log.debug("Moving %r from %r to %r", self, self.parent, parent)
self.clear_lookup_task()
self.tagger._ofa.stop_analyze(self)
if self.parent:
self.clear_pending()
self.parent.remove_file(self)
@@ -537,11 +538,6 @@ class File(LockableObject, Item):
else:
self.tagger.move_file_to_nat(self, track.id, node=track)
def lookup_trackid(self, trackid):
""" Try to identify the file using the trackid. """
self.clear_lookup_task()
self.lookup_task = self.tagger.xmlws.get_track_by_id(trackid, partial(self._lookup_finished, 'trackid'))
def lookup_puid(self, puid):
""" Try to identify the file using the PUID. """
self.tagger.window.set_statusbar_message(N_("Looking up the PUID for file %s..."), self.filename)

View File

@@ -274,7 +274,7 @@ class ID3File(File):
count = 0
# Convert rating to range between 0 and 255
rating = int(values[0]) * 255 / (settings['rating_steps'] - 1)
rating = int(round(float(values[0]) * 255 / (settings['rating_steps'] - 1)))
tags.add(id3.POPM(email=settings['rating_user_email'], rating=rating, count=count))
elif name in self.__rtranslate:
frameid = self.__rtranslate[name]

View File

@@ -20,8 +20,11 @@
import re
import unicodedata
from picard.util import format_time, translate_artist
from picard.const import RELEASE_FORMATS
AMAZON_ASIN_URL_REGEX = re.compile(r'^http://(?:www.)?(.*?)(?:\:[0-9]+)?/.*/([0-9B][0-9A-Z]{9})(?:[^0-9A-Z]|$)')
_artist_rel_types = {
"composer": "composer",
"conductor": "conductor",
@@ -69,7 +72,7 @@ def _relations_to_metadata(relation_lists, m, config):
if relation_list.target_type == 'artist':
for relation in relation_list.relation:
value = relation.artist[0].name[0].text
if config and config.setting['translate_artist_names']:
if config.setting['translate_artist_names']:
value = translate_artist(value, relation.artist[0].sort_name[0].text)
reltype = relation.type
attribs = []
@@ -86,33 +89,30 @@ def _relations_to_metadata(relation_lists, m, config):
name = _artist_rel_types[reltype]
except KeyError:
continue
m.add(name, value)
if value not in m[name]:
m.add(name, value)
elif relation_list.target_type == 'work':
for relation in relation_list.relation:
if relation.type == 'performance':
work = relation.work[0]
if 'relation_list' in work.children:
_relations_to_metadata(work.relation_list, m, config)
# TODO: Release, Track, URL relations
elif relation_list.target_type == 'url':
for relation in relation_list.relation:
if relation.type == 'amazon asin':
url = relation.target[0].text
match = AMAZON_ASIN_URL_REGEX.match(url)
if match is not None and 'asin' not in m:
m['asin'] = match.group(2)
def _set_artist_item(m, release, albumname, name, value):
if release:
m[albumname] = value
if name not in m:
m[name] = value
else:
m[name] = value
def artist_credit_from_node(node, config=None):
def artist_credit_from_node(node, config):
artist = ""
artistsort = ""
standardize_name = config and config.setting["standardize_artists"]
for credit in node.name_credit:
a = credit.artist[0]
artistsort += a.sort_name[0].text
if 'name' in credit.children and not standardize_name:
if 'name' in credit.children and not config.setting["standardize_artists"]:
artist += credit.name[0].text
else:
artist += a.name[0].text
@@ -122,12 +122,17 @@ def artist_credit_from_node(node, config=None):
return (artist, artistsort)
def artist_credit_to_metadata(node, m=None, release=None, config=None):
def artist_credit_to_metadata(node, m, config, release=False):
ids = [n.artist[0].id for n in node.name_credit]
_set_artist_item(m, release, 'musicbrainz_albumartistid', 'musicbrainz_artistid', ids)
artist, artistsort = artist_credit_from_node(node, config)
_set_artist_item(m, release, 'albumartist', 'artist', artist)
_set_artist_item(m, release, 'albumartistsort', 'artistsort', artistsort)
if release:
m["musicbrainz_albumartistid"] = ids
m["albumartist"] = artist
m["albumartistsort"] = artistsort
else:
m["musicbrainz_artistid"] = ids
m["artist"] = artist
m["artistsort"] = artistsort
def label_info_from_node(node):
@@ -142,26 +147,41 @@ def label_info_from_node(node):
return (labels, catalog_numbers)
def track_to_metadata(node, track, config=None):
def media_formats_from_node(node):
formats = {}
for medium in node.medium:
if "format" in medium.children:
text = medium.format[0].text
formats[text] = formats.get(text, 0) + 1
if formats:
return " + ".join([
(str(j) + u"×" if j > 1 else "") + RELEASE_FORMATS.get(i, i)
for i, j in formats.items()])
else:
return ""
def track_to_metadata(node, track, config):
m = track.metadata
recording_to_metadata(node.recording[0], track, config)
transl = m['releasestatus'] == "pseudo-release"
# overwrite with data we have on the track
standardize_title = config and config.setting["standardize_tracks"]
standardize_artist = config and config.setting["standardize_artists"]
for name, nodes in node.children.iteritems():
if not nodes:
continue
if name == 'title' and not standardize_title:
m['title'] = nodes[0].text
if name == 'position':
if name == 'title':
if not config.setting["standardize_tracks"] or transl:
m['title'] = nodes[0].text
elif name == 'position':
m['tracknumber'] = nodes[0].text
elif name == 'length' and nodes[0].text:
m.length = int(nodes[0].text)
elif name == 'artist_credit' and not standardize_artist:
artist_credit_to_metadata(nodes[0], m, config=config)
elif name == 'artist_credit':
if not config.setting["standardize_artists"] or transl:
artist_credit_to_metadata(nodes[0], m, config)
def recording_to_metadata(node, track, config=None):
def recording_to_metadata(node, track, config):
m = track.metadata
m.length = 0
m['musicbrainz_trackid'] = node.attribs['id']
@@ -175,11 +195,9 @@ def recording_to_metadata(node, track, config=None):
elif name == 'disambiguation':
m['~recordingcomment'] = nodes[0].text
elif name == 'artist_credit':
artist_credit_to_metadata(nodes[0], m, config=config)
if name == 'relation_list':
artist_credit_to_metadata(nodes[0], m, config)
elif name == 'relation_list':
_relations_to_metadata(nodes, m, config)
elif name == 'release_list' and nodes[0].count != '0':
release_to_metadata(nodes[0].release[0], m)
elif name == 'tag_list':
add_folksonomy_tags(nodes[0], track)
elif name == 'user_tag_list':
@@ -189,28 +207,44 @@ def recording_to_metadata(node, track, config=None):
elif name == 'user_rating':
m['~rating'] = nodes[0].text
def _should_standardise_title(config):
return config and config.setting["standardize_releases"]
def release_to_metadata(node, m, config=None, album=None):
def medium_to_metadata(node, m):
for name, nodes in node.children.iteritems():
if not nodes:
continue
if name == 'position':
m['discnumber'] = nodes[0].text
elif name == 'track_list':
m['totaltracks'] = nodes[0].count
elif name == 'title':
m['discsubtitle'] = nodes[0].text
elif name == 'format':
m['media'] = nodes[0].text
def release_to_metadata(node, m, config, album=None):
"""Make metadata dict from a XML 'release' node."""
m['musicbrainz_albumid'] = node.attribs['id']
if "status" in node.children:
m['releasestatus'] = node.status[0].text.lower()
transl = m['releasestatus'] == "pseudo-release"
for name, nodes in node.children.iteritems():
if not nodes:
continue
if name == 'release_group':
release_group_to_metadata(nodes[0], m, config, album)
elif name == 'status':
m['releasestatus'] = nodes[0].text.lower()
elif name == 'title' and not _should_standardise_title(config):
m['album'] = nodes[0].text
elif name == 'title':
if not config.setting["standardize_releases"] or transl:
m['album'] = nodes[0].text
elif name == 'disambiguation':
m['~releasecomment'] = nodes[0].text
elif name == 'asin':
m['asin'] = nodes[0].text
elif name == 'artist_credit':
artist_credit_to_metadata(nodes[0], m, True, config=config)
if not config.setting["standardize_artists"] or transl:
artist_credit_to_metadata(nodes[0], m, config, release=True)
elif name == 'date':
m['date'] = nodes[0].text
elif name == 'country':
@@ -231,20 +265,29 @@ def release_to_metadata(node, m, config=None, album=None):
elif name == 'user_tag_list':
add_user_folksonomy_tags(nodes[0], album)
def release_group_to_metadata(node, m, config=None, album=None):
def release_group_to_metadata(node, m, config, album=None):
"""Make metadata dict from a XML 'release-group' node taken from inside a 'release' node."""
if 'type' in node.attribs:
m['releasetype'] = node.type.lower()
if _should_standardise_title(config):
m['album'] = node.title[0].text
transl = m['releasestatus'] == "pseudo-release"
for name, nodes in node.children.iteritems():
if not nodes:
continue
if name == 'tag_list':
if name == 'title':
if config.setting["standardize_releases"] and not transl:
m['album'] = node.title[0].text
elif name == 'artist_credit':
if config.setting["standardize_artists"] and not transl:
artist_credit_to_metadata(nodes[0], m, config, release=True)
elif name == 'first_release_date':
m['~originaldate'] = nodes[0].text
elif name == 'tag_list':
add_folksonomy_tags(nodes[0], album)
elif name == 'user_tag_list':
add_user_folksonomy_tags(nodes[0], album)
add_user_folksonomy_tags(nodes[0], album)
def add_folksonomy_tags(node, obj):
if obj and 'tag' in node.children:

View File

@@ -35,6 +35,7 @@ class OFA(QtCore.QObject):
self.log.warning(
"Libofa not found! Fingerprinting will be disabled.")
self._decoders = []
self._analyze_tasks = {}
plugins = ["avcodec", "directshow", "quicktime", "gstreamer"]
for name in plugins:
try:
@@ -86,7 +87,8 @@ class OFA(QtCore.QObject):
def _lookup_fingerprint(self, next, filename, result=None, error=None):
try:
file = self.tagger.files[filename]
except (KeyError):
del self._analyze_tasks[file]
except KeyError:
# The file has been removed. do nothing
return
@@ -127,7 +129,20 @@ class OFA(QtCore.QObject):
return
# calculate fingerprint
if ofa is not None:
self.tagger.analyze_queue.put(file.filename)
if file not in self._analyze_tasks:
task = (partial(self.calculate_fingerprint, file.filename),
partial(self._lookup_fingerprint, self.tagger._lookup_puid, file.filename),
QtCore.Qt.LowEventPriority + 1)
self._analyze_tasks[file] = task
self.tagger.analyze_queue.put(task)
return
# no PUID
next(result=None)
def stop_analyze(self, file):
try:
task = self._analyze_tasks[file]
self.tagger.analyze_queue.remove(task)
del self._analyze_tasks[file]
except:
pass

View File

@@ -29,6 +29,7 @@ import signal
import sys
import traceback
import time
from collections import deque
# Install gettext "noop" function.
import __builtin__
@@ -84,7 +85,6 @@ from picard.util import (
mbid_validate
)
from picard.webservice import XmlWebService
from picard.mbxml import recording_to_metadata
class Tagger(QtGui.QApplication):
@@ -127,25 +127,17 @@ class Tagger(QtGui.QApplication):
self.thread_pool = thread.ThreadPool(self)
self.load_queue = queue.Queue()
self.load_queue.run_item = thread.generic_run_item
self.save_queue = queue.Queue()
self.save_queue.run_item = thread.generic_run_item
self.analyze_queue = queue.Queue()
self.analyze_queue.run_item = analyze_thread_run_item
self.analyze_queue.next = self._lookup_puid
self.other_queue = queue.Queue()
self.other_queue.run_item = thread.generic_run_item
threads = self.thread_pool.threads
threads.append(thread.Thread(self.thread_pool, [self.load_queue,
self.other_queue]))
threads.append(thread.Thread(self.thread_pool, [self.save_queue]))
threads.append(thread.Thread(self.thread_pool, [self.other_queue,
self.load_queue]))
threads.append(thread.Thread(self.thread_pool, [self.analyze_queue]))
threads.append(thread.Thread(self.thread_pool, self.load_queue))
threads.append(thread.Thread(self.thread_pool, self.load_queue))
threads.append(thread.Thread(self.thread_pool, self.save_queue))
threads.append(thread.Thread(self.thread_pool, self.other_queue))
threads.append(thread.Thread(self.thread_pool, self.other_queue))
threads.append(thread.Thread(self.thread_pool, self.analyze_queue))
self.thread_pool.start()
self.stopping = False
@@ -181,7 +173,6 @@ class Tagger(QtGui.QApplication):
# Initialize fingerprinting
self._ofa = musicdns.OFA()
self._ofa.init()
self.analyze_queue.ofa = self._ofa
# Load plugins
self.pluginmanager = PluginManager()
@@ -327,7 +318,7 @@ class Tagger(QtGui.QApplication):
else:
self.move_file_to_album(file, albumid)
elif mbid_validate(trackid):
file.lookup_trackid(trackid)
self.move_file_to_nat(file, trackid)
elif self.config.setting['analyze_new_files']:
self.analyze([file])
@@ -348,17 +339,16 @@ class Tagger(QtGui.QApplication):
file.load(self._file_loaded)
def process_directory_listing(self, root, queue, result=None, error=None):
delay = 10
try:
# Read directory listing
if result is not None and error is None:
files = []
directories = []
directories = deque()
try:
for path in result:
path = os.path.join(root, path)
if os.path.isdir(path):
directories.append(path)
directories.appendleft(path)
else:
try:
files.append(decode_filename(path))
@@ -368,25 +358,22 @@ class Tagger(QtGui.QApplication):
finally:
if files:
self.add_files(files)
delay = min(25 * len(files), 500)
queue = directories + queue
queue.extendleft(directories)
finally:
# Scan next directory in the queue
try:
path = queue.pop(0)
path = queue.popleft()
except IndexError: pass
else:
func = partial(self.other_queue.put,
(partial(os.listdir, path),
partial(self.process_directory_listing,
path, queue),
QtCore.Qt.LowEventPriority))
QtCore.QTimer.singleShot(delay, func)
self.other_queue.put((
partial(os.listdir, path),
partial(self.process_directory_listing, path, queue),
QtCore.Qt.LowEventPriority))
def add_directory(self, path):
path = encode_filename(path)
self.other_queue.put((partial(os.listdir, path),
partial(self.process_directory_listing, path, []),
partial(self.process_directory_listing, path, deque()),
QtCore.Qt.LowEventPriority))
def get_file_by_id(self, id):
@@ -496,13 +483,14 @@ class Tagger(QtGui.QApplication):
for file in files:
if self.files.has_key(file.filename):
file.clear_lookup_task()
self.analyze_queue.remove(file.filename)
self._ofa.stop_analyze(file)
del self.files[file.filename]
file.remove(from_parent)
def remove_album(self, album):
"""Remove the specified album."""
self.log.debug("Removing %r", album)
album.stop_loading()
self.remove_files(self.get_files_from_objects([album]))
self.albums.remove(album)
self.emit(QtCore.SIGNAL("album_removed"), album)
@@ -643,18 +631,6 @@ class Tagger(QtGui.QApplication):
def num_pending_files(self):
return len([file for file in self.files.values() if file.state == File.PENDING])
def analyze_thread_run_item(thread, queue, filename):
next = partial(queue.ofa._lookup_fingerprint, queue.next, filename)
priority = QtCore.Qt.LowEventPriority + 1
try:
result = queue.ofa.calculate_fingerprint(filename)
except:
import traceback
thread.log.error(traceback.format_exc())
thread.to_main(next, priority, error=sys.exc_info()[1])
else:
thread.to_main(next, priority, result=result)
def help():
print """Usage: %s [OPTIONS] [FILE] [FILE] ...

View File

@@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from PyQt4 import QtCore
from picard.metadata import Metadata, run_track_metadata_processors
from picard.metadata import Metadata
from picard.dataobj import DataObject
from picard.util import format_time, translate_artist, asciipunct, partial
from picard.mbxml import recording_to_metadata
@@ -42,6 +42,7 @@ class Track(DataObject):
DataObject.__init__(self, id)
self.album = album
self.linked_files = []
self.num_linked_files = 0
self.metadata = Metadata()
def __repr__(self):
@@ -50,6 +51,7 @@ class Track(DataObject):
def add_file(self, file):
if file not in self.linked_files:
self.linked_files.append(file)
self.num_linked_files += 1
self.album._add_file(self, file)
self.update_file_metadata(file)
@@ -69,6 +71,7 @@ class Track(DataObject):
if file not in self.linked_files:
return
self.linked_files.remove(file)
self.num_linked_files -= 1
file.metadata.copy(file.saved_metadata)
self.album._remove_file(self, file)
self.update()
@@ -84,7 +87,7 @@ class Track(DataObject):
yield file
def is_linked(self):
return len(self.linked_files)>0
return self.num_linked_files > 0
def can_save(self):
"""Return if this object can be saved."""
@@ -118,7 +121,7 @@ class Track(DataObject):
return False
def similarity(self):
if len(self.linked_files) == 1:
if self.num_linked_files == 1:
return self.linked_files[0].similarity
else:
return 1
@@ -134,7 +137,7 @@ class Track(DataObject):
else:
return m[column], similarity
def _customize_metadata(self, node, release, script, parser, ignore_tags=None):
def _customize_metadata(self, ignore_tags=None):
tm = self.metadata
# 'Translate' artist name
@@ -154,21 +157,6 @@ class Track(DataObject):
if self.config.setting['convert_punctuation']:
tm.apply_func(asciipunct)
# Track metadata plugins
try:
run_track_metadata_processors(self, tm, release, node)
except:
self.log.error(traceback.format_exc())
if script:
# Run TaggerScript
try:
parser.eval(script, tm)
except:
self.log.error(traceback.format_exc())
# Strip leading/trailing whitespace
tm.strip_whitespace()
def _convert_folksonomy_tags_to_genre(self, ignore_tags):
# Combine release and track tags
tags = dict(self.folksonomy_tags)
@@ -218,7 +206,18 @@ class NonAlbumTrack(Track):
return super(NonAlbumTrack, self).column(column)
def load(self):
self.tagger.xmlws.get_track_by_id(self.id, partial(self._recording_request_finished))
inc = ["artist-credits"]
mblogin = False
if self.config.setting["folksonomy_tags"]:
if self.config.setting["only_my_tags"]:
mblogin = True
inc += ["user-tags"]
else:
inc += ["tags"]
if self.config.setting["enable_ratings"]:
mblogin = True
inc += ["user-ratings"]
self.tagger.xmlws.get_track_by_id(self.id, partial(self._recording_request_finished), inc, mblogin=mblogin)
def _recording_request_finished(self, document, http, error):
if error:
@@ -239,7 +238,7 @@ class NonAlbumTrack(Track):
parser = ScriptParser()
else:
script = parser = None
self._customize_metadata(recording, None, script, parser)
self._customize_metadata(recording)
self.loaded = True
if self.callback:
self.callback()

View File

@@ -24,7 +24,7 @@ from picard.album import Album, NatAlbum
from picard.cluster import Cluster, ClusterList, UnmatchedFiles
from picard.file import File
from picard.track import Track, NonAlbumTrack
from picard.util import encode_filename, icontheme, partial
from picard.util import encode_filename, icontheme, partial, webbrowser2
from picard.config import Option, TextOption
from picard.plugin import ExtensionPoint
from picard.const import RELEASE_COUNTRIES
@@ -332,7 +332,7 @@ class BaseTreeView(QtGui.QTreeWidget):
self.connect(self, QtCore.SIGNAL("doubleClicked(QModelIndex)"), self.activate_item)
def switch_release_version(self, album):
def _switch_release_version(self, album):
index = self.sender().data().toInt()[0]
album.switch_release_version(album.other_versions[index])
@@ -347,7 +347,7 @@ class BaseTreeView(QtGui.QTreeWidget):
if isinstance(obj, Track):
menu.addAction(self.window.edit_tags_action)
plugin_actions = list(_track_actions)
if len(obj.linked_files) == 1:
if obj.num_linked_files == 1:
plugin_actions.extend(_file_actions)
if isinstance(obj, NonAlbumTrack):
menu.addAction(self.window.refresh_action)
@@ -369,31 +369,36 @@ class BaseTreeView(QtGui.QTreeWidget):
menu.addAction(self.window.save_action)
menu.addAction(self.window.remove_action)
if isinstance(obj, Album) and not isinstance(obj, NatAlbum):
if isinstance(obj, Album) and not isinstance(obj, NatAlbum) and obj.loaded:
releases_menu = QtGui.QMenu(_("&Other versions"), menu)
self._switch_release_version = partial(self.switch_release_version, obj)
for i, version in enumerate(obj.other_versions):
name = []
if "date" in version:
name.append(version["date"])
if "country" in version:
try: name.append(RELEASE_COUNTRIES[version["country"]])
except KeyError: name.append(version["country"])
if "media" in version:
name.append(version["media"])
version_name = " / ".join(name).replace('&', '&&')
action = releases_menu.addAction(version_name or _('[no release info]'))
action.setData(QtCore.QVariant(i))
action.setCheckable(True)
if obj.id == version["mbid"]:
action.setChecked(True)
self.connect(action, QtCore.SIGNAL("triggered(bool)"), self._switch_release_version)
if releases_menu.isEmpty():
text = _('No other versions') if obj.rgloaded else _('Loading...')
action = releases_menu.addAction(text)
action.setEnabled(False)
menu.addSeparator()
menu.addMenu(releases_menu)
loading = releases_menu.addAction(_('Loading...'))
loading.setEnabled(False)
def _add_other_versions():
releases_menu.removeAction(loading)
switch_release_version = partial(self._switch_release_version, obj)
actions = []
for i, version in enumerate(obj.other_versions):
keys = ("date", "country", "labels", "catnums", "tracks", "format")
name = " / ".join([version[k] for k in keys if version[k]]).replace("&", "&&")
if name == version["tracks"]:
name = "%s / %s" % (_('[no release info]'), name)
action = releases_menu.addAction(name)
action.setData(QtCore.QVariant(i))
action.setCheckable(True)
if obj.id == version["mbid"]:
action.setChecked(True)
self.connect(action, QtCore.SIGNAL("triggered(bool)"), switch_release_version)
if not obj.rgloaded:
if obj.rgid:
self.connect(obj, QtCore.SIGNAL("release_group_loaded"), _add_other_versions)
kwargs = {"release-group": obj.rgid, "limit": 100}
self.tagger.xmlws.browse_releases(obj._release_group_request_finished, **kwargs)
else:
_add_other_versions()
if plugin_actions:
plugin_menu = QtGui.QMenu(_("&Plugins"), menu)
@@ -438,7 +443,16 @@ class BaseTreeView(QtGui.QTreeWidget):
def mimeTypes(self):
"""List of MIME types accepted by this view."""
return ["text/uri-list", "application/picard.file-list", "application/picard.album-list"]
return ["text/uri-list",
"application/picard.file-list",
"application/picard.album-list"]
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.acceptProposedAction()
def startDrag(self, supportedActions):
"""Start drag, *without* using pixmap."""
@@ -551,7 +565,7 @@ class BaseTreeView(QtGui.QTreeWidget):
# application/picard.album-list
albums = data.data("application/picard.album-list")
if albums:
albums = [self.tagger.get_album_by_id(albumsId) for albumsId in str(albums).split("\n")]
albums = [self.tagger.load_album(id) for id in str(albums).split("\n")]
self.drop_albums(albums, target)
handled = True
return handled
@@ -625,7 +639,7 @@ class AlbumTreeView(BaseTreeView):
except KeyError:
self.log.debug("Item for %r not found", track)
return
if len(track.linked_files) == 1:
if track.num_linked_files == 1:
file = track.linked_files[0]
color = self.track_colors[file.state]
icon = self.panel.decide_file_icon(file)
@@ -641,7 +655,7 @@ class AlbumTreeView(BaseTreeView):
#Add linked files (there will either be 0 or >1)
oldnum = item.childCount()
newnum = len(track.linked_files)
newnum = track.num_linked_files
# remove old items
if oldnum > newnum:
for i in range(oldnum - newnum):
@@ -735,3 +749,4 @@ class AlbumTreeView(BaseTreeView):
self.panel.unregister_object(album)
if album == self.tagger.nats:
self.tagger.nats = None

View File

@@ -251,8 +251,7 @@ class MainWindow(QtGui.QMainWindow):
self.exit_action = QtGui.QAction(_(u"E&xit"), self)
# TR: Keyboard shortcut for "Exit"
self.exit_action.setShortcut(QtGui.QKeySequence(_(u"Ctrl+Q")))
self.connect(self.exit_action, QtCore.SIGNAL("triggered()"),
self.close)
self.connect(self.exit_action, QtCore.SIGNAL("triggered()"), self.close)
self.remove_action = QtGui.QAction(icontheme.lookup('list-remove'), _(u"&Remove"), self)
self.remove_action.setStatusTip(_(u"Remove selected files/albums"))
@@ -407,10 +406,8 @@ class MainWindow(QtGui.QMainWindow):
self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
else:
self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
self.cd_lookup_action.setEnabled(len(get_cdrom_drives()) > 0)
def create_toolbar(self):
self.toolbar = toolbar = self.addToolBar(_(u"&Toolbar"))
self.update_toolbar_style()
@@ -663,14 +660,14 @@ class MainWindow(QtGui.QMainWindow):
statusBar += _(" (Error: %s)") % obj.error
file = obj
elif isinstance(obj, Track):
if len(obj.linked_files) == 1:
if obj.num_linked_files == 1:
file = obj.linked_files[0]
orig_metadata = file.orig_metadata
metadata = file.metadata
statusBar = "%s (%d%%)" % (file.filename, file.similarity * 100)
if file.state == file.ERROR:
statusBar += _(" (Error: %s)") % file.error
elif len(obj.linked_files) == 0:
elif obj.num_linked_files == 0:
metadata = obj.metadata
else:
metadata = obj.metadata

View File

@@ -49,8 +49,9 @@ class PluginsOptionsPage(OptionsPage):
self.ui.setupUi(self)
self.items = {}
self.connect(self.ui.plugins, QtCore.SIGNAL("itemSelectionChanged()"), self.change_details)
self.ui.plugins.__class__.mimeTypes = self.mimeTypes
self.ui.plugins.__class__.dropEvent = self.dropEvent
self.ui.plugins.mimeTypes = self.mimeTypes
self.ui.plugins.dropEvent = self.dropEvent
self.ui.plugins.dragEnterEvent = self.dragEnterEvent
if sys.platform == "win32":
self.loader="file:///%s"
else:
@@ -154,6 +155,10 @@ class PluginsOptionsPage(OptionsPage):
def mimeTypes(self):
return ["text/uri-list"]
def dragEnterEvent(self, event):
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
def dropEvent(self, event):
for path in [os.path.normpath(unicode(u.toLocalFile())) for u in event.mimeData().urls()]:
self.install_plugin(path)

View File

@@ -18,6 +18,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import sys
import traceback
from picard.util.queue import Queue
from PyQt4 import QtCore
@@ -36,54 +37,36 @@ class ProxyToMainEvent(QtCore.QEvent):
class Thread(QtCore.QThread):
def __init__(self, parent, queues):
def __init__(self, parent, queue):
QtCore.QThread.__init__(self, parent)
self.queues = queues
self.queue = queue
self.stopping = False
def stop(self):
self.stopping = True
self.queues[0].put(None)
def get_job(self):
for queue in self.queues:
if queue.qsize() > 0:
return (queue, queue.get())
return (self.queues[0], self.queues[0].get())
self.queue.put(None)
def run(self):
while not self.stopping:
queue, item = self.get_job()
item = self.queue.get()
if item is None:
continue
queue.run_item(self, queue, item)
self.usleep(100)
def run_item(thread, item):
func, next, priority = item
try:
result = func()
except:
import traceback
self.log.error(traceback.format_exc())
self.to_main(next, priority, error=sys.exc_info()[1])
else:
self.to_main(next, priority, result=result)
self.run_item(item)
def run_item(self, item):
func, next, priority = item
try:
result = func()
except:
self.log.error(traceback.format_exc())
self.to_main(next, priority, error=sys.exc_info()[1])
else:
self.to_main(next, priority, result=result)
def to_main(self, func, priority, *args, **kwargs):
event = ProxyToMainEvent(func, args, kwargs)
QtCore.QCoreApplication.postEvent(self.parent(), event, priority)
def generic_run_item(thread, queue, item):
func, next, priority = item
try:
result = func()
except:
import traceback
thread.log.error(traceback.format_exc())
thread.to_main(next, priority, error=sys.exc_info()[1])
else:
thread.to_main(next, priority, result=result)
class ThreadPool(QtCore.QObject):
@@ -91,7 +74,7 @@ class ThreadPool(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
self.threads = []
self.threads = []
ThreadPool.instance = self
def start(self):
@@ -99,23 +82,18 @@ class ThreadPool(QtCore.QObject):
thread.start(QtCore.QThread.LowPriority)
def stop(self):
queues = set()
for thread in self.threads:
thread.stop()
# FIXME: if a queue is in more than 1 thread, unlock will be called
# more than once.
for thread in self.threads:
for queue in thread.queues:
queue.unlock()
#for thread in self.threads:
# self.log.debug("Waiting for %r", thread)
# thread.wait()
queues.add(thread.queue)
for queue in queues:
queue.unlock()
def event(self, event):
if isinstance(event, ProxyToMainEvent):
try: event.call()
try:
event.call()
except:
import traceback
self.log.error(traceback.format_exc())
return True
return False

View File

@@ -27,6 +27,7 @@ import os
import sys
import re
import traceback
import time
from collections import deque, defaultdict
from PyQt4 import QtCore, QtNetwork, QtXml
from picard import version_string
@@ -145,7 +146,7 @@ class XmlWebService(QtCore.QObject):
send = self._request_methods[method]
reply = send(request, data) if data is not None else send(request)
key = (host, port)
self._last_request_times[key] = QtCore.QTime.currentTime()
self._last_request_times[key] = time.time()
self._active_requests[reply] = (request, handler, xml)
return True
@@ -209,10 +210,10 @@ class XmlWebService(QtCore.QObject):
queue = self._high_priority_queues.get(key) or self._low_priority_queues.get(key)
if not queue:
continue
now = QtCore.QTime.currentTime()
now = time.time()
last = self._last_request_times.get(key)
request_delay = REQUEST_DELAY[key]
last_ms = last.msecsTo(now) if last is not None else request_delay
last_ms = (now - last) * 1000 if last is not None else request_delay
if last_ms >= request_delay:
self.log.debug("Last request to %s was %d ms ago, starting another one", key, last_ms)
d = request_delay
@@ -256,19 +257,15 @@ class XmlWebService(QtCore.QObject):
def _get_by_id(self, entitytype, entityid, handler, inc=[], params=[], priority=False, important=False, mblogin=False):
host = self.config.setting["server_host"]
port = self.config.setting["server_port"]
path = "/ws/2/%s/%s?inc=%s&%s" % (entitytype, entityid, "+".join(inc), "&".join(params))
path = "/ws/2/%s/%s?inc=%s" % (entitytype, entityid, "+".join(inc))
if params: path += "&" + "&".join(params)
return self.get(host, port, path, handler, priority=priority, important=important, mblogin=mblogin)
def get_release_group_by_id(self, releasegroupid, handler, priority=True, important=False):
inc = ['releases', 'media']
return self._get_by_id('release-group', releasegroupid, handler, inc, priority=priority, important=important)
def get_release_by_id(self, releaseid, handler, inc=[], priority=True, important=False, mblogin=False):
return self._get_by_id('release', releaseid, handler, inc, priority=priority, important=important, mblogin=mblogin)
def get_track_by_id(self, trackid, handler, priority=False, important=False):
inc = ['releases', 'release-groups', 'media', 'artist-credits']
return self._get_by_id('recording', trackid, handler, inc, priority=priority, important=important)
def get_track_by_id(self, trackid, handler, inc=[], priority=True, important=False, mblogin=False):
return self._get_by_id('recording', trackid, handler, inc, priority=priority, important=important, mblogin=mblogin)
def lookup_puid(self, puid, handler, priority=False, important=False):
inc = ['releases', 'release-groups', 'media', 'artist-credits']
@@ -303,6 +300,17 @@ class XmlWebService(QtCore.QObject):
def find_tracks(self, handler, **kwargs):
return self._find('recording', handler, kwargs)
def _browse(self, entitytype, handler, kwargs, inc=[], priority=False, important=False):
host = self.config.setting["server_host"]
port = self.config.setting["server_port"]
params = "&".join(["%s=%s" % (k, v) for k, v in kwargs.items()])
path = "/ws/2/%s?%s&inc=%s" % (entitytype, params, "+".join(inc))
return self.get(host, port, path, handler, priority=priority, important=important)
def browse_releases(self, handler, priority=True, important=True, **kwargs):
inc = ["media", "labels"]
return self._browse("release", handler, kwargs, inc, priority=priority, important=important)
def submit_puids(self, puids, handler):
path = '/ws/2/recording/?client=' + USER_AGENT_STRING
recordings = ''.join(['<recording id="%s"><puid-list><puid id="%s"/></puid-list></recording>' % i for i in puids.items()])
@@ -328,3 +336,4 @@ class XmlWebService(QtCore.QObject):
def download(self, host, port, path, handler, priority=False, important=False):
return self.get(host, port, path, handler, xml=False, priority=priority, important=important)

View File

@@ -28,7 +28,7 @@ try:
'optimize' : 2,
'argv_emulation' : True,
'iconfile' : 'picard.icns',
'frameworks' : ['libofa.0.dylib', 'libiconv.2.dylib', 'libdiscid.1.dylib'],
'frameworks' : ['libofa.0.dylib', 'libiconv.2.dylib', 'libdiscid.0.dylib'],
'includes' : ['sip', 'PyQt4.Qt', 'picard.util.astrcmp', 'picard.musicdns.ofa', 'picard.musicdns.avcodec'],
'excludes' : ['pydoc'],
'plist' : { 'CFBundleName' : 'MusicBrainz Picard',

View File

@@ -3,6 +3,13 @@ from picard.metadata import Metadata
from picard.mbxml import track_to_metadata, release_to_metadata
from picard.webservice import XmlNode
class config:
setting = {
"standardize_tracks": False,
"standardize_artists": False,
"standardize_releases": False
}
class XmlNode(object):
def __init__(self, text=u'', children={}, attribs={}):
@@ -50,7 +57,7 @@ class TrackTest(unittest.TestCase):
})
track = Track()
m = track.metadata = Metadata()
track_to_metadata(node, track)
track_to_metadata(node, track, config)
self.failUnlessEqual('123', m['musicbrainz_trackid'])
self.failUnlessEqual('456; 789', m['musicbrainz_artistid'])
self.failUnlessEqual('Foo', m['title'])
@@ -94,7 +101,7 @@ class ReleaseTest(unittest.TestCase):
})]
})
m = Metadata()
release_to_metadata(release, m)
release_to_metadata(release, m, config)
self.failUnlessEqual('123', m['musicbrainz_albumid'])
self.failUnlessEqual('456; 789', m['musicbrainz_artistid'])
self.failUnlessEqual('456; 789', m['musicbrainz_albumartistid'])