OSX下的pygst + pyinstaller

时间:2013-05-17 21:46:12

标签: python pyinstaller pygst

我正在尝试捆绑这个死的简单python脚本(player.py):

#!/usr/bin/env python

import sys, os
import gobject, glib
import pygst
pygst.require("0.10")
import gst

class Player(object):
    def __init__(self):
        self.player = gst.element_factory_make("playbin2", "player")
        fakesink = gst.element_factory_make("fakesink", "fakesink")
        self.player.set_property("video-sink", fakesink)
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)

    def play(self, url):
        self.player.set_state(gst.STATE_NULL)
        self.player.set_property("uri", url)
        self.player.set_state(gst.STATE_PLAYING)

    def on_message(self, bus, message):
        t = message.type
        if t == gst.MESSAGE_EOS:
            global loop
            loop.quit()
        elif t == gst.MESSAGE_ERROR:
            self.player.set_state(gst.STATE_NULL)
            err, debug = message.parse_error()
            print "Error: %s" % err, debug

p = Player()
p.play("file:///path/to/something.mp3")
gobject.threads_init()
loop = glib.MainLoop()
loop.run()

使用pyinstaller(2.1.0-dev)进入OSX(我的机器运行Mountain Lion)应用程序。

我的目标是创建一个我可以轻松分发的.app包。我还可以要求最终用户安装GStreamer SDK,即使自包含的应用程序是我的主要目标。

spec文件如下(player.spec):

# -*- mode: python -*-
import pygst
pygst.require('0.10')

a = Analysis(['player.py'],
             pathex=['/Users/mymy/devel/t/simple'],
             hiddenimports=[],
             hookspath=None,
             runtime_hooks=None)
pyz = PYZ(a.pure)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='player',
          debug=False,
          strip=None,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=None,
               upx=True,
               name='player')

