diff --git a/picard/album.py b/picard/album.py index 4233be4d2..124d6c9f7 100644 --- a/picard/album.py +++ b/picard/album.py @@ -28,6 +28,7 @@ from picard.metadata import Metadata from picard.dataobj import DataObject from picard.track import Track from picard.artist import Artist +from picard.util import translate_artist class AlbumLoadError(Exception): pass @@ -81,6 +82,8 @@ class Album(DataObject): except WebServiceError, e: self.hasLoadError = True raise AlbumLoadError, e + + translate = self.config.setting["translate_artist_names"] self.lock() @@ -88,8 +91,11 @@ class Album(DataObject): self.metadata["album"] = release.title self.metadata["artist"] = release.artist.name self.metadata["artist_sortname"] = release.artist.sortName - self.metadata["albumartist"] = release.artist.name - self.metadata["albumartist_sortname"] = release.artist.sortName + if translate: + self.metadata["artist"] = translate_artist( + self.metadata["artist"], self.metadata["artist_sortname"]) + self.metadata["albumartist"] = self.metadata["artist"] + self.metadata["albumartist_sortname"] = self.metadata["artist_sortname"] self.metadata["musicbrainz_albumid"] = extractUuid(release.id) self.metadata["musicbrainz_artistid"] = extractUuid(release.artist.id) self.metadata["musicbrainz_albumartistid"] = \ @@ -149,27 +155,33 @@ class Album(DataObject): script = None self.name = release.title - self.artist = Artist(release.artist.id, release.artist.name) + self.artist = Artist(self.metadata["musicbrainz_artistid"], + self.metadata["artist"]) self.duration = 0 self.tracks = [] tracknum = 1 for track in release.tracks: if track.artist: - artist = Artist(extractUuid(track.artist.id), - track.artist.name) + artist_id = extractUuid(track.artist.id) + artist_name = track.artist.name + artist_sortname = track.artist.sortName + if translate: + artist_name = translate_artist(artist_name, artist_sortname) else: - artist = Artist(extractUuid(release.artist.id), - release.artist.name) - tr = Track(extractUuid(track.id), track.title, artist, self) + artist_id = self.metadata["musicbrainz_artistid"] + artist_name = self.metadata["artist"] + artist_sortname = self.metadata["artist_sortname"] + print artist_name + tr = Track(extractUuid(track.id), track.title, + Artist(artist_id, artist_name), self) tr.duration = track.duration or 0 tr.metadata.copy(self.metadata) tr.metadata["title"] = track.title - if track.artist: - tr.metadata["artist"] = artist.name - tr.metadata["artist_sortname"] = track.artist.sortName - tr.metadata["musicbrainz_artistid"] = extractUuid(artist.id) - tr.metadata["musicbrainz_trackid"] = extractUuid(track.id) + tr.metadata["artist"] = artist_name + tr.metadata["artist_sortname"] = artist_sortname + tr.metadata["musicbrainz_artistid"] = artist_id + tr.metadata["musicbrainz_trackid"] = tr.id tr.metadata["tracknumber"] = str(tracknum) tr.metadata["~#length"] = tr.duration # Metadata processor plugins diff --git a/picard/ui/options/__init__.py b/picard/ui/options/__init__.py index 7e07d557e..42951a54c 100644 --- a/picard/ui/options/__init__.py +++ b/picard/ui/options/__init__.py @@ -26,6 +26,7 @@ from picard.ui.options import ( advanced, cdlookup, general, + metadata, naming, proxy, scripting, diff --git a/picard/ui/options/metadata.py b/picard/ui/options/metadata.py new file mode 100644 index 000000000..39f712b4c --- /dev/null +++ b/picard/ui/options/metadata.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2006 Lukáš Lalinský +# +# 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 QtCore, QtGui +from picard.api import IOptionsPage +from picard.component import Component, implements +from picard.config import BoolOption, TextOption + +class MetadataOptionsPage(Component): + + implements(IOptionsPage) + + options = [ + BoolOption("setting", "translate_artist_names", False), + ] + + def get_page_info(self): + return _("Metadata"), "metadata", None, 20 + + def get_page_widget(self, parent=None): + self.widget = QtGui.QWidget(parent) + from picard.ui.ui_options_metadata import Ui_Form + self.ui = Ui_Form() + self.ui.setupUi(self.widget) + self.ui.translate_artist_names.setChecked( + self.config.setting["translate_artist_names"]) + return self.widget + + def save_options(self): + self.config.setting["translate_artist_names"] = \ + self.ui.translate_artist_names.isChecked() diff --git a/picard/ui/ui_options_metadata.py b/picard/ui/ui_options_metadata.py new file mode 100644 index 000000000..b30115f96 --- /dev/null +++ b/picard/ui/ui_options_metadata.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui\options_metadata.ui' +# +# Created: Sun Oct 15 13:45:25 2006 +# by: PyQt4 UI code generator 4.0 +# E:\projects\picard-qt\setup.py build_ui +# +# WARNING! All changes made in this file will be lost! + +import sys +from PyQt4 import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(QtCore.QSize(QtCore.QRect(0,0,350,300).size()).expandedTo(Form.minimumSizeHint())) + + self.vboxlayout = QtGui.QVBoxLayout(Form) + self.vboxlayout.setMargin(9) + self.vboxlayout.setSpacing(2) + self.vboxlayout.setObjectName("vboxlayout") + + self.rename_files = QtGui.QGroupBox(Form) + self.rename_files.setObjectName("rename_files") + + self.gridlayout = QtGui.QGridLayout(self.rename_files) + self.gridlayout.setMargin(9) + self.gridlayout.setSpacing(2) + self.gridlayout.setObjectName("gridlayout") + + self.translate_artist_names = QtGui.QCheckBox(self.rename_files) + self.translate_artist_names.setObjectName("translate_artist_names") + self.gridlayout.addWidget(self.translate_artist_names,0,0,1,1) + self.vboxlayout.addWidget(self.rename_files) + + spacerItem = QtGui.QSpacerItem(61,201,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding) + self.vboxlayout.addItem(spacerItem) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + self.rename_files.setTitle(_("Metadata")) + self.translate_artist_names.setText(_("Translate foreign artist names to English where possible")) diff --git a/picard/util/__init__.py b/picard/util/__init__.py index b790e2998..c1fd38e2c 100644 --- a/picard/util/__init__.py +++ b/picard/util/__init__.py @@ -175,3 +175,21 @@ def make_short_filename(prefix, filename, length=250, max_length=250, parts.reverse() return os.path.join(*parts) +def reverse_sortname(sortname): + chunks = map(unicode.strip, sortname.split(u",")) + if len(chunks) == 2: + return u"%s %s" % (chunks[1], chunks[0]) + elif len(chunks) == 3: + return u"%s %s %s" % (chunks[2], chunks[1], chunks[0]) + elif len(chunks) == 4: + return u"%s %s, %s %s" % (chunks[1], chunks[0], chunks[3], chunks[2]) + else: + return sortname.strip() + +def translate_artist(name, sortname): + for c in name: + ctg = unicodedata.category(c) + if (ctg[0] not in ("P", "Z") and ctg != "Nd" and + unicodedata.name(c).find("LATIN") == -1): + return " & ".join(map(reverse_sortname, sortname.split("&"))) + return name diff --git a/test/test_utils.py b/test/test_utils.py index ded89c94f..819b967a0 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -73,3 +73,24 @@ class ShortFilenameTest(unittest.TestCase): def test_too_long(self): self.failUnlessRaises(IOError, util.make_short_filename, "/home/me/", os.path.join("a1234567890", "b1234567890"), 10) + +class TranslateArtistTest(unittest.TestCase): + + def test_latin(self): + self.failUnlessEqual(u"Jean Michel Jarre", util.translate_artist(u"Jean Michel Jarre", u"Jarre, Jean Michel")) + self.failIfEqual(u"Jarre, Jean Michel", util.translate_artist(u"Jean Michel Jarre", u"Jarre, Jean Michel")) + + def test_kanji(self): + self.failUnlessEqual(u"Tetsuya Komuro", util.translate_artist(u"小室哲哉", u"Komuro, Tetsuya")) + self.failIfEqual(u"Komuro, Tetsuya", util.translate_artist(u"小室哲哉", u"Komuro, Tetsuya")) + self.failIfEqual(u"小室哲哉", util.translate_artist(u"小室哲哉", u"Komuro, Tetsuya")) + + def test_kanji2(self): + self.failUnlessEqual(u"Ayumi Hamasaki & Keiko", util.translate_artist(u"浜崎あゆみ & KEIKO", u"Hamasaki, Ayumi & Keiko")) + self.failIfEqual(u"浜崎あゆみ & KEIKO", util.translate_artist(u"浜崎あゆみ & KEIKO", u"Hamasaki, Ayumi & Keiko")) + self.failIfEqual(u"Hamasaki, Ayumi & Keiko", util.translate_artist(u"浜崎あゆみ & KEIKO", u"Hamasaki, Ayumi & Keiko")) + + def test_cyrillic(self): + self.failUnlessEqual(u"Pyotr Ilyich Tchaikovsky", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich")) + self.failIfEqual(u"Tchaikovsky, Pyotr Ilyich", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich")) + self.failIfEqual(u"Пётр Ильич Чайковский", util.translate_artist(u"Пётр Ильич Чайковский", u"Tchaikovsky, Pyotr Ilyich")) diff --git a/ui/options_metadata.ui b/ui/options_metadata.ui new file mode 100644 index 000000000..9912e47d0 --- /dev/null +++ b/ui/options_metadata.ui @@ -0,0 +1,65 @@ + + + + + Form + + + + 0 + 0 + 350 + 300 + + + + + 9 + + + 2 + + + + + Metadata + + + + 9 + + + 2 + + + + + Translate foreign artist names to English where possible + + + + + + + + + + Qt::Vertical + + + + 61 + 201 + + + + + + + + + translate_artist_names + + + +