1

pyobjc と wxPython を使用して QTMovieView (および QTMovie) ウィジェットを wxPython パネルに埋め込み、必要な通知を登録しますが、QTMovieDidEndNotification はトリガーされません。

コードのダウンロード可能なバージョン: https://dl.dropboxusercontent.com/u/12781104/QT%20Test%20Download.zip

注目すべきコードは、通知オブザーバーを登録する QuicktimeCtrl クラスの processMovie メソッドにあります。

アプリケーションの終了時にクラッシュが予想されます。現時点では、メモリの解放に関係しているようです。他の問題を解決することに焦点を当てています。

wxPython バージョン 2.9 COCOA が必要です。下位バージョンは Carbon ベースで、NSView の代わりに NSHIObjects (少なくとも私の知る限り、QTMovieView を埋め込むことはできません) を GetHandle 呼び出しで呼び出します。

パネルにまとめられた QT Embed コード

'''
    Sublcasses wxMediaCtrl to fix aspect ratio issue
    http://forums.wxwidgets.org/viewtopic.php?t=23461&p=100319

    Why?
    - Yes wx.media.MediaCtrl has a quicktime backend
    but it has an issue with the aspect ratio not
    correctly displaying (stretches instead of black
    bars (seem to be from not setting 
    PreserveAspectRatio). Also has major issues with
    url playback (note only occurs when media is video
    mp3s play fine) with only very sporadic and none
    reliable ability to play (usually just fails to
    load)
    '''

import wx
import wx.media
from wx.lib.newevent import NewCommandEvent
import os

print "Import objc items"
# pyobj c stuff
import ctypes
import objc
from Foundation import NSURL, NSString, NSRect, NSDictionary
from AppKit import NSViewWidthSizable, NSViewHeightSizable
print "Importing QTMovie"
from QTKit import QTMovie as M, QTMovieDidEndNotification, QTMovieLoadStateDidChangeNotification, QTMovieView, QTMovieFileNameAttribute, QTMovieOpenAsyncOKAttribute, QTMovieLoadStateAttribute

print "Importing twisted logging module"
from twisted.python import log

# States
# might not be the numbers wx.media.MediaCtrl uses
MEDIASTATE_PLAYING = 0
MEDIASTATE_PAUSED =  1
MEDIASTATE_STOPPED = 2

# mc events are not control events so we get to admit our own "alias" versions
# Events
media_loaded_event  , EVT_MEDIA_LOADED   = NewCommandEvent()
media_play_event    , EVT_MEDIA_PLAY     = NewCommandEvent()
media_pause_event   , EVT_MEDIA_PAUSE    = NewCommandEvent()
media_stop_event    , EVT_MEDIA_STOP     = NewCommandEvent()
media_finished_event, EVT_MEDIA_FINISHED = NewCommandEvent()