到目前为止,我尝试了两种策略:

  1. macports:python(2.7.2),pygst(0.10),gst-plugins - *
  2. system python,GStreamer 0.10 SDK(Framework)
  3. 在这两种情况下,我都可以成功运行脚本。

    但是,当我尝试运行捆绑的可执行文件时,我得到以下内容:

    1. (MacPorts的)
    2. 显示部分GST调试日志:

      ~/devel/t/simple/dist/player > GST_DEBUG=4 ./player
      [..]
      0:00:00.113260000  8313    0x1001b1a00 DEBUG           GST_REGISTRY gstregistrychunks.c:573:gboolean gst_registry_chunks_load_feature(GstRegistry *, gchar **, gchar *, GstPlugin *): Plugin 'playback' feature 'playbin2' typename : 'GstElementFactory'
      0:00:00.113286000  8313    0x1001b1a00 DEBUG           GST_REGISTRY gstregistrychunks.c:621:gboolean gst_registry_chunks_load_feature(GstRegistry *, gchar **, gchar *, GstPlugin *): Element factory : 'Player Bin 2' with npadtemplates=0
      0:00:00.113300000  8313    0x1001b1a00 DEBUG           GST_REGISTRY gstregistrychunks.c:649:gboolean gst_registry_chunks_load_feature(GstRegistry *, gchar **, gchar *, GstPlugin *): Reading 2 Interfaces at address 0x101971191
      0:00:00.113318000  8313    0x1001b1a00 DEBUG           GST_REGISTRY gstregistry.c:558:gboolean gst_registry_add_feature(GstRegistry *, GstPluginFeature *):<registry0> adding feature 0x10097da20 (playbin2)
      0:00:00.113332000  8313    0x1001b1a00 DEBUG        GST_REFCOUNTING gstobject.c:844:gboolean gst_object_set_parent(GstObject *, GstObject *):<playbin2> set parent (ref and sink)
      0:00:00.113346000  8313    0x1001b1a00 DEBUG           GST_REGISTRY gstregistrychunks.c:709:gboolean gst_registry_chunks_load_feature(GstRegistry *, gchar **, gchar *, GstPlugin *): Added feature playbin2, plugin 0x100975be0 playback
      [..]
      0:00:00.242584000  8297    0x1001b1a00 DEBUG     GST_PLUGIN_LOADING gstpluginfeature.c:106:GstPluginFeature *gst_plugin_feature_load(GstPluginFeature *): loading plugin for feature 0x10097da20; 'playbin2'
      0:00:00.242620000  8297    0x1001b1a00 DEBUG     GST_PLUGIN_LOADING gstpluginfeature.c:110:GstPluginFeature *gst_plugin_feature_load(GstPluginFeature *): loading plugin playback
      0:00:00.242632000  8297    0x1001b1a00 DEBUG     GST_PLUGIN_LOADING gstplugin.c:1293:GstPlugin *gst_plugin_load_by_name(const gchar *): looking up plugin playback in default registry
      0:00:00.242662000  8297    0x1001b1a00 DEBUG     GST_PLUGIN_LOADING gstplugin.c:1296:GstPlugin *gst_plugin_load_by_name(const gchar *): loading plugin playback from file /opt/local/lib/gstreamer-0.10/libgstplaybin.so
      0:00:00.242677000  8297    0x1001b1a00 DEBUG     GST_PLUGIN_LOADING gstplugin.c:737:GstPlugin *gst_plugin_load_file(const gchar *, GError **): attempt to load plugin "/opt/local/lib/gstreamer-0.10/libgstplaybin.so"
      0:00:00.248338000  8297    0x1001b1a00 INFO      GST_PLUGIN_LOADING gstplugin.c:859:GstPlugin *gst_plugin_load_file(const gchar *, GError **): plugin "/opt/local/lib/gstreamer-0.10/libgstplaybin.so" loaded
      0:00:00.248374000  8297    0x1001b1a00 DEBUG     GST_PLUGIN_LOADING gstpluginfeature.c:115:GstPluginFeature *gst_plugin_feature_load(GstPluginFeature *): loaded plugin playback
      0:00:00.248390000  8297    0x1001b1a00 INFO      GST_PLUGIN_LOADING gstpluginfeature.c:145:GstPluginFeature *gst_plugin_feature_load(GstPluginFeature *): Tried to load plugin containing feature 'playbin2', but feature was not found.
      0:00:00.248402000  8297    0x1001b1a00 WARN     GST_ELEMENT_FACTORY gstelementfactory.c:410:GstElement *gst_element_factory_create(GstElementFactory *, const gchar *):<playbin2> loading plugin containing feature player returned NULL!
      0:00:00.248412000  8297    0x1001b1a00 INFO     GST_ELEMENT_FACTORY gstelementfactory.c:472:GstElement *gst_element_factory_make(const gchar *, const gchar *):<playbin2> couldn't create instance!
      Traceback (most recent call last):
        File "<string>", line 33, in <module>
        File "<string>", line 11, in __init__
      gst.ElementNotFoundError: playbin2
      

      获得相同的结果预先挂起:GST_PLUGIN_PATH = / opt / local / lib / gstreamer-0.10 /

      我尝试将插件及其依赖项复制到dist / player文件夹中,编写一个疯狂的install_name_tool mangling脚本,以便更正dylib的路径,但结果也不会改变。

      1. (GStreamer SDK)
      2. (PYTHONPATH = /库/框架/ GStreamer.framework /版本/ 0.10 / LIB / python2.7 /站点包/)

        ~/devel/t/simple/dist/player > ./player 
        ** Message: pygobject_register_sinkfunc is deprecated (GstObject)
        player.py:11: Warning: cannot register existing type `GstObject'
        player.py:11: Warning: g_once_init_leave: assertion `result != 0' failed
        player.py:11: Warning: gtype.c:2720: You forgot to call g_type_init()
        

        在这里挂起。如果我通过Activity Monitor对流程进行抽样,我会得到:

        [..]
        _wrap_gst_element_factory_make  (in gst._gst.so)
        gst_element_factory_make  (in libgstreamer-0.10.0.dylib)
        gst_element_factory_create  (in libgstreamer-0.10.0.dylib)
        gst_plugin_feature_load  (in libgstreamer-0.10.0.dylib)
        gst_plugin_load_by_name  (in libgstreamer-0.10.0.dylib)
        gst_plugin_load_file  (in libgstreamer-0.10.0.dylib)
        gst_plugin_register_func  (in libgstreamer-0.10.0.dylib)
        plugin_init  (in libgstplaybin.so)
        gst_play_bin2_plugin_init  (in libgstplaybin.so)
        gst_pipeline_get_type  (in libgstreamer-0.10.0.dylib)
        gst_bin_get_type  (in libgstreamer-0.10.0.dylib)
        gst_child_proxy_get_type  (in libgstreamer-0.10.0.dylib)
        gst_object_get_type  (in libgstreamer-0.10.0.dylib)
        g_once_init_enter  (in libglib-2.0.0.dylib)
        g_cond_wait  (in libglib-2.0.0.dylib)
        _pthread_cond_wait  (in libsystem_c.dylib)
        __psynch_cvwait  (in libsystem_kernel.dylib)
        

        非常感谢提示!

