mirror of
https://github.com/fergalmoran/picard.git
synced 2026-01-06 16:44:06 +00:00
Merge pull request #475 from rahul-raturi/embed_search
Searching tracks and displaying similar tracks in a dialog box.
This commit is contained in:
@@ -246,7 +246,7 @@ def media_formats_from_node(node):
|
||||
|
||||
def track_to_metadata(node, track):
|
||||
m = track.metadata
|
||||
recording_to_metadata(node.recording[0], track)
|
||||
recording_to_metadata(node.recording[0], m, track)
|
||||
m.add_unique('musicbrainz_trackid', node.id)
|
||||
# overwrite with data we have on the track
|
||||
for name, nodes in node.children.iteritems():
|
||||
@@ -265,8 +265,7 @@ def track_to_metadata(node, track):
|
||||
m['~length'] = format_time(m.length)
|
||||
|
||||
|
||||
def recording_to_metadata(node, track):
|
||||
m = track.metadata
|
||||
def recording_to_metadata(node, m, track=None):
|
||||
m.length = 0
|
||||
m.add_unique('musicbrainz_recordingid', node.id)
|
||||
for name, nodes in node.children.iteritems():
|
||||
@@ -281,7 +280,7 @@ def recording_to_metadata(node, track):
|
||||
m['~recordingcomment'] = nodes[0].text
|
||||
elif name == 'artist_credit':
|
||||
artist_credit_to_metadata(nodes[0], m)
|
||||
if 'name_credit' in nodes[0].children:
|
||||
if 'name_credit' in nodes[0].children and track:
|
||||
for name_credit in nodes[0].name_credit:
|
||||
if 'artist' in name_credit.children:
|
||||
for artist in name_credit.artist:
|
||||
@@ -375,7 +374,7 @@ def release_to_metadata(node, m, album=None):
|
||||
m['barcode'] = nodes[0].text
|
||||
elif name == 'relation_list':
|
||||
_relations_to_metadata(nodes, m)
|
||||
elif name == 'label_info_list' and nodes[0].count != '0':
|
||||
elif name == 'label_info_list' and getattr(nodes[0], 'count', '0') != '0':
|
||||
m['label'], m['catalognumber'] = label_info_from_node(nodes[0])
|
||||
elif name == 'text_representation':
|
||||
if 'language' in nodes[0].children:
|
||||
|
||||
@@ -1262,6 +1262,51 @@ qt_resource_data = "\
|
||||
\xb3\xa9\xe4\x06\xaa\xfb\x62\x36\x86\x02\x46\x8a\x63\x13\x00\x29\
|
||||
\x51\x09\x03\x00\x20\x62\x2f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
|
||||
\x42\x60\x82\
|
||||
\x00\x00\x02\xa1\
|
||||
\x47\
|
||||
\x49\x46\x38\x39\x61\x10\x00\x10\x00\xf2\x00\x00\xff\xff\xff\x00\
|
||||
\x00\x00\xc2\xc2\xc2\x42\x42\x42\x00\x00\x00\x62\x62\x62\x82\x82\
|
||||
\x82\x92\x92\x92\x21\xff\x0b\x4e\x45\x54\x53\x43\x41\x50\x45\x32\
|
||||
\x2e\x30\x03\x01\x00\x00\x00\x21\xfe\x1a\x43\x72\x65\x61\x74\x65\
|
||||
\x64\x20\x77\x69\x74\x68\x20\x61\x6a\x61\x78\x6c\x6f\x61\x64\x2e\
|
||||
\x69\x6e\x66\x6f\x00\x21\xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\x00\
|
||||
\x00\x00\x10\x00\x10\x00\x00\x03\x33\x08\xba\xdc\xfe\x30\xca\x49\
|
||||
\x6b\x13\x63\x08\x3a\x08\x19\x9c\x07\x4e\x98\x66\x09\x45\xb1\x31\
|
||||
\xc2\xba\x14\x99\xc1\xb6\x2e\x60\xc4\xc2\x71\xd0\x2d\x5b\x18\x39\
|
||||
\xdd\xa6\x07\x39\x18\x0c\x07\x4a\x6b\xe7\x48\x00\x00\x21\xf9\x04\
|
||||
\x09\x0a\x00\x00\x00\x2c\x00\x00\x00\x00\x10\x00\x10\x00\x00\x03\
|
||||
\x34\x08\xba\xdc\xfe\x4e\x8c\x21\x20\x1b\x84\x0c\xbb\xb0\xe6\x8a\
|
||||
\x44\x71\x42\x51\x54\x60\x31\x19\x20\x60\x4c\x45\x5b\x1a\xa8\x7c\
|
||||
\x1c\xb5\x75\xdf\xed\x61\x18\x07\x80\x20\xd7\x18\xe2\x86\x43\x19\
|
||||
\xb2\x25\x24\x2a\x12\x00\x21\xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\
|
||||
\x00\x00\x00\x10\x00\x10\x00\x00\x03\x36\x08\xba\x32\x23\x2b\xca\
|
||||
\x41\xc8\x90\xcc\x94\x56\x2f\x06\x85\x63\x1c\x0e\xf4\x19\x4e\xf1\
|
||||
\x49\x42\x61\x98\xab\x70\x1c\xf0\x0a\xcc\xb3\xbd\x1c\xc6\xa8\x2b\
|
||||
\x02\x59\xed\x17\xfc\x01\x83\xc3\x0f\x32\xa9\x64\x1a\x9f\xbf\x04\
|
||||
\x00\x21\xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\x00\x00\x00\x10\x00\
|
||||
\x10\x00\x00\x03\x33\x08\xba\x62\x25\x2b\xca\x32\x86\x91\xec\x9c\
|
||||
\x56\x5f\x85\x8b\xa6\x09\x85\x21\x0c\x04\x31\x44\x87\x61\x1c\x11\
|
||||
\xaa\x46\x82\xb0\xd1\x1f\x03\x62\x52\x5d\xf3\x3d\x1f\x30\x38\x2c\
|
||||
\x1a\x8f\xc8\xa4\x72\x39\x4c\x00\x00\x21\xf9\x04\x09\x0a\x00\x00\
|
||||
\x00\x2c\x00\x00\x00\x00\x10\x00\x10\x00\x00\x03\x32\x08\xba\x72\
|
||||
\x27\x2b\x4a\xe7\x64\x14\xf0\x18\xf3\x4c\x81\x0c\x26\x76\xc3\x60\
|
||||
\x5c\x62\x54\x94\x85\x84\xb9\x1e\x68\x59\x42\x29\xcf\xca\x40\x10\
|
||||
\x03\x1e\xe9\x3c\x1f\xc3\x26\x2c\x1a\x8f\xc8\xa4\x52\x92\x00\x00\
|
||||
\x21\xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\x00\x00\x00\x10\x00\x10\
|
||||
\x00\x00\x03\x33\x08\xba\x20\xc2\x90\x39\x17\xe3\x74\xe7\xbc\xda\
|
||||
\x9e\x30\x19\xc7\x1c\xe0\x21\x2e\x42\xb6\x9d\xca\x57\xac\xa2\x31\
|
||||
\x0c\x06\x0b\x14\x73\x61\xbb\xb0\x35\xf7\x95\x01\x81\x30\xb0\x09\
|
||||
\x89\xbb\x9f\x6d\x29\x4a\x00\x00\x21\xf9\x04\x09\x0a\x00\x00\x00\
|
||||
\x2c\x00\x00\x00\x00\x10\x00\x10\x00\x00\x03\x32\x08\xba\xdc\xfe\
|
||||
\xf0\x09\x11\xd9\x9c\x55\x5d\x9a\x01\xee\xda\x71\x70\x95\x60\x88\
|
||||
\xdd\x61\x9c\xdd\x34\x96\x85\x41\x46\xc5\x30\x14\x90\x60\x9b\xb6\
|
||||
\x01\x0d\x04\xc2\x40\x10\x9b\x31\x80\xc2\xd6\xce\x91\x00\x00\x21\
|
||||
\xf9\x04\x09\x0a\x00\x00\x00\x2c\x00\x00\x00\x00\x10\x00\x10\x00\
|
||||
\x00\x03\x32\x08\xba\xdc\xfe\x30\xca\x49\xab\x65\x42\xd4\x9c\x29\
|
||||
\xd7\x1e\x08\x08\xc3\x20\x8e\xc7\x71\x0e\x04\x31\x30\xa9\xca\xb0\
|
||||
\xae\x50\x18\xc2\x61\x18\x07\x56\xda\xa5\x02\x20\x75\x62\x18\x82\
|
||||
\x9e\x5b\x11\x90\x00\x00\x3b\x00\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\
|
||||
\x00\x00\x00\x83\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
@@ -3419,31 +3464,75 @@ qt_resource_data = "\
|
||||
\x8f\xc4\xad\x06\x0f\xc4\xcd\x1e\x8f\x8e\x7f\x01\xd7\x2b\x79\xd4\
|
||||
\xea\x76\x04\x5f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
|
||||
\
|
||||
\x00\x00\x01\x62\
|
||||
\x00\x00\x04\x2d\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
|
||||
\x00\x00\x01\x29\x49\x44\x41\x54\x78\x01\x63\x18\xa2\xe0\x3f\x63\
|
||||
\xb9\x74\x6b\x5c\x99\x74\x5b\xfc\x9b\x62\xeb\xf8\x37\xa5\x36\x71\
|
||||
\xff\x19\x18\x18\xe9\x62\x75\x85\x64\xbb\x71\x99\x74\xeb\x11\xa0\
|
||||
\x03\xfe\x57\xc8\xb4\x96\xbf\x29\xb1\x29\x7f\x5d\x62\xfd\x1f\x88\
|
||||
\xcf\xbc\x2c\xb2\xb4\xa2\x99\xc5\x95\xd2\x6d\xc2\x15\x52\x6d\x13\
|
||||
\x81\x16\xff\x01\x59\x8e\xee\x00\x28\xfe\xf7\xba\xc4\x6a\xd1\xab\
|
||||
\x52\x07\x09\xaa\x59\x9c\x66\x3c\x93\xb5\x5c\xa6\x35\x1f\x68\xe1\
|
||||
\x07\x98\xc5\x38\x1d\x80\xc0\x5f\x5e\x97\xd8\x34\xdc\xce\xf5\x64\
|
||||
\xa7\xc8\x72\x60\x50\x3b\x97\x4b\xb5\x5d\x81\x59\x48\xc8\x01\x98\
|
||||
\xd8\xea\xd6\xeb\x52\x1b\x1f\xd2\xe3\x59\xa6\x5d\xa5\x5c\xba\x6d\
|
||||
\x15\xcc\x22\xb2\x1d\x80\xc0\xbb\xdf\x16\xdb\x6a\x11\xb4\xb8\x44\
|
||||
\xbc\x9b\x1b\x68\x68\x03\xd0\xf0\x1f\x50\x4b\x28\x77\x00\x02\xff\
|
||||
\x02\xaa\x9d\xf8\x36\xd7\x9c\x0f\xab\xe5\xa5\xd2\xad\x06\x40\x43\
|
||||
\x9f\xc1\x0c\xa7\xba\x03\x10\xf8\xd9\xab\x52\x2b\x03\xcc\x60\x97\
|
||||
\x6e\x09\x05\x19\x4a\x07\x07\xfc\x7f\x55\x6a\x13\x3a\xe0\x0e\x18\
|
||||
\x75\xc0\xa8\x03\x46\x1d\x30\xea\x80\x51\x07\x8c\x3a\x60\xc0\xab\
|
||||
\x63\x42\x0d\x92\xef\xf4\x69\x90\xd0\xbf\x49\x46\xff\x46\x29\x10\
|
||||
\x7b\x0f\x68\xb3\x7c\xc8\x77\x4c\x30\x73\x8b\x54\xab\x11\x8e\xae\
|
||||
\xd9\xe9\xb7\x25\x56\x96\x43\xbd\x73\x4a\x7f\x00\x00\x00\x0b\xb8\
|
||||
\x4b\xac\x5f\x46\xcf\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\
|
||||
\x82\
|
||||
\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\
|
||||
\x00\x00\x02\x1c\x50\x4c\x54\x45\x00\x00\x00\x00\x00\xff\xff\x00\
|
||||
\x00\x80\x00\x80\xff\x80\x40\x71\x1c\x8e\xea\x6a\x40\x78\x1e\x87\
|
||||
\x71\x1c\x80\xe8\x74\x3a\x7b\x1c\x84\xec\x71\x39\xe7\x78\x38\x75\
|
||||
\x1b\x85\xec\x76\x39\xec\x74\x3c\x75\x1c\x86\xe9\x73\x39\x77\x1a\
|
||||
\x84\x77\x1b\x85\xec\x75\x3c\xea\x73\x3c\x78\x1c\x85\x77\x1b\x84\
|
||||
\xec\x75\x3a\x78\x1b\x85\xec\x74\x3c\xea\x74\x3a\x78\x1a\x86\x76\
|
||||
\x1b\x85\xeb\x75\x3a\x78\x1b\x84\xeb\x74\x3a\xb3\x80\xac\xf6\xbe\
|
||||
\x92\xeb\x73\x3a\xcb\xa8\xbb\xf8\xcc\xa1\x77\x1a\x85\x78\x1c\x85\
|
||||
\xeb\x73\x3c\x78\x1b\x85\xeb\x74\x3b\x77\x1c\x85\xeb\x74\x3c\x77\
|
||||
\x1b\x85\xeb\x74\x3b\x77\x1b\x85\xeb\x74\x3b\x77\x1b\x85\xeb\x74\
|
||||
\x3b\x77\x1b\x85\x77\x1b\x85\x78\x1b\x85\x79\x1e\x87\x79\x1e\x88\
|
||||
\x79\x1f\x86\x7a\x20\x87\x7a\x20\x89\x7d\x22\x8a\x7d\x23\x8c\x7e\
|
||||
\x24\x8c\x7f\x25\x8c\x80\x26\x8d\x84\x2c\x92\x85\x2c\x92\x88\x30\
|
||||
\x95\x88\x31\x96\x8a\x33\x97\x8c\x34\x98\x8c\x37\x9a\x8e\x38\x9c\
|
||||
\x8f\x39\x9b\x90\x3a\x9d\x94\x40\xa2\x97\x51\x9a\x99\x46\xa6\x9a\
|
||||
\x49\xa7\x9c\x49\xa9\x9f\x4e\xac\xa0\x4f\xac\xa5\x55\xb2\xa8\x58\
|
||||
\xb4\xac\x5d\xb8\xac\x5e\xb8\xad\x60\xba\xaf\x62\xbb\xb0\x64\xbc\
|
||||
\xb3\x66\xbe\xb4\x68\xc0\xb4\x6a\xc1\xb5\x69\xc1\xb5\x6a\xc2\xb6\
|
||||
\x6b\xc2\xb9\x82\xb3\xbb\x75\xc3\xc0\x80\xc6\xc2\x84\xc6\xc7\x8e\
|
||||
\xc8\xc8\xa2\xb9\xca\xa2\xbb\xcd\x99\xca\xcf\x9d\xcb\xd1\xb1\xbe\
|
||||
\xd5\xaa\xcd\xd8\xb1\xce\xda\xb4\xce\xda\xbd\xc5\xe1\xc8\xcb\xe4\
|
||||
\xd0\xca\xe6\xca\xd3\xea\xd3\xd3\xeb\x74\x3b\xeb\x75\x3d\xeb\x76\
|
||||
\x3d\xec\x78\x40\xec\x79\x42\xec\x7b\x43\xec\x7c\x44\xec\x7e\x46\
|
||||
\xec\x7e\x47\xed\x7f\x47\xed\x7f\x48\xed\x81\x4a\xed\x82\x4a\xed\
|
||||
\x82\x4b\xed\x84\x4d\xee\x87\x51\xee\x88\x52\xee\x8b\x55\xee\x8b\
|
||||
\x56\xf0\x94\x60\xf0\x97\x64\xf0\x98\x65\xf0\x9a\x66\xf1\x9c\x69\
|
||||
\xf1\x9e\x6b\xf1\xe6\xd4\xf2\xa1\x6f\xf2\xa2\x70\xf2\xa4\x73\xf2\
|
||||
\xa7\x75\xf2\xe3\xd7\xf2\xe9\xd3\xf3\xae\x7d\xf5\xb8\x88\xf5\xea\
|
||||
\xd8\xf6\xbc\x8f\xf6\xee\xd6\xf7\xc9\x9e\xf8\xcb\xa0\xf8\xcf\xa4\
|
||||
\xf8\xd0\xa6\xf9\xd6\xac\xf9\xd7\xae\xf9\xf4\xd8\xfb\xe0\xb9\xfb\
|
||||
\xe2\xba\xfb\xe2\xbb\xfb\xe3\xbc\xfb\xf7\xda\xfc\xe9\xc3\xfc\xeb\
|
||||
\xc5\xfc\xed\xc7\xfc\xf8\xda\xfd\xef\xca\xfd\xf0\xcb\xfd\xf3\xce\
|
||||
\xfd\xfb\xda\xfe\xf4\xcf\xfe\xf6\xd1\xfe\xf6\xd2\xfe\xf7\xd3\xfe\
|
||||
\xf8\xd5\xfe\xf9\xd4\xff\xfb\xd7\xff\xfc\xd9\xff\xfd\xda\xff\xfd\
|
||||
\xdb\xff\xfe\xdb\xa6\x92\x1f\xb1\x00\x00\x00\x34\x74\x52\x4e\x53\
|
||||
\x00\x01\x01\x02\x04\x09\x0c\x11\x12\x16\x1b\x1b\x20\x30\x36\x37\
|
||||
\x3f\x47\x4d\x5e\x5e\x6f\x77\x83\x83\x84\x84\x88\x91\x99\x99\xa2\
|
||||
\xa2\xa8\xb1\xb3\xbb\xbd\xc1\xc2\xc9\xd1\xd7\xde\xe3\xea\xee\xf3\
|
||||
\xf6\xfc\xfd\xfe\x98\x06\x23\x55\x00\x00\x01\x8c\x49\x44\x41\x54\
|
||||
\x38\xcb\x63\x60\x40\x01\x8c\x5c\xdc\x4c\x0c\x78\x00\xaf\xba\xa4\
|
||||
\x94\x06\x3f\x4e\x69\x0e\x19\x43\x13\x49\xa9\x02\x23\x05\x4e\xac\
|
||||
\xd2\xcc\xc2\xba\x26\x26\x20\x05\x05\x05\x7a\xa2\x2c\x98\xf2\x02\
|
||||
\x9a\x26\x26\x30\x05\x05\x05\x5a\x82\x68\xd2\x5c\xf2\xc6\x26\xc8\
|
||||
\x0a\x0a\x0a\x94\x79\x90\xa4\x59\xc5\xf4\x4d\x4c\xd0\x14\x14\x18\
|
||||
\x48\xb3\xc1\xe4\x85\xb4\x4d\x4c\x30\x15\x14\x14\xe8\x88\x40\xe4\
|
||||
\xc5\x21\x52\x36\xae\x26\xde\xc9\xb9\x2a\x48\x0a\x0a\x0a\x24\xc0\
|
||||
\x0a\x64\x41\xd2\x76\xbe\x91\xf1\x39\x9b\xfb\xd3\x15\x21\x0a\x8a\
|
||||
\xda\x9a\x0b\x81\x94\x1c\x5c\x81\x47\x74\x5a\xfe\xa4\x25\x71\x16\
|
||||
\x30\x2b\x66\x6c\xde\x3c\x15\x45\x41\x40\xd2\xa6\xec\x29\x79\x51\
|
||||
\x0e\x50\x05\x75\x9b\xbb\x7a\x36\x56\x22\x2b\x08\xce\x98\x6d\x99\
|
||||
\xd2\x17\x0b\x53\x50\xba\x6e\xda\xf4\xd5\x25\x48\x0a\xcc\xa2\x13\
|
||||
\x16\x74\xae\xc8\x8c\x31\x87\x2a\xa8\x5f\xbe\x6e\x61\x07\xb2\x15\
|
||||
\x8e\xb1\xb1\x89\x59\xa9\xb1\x21\x50\x6f\x36\x2e\x5e\x5a\x0b\x76\
|
||||
\x0a\x5c\x81\x67\x2c\x18\x04\x80\x15\xd4\x4c\x5e\xbf\xac\xa1\x00\
|
||||
\x55\x81\x1f\x44\x81\x17\x50\x81\xd2\xdc\x0d\x9b\x37\x6f\x9c\xd5\
|
||||
\x84\xaa\x20\x08\xa2\xc0\x19\xa8\x40\x75\xde\xc4\xea\xf2\xde\x45\
|
||||
\x6b\x26\xb4\x17\x21\x14\x98\x46\x40\x14\x58\xc3\x83\xba\x62\xd5\
|
||||
\xc6\x0d\xd3\x11\x0a\xec\x21\xf2\xe1\x88\xb8\x68\xd8\xdc\xde\xbd\
|
||||
\xb9\x0a\xae\xc0\x35\x36\x36\x34\x2c\x36\x36\x10\xa1\xa0\x6c\xed\
|
||||
\x8c\x99\x2b\x8b\xe1\x0a\x9c\xdc\x6d\x4d\xac\xfc\x63\x7d\x90\x62\
|
||||
\xb3\x75\xce\xfc\x16\xe4\x90\x04\x01\x37\x17\xb4\xe8\x86\x29\x10\
|
||||
\x37\x31\xc1\x9e\x1e\x60\xd1\x4d\x30\xc1\x10\x4e\x72\x84\x13\x2d\
|
||||
\x11\xc9\x9e\x88\x8c\x03\x04\xec\xf8\xb3\x1e\x38\xf3\xaa\x01\x33\
|
||||
\x2f\x1f\xbe\xec\x8d\x99\xfd\x01\x12\xd5\xd3\xad\x82\xe8\xbe\xc1\
|
||||
\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
|
||||
\x00\x00\x33\x3f\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
@@ -4759,6 +4848,10 @@ qt_resource_name = "\
|
||||
\x00\x6d\
|
||||
\x00\x61\x00\x74\x00\x63\x00\x68\x00\x2d\x00\x70\x00\x65\x00\x6e\x00\x64\x00\x69\x00\x6e\x00\x67\x00\x2d\x00\x36\x00\x30\x00\x2e\
|
||||
\x00\x70\x00\x6e\x00\x67\
|
||||
\x00\x0a\
|
||||
\x0a\xcb\x27\x16\
|
||||
\x00\x6c\
|
||||
\x00\x6f\x00\x61\x00\x64\x00\x65\x00\x72\x00\x2e\x00\x67\x00\x69\x00\x66\
|
||||
\x00\x05\
|
||||
\x00\x35\x9b\x52\
|
||||
\x00\x32\
|
||||
@@ -4935,78 +5028,79 @@ qt_resource_name = "\
|
||||
|
||||
qt_resource_struct = "\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1d\x00\x00\x00\x02\
|
||||
\x00\x00\x02\x72\x00\x00\x00\x00\x00\x01\x00\x00\x4d\x8d\
|
||||
\x00\x00\x02\xf8\x00\x02\x00\x00\x00\x12\x00\x00\x00\x37\
|
||||
\x00\x00\x02\x16\x00\x02\x00\x00\x00\x13\x00\x00\x00\x24\
|
||||
\x00\x00\x00\x12\x00\x02\x00\x00\x00\x01\x00\x00\x00\x23\
|
||||
\x00\x00\x01\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x22\
|
||||
\x00\x00\x01\x4e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x21\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x1e\x00\x00\x00\x02\
|
||||
\x00\x00\x02\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x50\x32\
|
||||
\x00\x00\x03\x12\x00\x02\x00\x00\x00\x12\x00\x00\x00\x38\
|
||||
\x00\x00\x02\x30\x00\x02\x00\x00\x00\x13\x00\x00\x00\x25\
|
||||
\x00\x00\x00\x12\x00\x02\x00\x00\x00\x01\x00\x00\x00\x24\
|
||||
\x00\x00\x01\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x23\
|
||||
\x00\x00\x01\x4e\x00\x02\x00\x00\x00\x01\x00\x00\x00\x22\
|
||||
\x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x03\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x54\x26\
|
||||
\x00\x00\x03\x26\x00\x00\x00\x00\x00\x01\x00\x00\x53\x90\
|
||||
\x00\x00\x00\x94\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\
|
||||
\x00\x00\x01\x06\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1f\
|
||||
\x00\x00\x02\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x50\xac\
|
||||
\x00\x00\x03\x64\x00\x00\x00\x00\x00\x01\x00\x00\x56\xcb\
|
||||
\x00\x00\x03\x40\x00\x00\x00\x00\x00\x01\x00\x00\x56\x35\
|
||||
\x00\x00\x00\x94\x00\x02\x00\x00\x00\x01\x00\x00\x00\x21\
|
||||
\x00\x00\x01\x06\x00\x02\x00\x00\x00\x01\x00\x00\x00\x20\
|
||||
\x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00\x53\x51\
|
||||
\x00\x00\x01\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x47\x52\
|
||||
\x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x46\xe5\
|
||||
\x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x01\x3e\
|
||||
\x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x8e\
|
||||
\x00\x00\x00\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x43\x17\
|
||||
\x00\x00\x02\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x50\x23\
|
||||
\x00\x00\x02\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x52\xc8\
|
||||
\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x40\xf4\
|
||||
\x00\x00\x02\x44\x00\x00\x00\x00\x00\x01\x00\x00\x4d\x14\
|
||||
\x00\x00\x02\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x4f\xb9\
|
||||
\x00\x00\x01\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x4c\x05\
|
||||
\x00\x00\x01\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x4a\xf5\
|
||||
\x00\x00\x03\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x54\xa3\
|
||||
\x00\x00\x02\x16\x00\x00\x00\x00\x00\x01\x00\x00\x4c\x8d\
|
||||
\x00\x00\x03\x84\x00\x00\x00\x00\x00\x01\x00\x00\x57\x48\
|
||||
\x00\x00\x01\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x48\x77\
|
||||
\x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00\x52\xfc\
|
||||
\x00\x00\x02\x88\x00\x00\x00\x00\x00\x01\x00\x00\x4f\x8a\
|
||||
\x00\x00\x02\x26\x00\x00\x00\x00\x00\x01\x00\x00\x4c\x8d\
|
||||
\x00\x00\x03\x22\x00\x00\x00\x00\x00\x01\x00\x00\x55\xa1\
|
||||
\x00\x00\x02\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x52\x2f\
|
||||
\x00\x00\x02\x40\x00\x00\x00\x00\x00\x01\x00\x00\x4f\x32\
|
||||
\x00\x00\x01\xca\x00\x00\x00\x00\x00\x01\x00\x00\x4b\x6b\
|
||||
\x00\x00\x01\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x6e\
|
||||
\x00\x00\x04\x4a\x00\x00\x00\x00\x00\x01\x00\x00\xce\x2d\
|
||||
\x00\x00\x04\x4a\x00\x00\x00\x00\x00\x01\x00\x01\x01\x70\
|
||||
\x00\x00\x04\x4a\x00\x00\x00\x00\x00\x01\x00\x00\xc7\x62\
|
||||
\x00\x00\x04\x4a\x00\x00\x00\x00\x00\x01\x00\x00\xcc\xc7\
|
||||
\x00\x00\x04\x4a\x00\x00\x00\x00\x00\x01\x00\x01\x19\xca\
|
||||
\x00\x00\x06\x4a\x00\x00\x00\x00\x00\x01\x00\x00\xbb\x5b\
|
||||
\x00\x00\x04\x98\x00\x00\x00\x00\x00\x01\x00\x00\x92\x3c\
|
||||
\x00\x00\x06\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x85\x4d\
|
||||
\x00\x00\x07\x44\x00\x00\x00\x00\x00\x01\x00\x00\xa2\xb9\
|
||||
\x00\x00\x05\xca\x00\x00\x00\x00\x00\x01\x00\x00\xae\x5d\
|
||||
\x00\x00\x07\x6e\x00\x00\x00\x00\x00\x01\x00\x00\xa4\xe2\
|
||||
\x00\x00\x06\x94\x00\x00\x00\x00\x00\x01\x00\x00\x7e\x20\
|
||||
\x00\x00\x03\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x80\xde\
|
||||
\x00\x00\x04\x64\x00\x00\x00\x00\x00\x01\x00\x00\x87\x7b\
|
||||
\x00\x00\x06\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xc7\
|
||||
\x00\x00\x07\xbe\x00\x00\x00\x00\x00\x01\x00\x00\xc0\x5d\
|
||||
\x00\x00\x04\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x95\xcc\
|
||||
\x00\x00\x04\x00\x00\x00\x00\x00\x00\x01\x00\x00\x82\xad\
|
||||
\x00\x00\x05\x3a\x00\x00\x00\x00\x00\x01\x00\x00\xaa\xde\
|
||||
\x00\x00\x06\x72\x00\x00\x00\x00\x00\x01\x00\x00\xc3\x00\
|
||||
\x00\x00\x05\x06\x00\x00\x00\x00\x00\x01\x00\x00\x9d\xe9\
|
||||
\x00\x00\x07\x16\x00\x00\x00\x00\x00\x01\x00\x00\x9a\x4a\
|
||||
\x00\x00\x07\x96\x00\x00\x00\x00\x00\x01\x00\x00\xb7\x08\
|
||||
\x00\x00\x06\x22\x00\x00\x00\x00\x00\x01\x00\x00\xb3\x89\
|
||||
\x00\x00\x04\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x59\x46\
|
||||
\x00\x00\x06\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x78\xd0\
|
||||
\x00\x00\x04\x98\x00\x00\x00\x00\x00\x01\x00\x00\x5f\x84\
|
||||
\x00\x00\x05\xca\x00\x00\x00\x00\x00\x01\x00\x00\x70\xcf\
|
||||
\x00\x00\x03\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x56\x1d\
|
||||
\x00\x00\x06\x04\x00\x00\x00\x00\x00\x01\x00\x00\x73\xd5\
|
||||
\x00\x00\x05\x5c\x00\x00\x00\x00\x00\x01\x00\x00\x69\xf6\
|
||||
\x00\x00\x04\x64\x00\x00\x00\x00\x00\x01\x00\x00\x5c\x7b\
|
||||
\x00\x00\x04\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x5b\xa2\
|
||||
\x00\x00\x04\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x61\x46\
|
||||
\x00\x00\x04\x00\x00\x00\x00\x00\x00\x01\x00\x00\x57\xa1\
|
||||
\x00\x00\x05\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x6e\xf8\
|
||||
\x00\x00\x05\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x67\xd3\
|
||||
\x00\x00\x03\x98\x00\x00\x00\x00\x00\x01\x00\x00\x55\x27\
|
||||
\x00\x00\x06\x72\x00\x00\x00\x00\x00\x01\x00\x00\x7c\x0e\
|
||||
\x00\x00\x05\x06\x00\x00\x00\x00\x00\x01\x00\x00\x64\xbf\
|
||||
\x00\x00\x05\x82\x00\x00\x00\x00\x00\x01\x00\x00\x6c\xd0\
|
||||
\x00\x00\x06\x22\x00\x00\x00\x00\x00\x01\x00\x00\x76\x36\
|
||||
\x00\x00\x04\x64\x00\x00\x00\x00\x00\x01\x00\x00\xd3\x9d\
|
||||
\x00\x00\x04\x64\x00\x00\x00\x00\x00\x01\x00\x01\x06\xe0\
|
||||
\x00\x00\x04\x64\x00\x00\x00\x00\x00\x01\x00\x00\xca\x07\
|
||||
\x00\x00\x04\x64\x00\x00\x00\x00\x00\x01\x00\x00\xcf\x6c\
|
||||
\x00\x00\x04\x64\x00\x00\x00\x00\x00\x01\x00\x01\x1f\x3a\
|
||||
\x00\x00\x06\x64\x00\x00\x00\x00\x00\x01\x00\x00\xbe\x00\
|
||||
\x00\x00\x04\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x94\xe1\
|
||||
\x00\x00\x06\xda\x00\x00\x00\x00\x00\x01\x00\x00\x87\xf2\
|
||||
\x00\x00\x07\x5e\x00\x00\x00\x00\x00\x01\x00\x00\xa5\x5e\
|
||||
\x00\x00\x05\xe4\x00\x00\x00\x00\x00\x01\x00\x00\xb1\x02\
|
||||
\x00\x00\x07\x88\x00\x00\x00\x00\x00\x01\x00\x00\xa7\x87\
|
||||
\x00\x00\x06\xae\x00\x00\x00\x00\x00\x01\x00\x00\x80\xc5\
|
||||
\x00\x00\x03\xe6\x00\x00\x00\x00\x00\x01\x00\x00\x83\x83\
|
||||
\x00\x00\x04\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x8a\x20\
|
||||
\x00\x00\x06\xfe\x00\x00\x00\x00\x00\x01\x00\x00\x8f\x6c\
|
||||
\x00\x00\x07\xd8\x00\x00\x00\x00\x00\x01\x00\x00\xc3\x02\
|
||||
\x00\x00\x04\xda\x00\x00\x00\x00\x00\x01\x00\x00\x98\x71\
|
||||
\x00\x00\x04\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x85\x52\
|
||||
\x00\x00\x05\x54\x00\x00\x00\x00\x00\x01\x00\x00\xad\x83\
|
||||
\x00\x00\x06\x8c\x00\x00\x00\x00\x00\x01\x00\x00\xc5\xa5\
|
||||
\x00\x00\x05\x20\x00\x00\x00\x00\x00\x01\x00\x00\xa0\x8e\
|
||||
\x00\x00\x07\x30\x00\x00\x00\x00\x00\x01\x00\x00\x9c\xef\
|
||||
\x00\x00\x07\xb0\x00\x00\x00\x00\x00\x01\x00\x00\xb9\xad\
|
||||
\x00\x00\x06\x3c\x00\x00\x00\x00\x00\x01\x00\x00\xb6\x2e\
|
||||
\x00\x00\x04\x34\x00\x00\x00\x00\x00\x01\x00\x00\x5b\xeb\
|
||||
\x00\x00\x06\x64\x00\x00\x00\x00\x00\x01\x00\x00\x7b\x75\
|
||||
\x00\x00\x04\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x62\x29\
|
||||
\x00\x00\x05\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x73\x74\
|
||||
\x00\x00\x03\xe6\x00\x00\x00\x00\x00\x01\x00\x00\x58\xc2\
|
||||
\x00\x00\x06\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x76\x7a\
|
||||
\x00\x00\x05\x76\x00\x00\x00\x00\x00\x01\x00\x00\x6c\x9b\
|
||||
\x00\x00\x04\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x5f\x20\
|
||||
\x00\x00\x04\x64\x00\x00\x00\x00\x00\x01\x00\x00\x5e\x47\
|
||||
\x00\x00\x04\xda\x00\x00\x00\x00\x00\x01\x00\x00\x63\xeb\
|
||||
\x00\x00\x04\x1a\x00\x00\x00\x00\x00\x01\x00\x00\x5a\x46\
|
||||
\x00\x00\x05\xc2\x00\x00\x00\x00\x00\x01\x00\x00\x71\x9d\
|
||||
\x00\x00\x05\x54\x00\x00\x00\x00\x00\x01\x00\x00\x6a\x78\
|
||||
\x00\x00\x03\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x57\xcc\
|
||||
\x00\x00\x06\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x7e\xb3\
|
||||
\x00\x00\x05\x20\x00\x00\x00\x00\x00\x01\x00\x00\x67\x64\
|
||||
\x00\x00\x05\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x6f\x75\
|
||||
\x00\x00\x06\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x78\xdb\
|
||||
"
|
||||
|
||||
def qInitResources():
|
||||
|
||||
@@ -271,9 +271,9 @@ class NonAlbumTrack(Track):
|
||||
log.error(traceback.format_exc())
|
||||
|
||||
def _parse_recording(self, recording):
|
||||
recording_to_metadata(recording, self)
|
||||
self._customize_metadata()
|
||||
m = self.metadata
|
||||
recording_to_metadata(recording, m, self)
|
||||
self._customize_metadata()
|
||||
run_track_metadata_processors(self.album, m, None, recording)
|
||||
if config.setting["enable_tagger_script"]:
|
||||
script = config.setting["tagger_script"]
|
||||
|
||||
@@ -257,6 +257,7 @@ class BaseTreeView(QtGui.QTreeWidget):
|
||||
if obj.num_linked_files == 1:
|
||||
menu.addAction(self.window.play_file_action)
|
||||
menu.addAction(self.window.open_folder_action)
|
||||
menu.addAction(self.window.tracks_search_action)
|
||||
plugin_actions.extend(_file_actions)
|
||||
menu.addAction(self.window.browser_lookup_action)
|
||||
menu.addSeparator()
|
||||
@@ -285,6 +286,7 @@ class BaseTreeView(QtGui.QTreeWidget):
|
||||
menu.addSeparator()
|
||||
menu.addAction(self.window.autotag_action)
|
||||
menu.addAction(self.window.analyze_action)
|
||||
menu.addAction(self.window.tracks_search_action)
|
||||
plugin_actions = list(_file_actions)
|
||||
elif isinstance(obj, Album):
|
||||
if can_view_info:
|
||||
|
||||
@@ -34,6 +34,7 @@ from picard.ui.filebrowser import FileBrowser
|
||||
from picard.ui.tagsfromfilenames import TagsFromFileNamesDialog
|
||||
from picard.ui.options.dialog import OptionsDialog
|
||||
from picard.ui.infodialog import FileInfoDialog, AlbumInfoDialog, ClusterInfoDialog
|
||||
from picard.ui.searchdialog import TrackSearchDialog
|
||||
from picard.ui.infostatus import InfoStatus
|
||||
from picard.ui.passworddialog import PasswordDialog
|
||||
from picard.ui.logview import LogView, HistoryView
|
||||
@@ -381,6 +382,10 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.browser_lookup_action.setEnabled(False)
|
||||
self.browser_lookup_action.triggered.connect(self.browser_lookup)
|
||||
|
||||
self.tracks_search_action = QtGui.QAction(icontheme.lookup('system-search'), _(u"Search similar tracks..."), self)
|
||||
self.tracks_search_action.setStatusTip(_(u"View similar tracks and optionally choose a different release"))
|
||||
self.tracks_search_action.triggered.connect(self.show_more_tracks)
|
||||
|
||||
self.show_file_browser_action = QtGui.QAction(_(u"File &Browser"), self)
|
||||
self.show_file_browser_action.setCheckable(True)
|
||||
if config.persist["view_file_browser"]:
|
||||
@@ -674,7 +679,13 @@ class MainWindow(QtGui.QMainWindow):
|
||||
"""Search for album, artist or track on the MusicBrainz website."""
|
||||
text = self.search_edit.text()
|
||||
type = self.search_combo.itemData(self.search_combo.currentIndex())
|
||||
self.tagger.search(text, type, config.setting["use_adv_search_syntax"])
|
||||
if config.setting["builtin_search"]:
|
||||
if type == "track":
|
||||
dialog = TrackSearchDialog(self)
|
||||
dialog.search(text)
|
||||
dialog.exec_()
|
||||
else:
|
||||
self.tagger.search(text, type, config.setting["use_adv_search_syntax"])
|
||||
|
||||
def add_files(self):
|
||||
"""Add files to the tagger."""
|
||||
@@ -791,6 +802,14 @@ class MainWindow(QtGui.QMainWindow):
|
||||
QtGui.QMessageBox.Yes)
|
||||
return ret == QtGui.QMessageBox.Yes
|
||||
|
||||
def show_more_tracks(self):
|
||||
obj = self.selected_objects[0]
|
||||
if isinstance(obj, Track):
|
||||
obj = obj.linked_files[0]
|
||||
dialog = TrackSearchDialog(self)
|
||||
dialog.load_similar_tracks(obj)
|
||||
dialog.exec_()
|
||||
|
||||
def view_info(self):
|
||||
if isinstance(self.selected_objects[0], Album):
|
||||
album = self.selected_objects[0]
|
||||
|
||||
@@ -40,6 +40,7 @@ class InterfaceOptionsPage(OptionsPage):
|
||||
options = [
|
||||
config.BoolOption("setting", "toolbar_show_labels", True),
|
||||
config.BoolOption("setting", "toolbar_multiselect", False),
|
||||
config.BoolOption("setting", "builtin_search", False),
|
||||
config.BoolOption("setting", "use_adv_search_syntax", False),
|
||||
config.BoolOption("setting", "quit_confirmation", True),
|
||||
config.TextOption("setting", "ui_language", u""),
|
||||
@@ -77,6 +78,7 @@ class InterfaceOptionsPage(OptionsPage):
|
||||
def load(self):
|
||||
self.ui.toolbar_show_labels.setChecked(config.setting["toolbar_show_labels"])
|
||||
self.ui.toolbar_multiselect.setChecked(config.setting["toolbar_multiselect"])
|
||||
self.ui.builtin_search.setChecked(config.setting["builtin_search"])
|
||||
self.ui.use_adv_search_syntax.setChecked(config.setting["use_adv_search_syntax"])
|
||||
self.ui.quit_confirmation.setChecked(config.setting["quit_confirmation"])
|
||||
current_ui_language = config.setting["ui_language"]
|
||||
@@ -87,6 +89,7 @@ class InterfaceOptionsPage(OptionsPage):
|
||||
def save(self):
|
||||
config.setting["toolbar_show_labels"] = self.ui.toolbar_show_labels.isChecked()
|
||||
config.setting["toolbar_multiselect"] = self.ui.toolbar_multiselect.isChecked()
|
||||
config.setting["builtin_search"] = self.ui.builtin_search.isChecked()
|
||||
config.setting["use_adv_search_syntax"] = self.ui.use_adv_search_syntax.isChecked()
|
||||
config.setting["quit_confirmation"] = self.ui.quit_confirmation.isChecked()
|
||||
self.tagger.window.update_toolbar_style()
|
||||
|
||||
462
picard/ui/searchdialog.py
Normal file
462
picard/ui/searchdialog.py
Normal file
@@ -0,0 +1,462 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Picard, the next-generation MusicBrainz tagger
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
from PyQt4 import QtGui, QtCore, QtNetwork
|
||||
from operator import itemgetter
|
||||
from functools import partial
|
||||
from collections import namedtuple
|
||||
from picard import config
|
||||
from picard.file import File
|
||||
from picard.ui import PicardDialog
|
||||
from picard.ui.util import StandardButton, ButtonLineEdit
|
||||
from picard.util import format_time, icontheme
|
||||
from picard.mbxml import (
|
||||
recording_to_metadata,
|
||||
release_to_metadata,
|
||||
release_group_to_metadata
|
||||
)
|
||||
from picard.i18n import ugettext_attr
|
||||
from picard.metadata import Metadata
|
||||
from picard.webservice import escape_lucene_query
|
||||
from picard.track import Track
|
||||
|
||||
|
||||
class ResultTable(QtGui.QTableWidget):
|
||||
|
||||
def __init__(self, column_titles):
|
||||
QtGui.QTableWidget.__init__(self, 0, len(column_titles))
|
||||
self.setHorizontalHeaderLabels(column_titles)
|
||||
self.setSelectionMode(
|
||||
QtGui.QAbstractItemView.SingleSelection)
|
||||
self.setSelectionBehavior(
|
||||
QtGui.QAbstractItemView.SelectRows)
|
||||
self.setEditTriggers(
|
||||
QtGui.QAbstractItemView.NoEditTriggers)
|
||||
self.horizontalHeader().setStretchLastSection(True)
|
||||
self.horizontalHeader().setResizeMode(
|
||||
QtGui.QHeaderView.Stretch)
|
||||
self.horizontalHeader().setResizeMode(
|
||||
QtGui.QHeaderView.Interactive)
|
||||
|
||||
class SearchBox(QtGui.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
QtGui.QWidget.__init__(self, parent)
|
||||
self.search_action = QtGui.QAction(icontheme.lookup('system-search'),
|
||||
_(u"Search"), self)
|
||||
self.search_action.triggered.connect(self.search)
|
||||
self.setupUi()
|
||||
|
||||
def setupUi(self):
|
||||
self.layout = QtGui.QVBoxLayout(self)
|
||||
self.search_row_widget = QtGui.QWidget(self)
|
||||
self.search_row_layout = QtGui.QHBoxLayout(self.search_row_widget)
|
||||
self.search_row_layout.setContentsMargins(1, 1, 1, 1)
|
||||
self.search_row_layout.setSpacing(1)
|
||||
self.search_edit = ButtonLineEdit(self.search_row_widget)
|
||||
self.search_row_layout.addWidget(self.search_edit)
|
||||
self.search_button = QtGui.QToolButton(self.search_row_widget)
|
||||
self.search_button.setAutoRaise(True)
|
||||
self.search_button.setDefaultAction(self.search_action)
|
||||
self.search_button.setIconSize(QtCore.QSize(22, 22))
|
||||
self.search_row_layout.addWidget(self.search_button)
|
||||
self.search_row_widget.setLayout(self.search_row_layout)
|
||||
self.layout.addWidget(self.search_row_widget)
|
||||
self.adv_opt_row_widget = QtGui.QWidget(self)
|
||||
self.adv_opt_row_layout = QtGui.QHBoxLayout(self.adv_opt_row_widget)
|
||||
self.adv_opt_row_layout.setAlignment(QtCore.Qt.AlignLeft)
|
||||
self.adv_opt_row_layout.setContentsMargins(1, 1, 1, 1)
|
||||
self.adv_opt_row_layout.setSpacing(1)
|
||||
self.use_adv_search_syntax = QtGui.QCheckBox(self.adv_opt_row_widget)
|
||||
self.use_adv_search_syntax.setText(_("Use advanced query syntax"))
|
||||
self.adv_opt_row_layout.addWidget(self.use_adv_search_syntax)
|
||||
self.adv_syntax_help = QtGui.QLabel(self.adv_opt_row_widget)
|
||||
self.adv_syntax_help.setOpenExternalLinks(True)
|
||||
self.adv_syntax_help.setText(_(
|
||||
" (<a href='https://musicbrainz.org/doc/Indexed_Search_Syntax'>"
|
||||
"Syntax Help</a>)"))
|
||||
self.adv_opt_row_layout.addWidget(self.adv_syntax_help)
|
||||
self.adv_opt_row_widget.setLayout(self.adv_opt_row_layout)
|
||||
self.layout.addWidget(self.adv_opt_row_widget)
|
||||
self.layout.setContentsMargins(1, 1, 1, 1)
|
||||
self.layout.setSpacing(1)
|
||||
self.setMaximumHeight(60)
|
||||
|
||||
def search(self):
|
||||
self.parent.search(self.search_edit.text())
|
||||
|
||||
def restore_checkbox_state(self):
|
||||
self.use_adv_search_syntax.setChecked(config.setting["use_adv_search_syntax"])
|
||||
|
||||
def save_checkbox_state(self):
|
||||
config.setting["use_adv_search_syntax"] = self.use_adv_search_syntax.isChecked()
|
||||
|
||||
|
||||
Retry = namedtuple("Retry", ["function", "query"])
|
||||
|
||||
|
||||
class SearchDialog(PicardDialog):
|
||||
|
||||
options = [
|
||||
config.Option("persist", "searchdialog_window_size", QtCore.QSize(720, 360)),
|
||||
config.Option("persist", "searchdialog_header_state", QtCore.QByteArray())
|
||||
]
|
||||
|
||||
def __init__(self, parent=None):
|
||||
PicardDialog.__init__(self, parent)
|
||||
self.search_results = []
|
||||
self.table = None
|
||||
self.setupUi()
|
||||
self.restore_state()
|
||||
|
||||
def setupUi(self):
|
||||
self.verticalLayout = QtGui.QVBoxLayout(self)
|
||||
self.verticalLayout.setObjectName(_("vertical_layout"))
|
||||
self.search_box = SearchBox(self)
|
||||
self.search_box.setObjectName(_("search_box"))
|
||||
self.verticalLayout.addWidget(self.search_box)
|
||||
self.center_widget = QtGui.QWidget(self)
|
||||
self.center_widget.setObjectName(_("center_widget"))
|
||||
self.center_layout = QtGui.QVBoxLayout(self.center_widget)
|
||||
self.center_layout.setObjectName(_("center_layout"))
|
||||
self.center_layout.setContentsMargins(1, 1, 1, 1)
|
||||
self.center_widget.setLayout(self.center_layout)
|
||||
self.verticalLayout.addWidget(self.center_widget)
|
||||
self.buttonBox = QtGui.QDialogButtonBox(self)
|
||||
self.buttonBox.setObjectName(_("button_box"))
|
||||
self.load_button = QtGui.QPushButton(_("Load into Picard"))
|
||||
self.load_button.setEnabled(False)
|
||||
self.buttonBox.addButton(
|
||||
self.load_button,
|
||||
QtGui.QDialogButtonBox.AcceptRole)
|
||||
self.buttonBox.addButton(
|
||||
StandardButton(StandardButton.CANCEL),
|
||||
QtGui.QDialogButtonBox.RejectRole)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
self.verticalLayout.addWidget(self.buttonBox)
|
||||
|
||||
def add_widget_to_center_layout(self, widget):
|
||||
"""Updates child widget of center_widget.
|
||||
|
||||
Child widgets represent dialog's current state, like progress,
|
||||
error, and displaying fetched results.
|
||||
"""
|
||||
|
||||
wid = self.center_layout.takeAt(0)
|
||||
if wid:
|
||||
if wid.widget().objectName() == "results_table":
|
||||
self.table = None
|
||||
wid.widget().deleteLater()
|
||||
self.center_layout.addWidget(widget)
|
||||
|
||||
def show_progress(self):
|
||||
"""Displays feedback while results are being fetched from server."""
|
||||
|
||||
self.progress_widget = QtGui.QWidget(self)
|
||||
self.progress_widget.setObjectName("progress_widget")
|
||||
layout = QtGui.QVBoxLayout(self.progress_widget)
|
||||
text_label = QtGui.QLabel(_('<strong>Loading...</strong>'), self.progress_widget)
|
||||
text_label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom)
|
||||
gif_label = QtGui.QLabel(self.progress_widget)
|
||||
movie = QtGui.QMovie(":/images/loader.gif")
|
||||
gif_label.setMovie(movie)
|
||||
movie.start()
|
||||
gif_label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop)
|
||||
layout.addWidget(text_label)
|
||||
layout.addWidget(gif_label)
|
||||
layout.setContentsMargins(1, 1, 1, 1)
|
||||
self.progress_widget.setLayout(layout)
|
||||
self.add_widget_to_center_layout(self.progress_widget)
|
||||
|
||||
def show_error(self, error, show_retry_button=False):
|
||||
"""Displays error inside the dialog.
|
||||
|
||||
Args:
|
||||
error -- Error string
|
||||
show_retry_button -- Whether to display retry button or not
|
||||
"""
|
||||
|
||||
self.error_widget = QtGui.QWidget(self)
|
||||
self.error_widget.setObjectName("error_widget")
|
||||
layout = QtGui.QVBoxLayout(self.error_widget)
|
||||
error_label = QtGui.QLabel(error, self.error_widget)
|
||||
error_label.setWordWrap(True)
|
||||
error_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
error_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
|
||||
layout.addWidget(error_label)
|
||||
if show_retry_button:
|
||||
retry_widget = QtGui.QWidget(self.error_widget)
|
||||
retry_layout = QtGui.QHBoxLayout(retry_widget)
|
||||
retry_button = QtGui.QPushButton(_("Retry"), self.error_widget)
|
||||
retry_button.clicked.connect(self.retry)
|
||||
retry_button.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Fixed))
|
||||
retry_layout.addWidget(retry_button)
|
||||
retry_layout.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop)
|
||||
retry_widget.setLayout(retry_layout)
|
||||
layout.addWidget(retry_widget)
|
||||
self.error_widget.setLayout(layout)
|
||||
self.add_widget_to_center_layout(self.error_widget)
|
||||
|
||||
def show_table(self, column_headers):
|
||||
"""Displays results table inside the dialog."""
|
||||
|
||||
self.table = ResultTable(self.table_headers)
|
||||
self.table.setObjectName("results_table")
|
||||
self.table.cellDoubleClicked.connect(self.row_double_clicked)
|
||||
self.table.horizontalHeader().sectionResized.connect(
|
||||
self.save_table_header_state)
|
||||
self.restore_table_header_state()
|
||||
self.add_widget_to_center_layout(self.table)
|
||||
def enable_loading_button():
|
||||
self.load_button.setEnabled(True)
|
||||
self.table.itemSelectionChanged.connect(
|
||||
enable_loading_button)
|
||||
|
||||
def row_double_clicked(self, row):
|
||||
"""Handle function for double click event inside the table."""
|
||||
|
||||
self.load_selection(row)
|
||||
self.accept()
|
||||
|
||||
def network_error(self, reply, error):
|
||||
error_msg = _("<strong>Following error occurred while fetching results:<br><br></strong>"
|
||||
"Network request error for %s:<br>%s (QT code %d, HTTP code %s)<br>" % (
|
||||
reply.request().url().toString(QtCore.QUrl.RemoveUserInfo),
|
||||
reply.errorString(),
|
||||
error,
|
||||
repr(reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)))
|
||||
)
|
||||
self.show_error(error_msg, show_retry_button=True)
|
||||
|
||||
def no_results_found(self):
|
||||
error_msg = _("<strong>No results found. Please try a different search query.</strong>")
|
||||
self.show_error(error_msg)
|
||||
|
||||
def accept(self):
|
||||
if self.table:
|
||||
sel_rows = self.table.selectionModel().selectedRows()
|
||||
if sel_rows:
|
||||
sel_row = sel_rows[0].row()
|
||||
self.load_selection(sel_row)
|
||||
self.save_state()
|
||||
QtGui.QDialog.accept(self)
|
||||
|
||||
def reject(self):
|
||||
self.save_state()
|
||||
QtGui.QDialog.reject(self)
|
||||
|
||||
def restore_state(self):
|
||||
size = config.persist["searchdialog_window_size"]
|
||||
if size:
|
||||
self.resize(size)
|
||||
self.search_box.restore_checkbox_state()
|
||||
|
||||
def restore_table_header_state(self):
|
||||
header = self.table.horizontalHeader()
|
||||
state = config.persist["searchdialog_header_state"]
|
||||
if state:
|
||||
header.restoreState(state)
|
||||
header.setResizeMode(QtGui.QHeaderView.Interactive)
|
||||
|
||||
def save_state(self):
|
||||
"""Saves dialog state i.e. window size, checkbox state, and table
|
||||
header size.
|
||||
"""
|
||||
|
||||
if self.table:
|
||||
self.save_table_header_state()
|
||||
config.persist["searchdialog_window_size"] = self.size()
|
||||
self.search_box.save_checkbox_state()
|
||||
|
||||
def save_table_header_state(self):
|
||||
state = self.table.horizontalHeader().saveState()
|
||||
config.persist["searchdialog_header_state"] = state
|
||||
|
||||
|
||||
class TrackSearchDialog(SearchDialog):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(TrackSearchDialog, self).__init__(parent)
|
||||
self.file_ = None
|
||||
self.setWindowTitle(_("Track Search Results"))
|
||||
self.table_headers = [
|
||||
_("Name"),
|
||||
_("Length"),
|
||||
_("Artist"),
|
||||
_("Release"),
|
||||
_("Date"),
|
||||
_("Country"),
|
||||
_("Type")
|
||||
]
|
||||
|
||||
def search(self, text):
|
||||
"""Performs search using query provided by the user."""
|
||||
|
||||
self.retry_params = Retry(self.search, text)
|
||||
self.search_box.search_edit.setText(text)
|
||||
self.show_progress()
|
||||
self.tagger.xmlws.find_tracks(self.handle_reply,
|
||||
query=text,
|
||||
search=True,
|
||||
limit=25)
|
||||
|
||||
def load_similar_tracks(self, file_):
|
||||
"""Performs search by using existing metadata information
|
||||
from the file."""
|
||||
|
||||
self.retry_params = Retry(self.load_similar_tracks, file_)
|
||||
self.file_ = file_
|
||||
metadata = file_.orig_metadata
|
||||
query = {
|
||||
'track': metadata['title'],
|
||||
'artist': metadata['artist'],
|
||||
'release': metadata['album'],
|
||||
'tnum': metadata['tracknumber'],
|
||||
'tracks': metadata['totaltracks'],
|
||||
'qdur': str(metadata.length / 2000),
|
||||
'isrc': metadata['isrc'],
|
||||
}
|
||||
if config.setting["use_adv_search_syntax"]:
|
||||
# Display the query in advance syntax format.
|
||||
query_str = ' '.join(['%s:(%s)' % (item, escape_lucene_query(value))
|
||||
for item, value in query.iteritems() if value])
|
||||
else:
|
||||
# Display only the track title
|
||||
query_str = query["track"]
|
||||
# `query_str` is used only for presenting purpose. Actual query consists of all filters and follows
|
||||
# advanced query syntax.
|
||||
query["limit"] = 25
|
||||
self.search_box.search_edit.setText(query_str)
|
||||
self.show_progress()
|
||||
self.tagger.xmlws.find_tracks(
|
||||
self.handle_reply,
|
||||
**query)
|
||||
|
||||
def retry(self):
|
||||
"""Retries the search using information from `retry_params`."""
|
||||
self.retry_params.function(self.retry_params.query)
|
||||
|
||||
def handle_reply(self, document, http, error):
|
||||
if error:
|
||||
self.network_error(http, error)
|
||||
return
|
||||
|
||||
try:
|
||||
tracks = document.metadata[0].recording_list[0].recording
|
||||
except (AttributeError, IndexError):
|
||||
self.no_results_found()
|
||||
return
|
||||
|
||||
if self.file_:
|
||||
sorted_results = sorted(
|
||||
(self.file_.orig_metadata.compare_to_track(
|
||||
track,
|
||||
File.comparison_weights)
|
||||
for track in tracks),
|
||||
reverse=True,
|
||||
key=itemgetter(0))
|
||||
tracks = [item[3] for item in sorted_results]
|
||||
|
||||
del self.search_results[:] # Clear existing data
|
||||
self.parse_tracks_from_xml(tracks)
|
||||
self.display_results()
|
||||
|
||||
def display_results(self):
|
||||
self.show_table(self.table_headers)
|
||||
for row, obj in enumerate(self.search_results):
|
||||
track = obj[0]
|
||||
table_item = QtGui.QTableWidgetItem
|
||||
self.table.insertRow(row)
|
||||
self.table.setItem(row, 0, table_item(track.get("title", "")))
|
||||
self.table.setItem(row, 1, table_item(track.get("~length", "")))
|
||||
self.table.setItem(row, 2, table_item(track.get("artist", "")))
|
||||
self.table.setItem(row, 3, table_item(track.get("album", "")))
|
||||
self.table.setItem(row, 4, table_item(track.get("date", "")))
|
||||
self.table.setItem(row, 5, table_item(track.get("country", "")))
|
||||
self.table.setItem(row, 6, table_item(track.get("releasetype", "")))
|
||||
|
||||
def parse_tracks_from_xml(self, tracks_xml):
|
||||
"""Extracts track information from XmlNode objects and stores that into Metadata objects.
|
||||
|
||||
Args:
|
||||
tracks_xml -- list of XmlNode objects
|
||||
"""
|
||||
for node in tracks_xml:
|
||||
if "release_list" in node.children and "release" in node.release_list[0].children:
|
||||
for rel_node in node.release_list[0].release:
|
||||
track = Metadata()
|
||||
recording_to_metadata(node, track)
|
||||
release_to_metadata(rel_node, track)
|
||||
rg_node = rel_node.release_group[0]
|
||||
release_group_to_metadata(rg_node, track)
|
||||
if "release_event_list" in rel_node.children:
|
||||
# Extract contries list from `release_event_list` element
|
||||
# Don't use `country` element as it contains information of a single release
|
||||
# event and is basically for backward compatibility.
|
||||
country = []
|
||||
for re in rel_node.release_event_list[0].release_event:
|
||||
try:
|
||||
country.append(
|
||||
re.area[0].iso_3166_1_code_list[0].iso_3166_1_code[0].text)
|
||||
except AttributeError:
|
||||
pass
|
||||
track["country"] = ", ".join(country)
|
||||
self.search_results.append((track, node))
|
||||
else:
|
||||
# This handles the case when no release is associated with a track
|
||||
# i.e. the track is a NAT
|
||||
track = Metadata()
|
||||
recording_to_metadata(node, track)
|
||||
track["album"] = _("Standalone Recording")
|
||||
self.search_results.append((track, node))
|
||||
|
||||
def load_selection(self, row):
|
||||
"""Loads album corresponding to selected track.
|
||||
If the search is performed for a file, also associates the file to
|
||||
corresponding track in the album.
|
||||
"""
|
||||
track, node = self.search_results[row]
|
||||
if track.get("musicbrainz_albumid"):
|
||||
# The track is not an NAT
|
||||
self.tagger.get_release_group_by_id(track["musicbrainz_releasegroupid"]).loaded_albums.add(
|
||||
track["musicbrainz_albumid"])
|
||||
if self.file_:
|
||||
# Search is performed for a file
|
||||
# Have to move that file from its existing album to the new one
|
||||
if isinstance(self.file_.parent, Track):
|
||||
album = self.file_.parent.album
|
||||
self.tagger.move_file_to_track(self.file_, track["musicbrainz_albumid"], track["musicbrainz_recordingid"])
|
||||
if album._files == 0:
|
||||
# Remove album if it has no more files associated
|
||||
self.tagger.remove_album(album)
|
||||
else:
|
||||
self.tagger.move_file_to_track(self.file_, track["musicbrainz_albumid"], track["musicbrainz_recordingid"])
|
||||
else:
|
||||
# No files associated. Just a normal search.
|
||||
self.tagger.load_album(track["musicbrainz_albumid"])
|
||||
else:
|
||||
# The track is a NAT
|
||||
if self.file_:
|
||||
album = self.file_.parent.album
|
||||
self.tagger.move_file_to_nat(track["musicbrainz_recordingid"])
|
||||
if album._files == 0:
|
||||
self.tagger.remove_album(album)
|
||||
else:
|
||||
self.tagger.load_nat(track["musicbrainz_recordingid"], node)
|
||||
@@ -26,6 +26,9 @@ class Ui_InterfaceOptionsPage(object):
|
||||
self.toolbar_multiselect = QtGui.QCheckBox(self.groupBox_2)
|
||||
self.toolbar_multiselect.setObjectName(_fromUtf8("toolbar_multiselect"))
|
||||
self.vboxlayout1.addWidget(self.toolbar_multiselect)
|
||||
self.builtin_search = QtGui.QCheckBox(self.groupBox_2)
|
||||
self.builtin_search.setObjectName("builtin_search")
|
||||
self.vboxlayout1.addWidget(self.builtin_search)
|
||||
self.use_adv_search_syntax = QtGui.QCheckBox(self.groupBox_2)
|
||||
self.use_adv_search_syntax.setObjectName(_fromUtf8("use_adv_search_syntax"))
|
||||
self.vboxlayout1.addWidget(self.use_adv_search_syntax)
|
||||
@@ -69,6 +72,7 @@ class Ui_InterfaceOptionsPage(object):
|
||||
self.groupBox_2.setTitle(_("Miscellaneous"))
|
||||
self.toolbar_show_labels.setText(_("Show text labels under icons"))
|
||||
self.toolbar_multiselect.setText(_("Allow selection of multiple directories"))
|
||||
self.builtin_search.setText(_("Use builtin search rather than looking in browser"))
|
||||
self.use_adv_search_syntax.setText(_("Use advanced query syntax"))
|
||||
self.quit_confirmation.setText(_("Show a quit confirmation dialog for unsaved changes"))
|
||||
self.starting_directory.setText(_("Begin browsing in the following directory:"))
|
||||
|
||||
@@ -63,7 +63,7 @@ CLIENT_STRING = str(QUrl.toPercentEncoding('%s %s-%s' % (PICARD_ORG_NAME,
|
||||
PICARD_VERSION_STR)))
|
||||
|
||||
|
||||
def _escape_lucene_query(text):
|
||||
def escape_lucene_query(text):
|
||||
return re.sub(r'([+\-&|!(){}\[\]\^"~*?:\\/])', r'\\\1', text)
|
||||
|
||||
|
||||
@@ -458,16 +458,28 @@ class XmlWebService(QtCore.QObject):
|
||||
host = config.setting["server_host"]
|
||||
port = config.setting["server_port"]
|
||||
filters = []
|
||||
query = []
|
||||
for name, value in kwargs.items():
|
||||
if name == 'limit':
|
||||
filters.append((name, str(value)))
|
||||
|
||||
limit = kwargs.pop("limit")
|
||||
if limit:
|
||||
filters.append(("limit", limit))
|
||||
|
||||
is_search = kwargs.pop("search", False)
|
||||
if is_search:
|
||||
if config.setting["use_adv_search_syntax"]:
|
||||
query = kwargs["query"]
|
||||
else:
|
||||
value = _escape_lucene_query(value).strip().lower()
|
||||
query = escape_lucene_query(kwargs["query"]).strip().lower()
|
||||
filters.append(("dismax", 'true'))
|
||||
else:
|
||||
query = []
|
||||
for name, value in kwargs.items():
|
||||
value = escape_lucene_query(value).strip().lower()
|
||||
if value:
|
||||
query.append('%s:(%s)' % (name, value))
|
||||
query = ' '.join(query)
|
||||
|
||||
if query:
|
||||
filters.append(('query', ' '.join(query)))
|
||||
filters.append(("query", query))
|
||||
queryargs = {}
|
||||
for name, value in filters:
|
||||
value = QUrl.toPercentEncoding(unicode(value))
|
||||
|
||||
BIN
resources/images/loader.gif
Normal file
BIN
resources/images/loader.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 673 B |
@@ -21,14 +21,15 @@ def natsort_key(s):
|
||||
return [ tryint(c) for c in re.split('(\d+)', s) ]
|
||||
|
||||
|
||||
def find_files(topdir, directory, pattern):
|
||||
def find_files(topdir, directory, patterns):
|
||||
tdir = os.path.join(topdir, directory)
|
||||
for root, dirs, files in os.walk(tdir):
|
||||
for basename in files:
|
||||
if fnmatch.fnmatch(basename, pattern):
|
||||
filepath = os.path.join(root, basename)
|
||||
filename = os.path.relpath(filepath, topdir)
|
||||
yield filename
|
||||
for pattern in patterns:
|
||||
if fnmatch.fnmatch(basename, pattern):
|
||||
filepath = os.path.join(root, basename)
|
||||
filename = os.path.relpath(filepath, topdir)
|
||||
yield filename
|
||||
|
||||
|
||||
def main():
|
||||
@@ -36,7 +37,7 @@ def main():
|
||||
topdir = os.path.abspath(os.path.join(scriptdir, ".."))
|
||||
resourcesdir = os.path.join(topdir, "resources")
|
||||
qrcfile = os.path.join(resourcesdir, "picard.qrc")
|
||||
images = [i for i in find_files(resourcesdir, 'images', '*.png')]
|
||||
images = [i for i in find_files(resourcesdir, 'images', ['*.gif', '*.png'])]
|
||||
newimages = 0
|
||||
for filename in images:
|
||||
filepath = os.path.join(resourcesdir, filename)
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
<file>images/arrow.png</file>
|
||||
<file>images/file-pending.png</file>
|
||||
<file>images/file.png</file>
|
||||
<file>images/loader.gif</file>
|
||||
<file>images/match-50.png</file>
|
||||
<file>images/match-60.png</file>
|
||||
<file>images/match-70.png</file>
|
||||
|
||||
Reference in New Issue
Block a user