diff --git a/.tx/config b/.tx/config index ea944391c..9ada80424 100644 --- a/.tx/config +++ b/.tx/config @@ -1,8 +1,8 @@ [main] -host = https://www.transifex.net +host = https://www.transifex.com [musicbrainz.picard] file_filter = po/.po source_file = po/picard.pot source_lang = en - +type = PO diff --git a/NEWS.txt b/NEWS.txt index 449f841f7..db44c9f11 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -16,6 +16,8 @@ * Main window is now emitting a "selection_updated" signal, plugin api version bumps to 1.3.0 * Append system information to user-agent string * Compilation tag/variable aligned with iTunes, set only for Various Artists type compilations. + * Ignore directories and files while indexing when show_hidden_files option is set to False (PICARD-528) + * Add ignore_regex option which allows one to ignore matching paths, can be set in Options > Advanced (PICARD-528) * Added an "artists" tag to track metadata, based on the one in Jaikoz, which contains the individual artist names from the artist credit. diff --git a/README.md b/README.md new file mode 100644 index 000000000..006cb9c79 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +MusicBrainz Picard +================== + +[MusicBrainz Picard](http://musicbrainz.org/doc/MusicBrainz_Picard) is a cross-platform (Linux/Mac OS X/Windows) application written in Python and is the official [MusicBrainz](http://musicbrainz.org) tagger. + +Picard supports the majority of audio file formats, is capable of using audio fingerprints ([AcoustIDs](http://musicbrainz.org/doc/AcoustID)), performing CD lookups and [disc ID](http://musicbrainz.org/doc/Disc_ID) submissions, and it has excellent Unicode support. Additionally, there are several plugins available that extend Picard's features. + +When tagging files, Picard uses an album-oriented approach. This approach allows it to utilize the MusicBrainz data as effectively as possible and correctly tag your music. For more information, [see the illustrated quick start guide to tagging](http://musicbrainz.org/doc/How_To_Tag_Files_With_Picard). + +Picard is named after Captain Jean-Luc Picard from the TV series Star Trek: The Next Generation. + +Binary downloads are available [here](http://musicbrainz.org/doc/MusicBrainz_Picard). + +To submit bugs or improvements, please use [Picard bug tracker](http://tickets.musicbrainz.org/browse/PICARD). diff --git a/picard/__init__.py b/picard/__init__.py index 22abb9e92..d1d6cb897 100644 --- a/picard/__init__.py +++ b/picard/__init__.py @@ -25,22 +25,42 @@ PICARD_ORG_NAME = "MusicBrainz" PICARD_VERSION = (1, 3, 0, 'dev', 2) -def version_to_string(version_tuple, short=False): - assert len(version_tuple) == 5 - assert version_tuple[3] in ('final', 'dev') - if short and version_tuple[3] == 'final': - if version_tuple[2] == 0: - version_str = '%d.%d' % version_tuple[:2] +class VersionError(Exception): + pass + + +def version_to_string(version, short=False): + if len(version) != 5: + raise VersionError("Length != 5") + if version[3] not in ('final', 'dev'): + raise VersionError("Should be either 'final' or 'dev'") + _version = [] + for p in version: + try: + n = int(p) + except ValueError: + n = p + pass + _version.append(n) + version = tuple(_version) + if short and version[3] == 'final': + if version[2] == 0: + version_str = '%d.%d' % version[:2] else: - version_str = '%d.%d.%d' % version_tuple[:3] + version_str = '%d.%d.%d' % version[:3] else: - version_str = '%d.%d.%d%s%d' % version_tuple + version_str = '%d.%d.%d%s%d' % version return version_str +_version_re = re.compile("(\d+)[._](\d+)[._](\d+)[._]?(dev|final)[._]?(\d+)$") def version_from_string(version_str): - g = re.match(r"^(\d+).(\d+).(\d+)(dev|final)(\d+)$", version_str).groups() - return (int(g[0]), int(g[1]), int(g[2]), g[3], int(g[4])) + m = _version_re.search(version_str) + if m: + g = m.groups() + return (int(g[0]), int(g[1]), int(g[2]), g[3], int(g[4])) + raise VersionError("String '%s' do not match regex '%s'" % (version_str, + _version_re.pattern)) __version__ = PICARD_VERSION_STR = version_to_string(PICARD_VERSION) diff --git a/picard/acoustid.py b/picard/acoustid.py index 2d7b526f1..c50c7e532 100644 --- a/picard/acoustid.py +++ b/picard/acoustid.py @@ -21,7 +21,7 @@ from collections import deque from functools import partial from PyQt4 import QtCore from picard import config, log -from picard.const import ACOUSTID_KEY, FPCALC_NAMES +from picard.const import FPCALC_NAMES from picard.util import find_executable from picard.webservice import XmlNode diff --git a/picard/cluster.py b/picard/cluster.py index 2bdc2cce1..ad718dc47 100644 --- a/picard/cluster.py +++ b/picard/cluster.py @@ -24,7 +24,7 @@ from heapq import heappush, heappop from PyQt4 import QtCore from picard import config from picard.metadata import Metadata -from picard.similarity import similarity2, similarity +from picard.similarity import similarity from picard.ui.item import Item from picard.util import format_time @@ -189,10 +189,10 @@ class Cluster(QtCore.QObject, Item): albumDict.add(album))) artist_cluster_engine = ClusterEngine(artistDict) - artist_cluster = artist_cluster_engine.cluster(threshold) + artist_cluster_engine.cluster(threshold) album_cluster_engine = ClusterEngine(albumDict) - album_cluster = album_cluster_engine.cluster(threshold) + album_cluster_engine.cluster(threshold) # Arrange tracks into albums albums = {} @@ -458,7 +458,5 @@ class ClusterEngine(object): self.idClusterIndex[match] = match0 del self.clusterBins[match1] - return self.clusterBins - def can_refresh(self): return False diff --git a/picard/config.py b/picard/config.py index 5a4f8c176..ed0923199 100644 --- a/picard/config.py +++ b/picard/config.py @@ -17,10 +17,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import re from operator import itemgetter from PyQt4 import QtCore from picard import (PICARD_APP_NAME, PICARD_ORG_NAME, PICARD_VERSION, - version_to_string, version_from_string, log) + version_to_string, version_from_string) from picard.util import LockableObject, rot13 @@ -84,7 +85,7 @@ class Config(QtCore.QSettings): TextOption("application", "version", '0.0.0dev0') self._version = version_from_string(self.application["version"]) - self._upgrade_hooks = [] + self._upgrade_hooks = dict() def switchProfile(self, profilename): """Sets the current profile.""" @@ -94,17 +95,15 @@ class Config(QtCore.QSettings): else: raise KeyError("Unknown profile '%s'" % (profilename,)) - def register_upgrade_hook(self, to_version_str, func, *args): + def register_upgrade_hook(self, func, *args): """Register a function to upgrade from one config version to another""" - to_version = version_from_string(to_version_str) + to_version = version_from_string(func.__name__) assert to_version <= PICARD_VERSION, "%r > %r !!!" % (to_version, PICARD_VERSION) - hook = { - 'to': to_version, + self._upgrade_hooks[to_version] = { 'func': func, 'args': args, 'done': False } - self._upgrade_hooks.append(hook) def run_upgrade_hooks(self): """Executes registered functions to upgrade config version to the latest""" @@ -118,29 +117,30 @@ class Config(QtCore.QSettings): version_to_string(PICARD_VERSION) )) return - # sort upgrade hooks by version - self._upgrade_hooks.sort(key=itemgetter("to")) - for hook in self._upgrade_hooks: - if self._version < hook['to']: + for version in sorted(self._upgrade_hooks): + hook = self._upgrade_hooks[version] + if self._version < version: try: hook['func'](*hook['args']) - except Exception as e: + except: + import traceback raise ConfigUpgradeError( "Error during config upgrade from version %s to %s " - "using %s(): %s" % ( + "using %s():\n%s" % ( version_to_string(self._version), - version_to_string(hook['to']), - hook['func'].__name__, e + version_to_string(version), + hook['func'].__name__, + traceback.format_exc() )) else: hook['done'] = True - self._version = hook['to'] + self._version = version self._write_version() else: # hook is not applicable, mark as done hook['done'] = True - if all(map(itemgetter("done"), self._upgrade_hooks)): + if all(map(itemgetter("done"), self._upgrade_hooks.values())): # all hooks were executed, ensure config is marked with latest version self._version = PICARD_VERSION self._write_version() diff --git a/picard/config_upgrade.py b/picard/config_upgrade.py new file mode 100644 index 000000000..702936e16 --- /dev/null +++ b/picard/config_upgrade.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2013-2014 Laurent Monin +# +# 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 + +import re +from picard import (log, config) + + +# TO ADD AN UPGRADE HOOK: +# ---------------------- +# add a function here, named after the version you want upgrade to +# ie. upgrade_to_v1_0_0_dev_1() for 1.0.0dev1 +# register it in upgrade_config() +# and modify PICARD_VERSION to match it +# + +_s = config.setting + + +# In version 1.0, the file naming formats for single and various +# artist releases were merged. +def upgrade_to_v1_0_0_final_0(): + def remove_va_file_naming_format(merge=True): + if merge: + _s["file_naming_format"] = ( + "$if($eq(%%compilation%%,1),\n$noop(Various Artist " + "albums)\n%s,\n$noop(Single Artist Albums)\n%s)" % ( + _s["va_file_naming_format"].toString(), + _s["file_naming_format"] + )) + _s.remove("va_file_naming_format") + _s.remove("use_va_format") + + if ("va_file_naming_format" in _s and + "use_va_format" in _s): + + msgbox = QtGui.QMessageBox() + if _s["use_va_format"].toBool(): + remove_va_file_naming_format() + msgbox.information(msgbox, + _("Various Artists file naming scheme removal"), + _("The separate file naming scheme for various artists " + "albums has been removed in this version of Picard.\n" + "Your file naming scheme has automatically been " + "merged with that of single artist albums."), + QtGui.QMessageBox.Ok) + + elif (_s["va_file_naming_format"].toString() != + r"$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldis" + "cs%,1),%discnumber%-,)$num(%tracknumber%,2) %artist% - " + "%title%"): + + answer = msgbox.question(msgbox, + _("Various Artists file naming scheme removal"), + _("The separate file naming scheme for various artists " + "albums has been removed in this version of Picard.\n" + "You currently do not use this option, but have a " + "separate file naming scheme defined.\n" + "Do you want to remove it or merge it with your file " + "naming scheme for single artist albums?"), + _("Merge"), _("Remove")) + + if answer: + remove_va_file_naming_format(merge=False) + else: + remove_va_file_naming_format() + else: + # default format, disabled + remove_va_file_naming_format(merge=False) + + +def upgrade_to_v1_3_0_dev_1(): + if "windows_compatible_filenames" in _s: + _s["windows_compatibility"] = _s["windows_compatible_filenames"] + _s.remove("windows_compatible_filenames") + log.info(_('Config upgrade: option "windows_compatible_filenames" ' + ' was renamed "windows_compatibility" (PICARD-110).')) + + +def upgrade_to_v1_3_0_dev_2(): + if "preserved_tags" in _s: + _s["preserved_tags"] = re.sub(r"\s+", ",", _s["preserved_tags"].strip()) + log.info(_('Config upgrade: option "preserved_tags" is now using ' + 'comma instead of spaces as tag separator (PICARD-536).')) + + +def upgrade_config(): + cfg = config._config + cfg.register_upgrade_hook(upgrade_to_v1_0_0_final_0) + cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_1) + cfg.register_upgrade_hook(upgrade_to_v1_3_0_dev_2) + cfg.run_upgrade_hooks() diff --git a/picard/const.py b/picard/const.py index 31152db57..68af4f98b 100644 --- a/picard/const.py +++ b/picard/const.py @@ -19,7 +19,6 @@ import os import sys -import re # Install gettext "noop" function in case const.py gets imported directly. import __builtin__ diff --git a/picard/coverart.py b/picard/coverart.py index df5cac28d..a71264f0f 100644 --- a/picard/coverart.py +++ b/picard/coverart.py @@ -24,10 +24,9 @@ import json import re import traceback -import picard.webservice from functools import partial from picard import config, log -from picard.metadata import Metadata, is_front_image +from picard.metadata import is_front_image from picard.util import mimetype, parse_amazon_url from picard.const import CAA_HOST, CAA_PORT from PyQt4.QtCore import QUrl, QObject diff --git a/picard/file.py b/picard/file.py index 3536ddce6..fb225b612 100644 --- a/picard/file.py +++ b/picard/file.py @@ -29,11 +29,9 @@ from operator import itemgetter from collections import defaultdict from PyQt4 import QtCore from picard import config, log -from picard.track import Track from picard.metadata import Metadata from picard.ui.item import Item from picard.script import ScriptParser -from picard.similarity import similarity2 from picard.util import ( decode_filename, encode_filename, @@ -144,7 +142,7 @@ class File(QtCore.QObject, Item): def has_error(self): return self.state == File.ERROR - def _load(self): + def _load(self, filename): """Load metadata from the file.""" raise NotImplementedError @@ -281,6 +279,8 @@ class File(QtCore.QObject, Item): new_filename = os.path.basename(new_filename) new_filename = make_short_filename(new_dirname, new_filename, config.setting['windows_compatibility'], config.setting['windows_compatibility_drive_root']) + # TODO: move following logic under util.filenaming + # (and reconsider its necessity) # win32 compatibility fixes if settings['windows_compatibility'] or sys.platform == 'win32': new_filename = new_filename.replace('./', '_/').replace('.\\', '_\\') @@ -359,8 +359,8 @@ class File(QtCore.QObject, Item): # image multiple times if (os.path.exists(new_filename) and os.path.getsize(new_filename) == len(data)): - log.debug("Identical file size, not saving %r", image_filename) - continue + log.debug("Identical file size, not saving %r", image_filename) + continue log.debug("Saving cover images to %r", image_filename) new_dirname = os.path.dirname(image_filename) if not os.path.isdir(new_dirname): diff --git a/picard/formats/mutagenext/compatid3.py b/picard/formats/mutagenext/compatid3.py index aa3612645..a3098c5eb 100644 --- a/picard/formats/mutagenext/compatid3.py +++ b/picard/formats/mutagenext/compatid3.py @@ -22,7 +22,7 @@ import struct from struct import pack, unpack import mutagen from mutagen._util import insert_bytes -from mutagen.id3 import ID3, Frame, Frames, Frames_2_2, TextFrame, TORY, \ +from mutagen.id3 import ID3, Frames, Frames_2_2, TextFrame, TORY, \ TYER, TIME, APIC, IPLS, TDAT, BitPaddedInt, MakeID3v1 diff --git a/picard/i18n.py b/picard/i18n.py index 8520ee252..03b61850e 100644 --- a/picard/i18n.py +++ b/picard/i18n.py @@ -1,3 +1,22 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2013 Laurent Monin +# +# 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. + import gettext import locale import os.path diff --git a/picard/log.py b/picard/log.py index d4faef298..1f4317f37 100644 --- a/picard/log.py +++ b/picard/log.py @@ -62,7 +62,7 @@ class Logger(object): for func in self._receivers: try: thread.to_main(func, level, time, message) - except Exception as e: + except: import traceback traceback.print_exc() diff --git a/picard/releasegroup.py b/picard/releasegroup.py index 70d4bc031..274ecba28 100644 --- a/picard/releasegroup.py +++ b/picard/releasegroup.py @@ -21,8 +21,7 @@ import traceback from collections import defaultdict from functools import partial from itertools import combinations -from PyQt4 import QtCore -from picard import config, log +from picard import log from picard.metadata import Metadata from picard.dataobj import DataObject from picard.mbxml import media_formats_from_node, label_info_from_node diff --git a/picard/tagger.py b/picard/tagger.py index b2d2208af..0158272ed 100644 --- a/picard/tagger.py +++ b/picard/tagger.py @@ -22,11 +22,9 @@ from PyQt4 import QtGui, QtCore import getopt import os.path -import re import shutil import signal import sys -from collections import deque from functools import partial from itertools import chain @@ -61,16 +59,17 @@ from picard.track import Track, NonAlbumTrack from picard.releasegroup import ReleaseGroup from picard.collection import load_user_collections from picard.ui.mainwindow import MainWindow -from picard.ui.itemviews import BaseTreeView from picard.plugin import PluginManager from picard.acoustidmanager import AcoustIDManager +from picard.config_upgrade import upgrade_config from picard.util import ( decode_filename, encode_filename, thread, mbid_validate, check_io_encoding, - uniqify + uniqify, + is_hidden_path, ) from picard.webservice import XmlWebService @@ -127,10 +126,12 @@ class Tagger(QtGui.QApplication): check_io_encoding() - self._upgrade_config() - + # Must be before config upgrade because upgrade dialogs need to be + # translated setup_gettext(localedir, config.setting["ui_language"], log.debug) + upgrade_config() + self.xmlws = XmlWebService() load_user_collections() @@ -162,64 +163,6 @@ class Tagger(QtGui.QApplication): self.nats = None self.window = MainWindow() - def _upgrade_config(self): - cfg = config._config - - # In version 1.0, the file naming formats for single and various - # artist releases were merged. - def upgrade_to_v1_0(): - 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%s,\n$noop(Single Artist Albums)\n%s)" % ( - config.setting["va_file_naming_format"].toString(), - config.setting["file_naming_format"] - )) - config.setting.remove("va_file_naming_format") - config.setting.remove("use_va_format") - - if ("va_file_naming_format" in config.setting and - "use_va_format" in config.setting): - - if config.setting["use_va_format"].toBool(): - remove_va_file_naming_format() - self.window.show_va_removal_notice() - - elif (config.setting["va_file_naming_format"].toString() != - r"$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldis" - "cs%,1),%discnumber%-,)$num(%tracknumber%,2) %artist% - " - "%title%"): - - if self.window.confirm_va_removal(): - remove_va_file_naming_format(merge=False) - else: - remove_va_file_naming_format() - else: - # default format, disabled - remove_va_file_naming_format(merge=False) - - def upgrade_to_v1_3(): - _s = config.setting - # the setting `windows_compatible_filenames` has been renamed - # to `windows_compatibility` - if "windows_compatible_filenames" in _s: - _s["windows_compatibility"] = _s["windows_compatible_filenames"] - _s.remove("windows_compatible_filenames") - log.debug("Config upgrade: windows_compatible_filenames " - "renamed windows_compatibility") - - # preserved_tags spaces to comma separator, PICARD-536 - if "preserved_tags" in _s: - _s["preserved_tags"] = re.sub(r"\s+", ",", _s["preserved_tags"].strip()) - log.debug("Config upgrade: convert preserved_tags separator " - "from spaces to comma") - - cfg.register_upgrade_hook("1.0.0final0", upgrade_to_v1_0) - cfg.register_upgrade_hook("1.3.0dev2", upgrade_to_v1_3) - - cfg.run_upgrade_hooks() - def move_files_to_album(self, files, albumid=None, album=None): """Move `files` to tracks on album `albumid`.""" if album is None: @@ -329,9 +272,20 @@ class Tagger(QtGui.QApplication): def add_files(self, filenames, target=None): """Add files to the tagger.""" log.debug("Adding files %r", filenames) + ignoreregex = None + pattern = config.setting['ignore_regex'] + if pattern: + ignoreregex = re.compile(pattern) + ignore_hidden = not config.persist["show_hidden_files"] new_files = [] for filename in filenames: filename = os.path.normpath(os.path.realpath(filename)) + if ignore_hidden and is_hidden_path(filename): + log.debug("File ignored (hidden): %s" % (filename)) + continue + if ignoreregex is not None and ignoreregex.search(filename): + log.info("File ignored (matching %s): %s" % (pattern, filename)) + continue if filename not in self.files: file = open_file(filename) if file: diff --git a/picard/ui/cdlookup.py b/picard/ui/cdlookup.py index cc5bd4810..cd9355590 100644 --- a/picard/ui/cdlookup.py +++ b/picard/ui/cdlookup.py @@ -50,7 +50,8 @@ class CDLookupDialog(QtGui.QDialog): item.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(release.id)) self.ui.release_list.setCurrentItem(self.ui.release_list.topLevelItem(0)) self.ui.ok_button.setEnabled(True) - [self.ui.release_list.resizeColumnToContents(i) for i in range(self.ui.release_list.columnCount() - 1)] + for i in range(self.ui.release_list.columnCount() - 1): + self.ui.release_list.resizeColumnToContents(i) # Sort by descending date, then ascending country self.ui.release_list.sortByColumn(3, QtCore.Qt.AscendingOrder) self.ui.release_list.sortByColumn(2, QtCore.Qt.DescendingOrder) diff --git a/picard/ui/filebrowser.py b/picard/ui/filebrowser.py index c469aaa67..310a2f567 100644 --- a/picard/ui/filebrowser.py +++ b/picard/ui/filebrowser.py @@ -1,4 +1,4 @@ -# -*- coding: UTF-8 -*- +# -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger # Copyright (C) 2006-2007 Lukáš Lalinský diff --git a/picard/ui/infostatus.py b/picard/ui/infostatus.py index 769a5b9db..bfb1c8dce 100644 --- a/picard/ui/infostatus.py +++ b/picard/ui/infostatus.py @@ -18,7 +18,6 @@ from PyQt4 import QtCore, QtGui from PyQt4.QtGui import QIcon -from picard import config from picard.util import icontheme from picard.ui.ui_infostatus import Ui_InfoStatus diff --git a/picard/ui/itemviews.py b/picard/ui/itemviews.py index 1a6d759d7..9b5d52ec8 100644 --- a/picard/ui/itemviews.py +++ b/picard/ui/itemviews.py @@ -308,7 +308,9 @@ class BaseTreeView(QtGui.QTreeWidget): action.setChecked(True) action.triggered.connect(partial(obj.switch_release_version, version["id"])) - _add_other_versions() if obj.release_group.loaded else \ + if obj.release_group.loaded: + _add_other_versions() + else: obj.release_group.load_versions(_add_other_versions) releases_menu.setEnabled(True) else: diff --git a/picard/ui/mainwindow.py b/picard/ui/mainwindow.py index 6d4ad1c2d..d8e004d5f 100644 --- a/picard/ui/mainwindow.py +++ b/picard/ui/mainwindow.py @@ -22,7 +22,6 @@ from PyQt4 import QtCore, QtGui import sys import os.path -from functools import partial from picard import config, log from picard.file import File from picard.track import Track @@ -669,23 +668,6 @@ class MainWindow(QtGui.QMainWindow): from picard.ui.logview import HistoryView HistoryView(self).show() - def confirm_va_removal(self): - return QtGui.QMessageBox.question(self, - _("Various Artists file naming scheme removal"), -_("""The separate file naming scheme for various artists albums has been -removed in this version of Picard. You currently do not use the this option, -but have a separate file naming scheme defined. Do you want to remove it or -merge it with your file naming scheme for single artist albums?"""), - _("Merge"), _("Remove")) - - def show_va_removal_notice(self): - QtGui.QMessageBox.information(self, - _("Various Artists file naming scheme removal"), -_("""The separate file naming scheme for various artists albums has been -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.goto('troubleshooting') diff --git a/picard/ui/metadatabox.py b/picard/ui/metadatabox.py index bec999ff0..94b7d08ba 100644 --- a/picard/ui/metadatabox.py +++ b/picard/ui/metadatabox.py @@ -290,7 +290,9 @@ class MetadataBox(QtGui.QTableWidget): self.set_tag_values(tag, [""]) def remove_selected_tags(self): - (self.remove_tag(tag) for tag in self.selected_tags() if self.tag_is_removable(tag)) + for tag in self.selected_tags(): + if self.tag_is_removable(tag): + self.remove_tag(tag) def tag_is_removable(self, tag): return self.tag_diff.status[tag] & TagStatus.NotRemovable == 0 diff --git a/picard/ui/options/__init__.py b/picard/ui/options/__init__.py index da17d3b4b..b8cf15731 100644 --- a/picard/ui/options/__init__.py +++ b/picard/ui/options/__init__.py @@ -33,6 +33,7 @@ class OptionsPage(QtGui.QWidget): PARENT = None SORT_ORDER = 1000 ACTIVE = True + STYLESHEET_ERROR = "QWidget { background-color: #f55; color: white; font-weight:bold }" def info(self): raise NotImplementedError diff --git a/picard/ui/options/about.py b/picard/ui/options/about.py index bb7a3c3c3..84d55d272 100644 --- a/picard/ui/options/about.py +++ b/picard/ui/options/about.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Picard, the next-generation MusicBrainz tagger -# Copyright (C) 2006 Lukáš Lalinský +# Copyright (C) 2006-2014 Lukáš Lalinský # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -75,7 +75,7 @@ Discid %(discid-version)s Thank you for using Picard. Picard relies on the MusicBrainz database, which is operated by the MetaBrainz Foundation with the help of thousands of volunteers. If you like this application please consider donating to the MetaBrainz Foundation to keep the service running.

