mirror of
https://github.com/fergalmoran/picard.git
synced 2026-01-03 15:13:57 +00:00
Improved Last.fm plugin.
This commit is contained in:
@@ -1,105 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
PLUGIN_NAME = u'Last.fm'
|
||||
PLUGIN_AUTHOR = u'Lukáš Lalinsky'
|
||||
PLUGIN_DESCRIPTION = u''
|
||||
|
||||
from musicbrainz2.model import VARIOUS_ARTISTS_ID
|
||||
from picard.metadata import register_album_metadata_processor, register_track_metadata_processor
|
||||
from xml.dom.minidom import parse
|
||||
import re
|
||||
import urllib
|
||||
|
||||
# TODO: move this to an options page
|
||||
MIN_TAG_COUNT = 15
|
||||
JOIN_TAGS = None # use e.g. "/" to produce only one "genre" tag with "Tag1/Tag2"
|
||||
IGNORE_TAGS = ["seen live"]
|
||||
USE_TRACK_TAGS = True
|
||||
USE_ARTIST_TAGS = True
|
||||
USE_ARTIST_IMAGES = True
|
||||
|
||||
def get_text(root):
|
||||
text = []
|
||||
for node in root.childNodes:
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
text.append(node.data)
|
||||
return "".join(text)
|
||||
|
||||
def get_tags(ws, url):
|
||||
"""Get tags from an URL."""
|
||||
try:
|
||||
stream = ws.get_from_url(url)
|
||||
except IOError:
|
||||
return []
|
||||
dom = parse(stream)
|
||||
tags = []
|
||||
for tag in dom.getElementsByTagName("toptags")[0].getElementsByTagName("tag"):
|
||||
name = get_text(tag.getElementsByTagName("name")[0]).strip()
|
||||
count = int(get_text(tag.getElementsByTagName("count")[0]).strip())
|
||||
if count < MIN_TAG_COUNT:
|
||||
break
|
||||
tags.append(name)
|
||||
stream.close()
|
||||
return filter(lambda t: t not in IGNORE_TAGS, tags)
|
||||
|
||||
def get_track_tags(ws, artist, track):
|
||||
"""Get track top tags."""
|
||||
url = "http://ws.audioscrobbler.com/1.0/track/%s/%s/toptags.xml" % (urllib.quote(artist, ""), urllib.quote(track, ""))
|
||||
return get_tags(ws, url)
|
||||
|
||||
def get_artist_tags(ws, artist):
|
||||
"""Get artist top tags."""
|
||||
url = "http://ws.audioscrobbler.com/1.0/artist/%s/toptags.xml" % (urllib.quote(artist, ""))
|
||||
return get_tags(ws, url)
|
||||
|
||||
def get_artist_image(ws, artist):
|
||||
"""Get the main artist image."""
|
||||
url = "http://ws.audioscrobbler.com/ass/artistmetadata.php?%s" % (urllib.urlencode({"artist": artist}))
|
||||
try:
|
||||
stream = ws.get_from_url(url)
|
||||
except IOError:
|
||||
return None
|
||||
res = stream.read()
|
||||
stream.close()
|
||||
res = res.split("\t")
|
||||
if len(res) != 4:
|
||||
return None
|
||||
image_url = res[3][1:-1]
|
||||
if not image_url.startswith("http://static.last.fm"):
|
||||
return None
|
||||
stream = ws.get_from_url(image_url)
|
||||
data = stream.read()
|
||||
stream.close()
|
||||
return data
|
||||
|
||||
def process_album(tagger, metadata, release):
|
||||
if USE_ARTIST_IMAGES and release.artist.id != VARIOUS_ARTISTS_ID:
|
||||
artist = metadata["artist"].encode("utf-8")
|
||||
if artist:
|
||||
ws = tagger.get_web_service()
|
||||
data = get_artist_image(ws, artist)
|
||||
if data:
|
||||
metadata.add("~artwork", ["image/jpeg", data])
|
||||
|
||||
def process_track(tagger, metadata, release, track):
|
||||
if USE_TRACK_TAGS or USE_ARTIST_TAGS:
|
||||
ws = tagger.get_web_service()
|
||||
artist = metadata["artist"].encode("utf-8")
|
||||
title = metadata["title"].encode("utf-8")
|
||||
tags = []
|
||||
if artist:
|
||||
if USE_TRACK_TAGS:
|
||||
tags = get_artist_tags(ws, artist)
|
||||
# No tags for artist? Why trying track tags...
|
||||
if not tags:
|
||||
return
|
||||
if title and USE_ARTIST_TAGS:
|
||||
tags.extend(get_track_tags(ws, artist, title))
|
||||
tags = list(set(tags))
|
||||
if tags:
|
||||
if JOIN_TAGS:
|
||||
tags = JOIN_TAGS.join(tags)
|
||||
metadata["genre"] = tags
|
||||
|
||||
register_track_metadata_processor(process_track)
|
||||
register_album_metadata_processor(process_album)
|
||||
159
contrib/plugins/lastfm/__init__.py
Normal file
159
contrib/plugins/lastfm/__init__.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
PLUGIN_NAME = u'Last.fm'
|
||||
PLUGIN_AUTHOR = u'Lukáš Lalinsky'
|
||||
PLUGIN_DESCRIPTION = u''
|
||||
|
||||
import re
|
||||
import urllib
|
||||
from PyQt4 import QtGui
|
||||
from musicbrainz2.model import VARIOUS_ARTISTS_ID
|
||||
from picard.metadata import register_album_metadata_processor, register_track_metadata_processor
|
||||
from picard.ui.options import register_options_page, OptionsPage
|
||||
from xml.dom.minidom import parse
|
||||
from picard.config import BoolOption, IntOption, TextOption
|
||||
from picard.plugins.lastfm.ui_options_lastfm import Ui_LastfmOptionsPage
|
||||
|
||||
# TODO: move this to an options page
|
||||
JOIN_TAGS = None # use e.g. "/" to produce only one "genre" tag with "Tag1/Tag2"
|
||||
TRANSLATE_TAGS = {
|
||||
"hip hop": "hip-hop",
|
||||
"synth-pop": "synthpop",
|
||||
"electronica": "electronic",
|
||||
}
|
||||
TITLE_CASE = True
|
||||
|
||||
|
||||
def get_text(root):
|
||||
text = []
|
||||
for node in root.childNodes:
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
text.append(node.data)
|
||||
return "".join(text)
|
||||
|
||||
|
||||
def get_tags(ws, url, min_usage, ignore):
|
||||
"""Get tags from an URL."""
|
||||
try:
|
||||
stream = ws.get_from_url(url)
|
||||
except IOError:
|
||||
return []
|
||||
dom = parse(stream)
|
||||
tags = []
|
||||
for tag in dom.getElementsByTagName("toptags")[0].getElementsByTagName("tag"):
|
||||
name = get_text(tag.getElementsByTagName("name")[0]).strip()
|
||||
count = int(get_text(tag.getElementsByTagName("count")[0]).strip())
|
||||
if count < min_usage:
|
||||
break
|
||||
try: name = TRANSLATE_TAGS[name]
|
||||
except KeyError: pass
|
||||
tags.append(name.title())
|
||||
stream.close()
|
||||
return filter(lambda t: t not in ignore, tags)
|
||||
|
||||
|
||||
def get_track_tags(ws, artist, track, min_usage, ignore):
|
||||
"""Get track top tags."""
|
||||
url = "http://ws.audioscrobbler.com/1.0/track/%s/%s/toptags.xml" % (urllib.quote(artist, ""), urllib.quote(track, ""))
|
||||
return get_tags(ws, url, min_usage, ignore)
|
||||
|
||||
|
||||
def get_artist_tags(ws, artist, min_usage, ignore):
|
||||
"""Get artist top tags."""
|
||||
url = "http://ws.audioscrobbler.com/1.0/artist/%s/toptags.xml" % (urllib.quote(artist, ""))
|
||||
return get_tags(ws, url, min_usage, ignore)
|
||||
|
||||
|
||||
def get_artist_image(ws, artist):
|
||||
"""Get the main artist image."""
|
||||
url = "http://ws.audioscrobbler.com/ass/artistmetadata.php?%s" % (urllib.urlencode({"artist": artist}))
|
||||
try:
|
||||
stream = ws.get_from_url(url)
|
||||
except IOError:
|
||||
return None
|
||||
res = stream.read()
|
||||
stream.close()
|
||||
res = res.split("\t")
|
||||
if len(res) != 4:
|
||||
return None
|
||||
image_url = res[3][1:-1]
|
||||
if not image_url.startswith("http://static.last.fm"):
|
||||
return None
|
||||
stream = ws.get_from_url(image_url)
|
||||
data = stream.read()
|
||||
stream.close()
|
||||
return data
|
||||
|
||||
|
||||
def process_album(tagger, metadata, release):
|
||||
if tagger.config.setting["lastfm_use_artist_images"] and release.artist.id != VARIOUS_ARTISTS_ID:
|
||||
artist = metadata["artist"].encode("utf-8")
|
||||
if artist:
|
||||
ws = tagger.get_web_service()
|
||||
data = get_artist_image(ws, artist)
|
||||
if data:
|
||||
metadata.add("~artwork", ["image/jpeg", data])
|
||||
|
||||
|
||||
def process_track(tagger, metadata, release, track):
|
||||
use_track_tags = tagger.config.setting["lastfm_use_track_tags"]
|
||||
use_artist_tags = tagger.config.setting["lastfm_use_artist_tags"]
|
||||
min_tag_usage = tagger.config.setting["lastfm_min_tag_usage"]
|
||||
ignore_tags = tagger.config.setting["lastfm_ignore_tags"].split(",")
|
||||
if use_track_tags or use_artist_tags:
|
||||
ws = tagger.get_web_service()
|
||||
artist = metadata["artist"].encode("utf-8")
|
||||
title = metadata["title"].encode("utf-8")
|
||||
tags = []
|
||||
if artist:
|
||||
if use_track_tags:
|
||||
tags = get_artist_tags(ws, artist, min_tag_usage, ignore_tags)
|
||||
# No tags for artist? Why trying track tags...
|
||||
if not tags:
|
||||
return
|
||||
if title and use_artist_tags:
|
||||
tags.extend(get_track_tags(ws, artist, title, min_tag_usage, ignore_tags))
|
||||
tags = list(set(tags))
|
||||
if tags:
|
||||
if JOIN_TAGS:
|
||||
tags = JOIN_TAGS.join(tags)
|
||||
metadata["genre"] = tags
|
||||
|
||||
|
||||
class LastfmOptionsPage(OptionsPage):
|
||||
|
||||
NAME = "lastfm"
|
||||
TITLE = "Last.fm"
|
||||
PARENT = "plugins"
|
||||
|
||||
options = [
|
||||
BoolOption("setting", "lastfm_use_track_tags", False),
|
||||
BoolOption("setting", "lastfm_use_artist_tags", False),
|
||||
BoolOption("setting", "lastfm_use_artist_images", False),
|
||||
IntOption("setting", "lastfm_min_tag_usage", 15),
|
||||
TextOption("setting", "lastfm_ignore_tags", "seen live"),
|
||||
]
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(LastfmOptionsPage, self).__init__(parent)
|
||||
self.ui = Ui_LastfmOptionsPage()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
def load(self):
|
||||
self.ui.use_track_tags.setChecked(self.config.setting["lastfm_use_track_tags"])
|
||||
self.ui.use_artist_tags.setChecked(self.config.setting["lastfm_use_artist_tags"])
|
||||
self.ui.use_artist_images.setChecked(self.config.setting["lastfm_use_artist_images"])
|
||||
self.ui.min_tag_usage.setValue(self.config.setting["lastfm_min_tag_usage"])
|
||||
self.ui.ignore_tags.setText(self.config.setting["lastfm_ignore_tags"])
|
||||
|
||||
def save(self):
|
||||
self.config.setting["lastfm_use_track_tags"] = self.ui.use_track_tags.isChecked()
|
||||
self.config.setting["lastfm_use_artist_tags"] = self.ui.use_artist_tags.isChecked()
|
||||
self.config.setting["lastfm_use_artist_images"] = self.ui.use_artist_images.isChecked()
|
||||
self.config.setting["lastfm_min_tag_usage"] = self.ui.min_tag_usage.value()
|
||||
self.config.setting["lastfm_ignore_tags"] = unicode(self.ui.ignore_tags.text())
|
||||
|
||||
|
||||
register_track_metadata_processor(process_track)
|
||||
register_album_metadata_processor(process_album)
|
||||
register_options_page(LastfmOptionsPage)
|
||||
139
contrib/plugins/lastfm/options_lastfm.ui
Normal file
139
contrib/plugins/lastfm/options_lastfm.ui
Normal file
@@ -0,0 +1,139 @@
|
||||
<ui version="4.0" >
|
||||
<class>LastfmOptionsPage</class>
|
||||
<widget class="QWidget" name="LastfmOptionsPage" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>281</width>
|
||||
<height>305</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="rename_files" >
|
||||
<property name="title" >
|
||||
<string>Last.fm</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_track_tags" >
|
||||
<property name="text" >
|
||||
<string>Use track tags</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_artist_tags" >
|
||||
<property name="text" >
|
||||
<string>Use artist tags</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_artist_images" >
|
||||
<property name="text" >
|
||||
<string>Use artist images</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="rename_files_2" >
|
||||
<property name="title" >
|
||||
<string>Tags</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="ignore_tags_2" >
|
||||
<property name="text" >
|
||||
<string>Ignore tags:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="ignore_tags" />
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy>
|
||||
<hsizetype>7</hsizetype>
|
||||
<vsizetype>5</vsizetype>
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Minimal tag usage:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>min_tag_usage</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="min_tag_usage" >
|
||||
<property name="suffix" >
|
||||
<string> %</string>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>121</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>use_track_tags</tabstop>
|
||||
<tabstop>ignore_tags</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
99
contrib/plugins/lastfm/ui_options_lastfm.py
Normal file
99
contrib/plugins/lastfm/ui_options_lastfm.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'ui/options_lastfm.ui'
|
||||
#
|
||||
# Created: Sun Jan 28 13:39:17 2007
|
||||
# by: PyQt4 UI code generator 4.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
class Ui_LastfmOptionsPage(object):
|
||||
def setupUi(self, LastfmOptionsPage):
|
||||
LastfmOptionsPage.setObjectName("LastfmOptionsPage")
|
||||
LastfmOptionsPage.resize(QtCore.QSize(QtCore.QRect(0,0,281,305).size()).expandedTo(LastfmOptionsPage.minimumSizeHint()))
|
||||
|
||||
self.vboxlayout = QtGui.QVBoxLayout(LastfmOptionsPage)
|
||||
self.vboxlayout.setMargin(9)
|
||||
self.vboxlayout.setSpacing(6)
|
||||
self.vboxlayout.setObjectName("vboxlayout")
|
||||
|
||||
self.rename_files = QtGui.QGroupBox(LastfmOptionsPage)
|
||||
self.rename_files.setObjectName("rename_files")
|
||||
|
||||
self.vboxlayout1 = QtGui.QVBoxLayout(self.rename_files)
|
||||
self.vboxlayout1.setMargin(9)
|
||||
self.vboxlayout1.setSpacing(2)
|
||||
self.vboxlayout1.setObjectName("vboxlayout1")
|
||||
|
||||
self.use_track_tags = QtGui.QCheckBox(self.rename_files)
|
||||
self.use_track_tags.setObjectName("use_track_tags")
|
||||
self.vboxlayout1.addWidget(self.use_track_tags)
|
||||
|
||||
self.use_artist_tags = QtGui.QCheckBox(self.rename_files)
|
||||
self.use_artist_tags.setObjectName("use_artist_tags")
|
||||
self.vboxlayout1.addWidget(self.use_artist_tags)
|
||||
|
||||
self.use_artist_images = QtGui.QCheckBox(self.rename_files)
|
||||
self.use_artist_images.setObjectName("use_artist_images")
|
||||
self.vboxlayout1.addWidget(self.use_artist_images)
|
||||
self.vboxlayout.addWidget(self.rename_files)
|
||||
|
||||
self.rename_files_2 = QtGui.QGroupBox(LastfmOptionsPage)
|
||||
self.rename_files_2.setObjectName("rename_files_2")
|
||||
|
||||
self.vboxlayout2 = QtGui.QVBoxLayout(self.rename_files_2)
|
||||
self.vboxlayout2.setMargin(9)
|
||||
self.vboxlayout2.setSpacing(2)
|
||||
self.vboxlayout2.setObjectName("vboxlayout2")
|
||||
|
||||
self.ignore_tags_2 = QtGui.QLabel(self.rename_files_2)
|
||||
self.ignore_tags_2.setObjectName("ignore_tags_2")
|
||||
self.vboxlayout2.addWidget(self.ignore_tags_2)
|
||||
|
||||
self.ignore_tags = QtGui.QLineEdit(self.rename_files_2)
|
||||
self.ignore_tags.setObjectName("ignore_tags")
|
||||
self.vboxlayout2.addWidget(self.ignore_tags)
|
||||
|
||||
self.hboxlayout = QtGui.QHBoxLayout()
|
||||
self.hboxlayout.setMargin(0)
|
||||
self.hboxlayout.setSpacing(6)
|
||||
self.hboxlayout.setObjectName("hboxlayout")
|
||||
|
||||
self.label_4 = QtGui.QLabel(self.rename_files_2)
|
||||
|
||||
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(7),QtGui.QSizePolicy.Policy(5))
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth())
|
||||
self.label_4.setSizePolicy(sizePolicy)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.hboxlayout.addWidget(self.label_4)
|
||||
|
||||
self.min_tag_usage = QtGui.QSpinBox(self.rename_files_2)
|
||||
self.min_tag_usage.setMaximum(100)
|
||||
self.min_tag_usage.setObjectName("min_tag_usage")
|
||||
self.hboxlayout.addWidget(self.min_tag_usage)
|
||||
self.vboxlayout2.addLayout(self.hboxlayout)
|
||||
self.vboxlayout.addWidget(self.rename_files_2)
|
||||
|
||||
spacerItem = QtGui.QSpacerItem(121,20,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding)
|
||||
self.vboxlayout.addItem(spacerItem)
|
||||
self.label_4.setBuddy(self.min_tag_usage)
|
||||
|
||||
self.retranslateUi(LastfmOptionsPage)
|
||||
QtCore.QMetaObject.connectSlotsByName(LastfmOptionsPage)
|
||||
LastfmOptionsPage.setTabOrder(self.use_track_tags,self.ignore_tags)
|
||||
|
||||
def retranslateUi(self, LastfmOptionsPage):
|
||||
self.rename_files.setTitle(_(u"Last.fm"))
|
||||
self.use_track_tags.setText(_(u"Use track tags"))
|
||||
self.use_artist_tags.setText(_(u"Use artist tags"))
|
||||
self.use_artist_images.setText(_(u"Use artist images"))
|
||||
self.rename_files_2.setTitle(_(u"Tags"))
|
||||
self.ignore_tags_2.setText(_(u"Ignore tags:"))
|
||||
self.label_4.setText(_(u"Minimal tag usage:"))
|
||||
self.min_tag_usage.setSuffix(_(u" %"))
|
||||
|
||||
Reference in New Issue
Block a user