wxPythonで自由工作:作品No.001

久々のPythonネタ!
と思ったら、GAEネタも書いてましたねそう言えば。
まあ、あれはカテゴリ別なんで無視です。
と言う訳で、久方ぶりのwxPythonです。

本日取り扱うのは、みんな大好きwx.media.MediaCtrlです。
要するに、メディアプレイヤーですね。
まあ、GUIを使って何か作ろうとしたとき、大抵は頭の隅をよぎる代物ではないかと思います。
世にどれだけのメディアプレイヤーが溢れているかなんて見向きもせずに。
いいんですけどね。
実際、作りたいというのが本音であって、実用的なものに昇華しようというのは二の次です。


さて。
そんな訳で、そういうことなんですが、その前に。
どんなものでもそうですが、マルチメディアを扱うというのは、準備段階から結構手間が掛かるものです。それが実利を求めるものなら、尚のこと。僕のように遊び半分で手を出す輩程度でも、それなりに面倒なことがあります。
では。
その面倒で手間のかかる準備から、僕の失敗談も交えつつ、話を始めましょう。


環境は、Ubuntu 10.10(amd64) minimal CDでのインストールですよ。

wxPythonのwx.media.MediaCtrlを使う準備

世の中には実に様々な方がいらっしゃいましてですね、サンプルコードなんて探そうと思えば山のようにあるものなんです。
実際、僕の場合も苦もなくサンプルコードは見つけられました。
見つけること『は』できました。
ということは、そこで何かしらの問題があったということです。
第一の問題、それが旧バージョンにおける機能未実装です。


サンプルコードを実行したら、

Not Implement Error

とか出て止まる……。
何でプログラムすぐ止まってしまうん?


原因は、wxPythonのバージョンにありました。
Pythonインタプリタを起動して、

>>> import wx
>>> wx.VERSION

としてみてください。(2, 8, 11, 0, '')とか出たら見事にアウトです!
やったね!キラッ☆
てな訳で、バージョンをアップしなければなりません。


ところで、Ubuntu10.10、すなわちmaverickではどのバージョンが使えるのか?
詳しくは、下記を参考にしてください。
wxPythonのWiki
基本、ここに書いてあること実行すればいいのです。
一例を。

% sudo apt-get install curl (インストールしてないのなら)
% sudo apt-get remove --purge python-wxgtk2.8 python-wxtools wx2.8-i18n
(古いものが入っていたら消す)
% curl -x http://proxy.hogehoge:8080 http://apt.wxwidgets.org/key.asc | sudo apt-key add -
(curlでプロキシを使うには、-xオプションで)
% sudo vi /etc/apt/sources.list(ページ参考に編集を)
% sudo apt-get update
% sudo apt-get install python-wxgtk2.8 python-wxtools wx2.8-i18n

これで、バージョンが(2, 8, 12, 0, '')ならオッケーです。


さて。
これでめでたくwx.media.MediaCtrl()が使えるようになりましたかね?
おやおや?今度は別のエラーが出ているぞ?
このエラー量産機が……。
ああ、一応言っておきますよ。
僕のことではありません。

Got an invalid playbin

で。
これは、バックエンドのライブラリ不足が問題でした。


MediaCtrlは、WindowsならDirectShow、MacならQuickTimeLinuxならGStreamerをバックエンドで利用しているそうです。
つまり、wxPython単体の環境を揃えても、それらが整っていないとMediaCtrlが機能しないということです。


なので。
Ubuntuな僕は以下をインストールしときました。

% sudo apt-get install libgstreamer{-plugins-base0.10-0,0.10-0} gstreamer0.10-{alsa,fluendo-mp3,ffmpeg,nice,plugins-{base,bad,good},pulseaudio,tools,x}

これで、mp3やmp4ぐらいは再生できるようになります。
少なくとも、mp3の再生は確認済です。
ただし。
Linuxにおいては、(もしかしたらMacでも)wmaやwmvはこのままでは出来ません。
出きるようにしていないので何とも言えませんが、おそらくそれなりの設定やライブラリが必要になります。
なんたって、Windows Media AudioにWindows Media Videoだもの。


そんなこんなで。
大体、躓いたのはこんなところですね。
僕の場合は、これでサンプルコード動きました。

サンプルコード

と言う訳で。
僕がちょっと手を加えた、けれどもほとんど参考にしたまんまのサンプルコードが以下です。

#!/usr/bin/python
import wx
import wx.media
import os

