mirror of
https://github.com/fergalmoran/picard.git
synced 2026-01-06 16:44:06 +00:00
@@ -60,7 +60,7 @@ class AcoustIDClient(QtCore.QObject):
|
||||
return artist_credit_el
|
||||
|
||||
def parse_recording(recording):
|
||||
if 'title' not in recording.children: # we have no metadata for this recording
|
||||
if 'title' not in recording.children: # we have no metadata for this recording
|
||||
return
|
||||
recording_id = recording.id[0].text
|
||||
recording_el = recording_list_el.append_child('recording')
|
||||
@@ -213,4 +213,3 @@ class AcoustIDClient(QtCore.QObject):
|
||||
if task[0] != file:
|
||||
new_queue.appendleft(task)
|
||||
self._queue = new_queue
|
||||
|
||||
|
||||
@@ -86,4 +86,3 @@ class AcoustIDManager(QtCore.QObject):
|
||||
for submission in fingerprints:
|
||||
submission.orig_trackid = submission.trackid
|
||||
self._check_unsubmitted()
|
||||
|
||||
|
||||
@@ -420,7 +420,7 @@ class Album(DataObject, Item):
|
||||
for track in self.tracks:
|
||||
for file in track.linked_files:
|
||||
if not file.is_saved():
|
||||
count+=1
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def column(self, column):
|
||||
@@ -429,7 +429,7 @@ class Album(DataObject, Item):
|
||||
linked_tracks = 0
|
||||
for track in self.tracks:
|
||||
if track.is_linked():
|
||||
linked_tracks+=1
|
||||
linked_tracks += 1
|
||||
text = u'%s\u200E (%d/%d' % (self.metadata['album'], linked_tracks, len(self.tracks))
|
||||
unmatched = self.get_num_unmatched_files()
|
||||
if unmatched:
|
||||
|
||||
@@ -24,6 +24,7 @@ import re
|
||||
from picard import log
|
||||
from picard.util import webbrowser2
|
||||
|
||||
|
||||
class FileLookup(object):
|
||||
|
||||
def __init__(self, parent, server, port, localPort):
|
||||
|
||||
@@ -339,7 +339,7 @@ class ClusterDict(object):
|
||||
return word
|
||||
|
||||
def getToken(self, index):
|
||||
token = None;
|
||||
token = None
|
||||
try:
|
||||
word, token = self.ids[index]
|
||||
except KeyError:
|
||||
@@ -416,7 +416,7 @@ class ClusterEngine(object):
|
||||
for i in xrange(self.clusterDict.getSize()):
|
||||
word, count = self.clusterDict.getWordAndCount(i)
|
||||
if word and count > 1:
|
||||
self.clusterBins[self.clusterCount] = [ i ]
|
||||
self.clusterBins[self.clusterCount] = [i]
|
||||
self.idClusterIndex[i] = self.clusterCount
|
||||
self.clusterCount = self.clusterCount + 1
|
||||
#print "init ",
|
||||
@@ -475,4 +475,3 @@ class ClusterEngine(object):
|
||||
|
||||
def can_refresh(self):
|
||||
return False
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import os, sys, re
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
# Install gettext "noop" function in case const.py gets imported directly.
|
||||
import __builtin__
|
||||
|
||||
@@ -53,31 +53,31 @@ COVERART_SITES = (
|
||||
AMAZON_SERVER = {
|
||||
"amazon.jp": {
|
||||
"server": "ec1.images-amazon.com",
|
||||
"id" : "09",
|
||||
"id": "09",
|
||||
},
|
||||
"amazon.co.jp": {
|
||||
"server": "ec1.images-amazon.com",
|
||||
"id" : "09",
|
||||
"id": "09",
|
||||
},
|
||||
"amazon.co.uk": {
|
||||
"server": "ec1.images-amazon.com",
|
||||
"id" : "02",
|
||||
"id": "02",
|
||||
},
|
||||
"amazon.de": {
|
||||
"server": "ec2.images-amazon.com",
|
||||
"id" : "03",
|
||||
"id": "03",
|
||||
},
|
||||
"amazon.com": {
|
||||
"server": "ec1.images-amazon.com",
|
||||
"id" : "01",
|
||||
"id": "01",
|
||||
},
|
||||
"amazon.ca": {
|
||||
"server": "ec1.images-amazon.com",
|
||||
"id" : "01", # .com and .ca are identical
|
||||
"id": "01", # .com and .ca are identical
|
||||
},
|
||||
"amazon.fr": {
|
||||
"server": "ec1.images-amazon.com",
|
||||
"id" : "08"
|
||||
"id": "08"
|
||||
},
|
||||
}
|
||||
|
||||
@@ -156,9 +156,9 @@ def _caa_append_image_to_trylist(try_list, imagedata):
|
||||
else:
|
||||
url = QUrl(imagedata["thumbnails"][thumbsize])
|
||||
extras = {
|
||||
'type': imagedata["types"][0].lower(), # FIXME: we pass only 1 type
|
||||
'type': imagedata["types"][0].lower(), # FIXME: we pass only 1 type
|
||||
'desc': imagedata["comment"],
|
||||
'front': imagedata['front'], # front image indicator from CAA
|
||||
'front': imagedata['front'], # front image indicator from CAA
|
||||
}
|
||||
_try_list_append_image_url(try_list, url, extras)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ CAA_TYPES = [
|
||||
{'name': "track", 'title': N_("Track")},
|
||||
{'name': "sticker", 'title': N_("Sticker")},
|
||||
{'name': "other", 'title': N_("Other")},
|
||||
{'name': "unknown", 'title': N_("Unknown")}, # pseudo type, used for the no type case
|
||||
{'name': "unknown", 'title': N_("Unknown")}, # pseudo type, used for the no type case
|
||||
]
|
||||
|
||||
CAA_TYPES_SEPARATOR = ' ' #separator to use when joining/splitting list of types
|
||||
CAA_TYPES_SEPARATOR = ' ' # separator to use when joining/splitting list of types
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
from picard.util import LockableObject
|
||||
|
||||
|
||||
class DataObject(LockableObject):
|
||||
|
||||
def __init__(self, id):
|
||||
|
||||
@@ -135,12 +135,12 @@ def _openLibrary():
|
||||
except OSError, e:
|
||||
raise NotImplementedError('Error opening library: ' + str(e))
|
||||
|
||||
assert False # not reached
|
||||
assert False # not reached
|
||||
|
||||
|
||||
def _setPrototypes(libDiscId):
|
||||
ct = ctypes
|
||||
libDiscId.discid_new.argtypes = ( )
|
||||
libDiscId.discid_new.argtypes = ()
|
||||
libDiscId.discid_new.restype = ct.c_void_p
|
||||
|
||||
libDiscId.discid_free.argtypes = (ct.c_void_p, )
|
||||
|
||||
@@ -18,16 +18,19 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
from mutagen import _util
|
||||
from picard.plugin import ExtensionPoint
|
||||
|
||||
_formats = ExtensionPoint()
|
||||
_extensions = {}
|
||||
|
||||
|
||||
def register_format(format):
|
||||
_formats.register(format.__module__, format)
|
||||
for ext in format.EXTENSIONS:
|
||||
_extensions[ext[1:]] = format
|
||||
|
||||
|
||||
def supported_formats():
|
||||
"""Returns list of supported formats."""
|
||||
formats = []
|
||||
@@ -35,6 +38,7 @@ def supported_formats():
|
||||
formats.append((format.EXTENSIONS, format.NAME))
|
||||
return formats
|
||||
|
||||
|
||||
def open(filename):
|
||||
"""Open the specified file and return a File instance with the appropriate format handler, or None."""
|
||||
i = filename.rfind(".")
|
||||
@@ -48,8 +52,6 @@ def open(filename):
|
||||
return format(filename)
|
||||
|
||||
|
||||
from mutagen import _util
|
||||
|
||||
def _insert_bytes_no_mmap(fobj, size, offset, BUFFER_SIZE=2**16):
|
||||
"""Insert size bytes of empty space starting at offset.
|
||||
|
||||
@@ -101,6 +103,7 @@ def _insert_bytes_no_mmap(fobj, size, offset, BUFFER_SIZE=2**16):
|
||||
if locked:
|
||||
_util.unlock(fobj)
|
||||
|
||||
|
||||
def _delete_bytes_no_mmap(fobj, size, offset, BUFFER_SIZE=2**16):
|
||||
"""Delete size bytes of empty space starting at offset.
|
||||
|
||||
|
||||
@@ -149,24 +149,28 @@ class APEv2File(File):
|
||||
cover_filename = 'Cover Art (Front)'
|
||||
cover_filename += mimetype.get_extension(image["mime"], '.jpg')
|
||||
tags['Cover Art (Front)'] = mutagen.apev2.APEValue(cover_filename + '\0' + image["data"], mutagen.apev2.BINARY)
|
||||
break # can't save more than one item with the same name
|
||||
# (mp3tags does this, but it's against the specs)
|
||||
break # can't save more than one item with the same name
|
||||
# (mp3tags does this, but it's against the specs)
|
||||
tags.save(encode_filename(filename))
|
||||
|
||||
|
||||
class MusepackFile(APEv2File):
|
||||
"""Musepack file."""
|
||||
EXTENSIONS = [".mpc", ".mp+"]
|
||||
NAME = "Musepack"
|
||||
_File = mutagen.musepack.Musepack
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(MusepackFile, self)._info(metadata, file)
|
||||
metadata['~format'] = "Musepack, SV%d" % file.info.version
|
||||
|
||||
|
||||
class WavPackFile(APEv2File):
|
||||
"""WavPack file."""
|
||||
EXTENSIONS = [".wv"]
|
||||
NAME = "WavPack"
|
||||
_File = mutagen.wavpack.WavPack
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(WavPackFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
@@ -179,11 +183,13 @@ class WavPackFile(APEv2File):
|
||||
self._rename(wvc_filename, metadata, config.setting)
|
||||
return File._save_and_rename(self, old_filename, metadata, config.setting)
|
||||
|
||||
|
||||
class OptimFROGFile(APEv2File):
|
||||
"""OptimFROG file."""
|
||||
EXTENSIONS = [".ofr", ".ofs"]
|
||||
NAME = "OptimFROG"
|
||||
_File = mutagen.optimfrog.OptimFROG
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(OptimFROGFile, self)._info(metadata, file)
|
||||
if file.filename.lower().endswith(".ofs"):
|
||||
@@ -191,20 +197,24 @@ class OptimFROGFile(APEv2File):
|
||||
else:
|
||||
metadata['~format'] = "OptimFROG Lossless Audio"
|
||||
|
||||
|
||||
class MonkeysAudioFile(APEv2File):
|
||||
"""Monkey's Audio file."""
|
||||
EXTENSIONS = [".ape"]
|
||||
NAME = "Monkey's Audio"
|
||||
_File = mutagen.monkeysaudio.MonkeysAudio
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(MonkeysAudioFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
|
||||
|
||||
class TAKFile(APEv2File):
|
||||
"""TAK file."""
|
||||
EXTENSIONS = [".tak"]
|
||||
NAME = "Tom's lossless Audio Kompressor"
|
||||
_File = mutagenext.tak.TAK
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(TAKFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
|
||||
@@ -25,6 +25,7 @@ from picard.metadata import Metadata, save_this_image_to_tags
|
||||
from mutagen.asf import ASF, ASFByteArrayAttribute
|
||||
import struct
|
||||
|
||||
|
||||
def unpack_image(data):
|
||||
"""
|
||||
Helper function to unpack image data from a WM/Picture tag.
|
||||
@@ -51,6 +52,7 @@ def unpack_image(data):
|
||||
image_data = data[pos:pos+size]
|
||||
return (mime.decode("utf-16-le"), image_data, type, description.decode("utf-16-le"))
|
||||
|
||||
|
||||
def pack_image(mime, data, type=3, description=""):
|
||||
"""
|
||||
Helper function to pack image data for a WM/Picture tag.
|
||||
@@ -62,6 +64,7 @@ def pack_image(mime, data, type=3, description=""):
|
||||
tag_data += data
|
||||
return tag_data
|
||||
|
||||
|
||||
class ASFFile(File):
|
||||
"""ASF (WMA) metadata reader/writer"""
|
||||
EXTENSIONS = [".wma"]
|
||||
|
||||
@@ -36,8 +36,10 @@ from urlparse import urlparse
|
||||
def patched_EncodedTextSpec_write(self, frame, value):
|
||||
try:
|
||||
enc, term = self._encodings[frame.encoding]
|
||||
except AttributeError: enc, term = self.encodings[frame.encoding]
|
||||
except AttributeError:
|
||||
enc, term = self.encodings[frame.encoding]
|
||||
return value.encode(enc, 'ignore') + term
|
||||
|
||||
id3.EncodedTextSpec.write = patched_EncodedTextSpec_write
|
||||
|
||||
|
||||
@@ -55,6 +57,8 @@ def patched_MultiSpec_write(self, frame, value):
|
||||
if data.endswith(term):
|
||||
data = data[:-len(term)]
|
||||
return data
|
||||
|
||||
|
||||
id3.MultiSpec._write_orig = id3.MultiSpec.write
|
||||
id3.MultiSpec.write = patched_MultiSpec_write
|
||||
|
||||
@@ -63,23 +67,25 @@ id3.TCMP = compatid3.TCMP
|
||||
id3.TSO2 = compatid3.TSO2
|
||||
|
||||
__ID3_IMAGE_TYPE_MAP = {
|
||||
"other": 0,
|
||||
"obi": 0,
|
||||
"tray": 0,
|
||||
"spine": 0,
|
||||
"sticker": 0,
|
||||
"front": 3,
|
||||
"back": 4,
|
||||
"booklet": 5,
|
||||
"medium": 6,
|
||||
"track": 6,
|
||||
}
|
||||
"other": 0,
|
||||
"obi": 0,
|
||||
"tray": 0,
|
||||
"spine": 0,
|
||||
"sticker": 0,
|
||||
"front": 3,
|
||||
"back": 4,
|
||||
"booklet": 5,
|
||||
"medium": 6,
|
||||
"track": 6,
|
||||
}
|
||||
|
||||
__ID3_REVERSE_IMAGE_TYPE_MAP = dict([(v, k) for k, v in __ID3_IMAGE_TYPE_MAP.iteritems()])
|
||||
|
||||
__ID3_REVERSE_IMAGE_TYPE_MAP = dict([(v,k) for k, v in __ID3_IMAGE_TYPE_MAP.iteritems()])
|
||||
|
||||
def image_type_from_id3_num(id3type):
|
||||
return __ID3_REVERSE_IMAGE_TYPE_MAP.get(id3type, "other")
|
||||
|
||||
|
||||
def image_type_as_id3_num(texttype):
|
||||
return __ID3_IMAGE_TYPE_MAP.get(texttype, 0)
|
||||
|
||||
@@ -324,9 +330,9 @@ class ID3File(File):
|
||||
tmcl.people.append([role, value])
|
||||
elif name.startswith('comment:'):
|
||||
desc = name.split(':', 1)[1]
|
||||
if desc.lower()[:4]=="itun":
|
||||
if desc.lower()[:4] == "itun":
|
||||
tags.delall('COMM:' + desc)
|
||||
tags.add(id3.COMM(encoding=0, desc=desc, lang='eng', text=[v+u'\x00' for v in values]))
|
||||
tags.add(id3.COMM(encoding=0, desc=desc, lang='eng', text=[v + u'\x00' for v in values]))
|
||||
else:
|
||||
tags.add(id3.COMM(encoding=encoding, desc=desc, lang='eng', text=values))
|
||||
elif name.startswith('lyrics:') or name == 'lyrics':
|
||||
@@ -401,8 +407,10 @@ class ID3File(File):
|
||||
tags.save(encode_filename(filename), v2=4, v1=v1)
|
||||
|
||||
if self._IsMP3 and config.setting["remove_ape_from_mp3"]:
|
||||
try: mutagen.apev2.delete(encode_filename(filename))
|
||||
except: pass
|
||||
try:
|
||||
mutagen.apev2.delete(encode_filename(filename))
|
||||
except:
|
||||
pass
|
||||
|
||||
def supports_tag(self, name):
|
||||
return name in self.__rtranslate or name in self.__rtranslate_freetext\
|
||||
@@ -417,15 +425,18 @@ class MP3File(ID3File):
|
||||
NAME = "MPEG-1 Audio"
|
||||
_File = mutagen.mp3.MP3
|
||||
_IsMP3 = True
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(MP3File, self)._info(metadata, file)
|
||||
metadata['~format'] = 'MPEG-1 Layer %d' % file.info.layer
|
||||
|
||||
|
||||
class TrueAudioFile(ID3File):
|
||||
"""TTA file."""
|
||||
EXTENSIONS = [".tta"]
|
||||
NAME = "The True Audio"
|
||||
_File = mutagen.trueaudio.TrueAudio
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(TrueAudioFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
|
||||
@@ -23,6 +23,7 @@ from picard.file import File
|
||||
from picard.metadata import Metadata, save_this_image_to_tags
|
||||
from picard.util import encode_filename
|
||||
|
||||
|
||||
class MP4File(File):
|
||||
EXTENSIONS = [".m4a", ".m4b", ".m4p", ".mp4"]
|
||||
NAME = "MPEG-4 Audio"
|
||||
|
||||
@@ -25,18 +25,23 @@ from mutagen._util import insert_bytes
|
||||
from mutagen.id3 import ID3, Frame, Frames, Frames_2_2, TextFrame, TORY, \
|
||||
TYER, TIME, APIC, IPLS, TDAT, BitPaddedInt, MakeID3v1
|
||||
|
||||
|
||||
class TCMP(TextFrame):
|
||||
pass
|
||||
|
||||
|
||||
class TSO2(TextFrame):
|
||||
pass
|
||||
|
||||
|
||||
class XDOR(TextFrame):
|
||||
pass
|
||||
|
||||
|
||||
class XSOP(TextFrame):
|
||||
pass
|
||||
|
||||
|
||||
class CompatID3(ID3):
|
||||
"""
|
||||
Additional features over mutagen.id3.ID3:
|
||||
@@ -56,7 +61,7 @@ class CompatID3(ID3):
|
||||
known_frames["XDOR"] = XDOR
|
||||
known_frames["XSOP"] = XSOP
|
||||
kwargs["known_frames"] = known_frames
|
||||
super(CompatID3, self).__init__(*args, **kwargs)
|
||||
super(CompatID3, self).__init__(*args, **kwargs)
|
||||
|
||||
def save(self, filename=None, v1=1, v2=4):
|
||||
"""Save changes to a file.
|
||||
@@ -90,28 +95,37 @@ class CompatID3(ID3):
|
||||
self.delete(filename)
|
||||
except EnvironmentError, err:
|
||||
from errno import ENOENT
|
||||
if err.errno != ENOENT: raise
|
||||
if err.errno != ENOENT:
|
||||
raise
|
||||
return
|
||||
|
||||
framedata = ''.join(framedata)
|
||||
framesize = len(framedata)
|
||||
|
||||
if filename is None: filename = self.filename
|
||||
try: f = open(filename, 'rb+')
|
||||
if filename is None:
|
||||
filename = self.filename
|
||||
try:
|
||||
f = open(filename, 'rb+')
|
||||
except IOError, err:
|
||||
from errno import ENOENT
|
||||
if err.errno != ENOENT: raise
|
||||
f = open(filename, 'ab') # create, then reopen
|
||||
if err.errno != ENOENT:
|
||||
raise
|
||||
f = open(filename, 'ab') # create, then reopen
|
||||
f = open(filename, 'rb+')
|
||||
try:
|
||||
idata = f.read(10)
|
||||
try: id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata)
|
||||
except struct.error: id3, insize = '', 0
|
||||
try:
|
||||
id3, vmaj, vrev, flags, insize = unpack('>3sBBB4s', idata)
|
||||
except struct.error:
|
||||
id3, insize = '', 0
|
||||
insize = BitPaddedInt(insize)
|
||||
if id3 != 'ID3': insize = -10
|
||||
if id3 != 'ID3':
|
||||
insize = -10
|
||||
|
||||
if insize >= framesize: outsize = insize
|
||||
else: outsize = (framesize + 1023) & ~0x3FF
|
||||
if insize >= framesize:
|
||||
outsize = insize
|
||||
else:
|
||||
outsize = (framesize + 1023) & ~0x3FF
|
||||
framedata += '\x00' * (outsize - framesize)
|
||||
|
||||
framesize = BitPaddedInt.to_str(outsize, width=4)
|
||||
@@ -128,13 +142,16 @@ class CompatID3(ID3):
|
||||
f.seek(-128, 2)
|
||||
except IOError, err:
|
||||
from errno import EINVAL
|
||||
if err.errno != EINVAL: raise
|
||||
f.seek(0, 2) # ensure read won't get "TAG"
|
||||
if err.errno != EINVAL:
|
||||
raise
|
||||
f.seek(0, 2) # ensure read won't get "TAG"
|
||||
|
||||
if f.read(3) == "TAG":
|
||||
f.seek(-128, 2)
|
||||
if v1 > 0: f.write(MakeID3v1(self))
|
||||
else: f.truncate()
|
||||
if v1 > 0:
|
||||
f.write(MakeID3v1(self))
|
||||
else:
|
||||
f.truncate()
|
||||
elif v1 == 2:
|
||||
f.seek(0, 2)
|
||||
f.write(MakeID3v1(self))
|
||||
@@ -145,10 +162,13 @@ class CompatID3(ID3):
|
||||
def __save_frame(self, frame, v2):
|
||||
flags = 0
|
||||
if self.PEDANTIC and isinstance(frame, TextFrame):
|
||||
if len(str(frame)) == 0: return ''
|
||||
if len(str(frame)) == 0:
|
||||
return ''
|
||||
framedata = frame._writeData()
|
||||
if v2 == 3: bits=8
|
||||
else: bits=7
|
||||
if v2 == 3:
|
||||
bits = 8
|
||||
else:
|
||||
bits = 7
|
||||
datasize = BitPaddedInt.to_str(len(framedata), width=4, bits=bits)
|
||||
header = pack('>4s4sH', type(frame).__name__, datasize, flags)
|
||||
return header + framedata
|
||||
@@ -161,7 +181,8 @@ class CompatID3(ID3):
|
||||
at some point.
|
||||
"""
|
||||
|
||||
if self.version < (2,3,0): del self.unknown_frames[:]
|
||||
if self.version < (2, 3, 0):
|
||||
del self.unknown_frames[:]
|
||||
|
||||
# TMCL, TIPL -> TIPL
|
||||
if "TIPL" in self or "TMCL" in self:
|
||||
@@ -177,7 +198,7 @@ class CompatID3(ID3):
|
||||
|
||||
# TODO:
|
||||
# * EQU2 -> EQUA
|
||||
# * RVA2 -> RVAD
|
||||
# * RVA2 -> RVAD
|
||||
|
||||
# TDOR -> TORY
|
||||
if "TDOR" in self:
|
||||
@@ -205,7 +226,10 @@ class CompatID3(ID3):
|
||||
if self.version < (2, 3):
|
||||
# ID3v2.2 PIC frames are slightly different.
|
||||
pics = self.getall("APIC")
|
||||
mimes = { "PNG": "image/png", "JPG": "image/jpeg" }
|
||||
mimes = {
|
||||
"PNG": "image/png",
|
||||
"JPG": "image/jpeg"
|
||||
}
|
||||
self.delall("APIC")
|
||||
for pic in pics:
|
||||
newpic = APIC(
|
||||
@@ -222,7 +246,8 @@ class CompatID3(ID3):
|
||||
# New frames added in v2.4.
|
||||
for key in ["ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG",
|
||||
"TMOO", "TPRO"]:
|
||||
if key in self: del(self[key])
|
||||
if key in self:
|
||||
del(self[key])
|
||||
|
||||
for frame in self.values():
|
||||
# ID3v2.3 doesn't support UTF-8 (and WMP can't read UTF-16 BE)
|
||||
|
||||
@@ -20,7 +20,10 @@ __all__ = ["TAK", "Open", "delete"]
|
||||
|
||||
from mutagen.apev2 import APEv2File, error, delete
|
||||
|
||||
class TAKHeaderError(error): pass
|
||||
|
||||
class TAKHeaderError(error):
|
||||
pass
|
||||
|
||||
|
||||
class TAKInfo(object):
|
||||
"""TAK stream information.
|
||||
@@ -37,6 +40,7 @@ class TAKInfo(object):
|
||||
def pprint(self):
|
||||
return "Tom's lossless Audio Kompressor"
|
||||
|
||||
|
||||
class TAK(APEv2File):
|
||||
_Info = TAKInfo
|
||||
_mimes = ["audio/x-tak"]
|
||||
|
||||
@@ -36,6 +36,7 @@ from picard.formats.id3 import image_type_from_id3_num, image_type_as_id3_num
|
||||
from picard.metadata import Metadata, save_this_image_to_tags
|
||||
from picard.util import encode_filename, sanitize_date
|
||||
|
||||
|
||||
class VCommentFile(File):
|
||||
"""Generic VComment-based file."""
|
||||
_File = None
|
||||
@@ -67,8 +68,10 @@ class VCommentFile(File):
|
||||
name += value[start + 2:-1]
|
||||
value = value[:start]
|
||||
elif name.startswith('rating'):
|
||||
try: name, email = name.split(':', 1)
|
||||
except ValueError: email = ''
|
||||
try:
|
||||
name, email = name.split(':', 1)
|
||||
except ValueError:
|
||||
email = ''
|
||||
if email != config.setting['rating_user_email']:
|
||||
continue
|
||||
name = '~rating'
|
||||
@@ -179,60 +182,73 @@ class VCommentFile(File):
|
||||
except TypeError:
|
||||
file.save()
|
||||
|
||||
|
||||
class FLACFile(VCommentFile):
|
||||
"""FLAC file."""
|
||||
EXTENSIONS = [".flac"]
|
||||
NAME = "FLAC"
|
||||
_File = mutagen.flac.FLAC
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(FLACFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
|
||||
|
||||
class OggFLACFile(VCommentFile):
|
||||
"""FLAC file."""
|
||||
EXTENSIONS = [".oggflac"]
|
||||
NAME = "Ogg FLAC"
|
||||
_File = mutagen.oggflac.OggFLAC
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(OggFLACFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
|
||||
|
||||
class OggSpeexFile(VCommentFile):
|
||||
"""Ogg Speex file."""
|
||||
EXTENSIONS = [".spx"]
|
||||
NAME = "Speex"
|
||||
_File = mutagen.oggspeex.OggSpeex
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(OggSpeexFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
|
||||
|
||||
class OggTheoraFile(VCommentFile):
|
||||
"""Ogg Theora file."""
|
||||
EXTENSIONS = [".oggtheora"]
|
||||
NAME = "Ogg Theora"
|
||||
_File = mutagen.oggtheora.OggTheora
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(OggTheoraFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
|
||||
|
||||
class OggVorbisFile(VCommentFile):
|
||||
"""Ogg Vorbis file."""
|
||||
EXTENSIONS = [".ogg"]
|
||||
NAME = "Ogg Vorbis"
|
||||
_File = mutagen.oggvorbis.OggVorbis
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(OggVorbisFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
|
||||
|
||||
class OggOpusFile(VCommentFile):
|
||||
"""Ogg Opus file."""
|
||||
EXTENSIONS = [".opus"]
|
||||
NAME = "Ogg Opus"
|
||||
_File = OggOpus
|
||||
|
||||
def _info(self, metadata, file):
|
||||
super(OggOpusFile, self)._info(metadata, file)
|
||||
metadata['~format'] = self.NAME
|
||||
|
||||
|
||||
def OggAudioFile(filename):
|
||||
"""Generic Ogg audio file."""
|
||||
options = [OggFLACFile, OggSpeexFile, OggVorbisFile]
|
||||
|
||||
@@ -58,6 +58,7 @@ def add_receiver(receiver):
|
||||
|
||||
_log_debug_messages = False
|
||||
|
||||
|
||||
def debug(message, *args, **kwargs):
|
||||
if _log_debug_messages:
|
||||
thread.proxy_to_main(_message, "D:", message, args, kwargs)
|
||||
|
||||
@@ -54,6 +54,8 @@ def _decamelcase(text):
|
||||
_REPLACE_MAP = {}
|
||||
_EXTRA_ATTRS = ['guest', 'additional', 'minor']
|
||||
_BLANK_SPECIAL_RELTYPES = {'vocal': 'vocals'}
|
||||
|
||||
|
||||
def _parse_attributes(attrs, reltype):
|
||||
attrs = [_decamelcase(_REPLACE_MAP.get(a, a)) for a in attrs]
|
||||
prefix = ' '.join([a for a in attrs if a in _EXTRA_ATTRS])
|
||||
@@ -243,6 +245,7 @@ def recording_to_metadata(node, track):
|
||||
m['~rating'] = nodes[0].text
|
||||
m['~length'] = format_time(m.length)
|
||||
|
||||
|
||||
def work_to_metadata(work, m):
|
||||
m.add("musicbrainz_workid", work.attribs['id'])
|
||||
if 'language' in work.children:
|
||||
@@ -250,6 +253,7 @@ def work_to_metadata(work, m):
|
||||
if 'relation_list' in work.children:
|
||||
_relations_to_metadata(work.relation_list, m)
|
||||
|
||||
|
||||
def medium_to_metadata(node, m):
|
||||
for name, nodes in node.children.iteritems():
|
||||
if not nodes:
|
||||
|
||||
@@ -26,6 +26,7 @@ from picard.mbxml import artist_credit_from_node
|
||||
|
||||
MULTI_VALUED_JOINER = '; '
|
||||
|
||||
|
||||
def is_front_image(image):
|
||||
# CAA has a flag for "front" image, use it in priority
|
||||
caa_front = image.get('front', None)
|
||||
@@ -34,6 +35,7 @@ def is_front_image(image):
|
||||
return (image['type'] == 'front')
|
||||
return caa_front
|
||||
|
||||
|
||||
def save_this_image_to_tags(image):
|
||||
if not config.setting["save_only_front_images_to_tags"]:
|
||||
return True
|
||||
@@ -41,6 +43,7 @@ def save_this_image_to_tags(image):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Metadata(dict):
|
||||
"""List of metadata items with dict-like access."""
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ _patterns = [
|
||||
re.compile(r"(?:.*(/|\\))?(?P<albumartist>.*)(/|\\)(?P<album>.*)(/|\\)(?P<artist>.*)-(?P<tracknum>\d{2})-(?P<title>.*)\.(?:\w{2,5})$"),
|
||||
]
|
||||
|
||||
|
||||
def parseFileName(filename, metadata):
|
||||
for pattern in _patterns:
|
||||
match = pattern.match(filename)
|
||||
@@ -79,4 +80,3 @@ if __name__ == "__main__":
|
||||
ok += 1
|
||||
print "OK"
|
||||
print len(testCases), ok
|
||||
|
||||
|
||||
@@ -26,11 +26,26 @@ from picard.metadata import MULTI_VALUED_JOINER
|
||||
from picard.plugin import ExtensionPoint
|
||||
from inspect import getargspec
|
||||
|
||||
class ScriptError(Exception): pass
|
||||
class ParseError(ScriptError): pass
|
||||
class EndOfFile(ParseError): pass
|
||||
class SyntaxError(ParseError): pass
|
||||
class UnknownFunction(ScriptError): pass
|
||||
|
||||
class ScriptError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ParseError(ScriptError):
|
||||
pass
|
||||
|
||||
|
||||
class EndOfFile(ParseError):
|
||||
pass
|
||||
|
||||
|
||||
class SyntaxError(ParseError):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownFunction(ScriptError):
|
||||
pass
|
||||
|
||||
|
||||
class ScriptText(unicode):
|
||||
|
||||
@@ -262,7 +277,7 @@ def register_script_function(function, name=None, eval_args=True,
|
||||
function will not be verified."""
|
||||
|
||||
argspec = getargspec(function)
|
||||
argcount = (len(argspec[0]) - 1,) # -1 for the parser
|
||||
argcount = (len(argspec[0]) - 1,) # -1 for the parser
|
||||
|
||||
if argspec[3] is not None:
|
||||
argcount = range(argcount[0] - len(argspec[3]), argcount[0] + 1)
|
||||
@@ -274,6 +289,7 @@ def register_script_function(function, name=None, eval_args=True,
|
||||
argcount if argcount and check_argcount else False)
|
||||
)
|
||||
|
||||
|
||||
def func_if(parser, _if, _then, _else=None):
|
||||
"""If ``if`` is not empty, it returns ``then``, otherwise it returns ``else``."""
|
||||
if _if.eval(parser):
|
||||
@@ -282,6 +298,7 @@ def func_if(parser, _if, _then, _else=None):
|
||||
return _else.eval(parser)
|
||||
return ''
|
||||
|
||||
|
||||
def func_if2(parser, *args):
|
||||
"""Returns first non empty argument."""
|
||||
for arg in args:
|
||||
@@ -290,48 +307,60 @@ def func_if2(parser, *args):
|
||||
return arg
|
||||
return ''
|
||||
|
||||
|
||||
def func_noop(parser, *args):
|
||||
"""Does nothing :)"""
|
||||
return ''
|
||||
|
||||
|
||||
def func_left(parser, text, length):
|
||||
"""Returns first ``num`` characters from ``text``."""
|
||||
return text[:int(length)]
|
||||
|
||||
|
||||
def func_right(parser, text, length):
|
||||
"""Returns last ``num`` characters from ``text``."""
|
||||
return text[-int(length):]
|
||||
|
||||
|
||||
def func_lower(parser, text):
|
||||
"""Returns ``text`` in lower case."""
|
||||
return text.lower()
|
||||
|
||||
|
||||
def func_upper(parser, text):
|
||||
"""Returns ``text`` in upper case."""
|
||||
return text.upper()
|
||||
|
||||
|
||||
def func_pad(parser, text, length, char):
|
||||
return char * (int(length) - len(text)) + text
|
||||
|
||||
|
||||
def func_strip(parser, text):
|
||||
return re.sub("\s+", " ", text).strip()
|
||||
|
||||
|
||||
def func_replace(parser, text, old, new):
|
||||
return text.replace(old, new)
|
||||
|
||||
|
||||
def func_in(parser, text, needle):
|
||||
if needle in text:
|
||||
return "1"
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def func_inmulti(parser, text, value, separator=MULTI_VALUED_JOINER):
|
||||
"""Splits ``text`` by ``separator``, and returns true if the resulting list contains ``value``."""
|
||||
return func_in(parser, text.split(separator) if separator else [text], value)
|
||||
|
||||
|
||||
def func_rreplace(parser, text, old, new):
|
||||
return re.sub(old, new, text)
|
||||
|
||||
|
||||
def func_rsearch(parser, text, pattern):
|
||||
match = re.search(pattern, text)
|
||||
if match:
|
||||
@@ -341,6 +370,7 @@ def func_rsearch(parser, text, pattern):
|
||||
return match.group(0)
|
||||
return u""
|
||||
|
||||
|
||||
def func_num(parser, text, length):
|
||||
format = "%%0%dd" % int(length)
|
||||
try:
|
||||
@@ -349,6 +379,7 @@ def func_num(parser, text, length):
|
||||
value = 0
|
||||
return format % value
|
||||
|
||||
|
||||
def func_unset(parser, name):
|
||||
"""Unsets the variable ``name``."""
|
||||
if name.startswith("_"):
|
||||
@@ -359,6 +390,7 @@ def func_unset(parser, name):
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def func_set(parser, name, value):
|
||||
"""Sets the variable ``name`` to ``value``."""
|
||||
if value:
|
||||
@@ -369,16 +401,19 @@ def func_set(parser, name, value):
|
||||
func_unset(parser, name)
|
||||
return ""
|
||||
|
||||
|
||||
def func_setmulti(parser, name, value, separator=MULTI_VALUED_JOINER):
|
||||
"""Sets the variable ``name`` to ``value`` as a list; splitting by the passed string, or "; " otherwise."""
|
||||
return func_set(parser, name, value.split(separator) if value and separator else value)
|
||||
|
||||
|
||||
def func_get(parser, name):
|
||||
"""Returns the variable ``name`` (equivalent to ``%name%``)."""
|
||||
if name.startswith("_"):
|
||||
name = "~" + name[1:]
|
||||
return parser.context.get(name, u"")
|
||||
|
||||
|
||||
def func_copy(parser, new, old):
|
||||
"""Copies content of variable ``old`` to variable ``new``."""
|
||||
if new.startswith("_"):
|
||||
@@ -388,6 +423,7 @@ def func_copy(parser, new, old):
|
||||
parser.context[new] = parser.context.getall(old)[:]
|
||||
return ""
|
||||
|
||||
|
||||
def func_copymerge(parser, new, old):
|
||||
"""Copies content of variable ``old`` and appends it into variable ``new``, removing duplicates. This is normally
|
||||
used to merge a multi-valued variable into another, existing multi-valued variable."""
|
||||
@@ -400,6 +436,7 @@ def func_copymerge(parser, new, old):
|
||||
parser.context[new] = newvals + list(set(oldvals) - set(newvals))
|
||||
return ""
|
||||
|
||||
|
||||
def func_trim(parser, text, char=None):
|
||||
"""Trims all leading and trailing whitespaces from ``text``. The optional
|
||||
second parameter specifies the character to trim."""
|
||||
@@ -408,6 +445,7 @@ def func_trim(parser, text, char=None):
|
||||
else:
|
||||
return text.strip()
|
||||
|
||||
|
||||
def func_add(parser, x, y):
|
||||
"""Add ``y`` to ``x``."""
|
||||
try:
|
||||
@@ -415,6 +453,7 @@ def func_add(parser, x, y):
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
|
||||
def func_sub(parser, x, y):
|
||||
"""Substracts ``y`` from ``x``."""
|
||||
try:
|
||||
@@ -422,6 +461,7 @@ def func_sub(parser, x, y):
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
|
||||
def func_div(parser, x, y):
|
||||
"""Divides ``x`` by ``y``."""
|
||||
try:
|
||||
@@ -429,6 +469,7 @@ def func_div(parser, x, y):
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
|
||||
def func_mod(parser, x, y):
|
||||
"""Returns the remainder of ``x`` divided by ``y``."""
|
||||
try:
|
||||
@@ -436,6 +477,7 @@ def func_mod(parser, x, y):
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
|
||||
def func_mul(parser, x, y):
|
||||
"""Multiplies ``x`` by ``y``."""
|
||||
try:
|
||||
@@ -443,6 +485,7 @@ def func_mul(parser, x, y):
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
|
||||
def func_or(parser, x, y):
|
||||
"""Returns true, if either ``x`` or ``y`` not empty."""
|
||||
if x or y:
|
||||
@@ -450,6 +493,7 @@ def func_or(parser, x, y):
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def func_and(parser, x, y):
|
||||
"""Returns true, if both ``x`` and ``y`` are not empty."""
|
||||
if x and y:
|
||||
@@ -457,6 +501,7 @@ def func_and(parser, x, y):
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def func_not(parser, x):
|
||||
"""Returns true, if ``x`` is empty."""
|
||||
if not x:
|
||||
@@ -464,6 +509,7 @@ def func_not(parser, x):
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def func_eq(parser, x, y):
|
||||
"""Returns true, if ``x`` equals ``y``."""
|
||||
if x == y:
|
||||
@@ -471,6 +517,7 @@ def func_eq(parser, x, y):
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def func_ne(parser, x, y):
|
||||
"""Returns true, if ``x`` not equals ``y``."""
|
||||
if x != y:
|
||||
@@ -478,6 +525,7 @@ def func_ne(parser, x, y):
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def func_lt(parser, x, y):
|
||||
"""Returns true, if ``x`` is lower than ``y``."""
|
||||
try:
|
||||
@@ -487,6 +535,7 @@ def func_lt(parser, x, y):
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def func_lte(parser, x, y):
|
||||
"""Returns true, if ``x`` is lower than or equals ``y``."""
|
||||
try:
|
||||
@@ -496,6 +545,7 @@ def func_lte(parser, x, y):
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def func_gt(parser, x, y):
|
||||
"""Returns true, if ``x`` is greater than ``y``."""
|
||||
try:
|
||||
@@ -505,6 +555,7 @@ def func_gt(parser, x, y):
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def func_gte(parser, x, y):
|
||||
"""Returns true, if ``x`` is greater than or equals ``y``."""
|
||||
try:
|
||||
@@ -514,9 +565,11 @@ def func_gte(parser, x, y):
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def func_len(parser, text):
|
||||
return str(len(text))
|
||||
|
||||
|
||||
def func_performer(parser, pattern="", join=", "):
|
||||
values = []
|
||||
for name, value in parser.context.items():
|
||||
@@ -524,12 +577,14 @@ def func_performer(parser, pattern="", join=", "):
|
||||
values.append(value)
|
||||
return join.join(values)
|
||||
|
||||
|
||||
def func_matchedtracks(parser, arg):
|
||||
if parser.file:
|
||||
if parser.file.parent:
|
||||
return str(parser.file.parent.album.get_num_matched_tracks())
|
||||
return "0"
|
||||
|
||||
|
||||
def func_firstalphachar(parser, text, nonalpha="#"):
|
||||
if len(text) == 0:
|
||||
return nonalpha
|
||||
@@ -539,9 +594,11 @@ def func_firstalphachar(parser, text, nonalpha="#"):
|
||||
else:
|
||||
return nonalpha
|
||||
|
||||
|
||||
def func_initials(parser, text):
|
||||
return "".join(a[:1] for a in text.split(" ") if a[:1].isalpha())
|
||||
|
||||
|
||||
def func_firstwords(parser, text, length):
|
||||
try:
|
||||
length = int(length)
|
||||
@@ -554,6 +611,7 @@ def func_firstwords(parser, text, length):
|
||||
return text[:length]
|
||||
return text[:length].rsplit(' ', 1)[0]
|
||||
|
||||
|
||||
def func_truncate(parser, text, length):
|
||||
try:
|
||||
length = int(length)
|
||||
@@ -561,6 +619,7 @@ def func_truncate(parser, text, length):
|
||||
length = None
|
||||
return text[:length].rstrip()
|
||||
|
||||
|
||||
register_script_function(func_if, "if", eval_args=False)
|
||||
register_script_function(func_if2, "if2", eval_args=False, check_argcount=False)
|
||||
register_script_function(func_noop, "noop", eval_args=False, check_argcount=False)
|
||||
|
||||
@@ -33,6 +33,7 @@ _replace_words = {
|
||||
"disc 8": "CD8",
|
||||
}
|
||||
|
||||
|
||||
def normalize(orig_string):
|
||||
"""Strips non-alphanumeric characters from a string unless doing so would make it blank."""
|
||||
string = strip_non_alnum(orig_string.lower())
|
||||
@@ -40,6 +41,7 @@ def normalize(orig_string):
|
||||
string = orig_string
|
||||
return string
|
||||
|
||||
|
||||
def similarity(a1, b1):
|
||||
"""Calculates similarity of single words as a function of their edit distance."""
|
||||
a2 = normalize(a1)
|
||||
@@ -52,6 +54,7 @@ def similarity(a1, b1):
|
||||
|
||||
_split_words_re = re.compile('\W+', re.UNICODE)
|
||||
|
||||
|
||||
def similarity2(a, b):
|
||||
"""Calculates similarity of a multi-word strings."""
|
||||
alist = filter(bool, _split_words_re.split(a.lower()))
|
||||
|
||||
@@ -29,10 +29,15 @@ from collections import deque
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
|
||||
|
||||
# A "fix" for http://python.org/sf/1438480
|
||||
def _patched_shutil_copystat(src, dst):
|
||||
try: _orig_shutil_copystat(src, dst)
|
||||
except OSError: pass
|
||||
try:
|
||||
_orig_shutil_copystat(src, dst)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
_orig_shutil_copystat = shutil.copystat
|
||||
shutil.copystat = _patched_shutil_copystat
|
||||
|
||||
@@ -66,6 +71,7 @@ from picard.util import (
|
||||
)
|
||||
from picard.webservice import XmlWebService
|
||||
|
||||
|
||||
class Tagger(QtGui.QApplication):
|
||||
|
||||
tagger_stats_changed = QtCore.pyqtSignal()
|
||||
@@ -163,7 +169,7 @@ class Tagger(QtGui.QApplication):
|
||||
def remove_va_file_naming_format(merge=True):
|
||||
if merge:
|
||||
config.setting["file_naming_format"] = \
|
||||
"$if($eq(%compilation%,1),\n$noop(Various Artist albums)\n"+\
|
||||
"$if($eq(%compilation%,1),\n$noop(Various Artist albums)\n" + \
|
||||
"%s,\n$noop(Single Artist Albums)\n%s)" %\
|
||||
(config.setting["va_file_naming_format"].toString(),
|
||||
config.setting["file_naming_format"])
|
||||
@@ -414,7 +420,7 @@ class Tagger(QtGui.QApplication):
|
||||
def remove_files(self, files, from_parent=True):
|
||||
"""Remove files from the tagger."""
|
||||
for file in files:
|
||||
if self.files.has_key(file.filename):
|
||||
if file.filename in self.files:
|
||||
file.clear_lookup_task()
|
||||
self._acoustid.stop_analyze(file)
|
||||
del self.files[file.filename]
|
||||
@@ -553,6 +559,7 @@ class Tagger(QtGui.QApplication):
|
||||
def instance(cls):
|
||||
return cls.__instance
|
||||
|
||||
|
||||
def help():
|
||||
print """Usage: %s [OPTIONS] [FILE] [FILE] ...
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from PyQt4 import QtCore, QtGui
|
||||
from picard.ui.ui_cdlookup import Ui_Dialog
|
||||
from picard.mbxml import artist_credit_from_node, label_info_from_node
|
||||
|
||||
|
||||
class CDLookupDialog(QtGui.QDialog):
|
||||
|
||||
def __init__(self, releases, disc, parent=None):
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import os, sys
|
||||
import os
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from picard import config
|
||||
from picard.formats import supported_formats
|
||||
|
||||
@@ -54,10 +54,10 @@ class InfoDialog(QtGui.QDialog):
|
||||
pixmap.loadFromData(data)
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
item.setIcon(icon)
|
||||
s = "%s (%s)\n%d x %d" % (bytes2human.decimal(size),
|
||||
bytes2human.binary(size),
|
||||
pixmap.width(),
|
||||
pixmap.height())
|
||||
s = "%s (%s)\n%d x %d" % (bytes2human.decimal(size),
|
||||
bytes2human.binary(size),
|
||||
pixmap.width(),
|
||||
pixmap.height())
|
||||
item.setText(s)
|
||||
self.ui.artwork_list.addItem(item)
|
||||
|
||||
@@ -81,7 +81,7 @@ class FileInfoDialog(InfoDialog):
|
||||
info.append((_('Format:'), file.orig_metadata['~format']))
|
||||
try:
|
||||
size = os.path.getsize(encode_filename(file.filename))
|
||||
sizestr = "%s (%s)" % (bytes2human.decimal(size), bytes2human.binary(size))
|
||||
sizestr = "%s (%s)" % (bytes2human.decimal(size), bytes2human.binary(size))
|
||||
info.append((_('Size:'), sizestr))
|
||||
except:
|
||||
pass
|
||||
@@ -95,9 +95,12 @@ class FileInfoDialog(InfoDialog):
|
||||
info.append((_('Bits per sample:'), str(file.orig_metadata['~bits_per_sample'])))
|
||||
if '~channels' in file.orig_metadata:
|
||||
ch = file.orig_metadata['~channels']
|
||||
if ch == 1: ch = _('Mono')
|
||||
elif ch == 2: ch = _('Stereo')
|
||||
else: ch = str(ch)
|
||||
if ch == 1:
|
||||
ch = _('Mono')
|
||||
elif ch == 2:
|
||||
ch = _('Stereo')
|
||||
else:
|
||||
ch = str(ch)
|
||||
info.append((_('Channels:'), ch))
|
||||
text = '<br/>'.join(map(lambda i: '<b>%s</b><br/>%s' % i, info))
|
||||
self.ui.info.setText(text)
|
||||
|
||||
@@ -46,7 +46,7 @@ class InfoStatus(QtGui.QWidget, Ui_InfoStatus):
|
||||
self.icon_cd = icontheme.lookup('media-optical')
|
||||
self.icon_file = QtGui.QIcon(":/images/file.png")
|
||||
self.icon_file_pending = QtGui.QIcon(":/images/file-pending.png")
|
||||
self.icon_download = QtGui.QIcon(":/images/16x16/action-go-down-16.png")
|
||||
self.icon_download = QtGui.QIcon(":/images/16x16/action-go-down-16.png")
|
||||
|
||||
def _init_tooltips(self):
|
||||
t1 = _("Files")
|
||||
|
||||
@@ -17,6 +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.
|
||||
|
||||
|
||||
class Item(object):
|
||||
|
||||
def can_save(self):
|
||||
|
||||
@@ -54,21 +54,27 @@ _clusterlist_actions = ExtensionPoint()
|
||||
_track_actions = ExtensionPoint()
|
||||
_file_actions = ExtensionPoint()
|
||||
|
||||
|
||||
def register_album_action(action):
|
||||
_album_actions.register(action.__module__, action)
|
||||
|
||||
|
||||
def register_cluster_action(action):
|
||||
_cluster_actions.register(action.__module__, action)
|
||||
|
||||
|
||||
def register_clusterlist_action(action):
|
||||
_clusterlist_actions.register(action.__module__, action)
|
||||
|
||||
|
||||
def register_track_action(action):
|
||||
_track_actions.register(action.__module__, action)
|
||||
|
||||
|
||||
def register_file_action(action):
|
||||
_file_actions.register(action.__module__, action)
|
||||
|
||||
|
||||
def get_match_color(similarity, basecolor):
|
||||
c1 = (basecolor.red(), basecolor.green(), basecolor.blue())
|
||||
c2 = (223, 125, 125)
|
||||
@@ -556,7 +562,7 @@ class TreeItem(QtGui.QTreeWidgetItem):
|
||||
obj.item = self
|
||||
if sortable:
|
||||
self.__lt__ = self._lt
|
||||
self.setTextAlignment(1, QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
|
||||
self.setTextAlignment(1, QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
|
||||
def _lt(self, other):
|
||||
column = self.treeWidget().sortColumn()
|
||||
@@ -609,7 +615,7 @@ class AlbumItem(TreeItem):
|
||||
if update_tracks:
|
||||
oldnum = self.childCount() - 1
|
||||
newnum = len(album.tracks)
|
||||
if oldnum > newnum: # remove old items
|
||||
if oldnum > newnum: # remove old items
|
||||
for i in xrange(oldnum - newnum):
|
||||
self.takeChild(newnum - 1)
|
||||
oldnum = newnum
|
||||
@@ -620,14 +626,14 @@ class AlbumItem(TreeItem):
|
||||
item.obj = track
|
||||
track.item = item
|
||||
item.update(update_album=False)
|
||||
if newnum > oldnum: # add new items
|
||||
if newnum > oldnum: # add new items
|
||||
items = []
|
||||
for i in xrange(newnum - 1, oldnum - 1, -1): # insertChildren is backwards
|
||||
for i in xrange(newnum - 1, oldnum - 1, -1): # insertChildren is backwards
|
||||
item = TrackItem(album.tracks[i], False)
|
||||
item.setHidden(False) # Workaround to make sure the parent state gets updated
|
||||
item.setHidden(False) # Workaround to make sure the parent state gets updated
|
||||
items.append(item)
|
||||
self.insertChildren(oldnum, items)
|
||||
for item in items: # Update after insertChildren so that setExpanded works
|
||||
for item in items: # Update after insertChildren so that setExpanded works
|
||||
item.update(update_album=False)
|
||||
self.setIcon(0, AlbumItem.icon_cd_saved if album.is_complete() else AlbumItem.icon_cd)
|
||||
for i, column in enumerate(MainPanel.columns):
|
||||
@@ -653,17 +659,17 @@ class TrackItem(TreeItem):
|
||||
icon = TrackItem.icon_note
|
||||
oldnum = self.childCount()
|
||||
newnum = track.num_linked_files
|
||||
if oldnum > newnum: # remove old items
|
||||
if oldnum > newnum: # remove old items
|
||||
for i in xrange(oldnum - newnum):
|
||||
self.takeChild(newnum - 1).obj.item = None
|
||||
oldnum = newnum
|
||||
for i in xrange(oldnum): # update existing items
|
||||
for i in xrange(oldnum): # update existing items
|
||||
item = self.child(i)
|
||||
file = track.linked_files[i]
|
||||
item.obj = file
|
||||
file.item = item
|
||||
item.update()
|
||||
if newnum > oldnum: # add new items
|
||||
if newnum > oldnum: # add new items
|
||||
items = []
|
||||
for i in xrange(newnum - 1, oldnum - 1, -1):
|
||||
item = FileItem(track.linked_files[i], False)
|
||||
|
||||
@@ -40,10 +40,14 @@ from picard.util import icontheme, webbrowser2, find_existing_path, throttle
|
||||
from picard.util.cdrom import get_cdrom_drives
|
||||
from picard.plugin import ExtensionPoint
|
||||
|
||||
|
||||
ui_init = ExtensionPoint()
|
||||
def register_ui_init (function):
|
||||
|
||||
|
||||
def register_ui_init(function):
|
||||
ui_init.register(function.__module__, function)
|
||||
|
||||
|
||||
class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
options = [
|
||||
@@ -192,7 +196,9 @@ class MainWindow(QtGui.QMainWindow):
|
||||
pos = config.persist["window_position"]
|
||||
size = config.persist["window_size"]
|
||||
self._desktopgeo = self.tagger.desktop().screenGeometry()
|
||||
if pos.x() > 0 and pos.y() > 0 and pos.x()+size.width() < self._desktopgeo.width() and pos.y()+size.height() < self._desktopgeo.height():
|
||||
if (pos.x() > 0 and pos.y() > 0
|
||||
and pos.x() + size.width() < self._desktopgeo.width()
|
||||
and pos.y() + size.height() < self._desktopgeo.height()):
|
||||
self.move(pos)
|
||||
if size.width() <= 0 or size.height() <= 0:
|
||||
size = QtCore.QSize(780, 560)
|
||||
@@ -619,7 +625,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
# Use a custom file selection dialog to allow the selection of multiple directories
|
||||
file_dialog = QtGui.QFileDialog(self, "", current_directory)
|
||||
file_dialog.setFileMode(QtGui.QFileDialog.DirectoryOnly)
|
||||
if sys.platform == "darwin": # The native dialog doesn't allow selecting >1 directory
|
||||
if sys.platform == "darwin": # The native dialog doesn't allow selecting >1 directory
|
||||
file_dialog.setOption(QtGui.QFileDialog.DontUseNativeDialog)
|
||||
tree_view = file_dialog.findChild(QtGui.QTreeView)
|
||||
tree_view.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
@@ -671,7 +677,6 @@ removed in this version of Picard. Your file naming scheme has automatically
|
||||
been merged with that of single artist albums."""),
|
||||
QtGui.QMessageBox.Ok)
|
||||
|
||||
|
||||
def open_bug_report(self):
|
||||
webbrowser2.open("http://musicbrainz.org/doc/Picard_Troubleshooting")
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ class MetadataBox(QtGui.QTableWidget):
|
||||
self.objects = set()
|
||||
self.selection_mutex = QtCore.QMutex()
|
||||
self.selection_dirty = False
|
||||
self.editing = None # the QTableWidgetItem being edited
|
||||
self.editing = None # the QTableWidgetItem being edited
|
||||
self.clipboard = [""]
|
||||
self.add_tag_action = QtGui.QAction(_(u"Add New Tag..."), parent)
|
||||
self.add_tag_action.triggered.connect(partial(self.edit_tag, ""))
|
||||
|
||||
@@ -27,6 +27,7 @@ class OptionsCheckError(Exception):
|
||||
self.title = title
|
||||
self.info = info
|
||||
|
||||
|
||||
class OptionsPage(QtGui.QWidget):
|
||||
|
||||
PARENT = None
|
||||
@@ -44,7 +45,7 @@ class OptionsPage(QtGui.QWidget):
|
||||
|
||||
def save(self):
|
||||
pass
|
||||
|
||||
|
||||
def display_error(self, error):
|
||||
dialog = QtGui.QMessageBox(QtGui.QMessageBox.Warning, error.title, error.info, QtGui.QMessageBox.Ok, self)
|
||||
dialog.exec_()
|
||||
@@ -52,5 +53,6 @@ class OptionsPage(QtGui.QWidget):
|
||||
|
||||
_pages = ExtensionPoint()
|
||||
|
||||
|
||||
def register_options_page(page_class):
|
||||
_pages.register(page_class.__module__, page_class)
|
||||
|
||||
@@ -53,9 +53,9 @@ class PluginsOptionsPage(OptionsPage):
|
||||
self.ui.plugins.dropEvent = self.dropEvent
|
||||
self.ui.plugins.dragEnterEvent = self.dragEnterEvent
|
||||
if sys.platform == "win32":
|
||||
self.loader="file:///%s"
|
||||
self.loader = "file:///%s"
|
||||
else:
|
||||
self.loader="file://%s"
|
||||
self.loader = "file://%s"
|
||||
self.ui.install_plugin.clicked.connect(self.open_plugins)
|
||||
self.ui.folder_open.clicked.connect(self.open_plugin_dir)
|
||||
self.ui.plugin_download.clicked.connect(self.open_plugin_site)
|
||||
|
||||
@@ -124,7 +124,7 @@ class RenamingOptionsPage(OptionsPage):
|
||||
'ascii_filenames': self.ui.ascii_filenames.isChecked(),
|
||||
'rename_files': self.ui.rename_files.isChecked(),
|
||||
'move_files': self.ui.move_files.isChecked(),
|
||||
'use_va_format': False, # TODO remove
|
||||
'use_va_format': False, # TODO remove
|
||||
'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()),
|
||||
'move_files_to': os.path.normpath(unicode(self.ui.move_files_to.text()))
|
||||
}
|
||||
@@ -137,9 +137,12 @@ class RenamingOptionsPage(OptionsPage):
|
||||
if not settings["move_files"]:
|
||||
return os.path.basename(filename)
|
||||
return filename
|
||||
except SyntaxError, e: return ""
|
||||
except TypeError, e: return ""
|
||||
except UnknownFunction, e: return ""
|
||||
except SyntaxError:
|
||||
return ""
|
||||
except TypeError:
|
||||
return ""
|
||||
except UnknownFunction:
|
||||
return ""
|
||||
|
||||
def update_examples(self):
|
||||
# TODO: Here should be more examples etc.
|
||||
@@ -259,12 +262,12 @@ class RenamingOptionsPage(OptionsPage):
|
||||
self.ui.move_files_to.setText(path)
|
||||
|
||||
def test(self):
|
||||
self.ui.renaming_error.setStyleSheet("");
|
||||
self.ui.renaming_error.setStyleSheet("")
|
||||
self.ui.renaming_error.setText("")
|
||||
try:
|
||||
self.check_format()
|
||||
except OptionsCheckError, e:
|
||||
self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR);
|
||||
self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR)
|
||||
self.ui.renaming_error.setText(e.info)
|
||||
return
|
||||
|
||||
|
||||
@@ -80,12 +80,12 @@ class ScriptingOptionsPage(OptionsPage):
|
||||
self.ui.tagger_script.textChanged.connect(self.live_checker)
|
||||
|
||||
def live_checker(self):
|
||||
self.ui.script_error.setStyleSheet("");
|
||||
self.ui.script_error.setStyleSheet("")
|
||||
self.ui.script_error.setText("")
|
||||
try:
|
||||
self.check()
|
||||
except OptionsCheckError, e:
|
||||
self.ui.script_error.setStyleSheet(self.STYLESHEET_ERROR);
|
||||
self.ui.script_error.setStyleSheet(self.STYLESHEET_ERROR)
|
||||
self.ui.script_error.setText(e.info)
|
||||
return
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class RatingWidget(QtGui.QWidget):
|
||||
self.tagger.xmlws.submit_ratings(ratings, None)
|
||||
|
||||
def paintEvent(self, event=None):
|
||||
painter = QtGui.QPainter(self)
|
||||
painter = QtGui.QPainter(self)
|
||||
offset = self._offset
|
||||
for i in range(1, self._maximum + 1):
|
||||
if i <= self._rating or i <= self._highlight:
|
||||
|
||||
@@ -42,4 +42,3 @@ class StandardButton(QtGui.QPushButton):
|
||||
icon = self.tagger.style().standardIcon(getattr(QtGui.QStyle, iconname))
|
||||
args = [icon, label]
|
||||
QtGui.QPushButton.__init__(self, *args)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import sys
|
||||
import unicodedata
|
||||
from time import time
|
||||
from PyQt4 import QtCore
|
||||
from encodings import rot_13;
|
||||
from encodings import rot_13
|
||||
from string import Template
|
||||
from functools import partial
|
||||
|
||||
@@ -76,6 +76,7 @@ class LockableObject(QtCore.QObject):
|
||||
|
||||
_io_encoding = sys.getfilesystemencoding()
|
||||
|
||||
|
||||
#The following was adapted from k3b's source code:
|
||||
#// On a glibc system the system locale defaults to ANSI_X3.4-1968
|
||||
#// It is very unlikely that one would set the locale to ANSI_X3.4-1968
|
||||
@@ -96,6 +97,7 @@ Translation: Picard will have problems with non-english characters
|
||||
in filenames until you change your charset.
|
||||
""")
|
||||
|
||||
|
||||
def encode_filename(filename):
|
||||
"""Encode unicode strings to filesystem encoding."""
|
||||
if isinstance(filename, unicode):
|
||||
@@ -106,6 +108,7 @@ def encode_filename(filename):
|
||||
else:
|
||||
return filename
|
||||
|
||||
|
||||
def decode_filename(filename):
|
||||
"""Decode strings from filesystem encoding to unicode."""
|
||||
if isinstance(filename, unicode):
|
||||
@@ -113,9 +116,11 @@ def decode_filename(filename):
|
||||
else:
|
||||
return filename.decode(_io_encoding)
|
||||
|
||||
|
||||
def pathcmp(a, b):
|
||||
return os.path.normcase(a) == os.path.normcase(b)
|
||||
|
||||
|
||||
def format_time(ms):
|
||||
"""Formats time in milliseconds to a string representation."""
|
||||
ms = float(ms)
|
||||
@@ -124,6 +129,7 @@ def format_time(ms):
|
||||
else:
|
||||
return "%d:%02d" % (round(ms / 1000.0) / 60, round(ms / 1000.0) % 60)
|
||||
|
||||
|
||||
def sanitize_date(datestr):
|
||||
"""Sanitize date format.
|
||||
|
||||
@@ -141,6 +147,7 @@ def sanitize_date(datestr):
|
||||
date.append(num)
|
||||
return ("", "%04d", "%04d-%02d", "%04d-%02d-%02d")[len(date)] % tuple(date)
|
||||
|
||||
|
||||
_unaccent_dict = {u'Æ': u'AE', u'æ': u'ae', u'Œ': u'OE', u'œ': u'oe', u'ß': 'ss'}
|
||||
_re_latin_letter = re.compile(r"^(LATIN [A-Z]+ LETTER [A-Z]+) WITH")
|
||||
def unaccent(string):
|
||||
@@ -160,26 +167,31 @@ def unaccent(string):
|
||||
result.append(char)
|
||||
return "".join(result)
|
||||
|
||||
|
||||
_re_non_ascii = re.compile(r'[^\x00-\x7F]', re.UNICODE)
|
||||
def replace_non_ascii(string, repl="_"):
|
||||
"""Replace non-ASCII characters from ``string`` by ``repl``."""
|
||||
return _re_non_ascii.sub(repl, asciipunct(string))
|
||||
|
||||
|
||||
_re_win32_incompat = re.compile(r'["*:<>?|]', re.UNICODE)
|
||||
def replace_win32_incompat(string, repl=u"_"):
|
||||
"""Replace win32 filename incompatible characters from ``string`` by
|
||||
``repl``."""
|
||||
return _re_win32_incompat.sub(repl, string)
|
||||
|
||||
|
||||
_re_non_alphanum = re.compile(r'\W+', re.UNICODE)
|
||||
def strip_non_alnum(string):
|
||||
"""Remove all non-alphanumeric characters from ``string``."""
|
||||
return _re_non_alphanum.sub(u" ", string).strip()
|
||||
|
||||
|
||||
_re_slashes = re.compile(r'[\\/]', re.UNICODE)
|
||||
def sanitize_filename(string, repl="_"):
|
||||
return _re_slashes.sub(repl, string)
|
||||
|
||||
|
||||
def make_short_filename(prefix, filename, max_path_length=240, max_length=200,
|
||||
mid_length=32, min_length=2):
|
||||
"""
|
||||
@@ -220,7 +232,7 @@ def make_short_filename(prefix, filename, max_path_length=240, max_length=200,
|
||||
break
|
||||
|
||||
if left > 0:
|
||||
raise IOError, "File name is too long."
|
||||
raise IOError("File name is too long.")
|
||||
|
||||
return os.path.join(*[a.strip() for a in reversed(parts)])
|
||||
|
||||
@@ -307,7 +319,11 @@ def load_release_type_scores(setting):
|
||||
scores = {}
|
||||
values = setting.split()
|
||||
for i in range(0, len(values), 2):
|
||||
scores[values[i]] = float(values[i+1]) if i+1 < len(values) else 0.0
|
||||
try:
|
||||
score = float(values[i + 1])
|
||||
except IndexError:
|
||||
score = 0.0
|
||||
scores[values[i]] = score
|
||||
return scores
|
||||
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ ICON_SIZE_MENU = ('16x16',)
|
||||
ICON_SIZE_TOOLBAR = ('22x22',)
|
||||
ICON_SIZE_ALL = ('22x22', '16x16')
|
||||
|
||||
|
||||
def lookup(name, size=ICON_SIZE_ALL):
|
||||
icon = QtGui.QIcon()
|
||||
if _current_theme:
|
||||
|
||||
@@ -28,6 +28,7 @@ MIME_TYPE_EXTENSION_MAP = {
|
||||
|
||||
EXTENSION_MIME_TYPE_MAP = dict([(b, a) for a, b in MIME_TYPE_EXTENSION_MAP.items()])
|
||||
|
||||
|
||||
def get_from_data(data, filename=None, default=None):
|
||||
"""Tries to determine the mime type from the given data."""
|
||||
if data.startswith('\xff\xd8\xff'):
|
||||
@@ -43,11 +44,13 @@ def get_from_data(data, filename=None, default=None):
|
||||
else:
|
||||
return default
|
||||
|
||||
|
||||
def get_from_filename(filename, default=None):
|
||||
"""Tries to determine the mime type from the given filename."""
|
||||
name, ext = os.path.splitext(os.path.basename(filename))
|
||||
return EXTENSION_MIME_TYPE_MAP.get(ext, default)
|
||||
|
||||
|
||||
def get_extension(mimetype, default=None):
|
||||
"""Returns the file extension for a given mime type."""
|
||||
return MIME_TYPE_EXTENSION_MAP.get(mimetype, default)
|
||||
return MIME_TYPE_EXTENSION_MAP.get(mimetype, default)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from collections import deque
|
||||
from PyQt4 import QtCore
|
||||
|
||||
|
||||
class Queue:
|
||||
"""Create a queue object with a given maximum size.
|
||||
|
||||
@@ -50,7 +51,7 @@ class Queue:
|
||||
finally:
|
||||
self.mutex.unlock()
|
||||
|
||||
def remove(self,item):
|
||||
def remove(self, item):
|
||||
"""Remove an item from the queue."""
|
||||
self.mutex.lock()
|
||||
try:
|
||||
|
||||
@@ -86,6 +86,7 @@ TAG_NAMES = {
|
||||
'~rating': N_('Rating'),
|
||||
}
|
||||
|
||||
|
||||
def display_tag_name(name):
|
||||
if ':' in name:
|
||||
name, desc = name.split(':', 1)
|
||||
@@ -101,4 +102,3 @@ def display_tag_name(name):
|
||||
return '%s []' % (_(new_name),)
|
||||
else:
|
||||
return _(new_name)
|
||||
|
||||
|
||||
@@ -74,11 +74,12 @@ class XmlNode(object):
|
||||
try:
|
||||
return self.attribs[name]
|
||||
except KeyError:
|
||||
raise AttributeError, name
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
_node_name_re = re.compile('[^a-zA-Z0-9]')
|
||||
|
||||
|
||||
def _node_name(n):
|
||||
return _node_name_re.sub('_', unicode(n))
|
||||
|
||||
@@ -338,7 +339,8 @@ class XmlWebService(QtCore.QObject):
|
||||
host = config.setting["server_host"]
|
||||
port = config.setting["server_port"]
|
||||
path = "/ws/2/%s/%s?inc=%s" % (entitytype, entityid, "+".join(inc))
|
||||
if params: path += "&" + "&".join(params)
|
||||
if params:
|
||||
path += "&" + "&".join(params)
|
||||
return self.get(host, port, path, handler, priority=priority, important=important, mblogin=mblogin)
|
||||
|
||||
def get_release_by_id(self, releaseid, handler, inc=[], priority=True, important=False, mblogin=False):
|
||||
@@ -361,8 +363,10 @@ class XmlWebService(QtCore.QObject):
|
||||
filters.append((name, value))
|
||||
else:
|
||||
value = _escape_lucene_query(value).strip().lower()
|
||||
if value: query.append('%s:(%s)' % (name, value))
|
||||
if query: filters.append(('query', ' '.join(query)))
|
||||
if value:
|
||||
query.append('%s:(%s)' % (name, value))
|
||||
if query:
|
||||
filters.append(('query', ' '.join(query)))
|
||||
params = []
|
||||
for name, value in filters:
|
||||
value = str(QUrl.toPercentEncoding(QtCore.QString(value)))
|
||||
|
||||
3
setup.py
3
setup.py
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import glob, re
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from StringIO import StringIO
|
||||
from ConfigParser import RawConfigParser
|
||||
|
||||
Reference in New Issue
Block a user