Donate now!

Credits
-Copyright © 2004-2011 Robert Kaye, Lukáš Lalinský and others%(translator-credits)s

+Copyright © 2004-2014 Robert Kaye, Lukáš Lalinský and others%(translator-credits)s

%(picard-doc-url)s

""") % args self.ui.label.setOpenExternalLinks(True) diff --git a/picard/ui/options/advanced.py b/picard/ui/options/advanced.py index 881312fa4..3f3593ed7 100644 --- a/picard/ui/options/advanced.py +++ b/picard/ui/options/advanced.py @@ -17,7 +17,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from picard.ui.options import OptionsPage, register_options_page +from PyQt4.QtGui import QPalette, QColor +import re + +from picard import config +from picard.ui.options import OptionsPage, OptionsCheckError, register_options_page +from picard.ui.ui_options_advanced import Ui_AdvancedOptionsPage class AdvancedOptionsPage(OptionsPage): @@ -26,7 +31,39 @@ class AdvancedOptionsPage(OptionsPage): TITLE = N_("Advanced") PARENT = None SORT_ORDER = 90 - ACTIVE = False + ACTIVE = True + + options = [ + config.TextOption("setting", "ignore_regex", ""), + ] + + def __init__(self, parent=None): + super(AdvancedOptionsPage, self).__init__(parent) + self.ui = Ui_AdvancedOptionsPage() + self.ui.setupUi(self) + self.ui.ignore_regex.textChanged.connect(self.live_checker) + + def load(self): + self.ui.ignore_regex.setText(config.setting["ignore_regex"]) + + def save(self): + config.setting["ignore_regex"] = unicode(self.ui.ignore_regex.text()) + + def live_checker(self, text): + self.ui.regex_error.setStyleSheet("") + self.ui.regex_error.setText("") + try: + self.check() + except OptionsCheckError as e: + self.ui.regex_error.setStyleSheet(self.STYLESHEET_ERROR) + self.ui.regex_error.setText(e.info) + return + + def check(self): + try: + re.compile(unicode(self.ui.ignore_regex.text())) + except re.error as e: + raise OptionsCheckError(_("Regex Error"), str(e)) register_options_page(AdvancedOptionsPage) diff --git a/picard/ui/options/dialog.py b/picard/ui/options/dialog.py index 6a27b107b..9c49cfeb7 100644 --- a/picard/ui/options/dialog.py +++ b/picard/ui/options/dialog.py @@ -19,7 +19,6 @@ from PyQt4 import QtCore, QtGui from picard import config -from picard.plugin import ExtensionPoint from picard.util import webbrowser2 from picard.ui.util import StandardButton from picard.ui.options import ( diff --git a/picard/ui/options/fingerprinting.py b/picard/ui/options/fingerprinting.py index 52f0bc355..3a3e434ce 100644 --- a/picard/ui/options/fingerprinting.py +++ b/picard/ui/options/fingerprinting.py @@ -18,7 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from picard import config from picard.util import webbrowser2, find_executable from picard.const import FPCALC_NAMES diff --git a/picard/ui/options/metadata.py b/picard/ui/options/metadata.py index 6ee2eba21..410535844 100644 --- a/picard/ui/options/metadata.py +++ b/picard/ui/options/metadata.py @@ -17,7 +17,6 @@ # 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 from picard import config from picard.ui.options import OptionsPage, register_options_page from picard.ui.ui_options_metadata import Ui_MetadataOptionsPage diff --git a/picard/ui/options/renaming.py b/picard/ui/options/renaming.py index 67ec77001..a487376b7 100644 --- a/picard/ui/options/renaming.py +++ b/picard/ui/options/renaming.py @@ -270,8 +270,6 @@ class RenamingOptionsPage(OptionsPage): file.metadata['musicbrainz_releasetrackid'] = 'eac99807-93d4-3668-9714-fa0c1b487ccf' return file - STYLESHEET_ERROR = "QWidget { background-color: #f55; color: white; font-weight:bold }" - def move_files_to_browse(self): path = QtGui.QFileDialog.getExistingDirectory(self, "", self.ui.move_files_to.text()) if path: diff --git a/picard/ui/options/scripting.py b/picard/ui/options/scripting.py index 6ec9d0e89..9df9c6e66 100644 --- a/picard/ui/options/scripting.py +++ b/picard/ui/options/scripting.py @@ -70,8 +70,6 @@ class ScriptingOptionsPage(OptionsPage): config.TextOption("setting", "tagger_script", ""), ] - STYLESHEET_ERROR = "QWidget { background-color: #f55; color: white; font-weight:bold }" - def __init__(self, parent=None): super(ScriptingOptionsPage, self).__init__(parent) self.ui = Ui_ScriptingOptionsPage() diff --git a/picard/ui/passworddialog.py b/picard/ui/passworddialog.py index 804e7e062..32335f519 100644 --- a/picard/ui/passworddialog.py +++ b/picard/ui/passworddialog.py @@ -17,7 +17,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from picard import config from picard.ui.ui_passworddialog import Ui_PasswordDialog from picard.util import rot13 diff --git a/picard/ui/ui_cdlookup.py b/picard/ui/ui_cdlookup.py index 4d12eed1a..31eca9cdd 100644 --- a/picard/ui/ui_cdlookup.py +++ b/picard/ui/ui_cdlookup.py @@ -66,6 +66,6 @@ class Ui_Dialog(object): Dialog.setWindowTitle(_("CD Lookup")) self.label.setText(_("The following releases on MusicBrainz match the CD:")) self.ok_button.setText(_("OK")) - self.lookup_button.setText(_(" Lookup manually ")) + self.lookup_button.setText(_("Lookup manually")) self.cancel_button.setText(_("Cancel")) diff --git a/picard/ui/ui_options_advanced.py b/picard/ui/ui_options_advanced.py new file mode 100644 index 000000000..8d3fb9c82 --- /dev/null +++ b/picard/ui/ui_options_advanced.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/options_advanced.ui' +# +# Created: Wed Dec 25 02:35:20 2013 +# by: PyQt4 UI code generator 4.9.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_AdvancedOptionsPage(object): + def setupUi(self, AdvancedOptionsPage): + AdvancedOptionsPage.setObjectName(_fromUtf8("AdvancedOptionsPage")) + AdvancedOptionsPage.resize(338, 435) + self.vboxlayout = QtGui.QVBoxLayout(AdvancedOptionsPage) + self.vboxlayout.setObjectName(_fromUtf8("vboxlayout")) + self.groupBox = QtGui.QGroupBox(AdvancedOptionsPage) + self.groupBox.setObjectName(_fromUtf8("groupBox")) + self.gridlayout = QtGui.QGridLayout(self.groupBox) + self.gridlayout.setSpacing(2) + self.gridlayout.setObjectName(_fromUtf8("gridlayout")) + self.label_ignore_regex = QtGui.QLabel(self.groupBox) + self.label_ignore_regex.setObjectName(_fromUtf8("label_ignore_regex")) + self.gridlayout.addWidget(self.label_ignore_regex, 1, 0, 1, 1) + self.ignore_regex = QtGui.QLineEdit(self.groupBox) + self.ignore_regex.setObjectName(_fromUtf8("ignore_regex")) + self.gridlayout.addWidget(self.ignore_regex, 2, 0, 1, 1) + self.regex_error = QtGui.QLabel(self.groupBox) + self.regex_error.setText(_fromUtf8("")) + self.regex_error.setObjectName(_fromUtf8("regex_error")) + self.gridlayout.addWidget(self.regex_error, 3, 0, 1, 1) + self.vboxlayout.addWidget(self.groupBox) + spacerItem = QtGui.QSpacerItem(181, 21, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.vboxlayout.addItem(spacerItem) + + self.retranslateUi(AdvancedOptionsPage) + QtCore.QMetaObject.connectSlotsByName(AdvancedOptionsPage) + + def retranslateUi(self, AdvancedOptionsPage): + self.groupBox.setTitle(_("Advanced options")) + self.label_ignore_regex.setText(_("Ignore file paths matching the following regular expression:")) + diff --git a/picard/ui/util.py b/picard/ui/util.py index d7b36d80c..d3056f8a5 100644 --- a/picard/ui/util.py +++ b/picard/ui/util.py @@ -18,7 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import sys -from PyQt4 import QtGui +from PyQt4 import QtCore, QtGui from picard import config from picard.util import find_existing_path diff --git a/picard/util/__init__.py b/picard/util/__init__.py index f4797fc79..03ea2dc5a 100644 --- a/picard/util/__init__.py +++ b/picard/util/__init__.py @@ -365,3 +365,9 @@ if sys.platform == 'win32': return ap1 == ap2 else: os_path_samefile = os.path.samefile + + +def is_hidden_path(path): + """Returns true if at least one element of the path starts with a dot""" + path = os.path.normpath(path) # we need to ignore /./ and /a/../ cases + return any(s.startswith('.') for s in path.split(os.sep)) diff --git a/picard/util/bytes2human.py b/picard/util/bytes2human.py index bfc7ceca7..ea8fb4eb2 100644 --- a/picard/util/bytes2human.py +++ b/picard/util/bytes2human.py @@ -18,8 +18,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import locale -import picard.i18n - """ Helper class to convert bytes to human-readable form @@ -46,26 +44,26 @@ _BYTES_STRINGS_I18N = ( ) -def decimal(number, prec=1): +def decimal(number, scale=1): """ Convert bytes to short human-readable string, decimal mode >>> [decimal(n) for n in [1000, 1024, 15500]] ['1 kB', '1 kB', '15.5 kB'] """ - return short_string(int(number), 1000) + return short_string(int(number), 1000, scale) -def binary(number, prec=1): +def binary(number, scale=1): """ Convert bytes to short human-readable string, binary mode >>> [binary(n) for n in [1000, 1024, 15500]] ['1000 B', '1 KiB', '15.1 KiB'] """ - return short_string(int(number), 1024, prec) + return short_string(int(number), 1024, scale) -def short_string(number, multiple, prec=1): +def short_string(number, multiple, scale=1): """ Returns short human-readable string for `number` bytes >>> [short_string(n, 1024, 2) for n in [1000, 1100, 15500]] @@ -75,12 +73,12 @@ def short_string(number, multiple, prec=1): """ num, unit = calc_unit(number, multiple) n = int(num) - nr = round(num, prec) + nr = round(num, scale) if n == nr or unit == 'B': fmt = '%d' num = n else: - fmt = '%%0.%df' % prec + fmt = '%%0.%df' % scale num = nr fmtnum = locale.format(fmt, num) return _("%s " + unit) % fmtnum @@ -118,8 +116,3 @@ def calc_unit(number, multiple=1000): return (sign * n, suffix) else: n /= multiple - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/picard/util/cdrom.py b/picard/util/cdrom.py index 64ae157d3..4b0cef41b 100644 --- a/picard/util/cdrom.py +++ b/picard/util/cdrom.py @@ -79,10 +79,11 @@ elif sys.platform == 'linux2' and QFile.exists(LINUX_CDROM_INFO): elif key == 'Can play audio': drive_audio_caps = [v == '1' for v in QString(values).trimmed().split(QRegExp("\\s+"), QString.SkipEmptyParts)] + break # no need to continue passed this line line = cdinfo.readLine() # Show only drives that are capable of playing audio - for drive in drive_names: - if drive_audio_caps[drive_names.indexOf(drive)]: + for index, drive in enumerate(drive_names): + if drive_audio_caps[index]: device = u'/dev/%s' % drive symlink_target = QFile.symLinkTarget(device) if symlink_target != '': diff --git a/picard/util/filenaming.py b/picard/util/filenaming.py index 1e166124d..9981bb57d 100644 --- a/picard/util/filenaming.py +++ b/picard/util/filenaming.py @@ -311,7 +311,7 @@ def make_short_filename(basedir, relpath, win_compat=False, relative_to=""): reserved = len(basedir) if not basedir.endswith(os.path.sep): reserved += 1 - return os.path.join(basedir, _make_win_short_filename(relpath, reserved)) + return _make_win_short_filename(relpath, reserved) # if we're being windows compatible, figure out how much # needs to be reserved for the basedir part if win_compat: @@ -337,4 +337,4 @@ def make_short_filename(basedir, relpath, win_compat=False, relative_to=""): # and filesystem-dependent limit = _get_filename_limit(basedir) relpath = shorten_path(relpath, limit, mode=SHORTEN_BYTES) - return os.path.join(basedir, relpath) + return relpath diff --git a/picard/util/thread.py b/picard/util/thread.py index 8c9b36fbf..095f8bbab 100644 --- a/picard/util/thread.py +++ b/picard/util/thread.py @@ -19,7 +19,7 @@ import sys import traceback -from PyQt4.QtCore import QThreadPool, QRunnable, QCoreApplication, QEvent +from PyQt4.QtCore import QRunnable, QCoreApplication, QEvent class ProxyToMainEvent(QEvent): diff --git a/picard/webservice.py b/picard/webservice.py index d9b17b8b8..55442e2b9 100644 --- a/picard/webservice.py +++ b/picard/webservice.py @@ -28,7 +28,6 @@ import re import time import os.path import platform -import urllib from collections import deque, defaultdict from functools import partial from PyQt4 import QtCore, QtNetwork @@ -55,8 +54,9 @@ USER_AGENT_STRING = '%s-%s/%s (%s;%s-%s)' % (PICARD_ORG_NAME, PICARD_APP_NAME, platform.platform(), platform.python_implementation(), platform.python_version()) -CLIENT_STRING = urllib.quote('%s %s-%s' % (PICARD_ORG_NAME, PICARD_APP_NAME, - PICARD_VERSION_STR)) +CLIENT_STRING = str(QUrl.toPercentEncoding('%s %s-%s' % (PICARD_ORG_NAME, + PICARD_APP_NAME, + PICARD_VERSION_STR))) def _escape_lucene_query(text): @@ -392,7 +392,7 @@ class XmlWebService(QtCore.QObject): query = [] for name, value in kwargs.items(): if name == 'limit': - filters.append((name, value)) + filters.append((name, str(value))) else: value = _escape_lucene_query(value).strip().lower() if value: diff --git a/po/picard.pot b/po/picard.pot index 211e2ee3f..b63f5dcd7 100644 --- a/po/picard.pot +++ b/po/picard.pot @@ -1,14 +1,14 @@ # Translations template for PROJECT. -# Copyright (C) 2013 ORGANIZATION +# Copyright (C) 2014 ORGANIZATION # This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2013. +# FIRST AUTHOR , 2014. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2013-11-26 22:35+0100\n" +"POT-Creation-Date: 2014-01-05 14:00+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -150,42 +150,42 @@ msgstr "" msgid "AcoustIDs successfully submitted!" msgstr "" -#: picard/album.py:64 picard/cluster.py:235 +#: picard/album.py:64 picard/cluster.py:234 msgid "Unmatched Files" msgstr "" -#: picard/album.py:181 +#: picard/album.py:187 #, python-format msgid "[could not load album %s]" msgstr "" -#: picard/album.py:267 +#: picard/album.py:272 #, python-format msgid "Album %s loaded" msgstr "" -#: picard/album.py:283 +#: picard/album.py:288 msgid "[loading album information]" msgstr "" -#: picard/album.py:455 +#: picard/album.py:460 #, python-format msgid "; %i image" msgid_plural "; %i images" msgstr[0] "" msgstr[1] "" -#: picard/cluster.py:151 picard/cluster.py:160 +#: picard/cluster.py:150 picard/cluster.py:159 #, python-format msgid "No matching releases for cluster %s" msgstr "" -#: picard/cluster.py:162 +#: picard/cluster.py:161 #, python-format msgid "Cluster %s identified!" msgstr "" -#: picard/cluster.py:169 +#: picard/cluster.py:168 #, python-format msgid "Looking up the metadata for cluster %s..." msgstr "" @@ -209,1193 +209,1235 @@ msgstr[1] "" msgid "Error loading collections: %s" msgstr "" -#: picard/const.py:55 -msgid "CD" +#: picard/config_upgrade.py:59 picard/config_upgrade.py:72 +msgid "Various Artists file naming scheme removal" msgstr "" -#: picard/const.py:56 -msgid "CD-R" +#: picard/config_upgrade.py:60 +msgid "" +"The separate file naming scheme for various artists albums has been " +"removed in this version of Picard.\n" +"Your file naming scheme has automatically been merged with that of single" +" artist albums." msgstr "" -#: picard/const.py:57 -msgid "HDCD" +#: picard/config_upgrade.py:73 +msgid "" +"The separate file naming scheme for various artists albums has been " +"removed in this version of Picard.\n" +"You currently do not use this option, but have a separate file naming " +"scheme defined.\n" +"Do you want to remove it or merge it with your file naming scheme for " +"single artist albums?" msgstr "" -#: picard/const.py:58 -msgid "8cm CD" +#: picard/config_upgrade.py:79 +msgid "Merge" msgstr "" -#: picard/const.py:59 -msgid "Vinyl" +#: picard/config_upgrade.py:79 picard/ui/metadatabox.py:254 +msgid "Remove" msgstr "" -#: picard/const.py:60 -msgid "7\" Vinyl" +#: picard/config_upgrade.py:94 +msgid "" +"Config upgrade: option \"windows_compatible_filenames\" was renamed " +"\"windows_compatibility\" (PICARD-110)." msgstr "" -#: picard/const.py:61 -msgid "10\" Vinyl" -msgstr "" - -#: picard/const.py:62 -msgid "12\" Vinyl" -msgstr "" - -#: picard/const.py:63 -msgid "Digital Media" -msgstr "" - -#: picard/const.py:64 -msgid "USB Flash Drive" -msgstr "" - -#: picard/const.py:65 -msgid "slotMusic" -msgstr "" - -#: picard/const.py:66 -msgid "Cassette" +#: picard/config_upgrade.py:101 +msgid "" +"Config upgrade: option \"preserved_tags\" is now using comma instead of " +"spaces as tag separator (PICARD-536)." msgstr "" #: picard/const.py:67 -msgid "DVD" +msgid "CD" msgstr "" #: picard/const.py:68 -msgid "DVD-Audio" +msgid "CD-R" msgstr "" #: picard/const.py:69 -msgid "DVD-Video" +msgid "HDCD" msgstr "" #: picard/const.py:70 -msgid "SACD" +msgid "8cm CD" msgstr "" #: picard/const.py:71 -msgid "DualDisc" +msgid "Vinyl" msgstr "" #: picard/const.py:72 -msgid "MiniDisc" +msgid "7\" Vinyl" msgstr "" #: picard/const.py:73 -msgid "Blu-ray" +msgid "10\" Vinyl" msgstr "" #: picard/const.py:74 -msgid "HD-DVD" +msgid "12\" Vinyl" msgstr "" #: picard/const.py:75 -msgid "Videotape" +msgid "Digital Media" msgstr "" #: picard/const.py:76 -msgid "VHS" +msgid "USB Flash Drive" msgstr "" #: picard/const.py:77 -msgid "Betamax" +msgid "slotMusic" msgstr "" #: picard/const.py:78 -msgid "VCD" +msgid "Cassette" msgstr "" #: picard/const.py:79 -msgid "SVCD" +msgid "DVD" msgstr "" #: picard/const.py:80 +msgid "DVD-Audio" +msgstr "" + +#: picard/const.py:81 +msgid "DVD-Video" +msgstr "" + +#: picard/const.py:82 +msgid "SACD" +msgstr "" + +#: picard/const.py:83 +msgid "DualDisc" +msgstr "" + +#: picard/const.py:84 +msgid "MiniDisc" +msgstr "" + +#: picard/const.py:85 +msgid "Blu-ray" +msgstr "" + +#: picard/const.py:86 +msgid "HD-DVD" +msgstr "" + +#: picard/const.py:87 +msgid "Videotape" +msgstr "" + +#: picard/const.py:88 +msgid "VHS" +msgstr "" + +#: picard/const.py:89 +msgid "Betamax" +msgstr "" + +#: picard/const.py:90 +msgid "VCD" +msgstr "" + +#: picard/const.py:91 +msgid "SVCD" +msgstr "" + +#: picard/const.py:92 msgid "UMD" msgstr "" -#: picard/const.py:81 picard/coverartarchive.py:33 +#: picard/const.py:93 picard/coverartarchive.py:33 #: picard/ui/ui_options_releases.py:226 msgid "Other" msgstr "" -#: picard/const.py:82 +#: picard/const.py:94 msgid "LaserDisc" msgstr "" -#: picard/const.py:83 +#: picard/const.py:95 msgid "Cartridge" msgstr "" -#: picard/const.py:84 +#: picard/const.py:96 msgid "Reel-to-reel" msgstr "" -#: picard/const.py:85 +#: picard/const.py:97 msgid "DAT" msgstr "" -#: picard/const.py:86 +#: picard/const.py:98 msgid "Wax Cylinder" msgstr "" -#: picard/const.py:87 +#: picard/const.py:99 msgid "Piano Roll" msgstr "" -#: picard/const.py:88 +#: picard/const.py:100 msgid "DCC" msgstr "" -#: picard/const.py:93 +#: picard/const.py:105 msgid "Bangladesh" msgstr "" -#: picard/const.py:94 +#: picard/const.py:106 msgid "Belgium" msgstr "" -#: picard/const.py:95 +#: picard/const.py:107 msgid "Burkina Faso" msgstr "" -#: picard/const.py:96 +#: picard/const.py:108 msgid "Bulgaria" msgstr "" -#: picard/const.py:97 +#: picard/const.py:109 msgid "Barbados" msgstr "" -#: picard/const.py:98 +#: picard/const.py:110 msgid "Wallis and Futuna Islands" msgstr "" -#: picard/const.py:99 +#: picard/const.py:111 msgid "Bermuda" msgstr "" -#: picard/const.py:100 +#: picard/const.py:112 msgid "Brunei Darussalam" msgstr "" -#: picard/const.py:101 +#: picard/const.py:113 msgid "Bolivia" msgstr "" -#: picard/const.py:102 +#: picard/const.py:114 msgid "Bahrain" msgstr "" -#: picard/const.py:103 +#: picard/const.py:115 msgid "Burundi" msgstr "" -#: picard/const.py:104 +#: picard/const.py:116 msgid "Benin" msgstr "" -#: picard/const.py:105 +#: picard/const.py:117 msgid "Bhutan" msgstr "" -#: picard/const.py:106 +#: picard/const.py:118 msgid "Jamaica" msgstr "" -#: picard/const.py:107 +#: picard/const.py:119 msgid "Bouvet Island" msgstr "" -#: picard/const.py:108 +#: picard/const.py:120 msgid "Botswana" msgstr "" -#: picard/const.py:109 +#: picard/const.py:121 msgid "Samoa" msgstr "" -#: picard/const.py:110 +#: picard/const.py:122 msgid "Brazil" msgstr "" -#: picard/const.py:111 +#: picard/const.py:123 msgid "Bahamas" msgstr "" -#: picard/const.py:112 +#: picard/const.py:124 msgid "Belarus" msgstr "" -#: picard/const.py:113 +#: picard/const.py:125 msgid "Belize" msgstr "" -#: picard/const.py:114 +#: picard/const.py:126 msgid "Russian Federation" msgstr "" -#: picard/const.py:115 +#: picard/const.py:127 msgid "Rwanda" msgstr "" -#: picard/const.py:116 +#: picard/const.py:128 msgid "Reunion" msgstr "" -#: picard/const.py:117 +#: picard/const.py:129 msgid "Turkmenistan" msgstr "" -#: picard/const.py:118 +#: picard/const.py:130 msgid "Tajikistan" msgstr "" -#: picard/const.py:119 +#: picard/const.py:131 msgid "Romania" msgstr "" -#: picard/const.py:120 +#: picard/const.py:132 msgid "Tokelau" msgstr "" -#: picard/const.py:121 +#: picard/const.py:133 msgid "Guinea-Bissa" msgstr "" -#: picard/const.py:122 +#: picard/const.py:134 msgid "Guam" msgstr "" -#: picard/const.py:123 +#: picard/const.py:135 msgid "Guatemala" msgstr "" -#: picard/const.py:124 +#: picard/const.py:136 msgid "Greece" msgstr "" -#: picard/const.py:125 +#: picard/const.py:137 msgid "Equatorial Guinea" msgstr "" -#: picard/const.py:126 +#: picard/const.py:138 msgid "Guadeloupe" msgstr "" -#: picard/const.py:127 +#: picard/const.py:139 msgid "Japan" msgstr "" -#: picard/const.py:128 +#: picard/const.py:140 msgid "Guyana" msgstr "" -#: picard/const.py:129 +#: picard/const.py:141 msgid "French Guiana" msgstr "" -#: picard/const.py:130 +#: picard/const.py:142 msgid "Georgia" msgstr "" -#: picard/const.py:131 +#: picard/const.py:143 msgid "Grenada" msgstr "" -#: picard/const.py:132 +#: picard/const.py:144 msgid "United Kingdom" msgstr "" -#: picard/const.py:133 +#: picard/const.py:145 msgid "Gabon" msgstr "" -#: picard/const.py:134 +#: picard/const.py:146 msgid "El Salvador" msgstr "" -#: picard/const.py:135 +#: picard/const.py:147 msgid "Guinea" msgstr "" -#: picard/const.py:136 +#: picard/const.py:148 msgid "Gambia" msgstr "" -#: picard/const.py:137 +#: picard/const.py:149 msgid "Greenland" msgstr "" -#: picard/const.py:138 +#: picard/const.py:150 msgid "Gibraltar" msgstr "" -#: picard/const.py:139 +#: picard/const.py:151 msgid "Ghana" msgstr "" -#: picard/const.py:140 +#: picard/const.py:152 msgid "Oman" msgstr "" -#: picard/const.py:141 +#: picard/const.py:153 msgid "Tunisia" msgstr "" -#: picard/const.py:142 +#: picard/const.py:154 msgid "Jordan" msgstr "" -#: picard/const.py:143 +#: picard/const.py:155 msgid "Haiti" msgstr "" -#: picard/const.py:144 +#: picard/const.py:156 msgid "Hungary" msgstr "" -#: picard/const.py:145 +#: picard/const.py:157 msgid "Hong Kong" msgstr "" -#: picard/const.py:146 +#: picard/const.py:158 msgid "Honduras" msgstr "" -#: picard/const.py:147 +#: picard/const.py:159 msgid "Heard and Mc Donald Islands" msgstr "" -#: picard/const.py:148 +#: picard/const.py:160 msgid "Venezuela" msgstr "" -#: picard/const.py:149 +#: picard/const.py:161 msgid "Puerto Rico" msgstr "" -#: picard/const.py:150 +#: picard/const.py:162 msgid "Palau" msgstr "" -#: picard/const.py:151 +#: picard/const.py:163 msgid "Portugal" msgstr "" -#: picard/const.py:152 +#: picard/const.py:164 msgid "Svalbard and Jan Mayen Islands" msgstr "" -#: picard/const.py:153 +#: picard/const.py:165 msgid "Paraguay" msgstr "" -#: picard/const.py:154 +#: picard/const.py:166 msgid "Iraq" msgstr "" -#: picard/const.py:155 +#: picard/const.py:167 msgid "Panama" msgstr "" -#: picard/const.py:156 +#: picard/const.py:168 msgid "French Polynesia" msgstr "" -#: picard/const.py:157 +#: picard/const.py:169 msgid "Papua New Guinea" msgstr "" -#: picard/const.py:158 +#: picard/const.py:170 msgid "Peru" msgstr "" -#: picard/const.py:159 +#: picard/const.py:171 msgid "Pakistan" msgstr "" -#: picard/const.py:160 +#: picard/const.py:172 msgid "Philippines" msgstr "" -#: picard/const.py:161 +#: picard/const.py:173 msgid "Pitcairn" msgstr "" -#: picard/const.py:162 +#: picard/const.py:174 msgid "Poland" msgstr "" -#: picard/const.py:163 +#: picard/const.py:175 msgid "St. Pierre and Miquelon" msgstr "" -#: picard/const.py:164 +#: picard/const.py:176 msgid "Zambia" msgstr "" -#: picard/const.py:165 +#: picard/const.py:177 msgid "Western Sahara" msgstr "" -#: picard/const.py:166 +#: picard/const.py:178 msgid "Estonia" msgstr "" -#: picard/const.py:167 +#: picard/const.py:179 msgid "Egypt" msgstr "" -#: picard/const.py:168 +#: picard/const.py:180 msgid "South Africa" msgstr "" -#: picard/const.py:169 +#: picard/const.py:181 msgid "Ecuador" msgstr "" -#: picard/const.py:170 +#: picard/const.py:182 msgid "Italy" msgstr "" -#: picard/const.py:171 +#: picard/const.py:183 msgid "Viet Nam" msgstr "" -#: picard/const.py:172 +#: picard/const.py:184 msgid "Solomon Islands" msgstr "" -#: picard/const.py:173 +#: picard/const.py:185 msgid "Ethiopia" msgstr "" -#: picard/const.py:174 +#: picard/const.py:186 msgid "Somalia" msgstr "" -#: picard/const.py:175 +#: picard/const.py:187 msgid "Zimbabwe" msgstr "" -#: picard/const.py:176 +#: picard/const.py:188 msgid "Saudi Arabia" msgstr "" -#: picard/const.py:177 +#: picard/const.py:189 msgid "Spain" msgstr "" -#: picard/const.py:178 +#: picard/const.py:190 msgid "Eritrea" msgstr "" -#: picard/const.py:179 +#: picard/const.py:191 msgid "Moldova, Republic of" msgstr "" -#: picard/const.py:180 +#: picard/const.py:192 msgid "Madagascar" msgstr "" -#: picard/const.py:181 +#: picard/const.py:193 msgid "Morocco" msgstr "" -#: picard/const.py:182 +#: picard/const.py:194 msgid "Monaco" msgstr "" -#: picard/const.py:183 +#: picard/const.py:195 msgid "Uzbekistan" msgstr "" -#: picard/const.py:184 +#: picard/const.py:196 msgid "Myanmar" msgstr "" -#: picard/const.py:185 +#: picard/const.py:197 msgid "Mali" msgstr "" -#: picard/const.py:186 +#: picard/const.py:198 msgid "Macau" msgstr "" -#: picard/const.py:187 +#: picard/const.py:199 msgid "Mongolia" msgstr "" -#: picard/const.py:188 +#: picard/const.py:200 msgid "Marshall Islands" msgstr "" -#: picard/const.py:189 +#: picard/const.py:201 msgid "Macedonia, The Former Yugoslav Republic of" msgstr "" -#: picard/const.py:190 +#: picard/const.py:202 msgid "Mauritius" msgstr "" -#: picard/const.py:191 +#: picard/const.py:203 msgid "Malta" msgstr "" -#: picard/const.py:192 +#: picard/const.py:204 msgid "Malawi" msgstr "" -#: picard/const.py:193 +#: picard/const.py:205 msgid "Maldives" msgstr "" -#: picard/const.py:194 +#: picard/const.py:206 msgid "Martinique" msgstr "" -#: picard/const.py:195 +#: picard/const.py:207 msgid "Northern Mariana Islands" msgstr "" -#: picard/const.py:196 +#: picard/const.py:208 msgid "Montserrat" msgstr "" -#: picard/const.py:197 +#: picard/const.py:209 msgid "Mauritania" msgstr "" -#: picard/const.py:198 +#: picard/const.py:210 msgid "Uganda" msgstr "" -#: picard/const.py:199 +#: picard/const.py:211 msgid "Malaysia" msgstr "" -#: picard/const.py:200 +#: picard/const.py:212 msgid "Mexico" msgstr "" -#: picard/const.py:201 +#: picard/const.py:213 msgid "Israel" msgstr "" -#: picard/const.py:202 +#: picard/const.py:214 msgid "France" msgstr "" -#: picard/const.py:203 +#: picard/const.py:215 msgid "British Indian Ocean Territory" msgstr "" -#: picard/const.py:204 +#: picard/const.py:216 msgid "St. Helena" msgstr "" -#: picard/const.py:205 +#: picard/const.py:217 msgid "Finland" msgstr "" -#: picard/const.py:206 +#: picard/const.py:218 msgid "Fiji" msgstr "" -#: picard/const.py:207 +#: picard/const.py:219 msgid "Falkland Islands (Malvinas)" msgstr "" -#: picard/const.py:208 +#: picard/const.py:220 msgid "Micronesia, Federated States of" msgstr "" -#: picard/const.py:209 +#: picard/const.py:221 msgid "Faroe Islands" msgstr "" -#: picard/const.py:210 +#: picard/const.py:222 msgid "Nicaragua" msgstr "" -#: picard/const.py:211 +#: picard/const.py:223 msgid "Netherlands" msgstr "" -#: picard/const.py:212 +#: picard/const.py:224 msgid "Norway" msgstr "" -#: picard/const.py:213 +#: picard/const.py:225 msgid "Namibia" msgstr "" -#: picard/const.py:214 +#: picard/const.py:226 msgid "Vanuatu" msgstr "" -#: picard/const.py:215 +#: picard/const.py:227 msgid "New Caledonia" msgstr "" -#: picard/const.py:216 +#: picard/const.py:228 msgid "Niger" msgstr "" -#: picard/const.py:217 +#: picard/const.py:229 msgid "Norfolk Island" msgstr "" -#: picard/const.py:218 +#: picard/const.py:230 msgid "Nigeria" msgstr "" -#: picard/const.py:219 +#: picard/const.py:231 msgid "New Zealand" msgstr "" -#: picard/const.py:220 +#: picard/const.py:232 msgid "Zaire" msgstr "" -#: picard/const.py:221 +#: picard/const.py:233 msgid "Nepal" msgstr "" -#: picard/const.py:222 +#: picard/const.py:234 msgid "Nauru" msgstr "" -#: picard/const.py:223 +#: picard/const.py:235 msgid "Niue" msgstr "" -#: picard/const.py:224 +#: picard/const.py:236 msgid "Cook Islands" msgstr "" -#: picard/const.py:225 +#: picard/const.py:237 msgid "Cote d'Ivoire" msgstr "" -#: picard/const.py:226 +#: picard/const.py:238 msgid "Switzerland" msgstr "" -#: picard/const.py:227 +#: picard/const.py:239 msgid "Colombia" msgstr "" -#: picard/const.py:228 +#: picard/const.py:240 msgid "China" msgstr "" -#: picard/const.py:229 +#: picard/const.py:241 msgid "Cameroon" msgstr "" -#: picard/const.py:230 +#: picard/const.py:242 msgid "Chile" msgstr "" -#: picard/const.py:231 +#: picard/const.py:243 msgid "Cocos (Keeling) Islands" msgstr "" -#: picard/const.py:232 +#: picard/const.py:244 msgid "Canada" msgstr "" -#: picard/const.py:233 +#: picard/const.py:245 msgid "Congo" msgstr "" -#: picard/const.py:234 +#: picard/const.py:246 msgid "Central African Republic" msgstr "" -#: picard/const.py:235 +#: picard/const.py:247 msgid "Czech Republic" msgstr "" -#: picard/const.py:236 +#: picard/const.py:248 msgid "Cyprus" msgstr "" -#: picard/const.py:237 +#: picard/const.py:249 msgid "Christmas Island" msgstr "" -#: picard/const.py:238 +#: picard/const.py:250 msgid "Costa Rica" msgstr "" -#: picard/const.py:239 +#: picard/const.py:251 msgid "Cape Verde" msgstr "" -#: picard/const.py:240 +#: picard/const.py:252 msgid "Cuba" msgstr "" -#: picard/const.py:241 +#: picard/const.py:253 msgid "Swaziland" msgstr "" -#: picard/const.py:242 +#: picard/const.py:254 msgid "Syrian Arab Republic" msgstr "" -#: picard/const.py:243 +#: picard/const.py:255 msgid "Kyrgyzstan" msgstr "" -#: picard/const.py:244 +#: picard/const.py:256 msgid "Kenya" msgstr "" -#: picard/const.py:245 +#: picard/const.py:257 msgid "Suriname" msgstr "" -#: picard/const.py:246 +#: picard/const.py:258 msgid "Kiribati" msgstr "" -#: picard/const.py:247 +#: picard/const.py:259 msgid "Cambodia" msgstr "" -#: picard/const.py:248 +#: picard/const.py:260 msgid "Saint Kitts and Nevis" msgstr "" -#: picard/const.py:249 +#: picard/const.py:261 msgid "Comoros" msgstr "" -#: picard/const.py:250 +#: picard/const.py:262 msgid "Sao Tome and Principe" msgstr "" -#: picard/const.py:251 +#: picard/const.py:263 msgid "Slovenia" msgstr "" -#: picard/const.py:252 +#: picard/const.py:264 msgid "Kuwait" msgstr "" -#: picard/const.py:253 +#: picard/const.py:265 msgid "Senegal" msgstr "" -#: picard/const.py:254 +#: picard/const.py:266 msgid "San Marino" msgstr "" -#: picard/const.py:255 +#: picard/const.py:267 msgid "Sierra Leone" msgstr "" -#: picard/const.py:256 +#: picard/const.py:268 msgid "Seychelles" msgstr "" -#: picard/const.py:257 +#: picard/const.py:269 msgid "Kazakhstan" msgstr "" -#: picard/const.py:258 +#: picard/const.py:270 msgid "Cayman Islands" msgstr "" -#: picard/const.py:259 +#: picard/const.py:271 msgid "Singapore" msgstr "" -#: picard/const.py:260 +#: picard/const.py:272 msgid "Sweden" msgstr "" -#: picard/const.py:261 +#: picard/const.py:273 msgid "Sudan" msgstr "" -#: picard/const.py:262 +#: picard/const.py:274 msgid "Dominican Republic" msgstr "" -#: picard/const.py:263 +#: picard/const.py:275 msgid "Dominica" msgstr "" -#: picard/const.py:264 +#: picard/const.py:276 msgid "Djibouti" msgstr "" -#: picard/const.py:265 +#: picard/const.py:277 msgid "Denmark" msgstr "" -#: picard/const.py:266 +#: picard/const.py:278 msgid "Virgin Islands (British)" msgstr "" -#: picard/const.py:267 +#: picard/const.py:279 msgid "Germany" msgstr "" -#: picard/const.py:268 +#: picard/const.py:280 msgid "Yemen" msgstr "" -#: picard/const.py:269 +#: picard/const.py:281 msgid "Algeria" msgstr "" -#: picard/const.py:270 +#: picard/const.py:282 msgid "United States" msgstr "" -#: picard/const.py:271 +#: picard/const.py:283 msgid "Uruguay" msgstr "" -#: picard/const.py:272 +#: picard/const.py:284 msgid "Mayotte" msgstr "" -#: picard/const.py:273 +#: picard/const.py:285 msgid "United States Minor Outlying Islands" msgstr "" -#: picard/const.py:274 +#: picard/const.py:286 msgid "Lebanon" msgstr "" -#: picard/const.py:275 +#: picard/const.py:287 msgid "Saint Lucia" msgstr "" -#: picard/const.py:276 +#: picard/const.py:288 msgid "Lao People's Democratic Republic" msgstr "" -#: picard/const.py:277 +#: picard/const.py:289 msgid "Tuvalu" msgstr "" -#: picard/const.py:278 +#: picard/const.py:290 msgid "Taiwan" msgstr "" -#: picard/const.py:279 +#: picard/const.py:291 msgid "Trinidad and Tobago" msgstr "" -#: picard/const.py:280 +#: picard/const.py:292 msgid "Turkey" msgstr "" -#: picard/const.py:281 +#: picard/const.py:293 msgid "Sri Lanka" msgstr "" -#: picard/const.py:282 +#: picard/const.py:294 msgid "Liechtenstein" msgstr "" -#: picard/const.py:283 +#: picard/const.py:295 msgid "Latvia" msgstr "" -#: picard/const.py:284 +#: picard/const.py:296 msgid "Tonga" msgstr "" -#: picard/const.py:285 +#: picard/const.py:297 msgid "Lithuania" msgstr "" -#: picard/const.py:286 +#: picard/const.py:298 msgid "Luxembourg" msgstr "" -#: picard/const.py:287 +#: picard/const.py:299 msgid "Liberia" msgstr "" -#: picard/const.py:288 +#: picard/const.py:300 msgid "Lesotho" msgstr "" -#: picard/const.py:289 +#: picard/const.py:301 msgid "Thailand" msgstr "" -#: picard/const.py:290 +#: picard/const.py:302 msgid "French Southern Territories" msgstr "" -#: picard/const.py:291 +#: picard/const.py:303 msgid "Togo" msgstr "" -#: picard/const.py:292 +#: picard/const.py:304 msgid "Chad" msgstr "" -#: picard/const.py:293 +#: picard/const.py:305 msgid "Turks and Caicos Islands" msgstr "" -#: picard/const.py:294 +#: picard/const.py:306 msgid "Libyan Arab Jamahiriya" msgstr "" -#: picard/const.py:295 +#: picard/const.py:307 msgid "Vatican City State (Holy See)" msgstr "" -#: picard/const.py:296 +#: picard/const.py:308 msgid "Saint Vincent and The Grenadines" msgstr "" -#: picard/const.py:297 +#: picard/const.py:309 msgid "United Arab Emirates" msgstr "" -#: picard/const.py:298 +#: picard/const.py:310 msgid "Andorra" msgstr "" -#: picard/const.py:299 +#: picard/const.py:311 msgid "Antigua and Barbuda" msgstr "" -#: picard/const.py:300 +#: picard/const.py:312 msgid "Afghanistan" msgstr "" -#: picard/const.py:301 +#: picard/const.py:313 msgid "Anguilla" msgstr "" -#: picard/const.py:302 +#: picard/const.py:314 msgid "Virgin Islands (U.S.)" msgstr "" -#: picard/const.py:303 +#: picard/const.py:315 msgid "Iceland" msgstr "" -#: picard/const.py:304 +#: picard/const.py:316 msgid "Iran (Islamic Republic of)" msgstr "" -#: picard/const.py:305 +#: picard/const.py:317 msgid "Armenia" msgstr "" -#: picard/const.py:306 +#: picard/const.py:318 msgid "Albania" msgstr "" -#: picard/const.py:307 +#: picard/const.py:319 msgid "Angola" msgstr "" -#: picard/const.py:308 +#: picard/const.py:320 msgid "Netherlands Antilles" msgstr "" -#: picard/const.py:309 +#: picard/const.py:321 msgid "Antarctica" msgstr "" -#: picard/const.py:310 +#: picard/const.py:322 msgid "American Samoa" msgstr "" -#: picard/const.py:311 +#: picard/const.py:323 msgid "Argentina" msgstr "" -#: picard/const.py:312 +#: picard/const.py:324 msgid "Australia" msgstr "" -#: picard/const.py:313 +#: picard/const.py:325 msgid "Austria" msgstr "" -#: picard/const.py:314 +#: picard/const.py:326 msgid "Aruba" msgstr "" -#: picard/const.py:315 +#: picard/const.py:327 msgid "India" msgstr "" -#: picard/const.py:316 +#: picard/const.py:328 msgid "Tanzania, United Republic of" msgstr "" -#: picard/const.py:317 +#: picard/const.py:329 msgid "Azerbaijan" msgstr "" -#: picard/const.py:318 +#: picard/const.py:330 msgid "Ireland" msgstr "" -#: picard/const.py:319 +#: picard/const.py:331 msgid "Indonesia" msgstr "" -#: picard/const.py:320 +#: picard/const.py:332 msgid "Ukraine" msgstr "" -#: picard/const.py:321 +#: picard/const.py:333 msgid "Qatar" msgstr "" -#: picard/const.py:322 +#: picard/const.py:334 msgid "Mozambique" msgstr "" -#: picard/const.py:323 +#: picard/const.py:335 msgid "Bosnia and Herzegovina" msgstr "" -#: picard/const.py:324 +#: picard/const.py:336 msgid "Congo, The Democratic Republic of the" msgstr "" -#: picard/const.py:325 +#: picard/const.py:337 msgid "Serbia and Montenegro (historical, 2003-2006)" msgstr "" -#: picard/const.py:326 +#: picard/const.py:338 msgid "Serbia" msgstr "" -#: picard/const.py:327 +#: picard/const.py:339 msgid "Montenegro" msgstr "" -#: picard/const.py:328 +#: picard/const.py:340 msgid "Croatia" msgstr "" -#: picard/const.py:329 +#: picard/const.py:341 msgid "Korea (North), Democratic People's Republic of" msgstr "" -#: picard/const.py:330 +#: picard/const.py:342 msgid "Korea (South), Republic of" msgstr "" -#: picard/const.py:331 +#: picard/const.py:343 msgid "Slovakia" msgstr "" -#: picard/const.py:332 +#: picard/const.py:344 msgid "Soviet Union (historical, 1922-1991)" msgstr "" -#: picard/const.py:333 +#: picard/const.py:345 msgid "East Timor" msgstr "" -#: picard/const.py:334 +#: picard/const.py:346 msgid "Czechoslovakia (historical, 1918-1992)" msgstr "" -#: picard/const.py:335 +#: picard/const.py:347 msgid "Europe" msgstr "" -#: picard/const.py:336 +#: picard/const.py:348 msgid "East Germany (historical, 1949-1990)" msgstr "" -#: picard/const.py:337 +#: picard/const.py:349 msgid "[Unknown Country]" msgstr "" -#: picard/const.py:338 +#: picard/const.py:350 msgid "[Worldwide]" msgstr "" -#: picard/const.py:339 +#: picard/const.py:351 msgid "Yugoslavia (historical, 1918-1992)" msgstr "" -#: picard/const.py:351 +#: picard/const.py:363 msgid "Danish" msgstr "" -#: picard/const.py:352 +#: picard/const.py:364 msgid "German" msgstr "" -#: picard/const.py:354 +#: picard/const.py:366 msgid "English" msgstr "" -#: picard/const.py:355 +#: picard/const.py:367 msgid "English (Canada)" msgstr "" -#: picard/const.py:356 +#: picard/const.py:368 msgid "English (UK)" msgstr "" -#: picard/const.py:358 +#: picard/const.py:370 msgid "Spanish" msgstr "" -#: picard/const.py:359 +#: picard/const.py:371 msgid "Estonian" msgstr "" -#: picard/const.py:361 +#: picard/const.py:373 msgid "Finnish" msgstr "" -#: picard/const.py:363 +#: picard/const.py:375 msgid "French" msgstr "" -#: picard/const.py:372 +#: picard/const.py:384 msgid "Italian" msgstr "" -#: picard/const.py:379 +#: picard/const.py:391 msgid "Dutch" msgstr "" -#: picard/const.py:381 +#: picard/const.py:393 msgid "Polish" msgstr "" -#: picard/const.py:383 +#: picard/const.py:395 msgid "Brazilian Portuguese" msgstr "" -#: picard/const.py:390 +#: picard/const.py:402 msgid "Swedish" msgstr "" -#: picard/coverart.py:99 +#: picard/coverart.py:98 #, python-format msgid "Coverart %s downloaded" msgstr "" -#: picard/coverart.py:260 +#: picard/coverart.py:259 #, python-format msgid "Downloading http://%s:%i%s" msgstr "" @@ -1461,19 +1503,23 @@ msgid "Looking up the metadata for file %s..." msgstr "" #: picard/releasegroup.py:64 +msgid "[no barcode]" +msgstr "" + +#: picard/releasegroup.py:84 msgid "[no release info]" msgstr "" -#: picard/tagger.py:348 +#: picard/tagger.py:298 #, python-format msgid "Loading directory %s" msgstr "" -#: picard/tagger.py:484 +#: picard/tagger.py:434 msgid "CD Lookup Error" msgstr "" -#: picard/tagger.py:485 +#: picard/tagger.py:435 #, python-format msgid "" "Error while reading CD:\n" @@ -1507,7 +1553,7 @@ msgstr "" msgid "Catalog #s" msgstr "" -#: picard/ui/cdlookup.py:35 picard/util/tags.py:77 +#: picard/ui/cdlookup.py:35 picard/util/tags.py:78 msgid "Barcode" msgstr "" @@ -1594,19 +1640,19 @@ msgstr "" msgid "&Info" msgstr "" -#: picard/ui/infostatus.py:52 +#: picard/ui/infostatus.py:51 msgid "Files" msgstr "" -#: picard/ui/infostatus.py:53 +#: picard/ui/infostatus.py:52 msgid "Albums" msgstr "" -#: picard/ui/infostatus.py:54 +#: picard/ui/infostatus.py:53 msgid "Pending files" msgstr "" -#: picard/ui/infostatus.py:55 +#: picard/ui/infostatus.py:54 msgid "Pending requests" msgstr "" @@ -1614,7 +1660,7 @@ msgstr "" msgid "Title" msgstr "" -#: picard/ui/itemviews.py:95 picard/util/tags.py:86 +#: picard/ui/itemviews.py:95 picard/util/tags.py:87 msgid "Length" msgstr "" @@ -1634,31 +1680,31 @@ msgstr "" msgid "Loading..." msgstr "" -#: picard/ui/itemviews.py:331 +#: picard/ui/itemviews.py:333 msgid "Collections" msgstr "" -#: picard/ui/itemviews.py:334 +#: picard/ui/itemviews.py:336 msgid "&Plugins" msgstr "" -#: picard/ui/itemviews.py:510 +#: picard/ui/itemviews.py:512 msgid "file view" msgstr "" -#: picard/ui/itemviews.py:511 +#: picard/ui/itemviews.py:513 msgid "Contains unmatched files and clusters" msgstr "" -#: picard/ui/itemviews.py:531 +#: picard/ui/itemviews.py:533 msgid "Clusters" msgstr "" -#: picard/ui/itemviews.py:540 +#: picard/ui/itemviews.py:542 msgid "album view" msgstr "" -#: picard/ui/itemviews.py:541 +#: picard/ui/itemviews.py:543 msgid "Contains albums and matched files" msgstr "" @@ -1670,34 +1716,34 @@ msgstr "" msgid "Status History" msgstr "" -#: picard/ui/mainwindow.py:77 +#: picard/ui/mainwindow.py:78 msgid "MusicBrainz Picard" msgstr "" -#: picard/ui/mainwindow.py:158 +#: picard/ui/mainwindow.py:159 msgid "Unsaved Changes" msgstr "" -#: picard/ui/mainwindow.py:159 +#: picard/ui/mainwindow.py:160 msgid "Are you sure you want to quit Picard?" msgstr "" -#: picard/ui/mainwindow.py:160 +#: picard/ui/mainwindow.py:161 #, python-format msgid "There is %d unsaved file. Closing Picard will lose all unsaved changes." msgid_plural "There are %d unsaved files. Closing Picard will lose all unsaved changes." msgstr[0] "" msgstr[1] "" -#: picard/ui/mainwindow.py:167 +#: picard/ui/mainwindow.py:168 msgid "&Quit Picard" msgstr "" -#: picard/ui/mainwindow.py:219 +#: picard/ui/mainwindow.py:220 msgid "Ready" msgstr "" -#: picard/ui/mainwindow.py:223 +#: picard/ui/mainwindow.py:224 msgid "" "Picard listens on a port to integrate with your browser and downloads " "release information when you click the \"Tagger\" buttons on the " @@ -1955,47 +2001,17 @@ msgstr "" msgid "All Supported Formats" msgstr "" -#: picard/ui/mainwindow.py:673 picard/ui/mainwindow.py:682 -msgid "Various Artists file naming scheme removal" -msgstr "" - -#: picard/ui/mainwindow.py:674 -msgid "" -"The separate file naming scheme for various artists albums has been\n" -"removed in this version of Picard. You currently do not use the this " -"option,\n" -"but have a separate file naming scheme defined. Do you want to remove it " -"or\n" -"merge it with your file naming scheme for single artist albums?" -msgstr "" - -#: picard/ui/mainwindow.py:678 -msgid "Merge" -msgstr "" - -#: picard/ui/mainwindow.py:678 picard/ui/metadatabox.py:254 -msgid "Remove" -msgstr "" - -#: picard/ui/mainwindow.py:683 -msgid "" -"The separate file naming scheme for various artists albums has been\n" -"removed in this version of Picard. Your file naming scheme has " -"automatically\n" -"been merged with that of single artist albums." -msgstr "" - -#: picard/ui/mainwindow.py:727 +#: picard/ui/mainwindow.py:710 msgid "Configuration Required" msgstr "" -#: picard/ui/mainwindow.py:728 +#: picard/ui/mainwindow.py:711 msgid "" "Audio fingerprinting is not yet configured. Would you like to configure " "it now?" msgstr "" -#: picard/ui/mainwindow.py:806 picard/ui/mainwindow.py:813 +#: picard/ui/mainwindow.py:789 picard/ui/mainwindow.py:796 #, python-format msgid " (Error: %s)" msgstr "" @@ -2084,7 +2100,7 @@ msgid "OK" msgstr "" #: picard/ui/ui_cdlookup.py:69 -msgid " Lookup manually " +msgid "Lookup manually" msgstr "" #: picard/ui/ui_cdlookup.py:70 @@ -2276,7 +2292,7 @@ msgstr "" msgid "Username:" msgstr "" -#: picard/ui/ui_options_general.py:98 picard/ui/options/general.py:29 +#: picard/ui/ui_options_general.py:98 picard/ui/options/general.py:30 msgid "General" msgstr "" @@ -2332,7 +2348,7 @@ msgstr "" msgid "Minimal similarity for cluster lookups:" msgstr "" -#: picard/ui/ui_options_metadata.py:114 picard/ui/options/metadata.py:30 +#: picard/ui/ui_options_metadata.py:114 picard/ui/options/metadata.py:29 msgid "Metadata" msgstr "" @@ -2401,7 +2417,7 @@ msgstr "" msgid "Name" msgstr "" -#: picard/ui/ui_options_plugins.py:133 picard/util/tags.py:37 +#: picard/ui/ui_options_plugins.py:133 picard/util/tags.py:38 msgid "Version" msgstr "" @@ -2457,7 +2473,7 @@ msgstr "" msgid "EP" msgstr "" -#: picard/ui/ui_options_releases.py:219 picard/util/tags.py:68 +#: picard/ui/ui_options_releases.py:219 msgid "Compilation" msgstr "" @@ -2572,7 +2588,7 @@ msgid "" msgstr "" #: picard/ui/ui_options_tags.py:172 -msgid "Tags are separated by spaces, and are case-sensitive." +msgid "Tags are separated by commas, and are case-sensitive." msgstr "" #: picard/ui/ui_options_tags.py:173 @@ -2650,24 +2666,24 @@ msgstr "" msgid "&Cancel" msgstr "" -#: picard/ui/options/about.py:32 +#: picard/ui/options/about.py:33 msgid "About" msgstr "" -#: picard/ui/options/about.py:47 +#: picard/ui/options/about.py:48 msgid "is not installed" msgstr "" -#: picard/ui/options/about.py:56 +#: picard/ui/options/about.py:59 msgid "translator-credits" msgstr "" -#: picard/ui/options/about.py:59 +#: picard/ui/options/about.py:62 #, python-format msgid "
Translated to LANG by %s" msgstr "" -#: picard/ui/options/about.py:63 +#: picard/ui/options/about.py:66 #, python-format msgid "" "