class MediaPlayerPanel(wx.Panel):
    def __init__(self, parent,id):
        wx.Panel.__init__(self,parent,-1,style=wx.TAB_TRAVERSAL|wx.CLIP_CHILDREN)
        self.SetBackgroundColour("#BCFFCD")

        try:
            self.mc = wx.media.MediaCtrl(self, style=wx.SUNKEN_BORDER)
        except NotImplementedError:
            self.Destroy()
            raise # program exit.

        loadButton = wx.Button(self, wx.ID_ANY, "Load File")
        self.Bind(wx.EVT_BUTTON, self.onLoadFile, loadButton)

        playButton = wx.Button(self, wx.ID_ANY, "Play")
        playButton.SetToolTip(wx.ToolTip("load a file first")) # message when on mouse
        self.Bind(wx.EVT_BUTTON, self.onPlay, playButton)

        pauseButton = wx.Button(self, wx.ID_ANY, "Pause")
        pauseButton.SetToolTip(wx.ToolTip("press Play to resume"))
        self.Bind(wx.EVT_BUTTON, self.onPause, pauseButton)

        stopButton = wx.Button(self, wx.ID_ANY, "Stop")
        stopButton.SetToolTip(wx.ToolTip("also resets to start"))
        self.Bind(wx.EVT_BUTTON, self.onStop, stopButton)

        self.slider = wx.Slider(self, wx.ID_ANY, 1000000, 0, 1000000,
            size=(410, -1),
            style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS|wx.SL_LABELS)
        self.slider.Bind(wx.EVT_SLIDER, self.onSeek)

        ext = "load .mp3 .mpg .mid .wav .wma .au or .avi files"
        self.st_info = wx.StaticText(self, wx.ID_ANY, ext, size=(300,-1))

        sizer = wx.GridBagSizer(vgap=5, hgap=5)
        sizer.Add(loadButton, pos=(1,1))
        sizer.Add(self.st_info, pos=(1,2), span=(1,2))
        sizer.Add(self.slider, pos=(2,1), span=(2,3))
        sizer.Add(playButton, pos=(4,1))
        sizer.Add(pauseButton, pos=(4,2))
        sizer.Add(stopButton, pos=(4,3))
        # for .avi or .mpg video files use lower grid area
        sizer.Add(self.mc, pos=(5,1), span=(7,3))
        self.SetSizer(sizer)

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.onTimer)
        # update every 100 milliseconds
        self.timer.Start(100)

    def onLoadFile(self, evt):
        mask = "Media Files|*.mp3;*.mpg;*.mid;*.wav;*.au;*.avi|All (.*)|*.*"
        dlg = wx.FileDialog(self,
            message="Choose a media file (.mp3 .mpg .mid .wav .wma .au .avi)",
            defaultDir=os.getcwd(), defaultFile="",
            wildcard=mask, style=wx.OPEN|wx.CHANGE_DIR)
        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.doLoadFile(path)
        dlg.Destroy()

    def doLoadFile(self, path):
        if not self.mc.Load(path):
            wx.MessageBox("Unable to load %s: Unsupported format?" % path,
            "ERROR", wx.ICON_ERROR|wx.OK)
        else:
            folder, self.filename = os.path.split(path)
            self.st_info.SetLabel("  %s" % self.filename)
            # set the slider range min to max
            self.slider.SetRange(0, self.mc.Length())
            self.mc.Play()

    def onPlay(self, evt):
        self.slider.SetRange(0, self.mc.Length())
        s1 = "  %s" % self.filename
        s2 = "  size: %s ms" % self.mc.Length()
        self.st_info.SetLabel(s1+s2)
        self.mc.Play()

    def onPause(self, evt):
        self.mc.Pause()

    def onStop(self, evt):
        self.mc.Stop()

    def onSeek(self, evt):
        """allows dragging the slider pointer to this position"""
        offset = self.slider.GetValue()
        self.mc.Seek(offset)

    def onTimer(self, evt):
        """moves the slider pointer"""
        offset = self.mc.Tell()
        self.slider.SetValue(offset)

def main():
    app = wx.PySimpleApp()
    frame = wx.Frame(None, -1, "play audio and video files", size = (500, 500))
    MediaPlayerPanel(frame, -1)
    frame.Show(True)
    app.MainLoop()

if __name__ == "__main__":
    main()

参考URL


まあ、細かい説明は省きましょう。
プログラム載せたものだから、相当長い上に重い記事になってしまいましたし。
GitHubでも使えばいいんですかね?同じことかな?
ともかく。
シンプルなプレイヤーの一例です。この雛形さえあれば、後はカスタマイズし放題ですね。
イヤッフゥ!

あとがき

ちなみに。
なんで、上記のサンプルコードがFrameではなくPanelなのかというと、その方が汎用性が高いと思うからです。
Notebookとかにも使えるし。
なので、僕は基本的にはPanel上でアプリケーションの作り込みをしています。
で、必要に応じて他プログラムでimportして使う。
そういう背景から、if __name__〜の記述があるという訳なのです。


こんな感じで。
今日はお暇させていただきます。