1 个答案:

答案 0 :(得分:2)

我终于为以下设置提供了一个有效的解决方案:

  • macports(python @ 2.7.3,py27-gst-python @ 0.10.22,gstreamer010 @ 0.10.36)
  • pyinstaller 2.1-dev(ccb6f3d3d924a0dc2f9e92aa6278c28a2d743d39)
  • OSX 10.8.3

pyinstaller spec文件(player.spec):

# -*- mode: python -*-
import os
import pygst
pygst.require('0.10')

a = Analysis(['rthook.py', 'player.py'],
             pathex=[os.curdir],
             hiddenimports=[],
             hookspath=None,
             runtime_hooks=None)
pyz = PYZ(a.pure)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='player',
          debug=False,
          strip=None,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=None,
               upx=True,
               name='player')

_gst.so hook(hook-gst._gst.py):

我为gst创建了一个非常原始的钩子。我试图将它放在本地目录中,通过Analysis对象的 hookspath 参数引用它,但我无法弄清楚为什么pyinstaller会忽略它。因此我将其移至 / path / to / pyinstaller / PyInstaller / hooks 文件夹:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os

GST_PLUGINS = '/opt/local/lib/gstreamer-0.10/'

def hook(mod):
    for f in [so for so in os.listdir(GST_PLUGINS) if so[-3:].lower() == '.so']:
        mod.binaries.append((os.path.join('gst-plugins', f),
                os.path.join(GST_PLUGINS, f),
                'BINARY'))

    return mod

pyinstaller负责计算插件的依赖树,将.sos复制到位,以及依赖的dylib,最后修复两者的mach'o头。

我还创建了一个空文件 /path/to/pyinstaller/PyInstaller/hooks/hook-gst.py 来阻止pyinstaller抱怨丢失的父钩子。而且,无论如何,钩子代码可以直接转到 hook-gst.py

运行时挂钩文件(rthook.py)

最后,我添加了一个在Analysis对象上引用的运行时钩子文件,它设置了有助于gstreamer定位插件的环境变量。此代码在 player.py 之前在捆绑的可执行文件上执行(遵循Kivy设置pyinstaller的方式并感谢他们的宝贵提示):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys

if hasattr(sys, '_MEIPASS'):
    # PyInstaller >= 1.6
    root = sys._MEIPASS
elif '_MEIPASS2' in environ:
    # PyInstaller < 1.6 (tested on 1.5 only)
    root = os.environ['_MEIPASS2']
else:
    root = os.path.dirname(sys.argv[0])

os.chdir(root)

os.environ['GST_REGISTRY_FORK'] = 'no'
os.environ['GST_PLUGIN_PATH'] = os.path.join(root, 'gst-plugins')

似乎禁用GST_REGISTRY_FORK是获得工作结果的唯一方法。第一个插件扫描后,保留默认设置(活动)会导致分段错误。

pyinstaller调用

可以使用:

调用

pyinstaller

$ /path/to/pyinstaller/pyinstaller.py player.spec