\n" -"

Donate " -"now!

\n" +"

Donate now!

" +"\n" "

Credits
\n" "Copyright © 2004-2011 Robert Kaye, Lukáš Lalinský and others" "%(translator-credits)s

\n" -"

http://musicbrainz.org/doc/MusicBrainz_Picard

" -"\n" +"

%(picard-doc-" +"url)s

\n" msgstr "" #: picard/ui/options/advanced.py:26 @@ -2715,11 +2730,11 @@ msgstr "" msgid "System default" msgstr "" -#: picard/ui/options/interface.py:92 +#: picard/ui/options/interface.py:96 msgid "Language changed" msgstr "" -#: picard/ui/options/interface.py:92 +#: picard/ui/options/interface.py:96 msgid "" "You have changed the interface language. You have to restart Picard in " "order for the change to take effect." @@ -2749,15 +2764,15 @@ msgstr "" msgid "File naming" msgstr "" -#: picard/ui/options/renaming.py:166 +#: picard/ui/options/renaming.py:184 msgid "Error" msgstr "" -#: picard/ui/options/renaming.py:166 +#: picard/ui/options/renaming.py:184 msgid "The location to move files to must not be empty." msgstr "" -#: picard/ui/options/renaming.py:176 +#: picard/ui/options/renaming.py:194 msgid "The file naming format must not be empty." msgstr "" @@ -2769,62 +2784,62 @@ msgstr "" msgid "Script Error" msgstr "" -#: picard/util/bytes2human.py:35 +#: picard/util/bytes2human.py:33 #, python-format msgid "%s B" msgstr "" -#: picard/util/bytes2human.py:36 +#: picard/util/bytes2human.py:34 #, python-format msgid "%s kB" msgstr "" -#: picard/util/bytes2human.py:37 +#: picard/util/bytes2human.py:35 #, python-format msgid "%s KiB" msgstr "" -#: picard/util/bytes2human.py:38 +#: picard/util/bytes2human.py:36 #, python-format msgid "%s MB" msgstr "" -#: picard/util/bytes2human.py:39 +#: picard/util/bytes2human.py:37 #, python-format msgid "%s MiB" msgstr "" -#: picard/util/bytes2human.py:40 +#: picard/util/bytes2human.py:38 #, python-format msgid "%s GB" msgstr "" -#: picard/util/bytes2human.py:41 +#: picard/util/bytes2human.py:39 #, python-format msgid "%s GiB" msgstr "" -#: picard/util/bytes2human.py:42 +#: picard/util/bytes2human.py:40 #, python-format msgid "%s TB" msgstr "" -#: picard/util/bytes2human.py:43 +#: picard/util/bytes2human.py:41 #, python-format msgid "%s TiB" msgstr "" -#: picard/util/bytes2human.py:44 +#: picard/util/bytes2human.py:42 #, python-format msgid "%s PB" msgstr "" -#: picard/util/bytes2human.py:45 +#: picard/util/bytes2human.py:43 #, python-format msgid "%s PiB" msgstr "" -#: picard/util/bytes2human.py:86 +#: picard/util/bytes2human.py:84 #, python-format msgid "%s " msgstr "" @@ -2834,242 +2849,250 @@ msgid "Original Release Date" msgstr "" #: picard/util/tags.py:26 -msgid "Album Artist" +msgid "Original Year" msgstr "" #: picard/util/tags.py:27 -msgid "Track Number" +msgid "Album Artist" msgstr "" #: picard/util/tags.py:28 -msgid "Total Tracks" +msgid "Track Number" msgstr "" #: picard/util/tags.py:29 -msgid "Disc Number" +msgid "Total Tracks" msgstr "" #: picard/util/tags.py:30 -msgid "Total Discs" +msgid "Disc Number" msgstr "" #: picard/util/tags.py:31 -msgid "Album Artist Sort Order" +msgid "Total Discs" msgstr "" #: picard/util/tags.py:32 -msgid "Artist Sort Order" +msgid "Album Artist Sort Order" msgstr "" #: picard/util/tags.py:33 -msgid "Title Sort Order" +msgid "Artist Sort Order" msgstr "" #: picard/util/tags.py:34 -msgid "Album Sort Order" +msgid "Title Sort Order" msgstr "" #: picard/util/tags.py:35 -msgid "ASIN" +msgid "Album Sort Order" msgstr "" #: picard/util/tags.py:36 +msgid "ASIN" +msgstr "" + +#: picard/util/tags.py:37 msgid "Grouping" msgstr "" -#: picard/util/tags.py:38 +#: picard/util/tags.py:39 msgid "ISRC" msgstr "" -#: picard/util/tags.py:39 +#: picard/util/tags.py:40 msgid "Mood" msgstr "" -#: picard/util/tags.py:40 +#: picard/util/tags.py:41 msgid "BPM" msgstr "" -#: picard/util/tags.py:41 +#: picard/util/tags.py:42 msgid "Copyright" msgstr "" -#: picard/util/tags.py:42 +#: picard/util/tags.py:43 msgid "License" msgstr "" -#: picard/util/tags.py:43 +#: picard/util/tags.py:44 msgid "Composer" msgstr "" -#: picard/util/tags.py:44 +#: picard/util/tags.py:45 msgid "Writer" msgstr "" -#: picard/util/tags.py:45 +#: picard/util/tags.py:46 msgid "Conductor" msgstr "" -#: picard/util/tags.py:46 +#: picard/util/tags.py:47 msgid "Lyricist" msgstr "" -#: picard/util/tags.py:47 +#: picard/util/tags.py:48 msgid "Arranger" msgstr "" -#: picard/util/tags.py:48 +#: picard/util/tags.py:49 msgid "Producer" msgstr "" -#: picard/util/tags.py:49 +#: picard/util/tags.py:50 msgid "Engineer" msgstr "" -#: picard/util/tags.py:50 +#: picard/util/tags.py:51 msgid "Subtitle" msgstr "" -#: picard/util/tags.py:51 +#: picard/util/tags.py:52 msgid "Disc Subtitle" msgstr "" -#: picard/util/tags.py:52 +#: picard/util/tags.py:53 msgid "Remixer" msgstr "" -#: picard/util/tags.py:53 +#: picard/util/tags.py:54 msgid "MusicBrainz Recording Id" msgstr "" -#: picard/util/tags.py:54 +#: picard/util/tags.py:55 msgid "MusicBrainz Track Id" msgstr "" -#: picard/util/tags.py:55 +#: picard/util/tags.py:56 msgid "MusicBrainz Release Id" msgstr "" -#: picard/util/tags.py:56 +#: picard/util/tags.py:57 msgid "MusicBrainz Artist Id" msgstr "" -#: picard/util/tags.py:57 +#: picard/util/tags.py:58 msgid "MusicBrainz Release Artist Id" msgstr "" -#: picard/util/tags.py:58 +#: picard/util/tags.py:59 msgid "MusicBrainz Work Id" msgstr "" -#: picard/util/tags.py:59 +#: picard/util/tags.py:60 msgid "MusicBrainz Release Group Id" msgstr "" -#: picard/util/tags.py:60 +#: picard/util/tags.py:61 msgid "MusicBrainz Disc Id" msgstr "" -#: picard/util/tags.py:61 +#: picard/util/tags.py:62 msgid "MusicBrainz Sort Name" msgstr "" -#: picard/util/tags.py:62 +#: picard/util/tags.py:63 msgid "MusicIP PUID" msgstr "" -#: picard/util/tags.py:63 +#: picard/util/tags.py:64 msgid "MusicIP Fingerprint" msgstr "" -#: picard/util/tags.py:64 +#: picard/util/tags.py:65 msgid "AcoustID" msgstr "" -#: picard/util/tags.py:65 +#: picard/util/tags.py:66 msgid "AcoustID Fingerprint" msgstr "" -#: picard/util/tags.py:66 +#: picard/util/tags.py:67 msgid "Disc Id" msgstr "" -#: picard/util/tags.py:67 +#: picard/util/tags.py:68 msgid "Website" msgstr "" #: picard/util/tags.py:69 -msgid "Comment" +msgid "Compilation (iTunes)" msgstr "" #: picard/util/tags.py:70 -msgid "Genre" +msgid "Comment" msgstr "" #: picard/util/tags.py:71 -msgid "Encoded By" +msgid "Genre" msgstr "" #: picard/util/tags.py:72 -msgid "Performer" +msgid "Encoded By" msgstr "" #: picard/util/tags.py:73 -msgid "Release Type" +msgid "Performer" msgstr "" #: picard/util/tags.py:74 -msgid "Release Status" +msgid "Release Type" msgstr "" #: picard/util/tags.py:75 -msgid "Release Country" +msgid "Release Status" msgstr "" #: picard/util/tags.py:76 +msgid "Release Country" +msgstr "" + +#: picard/util/tags.py:77 msgid "Record Label" msgstr "" -#: picard/util/tags.py:78 +#: picard/util/tags.py:79 msgid "Catalog Number" msgstr "" -#: picard/util/tags.py:79 +#: picard/util/tags.py:80 msgid "Format" msgstr "" -#: picard/util/tags.py:80 +#: picard/util/tags.py:81 msgid "DJ-Mixer" msgstr "" -#: picard/util/tags.py:81 +#: picard/util/tags.py:82 msgid "Media" msgstr "" -#: picard/util/tags.py:82 +#: picard/util/tags.py:83 msgid "Lyrics" msgstr "" -#: picard/util/tags.py:83 +#: picard/util/tags.py:84 msgid "Mixer" msgstr "" -#: picard/util/tags.py:84 +#: picard/util/tags.py:85 msgid "Language" msgstr "" -#: picard/util/tags.py:85 +#: picard/util/tags.py:86 msgid "Script" msgstr "" -#: picard/util/tags.py:87 +#: picard/util/tags.py:88 msgid "Rating" msgstr "" -#: picard/util/webbrowser2.py:88 +#: picard/util/webbrowser2.py:90 msgid "Web Browser Error" msgstr "" -#: picard/util/webbrowser2.py:88 +#: picard/util/webbrowser2.py:90 #, python-format msgid "" "Error while launching a web browser:\n" diff --git a/setup.py b/setup.py index 306869719..134d6f916 100755 --- a/setup.py +++ b/setup.py @@ -89,8 +89,6 @@ class picard_test(Command): self.verbosity = int(self.verbosity) def run(self): - import os.path - import glob import unittest names = [] diff --git a/test/test_bytes2human.py b/test/test_bytes2human.py index 7a37737c2..9f8d53305 100644 --- a/test/test_bytes2human.py +++ b/test/test_bytes2human.py @@ -30,8 +30,11 @@ class Testbytes2human(unittest.TestCase): self.assertEqual(bytes2human.binary(45682), '44.6 KiB') self.assertEqual(bytes2human.binary(-45682), '-44.6 KiB') + self.assertEqual(bytes2human.binary(-45682, 2), '-44.61 KiB') self.assertEqual(bytes2human.decimal(45682), '45.7 kB') + self.assertEqual(bytes2human.decimal(45682, 2), '45.68 kB') self.assertEqual(bytes2human.decimal(9223372036854775807), '9223.4 PB') + self.assertEqual(bytes2human.decimal(9223372036854775807, 3), '9223.372 PB') self.assertEqual(bytes2human.decimal(123.6), '123 B') self.assertRaises(ValueError, bytes2human.decimal, 'xxx') self.assertRaises(ValueError, bytes2human.decimal, '123.6') diff --git a/test/test_util_filenaming.py b/test/test_util_filenaming.py index b7b2056fa..e04441ca5 100644 --- a/test/test_util_filenaming.py +++ b/test/test_util_filenaming.py @@ -17,7 +17,7 @@ class ShortFilenameTest(unittest.TestCase): def test_bmp_unicode_on_unicode_fs(self): char = u"\N{LATIN SMALL LETTER SHARP S}" fn = make_short_filename(self.root, os.path.join(*[char * 120] * 2)) - self.assertEqual(fn, os.path.join(self.root, *[char * 120] * 2)) + self.assertEqual(fn, os.path.join(*[char * 120] * 2)) @unittest.skipUnless(sys.platform not in ("win32", "darwin"), "non-windows, non-osx test") def test_bmp_unicode_on_nix(self): @@ -25,28 +25,28 @@ class ShortFilenameTest(unittest.TestCase): max_len = 255 divisor = len(char.encode(sys.getfilesystemencoding())) fn = make_short_filename(self.root, os.path.join(*[char * 200] * 2)) - self.assertEqual(fn, os.path.join(self.root, *[char * (max_len // divisor)] * 2)) + self.assertEqual(fn, os.path.join(*[char * (max_len // divisor)] * 2)) @unittest.skipUnless(sys.platform == "darwin", "os x test") def test_precomposed_unicode_on_osx(self): char = u"\N{LATIN SMALL LETTER A WITH BREVE}" max_len = 255 fn = make_short_filename(self.root, os.path.join(*[char * 200] * 2)) - self.assertEqual(fn, os.path.join(self.root, *[char * (max_len // 2)] * 2)) + self.assertEqual(fn, os.path.join(*[char * (max_len // 2)] * 2)) @unittest.skipUnless(sys.platform == "win32", "windows test") def test_nonbmp_unicode_on_windows(self): char = u"\N{MUSICAL SYMBOL G CLEF}" remaining = 259 - (3 + 10 + 1 + 200 + 1) fn = make_short_filename(self.root, os.path.join(*[char * 100] * 2)) - self.assertEqual(fn, os.path.join(self.root, char * 100, char * (remaining // 2))) + self.assertEqual(fn, os.path.join(char * 100, char * (remaining // 2))) @unittest.skipUnless(sys.platform == "darwin", "os x test") def test_nonbmp_unicode_on_osx(self): char = u"\N{MUSICAL SYMBOL G CLEF}" max_len = 255 fn = make_short_filename(self.root, os.path.join(*[char * 200] * 2)) - self.assertEqual(fn, os.path.join(self.root, *[char * (max_len // 2)] * 2)) + self.assertEqual(fn, os.path.join(*[char * (max_len // 2)] * 2)) @unittest.skipUnless(sys.platform not in ("win32", "darwin"), "non-windows, non-osx test") def test_nonbmp_unicode_on_nix(self): @@ -54,7 +54,7 @@ class ShortFilenameTest(unittest.TestCase): max_len = 255 divisor = len(char.encode(sys.getfilesystemencoding())) fn = make_short_filename(self.root, os.path.join(*[char * 100] * 2)) - self.assertEqual(fn, os.path.join(self.root, *[char * (max_len // divisor)] * 2)) + self.assertEqual(fn, os.path.join(*[char * (max_len // divisor)] * 2)) @unittest.skipUnless(sys.platform not in ("win32", "darwin"), "non-windows, non-osx test") def test_nonbmp_unicode_on_nix_with_windows_compat(self): @@ -63,45 +63,49 @@ class ShortFilenameTest(unittest.TestCase): remaining = 259 - (3 + 10 + 1 + 200 + 1) divisor = len(char.encode(sys.getfilesystemencoding())) fn = make_short_filename(self.root, os.path.join(*[char * 100] * 2), win_compat=True) - self.assertEqual(fn, os.path.join(self.root, char * (max_len // divisor), char * (remaining // 2))) + self.assertEqual(fn, os.path.join(char * (max_len // divisor), char * (remaining // 2))) def test_windows_shortening(self): fn = make_short_filename(self.root, os.path.join("a" * 200, "b" * 200, "c" * 200 + ".ext"), win_compat=True) - self.assertEqual(fn, os.path.join(self.root, "a" * 116, "b" * 116, "c" * 7 + ".ext")) + self.assertEqual(fn, os.path.join("a" * 116, "b" * 116, "c" * 7 + ".ext")) @unittest.skipUnless(sys.platform != "win32", "non-windows test") def test_windows_shortening_with_ancestor_on_nix(self): + root = os.path.join(self.root, "w" * 10, "x" * 10, "y" * 9, "z" * 9) fn = make_short_filename( - os.path.join(self.root, "w" * 10, "x" * 10, "y" * 9, "z" * 9), os.path.join("b" * 200, "c" * 200, "d" * 200 + ".ext"), + root, os.path.join("b" * 200, "c" * 200, "d" * 200 + ".ext"), win_compat=True, relative_to = self.root) - self.assertEqual(fn, os.path.join(self.root, "w" * 10, "x" * 10, "y" * 9, "z" * 9, "b" * 100, "c" * 100, "d" * 7 + ".ext")) + self.assertEqual(fn, os.path.join("b" * 100, "c" * 100, "d" * 7 + ".ext")) def test_windows_node_maxlength_shortening(self): max_len = 226 remaining = 259 - (3 + 10 + 1 + max_len + 1) fn = make_short_filename(self.root, os.path.join("a" * 300, "b" * 100 + ".ext"), win_compat=True) - self.assertEqual(fn, os.path.join(self.root, "a" * max_len, "b" * (remaining - 4) + ".ext")) + self.assertEqual(fn, os.path.join("a" * max_len, "b" * (remaining - 4) + ".ext")) def test_windows_selective_shortening(self): root = self.root + "x" * (44 - 10 - 3) fn = make_short_filename(root, os.path.join( os.path.join(*["a" * 9] * 10 + ["b" * 15] * 10), "c" * 10), win_compat=True) - self.assertEqual(fn, os.path.join(root, os.path.join(*["a" * 9] * 10 + ["b" * 9] * 10), "c" * 10)) + self.assertEqual(fn, os.path.join(os.path.join(*["a" * 9] * 10 + ["b" * 9] * 10), "c" * 10)) def test_windows_shortening_not_needed(self): - fn = make_short_filename(self.root + "x" * 33, os.path.join( + root = self.root + "x" * 33 + fn = make_short_filename(root, os.path.join( os.path.join(*["a" * 9] * 20), "b" * 10), win_compat=True) - self.assertEqual(fn, os.path.join(self.root + "x" * 33, os.path.join(*["a" * 9] * 20), "b" * 10)) + self.assertEqual(fn, os.path.join(os.path.join(*["a" * 9] * 20), "b" * 10)) def test_windows_path_too_long(self): + root = self.root + "x" * 230 self.assertRaises(IOError, make_short_filename, - self.root + "x" * 230, os.path.join("a", "b", "c", "d"), win_compat=True) + root, os.path.join("a", "b", "c", "d"), win_compat=True) def test_windows_path_not_too_long(self): - fn = make_short_filename(self.root + "x" * 230, os.path.join("a", "b", "c"), win_compat=True) - self.assertEqual(fn, os.path.join(self.root + "x" * 230, "a", "b", "c")) + root = self.root + "x" * 230 + fn = make_short_filename(root, os.path.join("a", "b", "c"), win_compat=True) + self.assertEqual(fn, os.path.join("a", "b", "c")) def test_whitespace(self): fn = make_short_filename(self.root, os.path.join("a1234567890 ", " b1234567890 ")) - self.assertEqual(fn, os.path.join(self.root, "a1234567890", "b1234567890")) + self.assertEqual(fn, os.path.join("a1234567890", "b1234567890")) diff --git a/test/test_utils.py b/test/test_utils.py index 12f863fe2..13462a7a0 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import os.path import unittest from picard import util @@ -119,3 +120,18 @@ class SaveReleaseTypeScoresTest(unittest.TestCase): self.assertTrue("Single 0.50" in saved_scores) self.assertTrue("Other 0.00" in saved_scores) self.assertEqual(6, len(saved_scores.split())) + + +class HiddenPathTest(unittest.TestCase): + + def test(self): + self.assertEqual(util.is_hidden_path('/a/.b/c.mp3'), True) + self.assertEqual(util.is_hidden_path('/a/b/c.mp3'), False) + self.assertEqual(util.is_hidden_path('/a/.b/.c.mp3'), True) + self.assertEqual(util.is_hidden_path('/a/b/.c.mp3'), True) + self.assertEqual(util.is_hidden_path('c.mp3'), False) + self.assertEqual(util.is_hidden_path('.c.mp3'), True) + self.assertEqual(util.is_hidden_path('/a/./c.mp3'), False) + self.assertEqual(util.is_hidden_path('/a/./.c.mp3'), True) + self.assertEqual(util.is_hidden_path('/a/../c.mp3'), False) + self.assertEqual(util.is_hidden_path('/a/../.c.mp3'), True) diff --git a/test/test_versions.py b/test/test_versions.py index 69d5a813a..a473fb771 100644 --- a/test/test_versions.py +++ b/test/test_versions.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- import unittest -from picard import version_to_string, version_from_string +from picard import (version_to_string, + version_from_string, + VersionError) class VersionsTest(unittest.TestCase): @@ -23,8 +25,8 @@ class VersionsTest(unittest.TestCase): def test_version_conv_4(self): l, s = (1, 0, 2, '', 0), '1.0.2' - self.assertRaises(AssertionError, version_to_string, (l)) - self.assertRaises(AttributeError, version_from_string, (s)) + self.assertRaises(VersionError, version_to_string, (l)) + self.assertRaises(VersionError, version_from_string, (s)) def test_version_conv_5(self): l, s = (999, 999, 999, 'dev', 999), '999.999.999dev999' @@ -32,9 +34,8 @@ class VersionsTest(unittest.TestCase): self.assertEqual(l, version_from_string(s)) def test_version_conv_6(self): - self.assertRaises(TypeError, version_to_string, ('1', 0, 2, 'final', 0)) - self.assertRaises(AssertionError, version_to_string, (1, 0)) - self.assertRaises(TypeError, version_from_string, 1) + l = (1, 0, 2, 'xx', 0) + self.assertRaises(VersionError, version_to_string, (l)) def test_version_conv_7(self): l, s = (1, 1, 0, 'final', 0), '1.1' @@ -51,3 +52,19 @@ class VersionsTest(unittest.TestCase): def test_version_conv_10(self): l, s = (1, 1, 0, 'dev', 0), '1.1.0dev0' self.assertEqual(version_to_string(l, short=True), s) + + def test_version_conv_11(self): + l, s = ('1', '1', '0', 'dev', '0'), '1.1.0dev0' + self.assertEqual(version_to_string(l), s) + + def test_version_conv_12(self): + l, s = (1, 1, 0, 'dev', 0), '1_1_0_dev_0' + self.assertEqual(l, version_from_string(s)) + + def test_version_conv_13(self): + l, s = (1, 1, 0, 'dev', 0), 'anything_28_1_1_0_dev_0' + self.assertEqual(l, version_from_string(s)) + + def test_version_conv_14(self): + l = 'anything_28x_1_0_dev_0' + self.assertRaises(VersionError, version_to_string, (l)) diff --git a/ui/cdlookup.ui b/ui/cdlookup.ui index b1738ae42..36c6af31b 100644 --- a/ui/cdlookup.ui +++ b/ui/cdlookup.ui @@ -68,7 +68,7 @@ - Lookup manually + Lookup manually diff --git a/ui/options_advanced.ui b/ui/options_advanced.ui new file mode 100644 index 000000000..0d8a399c5 --- /dev/null +++ b/ui/options_advanced.ui @@ -0,0 +1,63 @@ + + + AdvancedOptionsPage + + + + 0 + 0 + 338 + 435 + + + + + + + Advanced options + + + + 2 + + + + + Ignore file paths matching the following regular expression: + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 181 + 21 + + + + + + + + ignore_regex + + + +