class QuicktimeCtrl(wx.Panel):
    def __init__(self, *args, **kw):
        wx.Panel.__init__(self, *args, **kw)
        self.SetBackgroundColour("BLACK")

        ptr = self.GetHandle()
        void_ptr = ctypes.c_void_p(ptr)
        view = objc.objc_object(c_void_p=void_ptr)

        pos = (0, 0)
        size = self.GetSize()


        r = NSRect(pos, size)
        self.mv = QTMovieView.alloc().initWithFrame_(r)
        # setup the MacOSX equivalent of sizers
        self.mv.setAutoresizesSubviews_(True)
        self.mv.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable)
        # don't show video controls
        self.mv.setControllerVisible_(False)
        # preseve aspect ratio
        self.mv.setPreservesAspectRatio_(True)

        self.stop = True
        self.pause = False

        view.addSubview_(self.mv)

    def _send_event(self, event):
        print "Firing an event"
        evt = event(self.GetId())
        wx.PostEvent(self, evt)

    def OnMediaLoaded(self, event):
        self._send_event(media_loaded_event)

    def OnMediaPlaying(self, event):
        self._send_event(media_play_event)

    def OnMediaPaused(self, event):
        self._send_event(media_pause_event)

    def OnMediaFinished(self, event):
        self._send_event(media_finished_event)

    def OnMediaStopped(self, event):
        self._send_event(media_stop_event)

    def Load(self, file_path):
        # get movie constraints
        encoded_path = NSString.alloc().initWithString_(file_path)
        dict = NSDictionary.dictionaryWithDictionary_({QTMovieFileNameAttribute : encoded_path,
                                                      QTMovieOpenAsyncOKAttribute: False}
                                                      )

        (movie, error) = M.movieWithAttributes_error_(dict, None)


        if movie is None:
            print file_path
            print "[QT] An error occured"
            print error
            return False

        return self.processMovie(movie)

    def LoadURI(self, uri):
        encoded_url = NSURL.alloc().initWithString_(uri)

        dict = NSDictionary.dictionaryWithDictionary_({QTMovieURLAttribute : encoded_url,
                                                      QTMovieOpenAsyncOKAttribute: False}
                                                      )

        (movie, error) = M.movieWithAttributes_error_(dict, None)


        if movie is None:
            print uri
            print "[QT] An error occured"
            print error
            return False

        return self.processMovie(movie)

    def processMovie(self, m):
        #m.setDelegate_(self)
        # dispose of any old movie and set new one
        self.mv.setMovie_(m)

        #enum {
        #    QTMovieLoadStateError = -1L,
        #    QTMovieLoadStateLoading = 1000,
        #    QTMovieLoadStateLoaded = 2000,
        #    QTMovieLoadStatePlayable = 10000,
        #    QTMovieLoadStatePlaythroughOK = 20000,
        #    QTMovieLoadStateComplete = 100000L
        #};
        #typedef NSInteger QTMovieLoadState;
        loadState = m.attributeForKey_(QTMovieLoadStateAttribute).longValue()
        if loadState == -1:
            # error
            print "[QT] Error playing media"
            return False

        if loadState == 1000:
            # error in qt as it should be loaded synchnously
            print "[QT] Error wans't loaded synchronously"
            return False

        elif loadState == 2000:
            # loaded but not playable
            # attach a handler to get when it is playable
            # and send load
            self.loaded = True
            self.playable = False
            notf = NSNotificationCenter.defaultCenter()
            load_selector = objc.selector(self.OnQTLoad, signature = "v@:@")
            end_selector = objc.selector(self.OnQTMovieEnd, signature = "v@:@")
            notf.addObserver_selector_name_object_(self, load_selector, QTMovieLoadStateDidChangeNotification, m)
            notf.addObserver_selector_name_object_(self, end_selector,  QTMovieDidEndNotification,             m)

            return True

        elif loadState >= 10000:
            self.loaded = True
            self.playable = True
            # loaded and playable
            # fire evt
            wx.CallAfter(self._send_event, media_loaded_event)
            return True

        return False

    def OnQTLoad(self, m):
        print "QT LOAD"
        loadState = m.attributeForKey_(QTMovieLoadStateAttribute).longValue()
        if loadState == -1:
            # error
            print "[QT] [In Notification] Error playing media"
            # send stop event 
            self._send_event(media_stop_event)

        if loadState == 1000:
            # error in qt as it should be loaded synchnously
            print "[QT] [In Notification] Error wans't loaded synchronously"
            self._send_event(media_stop_event)

        if loadState >= 10000:
            # if now playable
            self._send_event(media_loaded_event)

    def OnQTMovieEnd(self, notf):
        print "QT END"
        print "THIS SHOULD BE PRINTED WHEN THE MOVIE ENDS\n\n\n\n\n\n"
        # movie is finished
        self.Stop() # dont care if succeeds not much we can do otherwise
        # then fire finish event
        self._send_event(media_finished_event)


    def Play(self):
        if self.mv.movie() is None:
            return False

        self.mv.play_(None)
        # confirms it works
        rate = self.mv.movie().rate()
        print "[Play] rate %s " % str(rate)
        if rate == 1.0:
            self.stop = False
            self.pause = False
            self._send_event(media_play_event)
            return True
        else:
            return False


    def Pause(self):
        if self.mv.movie() is None:
            return False

        self.mv.pause_(None)
        rate = self.mv.movie().rate()
        print "[Pause] rate %s " % str(rate)
        if rate == 0.0:
            self.stop = False
            self.pause = True
            self._send_event(media_pause_event)
            return True
        else:
            return False


    def Stop(self):
        if self.mv.movie() is None:
            return False

        # sets it to the beginning; follows wxMediaCtrl that hitting play after starts from the beginning
        self.mv.gotoBeginning_(None)
        rate = self.mv.movie().rate()
        print "[Stop] rate %s " % str(rate)
        if rate == 0.0:
            self.stop = True
            self.pause = False
            self._send_event(media_stop_event)
            return True
        else:
            return False

    def GetState(self):
        if self.stop:
            return MEDIASTATE_STOPPED
        if self.pause:
            return MEDIASTATE_PAUSED

        return MEDIASTATE_PLAYING

    def SetVolume(self, volume):
        if self.mv.movie() is None:
            return False

        # takes same 0 to 1 value as MediaCtrl so just pass through
        # http://docs.wxwidgets.org/2.8/wx_wxmediactrl.html#wxmediactrlsetvolume
        self.mv.movie().setVolume(float(volume))
        return True

    def Tell(self):
        return 0

    def Length(self):
        return 1

    def Seek(self, position):
        return True

