L'Isola di Niente
L'Isola di Niente » PyGObject Tips » 動画プレイヤー(GStreamer フロントエンド)を作る

動画プレイヤー(GStreamer フロントエンド)を作る

GStreamer: open source multimedia framework
を利用した動画プレイヤーの作り方。
GNOME の人は Nautilus のサムネイルにも使われるのでお馴染みです。

GStreamer は長いこと 0.10 でしたが現在は 1.* になっています。
PyGst はもう使えなくなるので gir と推奨の Python3 に移行しましょう。

Novacut/GStreamer1.0 - Ubuntu Wiki
~jderose/+junk/gst-examples

下記はチト古い記事だけど日本語なので。
GStreamer概要 - SourceForge.JP Magazine

端末にて実験

Ubuntu の御仁はインストール時に入るでしょうけど、とにかく GStreamer デコーダーを入れてください。
デコーダーが入っている動画形式であれば Totem で再生可能かつ Nautilus でサムネイルできるはず。

その環境を構築した状態で以下のようなコマンドを打ち込んでみる。
動画ファイルの URI は自分の手持ちファイルに変更してください。
$ gst-launch-1.0 playbin uri=file:///home/sasakima-nao/movie/impreza.flv
DirectShow の IVideoWindow みたいなのが出てきて指定したファイルが再生できたはずです。
GStreamer は GNOME の様々な場面で利用されるため部品として提供されています。
つまり GStreamer で再生できる環境があるなら動画プレイヤーを作成する環境は揃っているということ。

作ってみる

Gst.ElementFactory.make() で playbin というパイプラインを作成。
DirectShow の IGraphBuilder のように面倒なことはコイツに「おまかせ」できるようだ。

get_bus() でパイプラインの gst.Bus オブジェクトを取得。

add_signal_watch() でメッセージを発行するように指定。

enable_sync_message_emission() はよく解らないんですけど…
映像と音声の同期メッセージを放出するように指示する関数っぽい。

そして GtkDrawingArea に貼り付ける。
一旦表示した所で xid を保存してソレを imagesink に割り当てます。

#!/usr/bin/env python3

import sys
import gi
try:
    gi.require_version("Gtk", "3.0")
    gi.require_version("Gst", "1.0")
except Exception as e:
    print("Error: {0}".format(e))
    sys.exit()

from gi.repository import Gtk, GObject, Gst, GstAudio, Gdk, GdkX11, GstVideo

class Win(Gtk.Window):
    """
        動画ファイルをドロップすると再生する最小限のプレイヤー
    """
    def __init__(self):
        """
            DnD で再生するだけの処理
        """
        Gtk.Window.__init__(self)
        # GStreamer を使う最小限の処理
        self.player = Gst.ElementFactory.make("playbin", None)
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.gst_on_message)
        bus.connect("sync-message::element", self.gst_on_sync_message)
        # この DrawingArea に動画を貼り付ける
        self.video_window = Gtk.DrawingArea()
        self.add(self.video_window)
        # DnD
        dnd_list = Gtk.TargetEntry.new("text/uri-list", 0, 0)
        self.drag_dest_set(
                Gtk.DestDefaults.MOTION |
                Gtk.DestDefaults.HIGHLIGHT |
                Gtk.DestDefaults.DROP,
                [dnd_list],
                Gdk.DragAction.MOVE )
        self.drag_dest_add_uri_targets()
        # シグナルハンドラ
        self.connect("drag-data-received", self.on_drag_data_received)
        self.connect("delete-event", self.on_delete_event)      
        self.set_title("動画プレイヤー")
        self.show_all()
        # xid は show() の後に得る
        self.xid = self.video_window.props.window.get_xid()

    def set_uri(self, uri):
        """
            playbin に uri をセット
            一旦 NULL にしないと失敗する
        """
        self.player.set_state(Gst.State.NULL)
        self.player.props.uri = uri
        self.player.set_state(Gst.State.PLAYING)
        # W バッファリングを無効にする
        self.video_window.set_double_buffered(False)

    def on_delete_event(self, widget, event=None):
        """
            [閉じる] ボタンが押された
        """
        self.player.set_state(Gst.State.NULL)
        Gtk.main_quit()
        return True

    def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
        """
            ファイルがドロップされた
        """
        drop = data.get_uris()[0]
        self.set_uri(drop)

    def gst_on_message(self, bus, message):
        """
            GStreamer から飛んでくるシグナルのハンドラ
        """
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
        elif t == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
	
    def gst_on_sync_message(self, bus, message):
        """
            動画画面を GtkDrawingArea に貼り付ける
        """
        if message.get_structure().get_name() == "prepare-window-handle":
            imagesink = message.src
            imagesink.set_window_handle(self.xid)

if __name__ == "__main__":
    # 初期化およびスレッド必須
    GObject.threads_init()
    Gtk.init(None)
    Gst.init(None)
    Win()
    Gtk.main()

とドラッグアンドドロップで再生できるプレイヤーはこんな感じで作れます。
他細かい制御なんかは本家チュートリアル、拙作 Y901x 等の GPL コードを見て解析等をしてください。
Copyright(C) sasakima-nao All rights reserved 2002 --- 2017.