diff --git a/picard/acoustid.py b/picard/acoustid.py index 0662d8c2f..2f91cba24 100644 --- a/picard/acoustid.py +++ b/picard/acoustid.py @@ -20,7 +20,7 @@ from collections import deque from PyQt4 import QtCore from picard.const import ACOUSTID_KEY -from picard.util import partial, call_next +from picard.util import partial, call_next, find_executable from picard.webservice import XmlNode @@ -32,6 +32,11 @@ class AcoustIDClient(QtCore.QObject): self._running = 0 self._max_processes = 2 + if not self.config.setting["acoustid_fpcalc"]: + fpcalc_path = find_executable("fpcalc") + if fpcalc_path: + self.config.setting["acoustid_fpcalc"] = fpcalc_path + def init(self): pass diff --git a/picard/ui/options/fingerprinting.py b/picard/ui/options/fingerprinting.py index 9b8ad3ce8..3b4aba02b 100644 --- a/picard/ui/options/fingerprinting.py +++ b/picard/ui/options/fingerprinting.py @@ -20,7 +20,7 @@ import os, sys import picard.musicdns from PyQt4 import QtCore, QtGui -from picard.util import webbrowser2 +from picard.util import webbrowser2, find_executable from picard.config import BoolOption, TextOption from picard.ui.options import OptionsPage, OptionsCheckError, register_options_page from picard.ui.ui_options_fingerprinting import Ui_FingerprintingOptionsPage @@ -77,7 +77,7 @@ class FingerprintingOptionsPage(OptionsPage): if self.ui.enable_fingerprinting.isChecked() and self.ui.use_acoustid.isChecked(): self.ui.acoustid_settings.setEnabled(True) if self.ui.acoustid_fpcalc.text().isEmpty(): - fpcalc_path = self.find_executable("fpcalc") + fpcalc_path = find_executable("fpcalc") if fpcalc_path: self.ui.acoustid_fpcalc.setText(fpcalc_path) else: @@ -95,16 +95,5 @@ class FingerprintingOptionsPage(OptionsPage): def acoustid_apikey_get(self): webbrowser2.open("http://acoustid.org/api-key") - def find_executable(self, name): - if sys.platform == 'win32': - executables = [name + '.exe'] - else: - executables = [name] - for path in os.environ.get('PATH', '').split(os.pathsep): - for executable in executables: - f = os.path.join(path, executable) - if os.path.isfile(f): - return f - register_options_page(FingerprintingOptionsPage) diff --git a/picard/util/__init__.py b/picard/util/__init__.py index 74b4c98e6..864a0293b 100644 --- a/picard/util/__init__.py +++ b/picard/util/__init__.py @@ -18,7 +18,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import os.path +import os import re import sys import unicodedata @@ -322,6 +322,20 @@ def find_existing_path(path): return decode_filename(path) +def find_executable(name): + if sys.platform == 'win32': + executables = [name + '.exe'] + else: + executables = [name] + paths = [os.path.dirname(sys.executable)] if sys.executable else [] + paths += os.environ.get('PATH', '').split(os.pathsep) + for path in paths: + for executable in executables: + f = os.path.join(path, executable) + if os.path.isfile(f): + return f + + def call_next(func): def func_wrapper(self, *args, **kwargs): next = args[0] diff --git a/setup.py b/setup.py index b79c80f64..ee28df87a 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import glob, re -import os.path +import os import sys from StringIO import StringIO from ConfigParser import RawConfigParser @@ -29,8 +29,10 @@ try: 'argv_emulation' : True, 'iconfile' : 'picard.icns', 'frameworks' : ['libofa.0.dylib', 'libiconv.2.dylib', 'libdiscid.0.dylib'], - 'includes' : ['sip', 'PyQt4.Qt', 'picard.util.astrcmp', 'picard.musicdns.ofa', 'picard.musicdns.avcodec'], - 'excludes' : ['pydoc'], + 'includes' : ['sip', 'PyQt4', 'picard.util.astrcmp', 'picard.musicdns.ofa', 'picard.musicdns.avcodec'], + 'excludes' : ['pydoc', 'PyQt4.QtDeclarative', 'PyQt4.QtDesigner', 'PyQt4.QtHelp', 'PyQt4.QtMultimedia', + 'PyQt4.QtOpenGL', 'PyQt4.QtScript', 'PyQt4.QtScriptTools', 'PyQt4.QtSql', 'PyQt4.QtSvg', + 'PyQt4.QtTest', 'PyQt4.QtWebKit', 'PyQt4.QtXmlPatterns', 'PyQt4.phonon'], 'plist' : { 'CFBundleName' : 'MusicBrainz Picard', 'CFBundleGetInfoString' : 'Picard, the next generation MusicBrainz tagger (see http://musicbrainz.org/doc/MusicBrainz_Picard)', 'CFBundleIdentifier':'org.musicbrainz.picard', @@ -540,11 +542,41 @@ def find_file_in_path(filename): return file_path if do_py2app: + from subprocess import call + from py2app.util import copy_file, find_app + from PyQt4 import QtCore + class BuildAPP(py2app): def run(self): py2app.run(self) - args['scripts'] = [ 'tagger.py' ] + # XXX py2app can't copy Qt plugins. + plugins = os.path.join(unicode(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.PluginsPath)), "imageformats") + dest = os.path.abspath("dist/MusicBrainz Picard.app/Contents/plugins/imageformats") + self.mkpath(dest) + for lib in ("libqgif.dylib", "libqjpeg.dylib", "libqtiff.dylib"): + src_file = os.path.join(plugins, lib) + if os.path.isfile(src_file): + dest_file = os.path.join(dest, lib) + copy_file(src_file, dest_file) + call(["install_name_tool", "-change", "QtCore.framework/Versions/4/QtCore", "@executable_path/../Frameworks/QtCore.framework/Versions/4/QtCore", dest_file]) + call(["install_name_tool", "-change", "QtGui.framework/Versions/4/QtGui", "@executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui", dest_file]) + + # XXX Without qt.conf, launching the app bundle results in a ton of warnings about + # "loading two sets of Qt binaries into the same process," followed by it crashing. + # Tools like macdeployqt create this file automatically, with the same contents. + fp = open("dist/MusicBrainz Picard.app/Contents/Resources/qt.conf", "w") + fp.writelines(["[Paths]", "Prefix="]) + fp.close() + + # XXX Find and bundle fpcalc, since py2app can't. + fpcalc = find_app("fpcalc") + if fpcalc: + dest_fpcalc = os.path.abspath("dist/MusicBrainz Picard.app/Contents/MacOS/fpcalc") + copy_file(fpcalc, dest_fpcalc) + os.chmod(dest_fpcalc, 0755) + + args['scripts'] = ['tagger.py'] args['cmdclass']['py2app'] = BuildAPP # FIXME: this should check for the actual command ('install' vs. 'bdist_nsis', 'py2app', ...), not installed libraries