および単純なテストアプリ (MediaCtrl で簡単に置き換えることができますが、問題があります)

# Player
import re
from json import dumps
from urllib import quote

import wxversion
wxversion.select('2.9-osx_cocoa')


import wx
#import wx.media
from wx.lib.buttons import GenBitmapButton as BitmapButton

from quicktime_adapter import QuicktimeCtrl, EVT_MEDIA_STOP, EVT_MEDIA_PLAY, EVT_MEDIA_PAUSE, EVT_MEDIA_LOADED, EVT_MEDIA_FINISHED


CENTER = wx.ALIGN_CENTER | wx.ALL

class Panel(wx.Panel):

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, size = (400, 500))

        #self.url_re = re.compile("^http\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?$")

        self.mpc = QuicktimeCtrl(self, -1, style=wx.SIMPLE_BORDER)
        self.load_file_button = wx.Button(self, -1, label = "Load File")
        self.load_url_button = wx.Button(self, -1, label = "Load URL")
        self.play_button = wx.Button(self, -1, label = "Play")
        self.pause_button = wx.Button(self, -1, label = "Pause")
        self.stop_button = wx.Button(self, -1, label = "Stop")
        size = (40, 40)
        size_bttn = (50, 50)

        #image = wx.Image('play.png', wx.BITMAP_TYPE_ANY).ShrinkBy(10, 10).ConvertToBitmap()
        #self.play_button = BitmapButton(self, -1,  image, size = size_bttn, style=wx.BORDER_NONE)

        #image = wx.Image('pause.png', wx.BITMAP_TYPE_ANY).ShrinkBy(10, 10).ConvertToBitmap()
        #self.pause_button = BitmapButton(self, -1,  image, size = size_bttn, style=wx.BORDER_NONE)

        #image = wx.Image('stop.png', wx.BITMAP_TYPE_ANY).ShrinkBy(10, 10).ConvertToBitmap()
        #self.stop_button = BitmapButton(self, -1,  image, size = size_bttn, style=wx.BORDER_NONE)

        self.play_button.Disable()

        bttn_sizer = wx.BoxSizer(wx.HORIZONTAL)
        load_buttons_sizer = wx.BoxSizer(wx.VERTICAL)
        app_sizer = wx.BoxSizer(wx.VERTICAL)

        load_buttons_sizer.Add(self.load_file_button, border = 5, flag = wx.ALL, proportion = 0)
        load_buttons_sizer.Add(self.load_url_button, border = 5, flag = wx.ALL, proportion = 0)

        #bttn_sizer.Add(self.load_file_button, border = 5, flag = CENTER, proportion = 0)
        #bttn_sizer.Add(self.load_url_button, border = 5, flag = CENTER, proportion = 0)
        bttn_sizer.Add(load_buttons_sizer, border = 0, flag = wx.ALIGN_LEFT | wx.ALL, proportion = 0)
        bttn_sizer.AddStretchSpacer(1)
        bttn_sizer.Add(self.play_button, border = 5, flag = CENTER, proportion = 0)
        bttn_sizer.Add(self.pause_button, border = 5, flag = CENTER, proportion = 0)
        bttn_sizer.Add(self.stop_button, border = 5, flag = CENTER, proportion = 0)

        app_sizer.Add(self.mpc, border = 10, flag = CENTER | wx.EXPAND, proportion = 1)
        app_sizer.Add(bttn_sizer, border = 5, proportion = 1)
        self.SetSizer(app_sizer)
        self.Fit()
        #wx.CallAfter(self.OnLoadURL, None)

        # Binds
        self.Bind(EVT_MEDIA_LOADED, self.OnMediaLoaded, self.mpc)
        self.Bind(EVT_MEDIA_PLAY, self.OnMediaPlaying, self.mpc)
        self.Bind(EVT_MEDIA_PAUSE, self.OnMediaPaused, self.mpc)
        self.Bind(EVT_MEDIA_STOP, self.OnMediaStopped, self.mpc)
        self.Bind(EVT_MEDIA_FINISHED, self.OnMediaFinished, self.mpc)

        self.Bind(wx.EVT_BUTTON, self.OnLoadFile, self.load_file_button)
        self.Bind(wx.EVT_BUTTON, self.OnLoadURL, self.load_url_button)
        self.Bind(wx.EVT_BUTTON, self.OnPlay, self.play_button)
        self.Bind(wx.EVT_BUTTON, self.OnPause, self.pause_button)
        self.Bind(wx.EVT_BUTTON, self.OnStop, self.stop_button)

    def OnLoadFile(self, event):
        self.play_button.Disable()
        dlg = wx.FileDialog(self, message="Choose a media file", style=wx.OPEN | wx.CHANGE_DIR )
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            if not self.mpc.Load(path):
                wx.MessageBox("Unable to load %s: Unsupported format?" % path, "ERROR", wx.ICON_ERROR | wx.OK)
            else:
                self.mpc.SetInitialSize()
                self.GetSizer().Layout()
        dlg.Destroy()

    def OnLoadURL(self, event):
        self.play_button.Disable()
        dlg = wx.TextEntryDialog(self, "Enter URL", "URL", defaultValue = "")
        if dlg.ShowModal() == wx.ID_OK:
            url = dlg.GetValue()
            if url is not None:
                self.play_button.Disable()
                self.mpc.LoadURI(url)

            else:
                wx.MessageBox("Error: The URL you enter is invalid, Please enter a valid URL.", "Invalid URL", wx.ICON_ERROR | wx.OK)

        dlg.Destroy()

    def LoadURL(self, url, postdata = None):
        self.mpc.LoadURI(url, postdata)

    def OnPlay(self, event):
        print "OnPlay"
        self.mpc.Play()

    def OnPause(self, event):
        print "OnPause"
        self.mpc.Pause()

    def OnStop(self, event):
        print "OnStop"
        self.mpc.Stop()

    def OnMediaLoaded(self, event):
        print "Media Loaded"
        self.play_button.Enable()


    def OnMediaPlaying(self, event):
        print "Playing"

    def OnMediaPaused(self, event):
        print "Paused"

    def OnMediaStopped(self, event):
        print "Stopped"

    def OnMediaFinished(self, event):
        print "Finished"


if __name__ == "__main__":
    #from twisted.internet import wxreactor
    #wxreactor.install()
    #from twisted.internet import reactor
    app = wx.App(False)
    f = wx.Frame(None, -1, size = (400, 500), title = "Player")
    f.p = Panel(f)
    f.Show()
    app.MainLoop()
    #reactor.registerWxApp(app)
    #reactor.run()
4

0 に答える 0