Python GStreamer チュートリアル

③ Playbin

プレービン? 何のことでしょ。早速習います。チュートリアルの原文はこちら。Python3で動かすために、サンプルコードの該当箇所を変更しています。


3 Playbin

2.1節でコマンドラインから playbin 要素を演習しましたが、ここでは Python から使用します。これは高レベルの自動音声動画プレーヤです。下記のように playbin オブジェクトを作成します:

import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst
Gst.init(None)
# ...
my_playbin = Gst.ElementFactory.make("playbin", None)
assert my_playbin
print(my_playbin)
動作テスト - Playbin

playbinの情報を得るためには下記を実行します:

gst-inspect-1.0 playbin

この図は playbin が内部的にどのように構築されているかを示しています。“optional stuff” は、プラットフォーム固有であったり、プロパティで設定できるものです。

playbinが内部的にどのように構築されているか

“uri” プロパティには GStreamer プラグインでサポートされているプロトコルを指定します。一つの良い機能は、以下のようにシンクを自分のビンに切り替えることができることです。playbin は常に特定の環境に最適なパイプラインを設定しようとします。playbin に実装されていない特別な機能を必要としない場合は、ほとんどの場合、「箱から出して」動作するようになっています。それでは、いくつかの例を見てみましょう。

3.1 playbinを使ったオーディオ

最初の例はシンプルなオーディオプレーヤーで、ファイルを絶対パスで挿入すると再生されます。コードは以下の通りです。スクリプト名を指定して実行できます。

python3 playbin-example-audio.py

テキスト入力の小さなウィンドウが開きます。何かオーディオファイルのフルパスを入力し、“Start” をクリックします。

Playbinを使ったオーディオ
# playbin-example-audio.py

import os
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject


class GTK_Main(object):

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Audio-Player")
        window.set_default_size(300, -1)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.entry = Gtk.Entry()
        vbox.pack_start(self.entry, False, True, 0)
        self.button = Gtk.Button("Start")
        self.button.connect("clicked", self.start_stop)
        vbox.add(self.button)
        window.show_all()

        self.player = Gst.ElementFactory.make("playbin", "player")
        fakesink = Gst.ElementFactory.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 start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.set_property("uri", "file://" + filepath)
                self.player.set_state(Gst.State.PLAYING)
            else:
                self.player.set_state(Gst.State.NULL)
                self.button.set_label("Start")

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif t == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            err, debug = message.parse_error()
            print("Error: %s" % err, debug)
            self.button.set_label("Start")


Gst.init(None)
GTK_Main()
GObject.threads_init()
Gtk.main()

3.2 ビデオを追加

playbin はオーディオとビデオの両方のストリームを自動でプラグインします。また、videosink は fakesink 要素に切り替えられており、これは出力を /dev/null に向けるための GStreamer の答えです。ビデオ再生を有効にしたい場合は、以下の行をコメントアウトしてください。

fakesink = Gst.ElementFactory.make("fakesink", "fakesink")
self.player.set_property("video-sink", fakesink)

指定したウィンドウにビデオ出力を表示させたい場合は、バス上で enable_sync_message_emission() メソッドを使用する必要があります。ここでは、プログラムにビデオウィンドウを埋め込んだ例を示します。

動作テスト - playbin - ビデオ
# playbin-example-video.py

import sys, os
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject

# Needed for window.get_xid(), xvimagesink.set_window_handle(), respectively:
gi.require_version("GstVideo", "1.0")
from gi.repository import GdkX11, GstVideo

class GTK_Main(object):

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Video-Player")
        window.set_default_size(500, 400)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        hbox = Gtk.HBox()
        vbox.pack_start(hbox, False, False, 0)
        self.entry = Gtk.Entry()
        hbox.add(self.entry)
        self.button = Gtk.Button("Start")
        hbox.pack_start(self.button, False, False, 0)
        self.button.connect("clicked", self.start_stop)
        self.movie_window = Gtk.DrawingArea()
        vbox.add(self.movie_window)
        window.show_all()

        self.player = Gst.ElementFactory.make("playbin", "player")
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("sync-message::element", self.on_sync_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.set_property("uri", "file://" + filepath)
                self.player.set_state(Gst.State.PLAYING)
            else:
                self.player.set_state(Gst.State.NULL)
                self.button.set_label("Start")

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif t == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            err, debug = message.parse_error()
            print("Error: %s" % err, debug)
            self.button.set_label("Start")

    def on_sync_message(self, bus, message):
        if message.get_structure().get_name() == 'prepare-window-handle':
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            imagesink.set_window_handle(self.movie_window.get_property('window').get_xid())


GObject.threads_init()
Gst.init(None)
GTK_Main()
Gtk.main()

また、もう少し複雑にするために、playbinのビデオシンクを Gst.GhostPad のある Gst.Bin に切り替えることもできます。以下は、タイムオーバーレイの例です。

bin = Gst.Bin.new("my-bin")
timeoverlay = Gst.ElementFactory.make("timeoverlay")
bin.add(timeoverlay)
pad = timeoverlay.get_static_pad("video_sink")
ghostpad = Gst.GhostPad.new("sink", pad)
bin.add_pad(ghostpad)
videosink = Gst.ElementFactory.make("autovideosink")
bin.add(videosink)
timeoverlay.link(videosink)
self.player.set_property("video-sink", bin)

このコードを上の例に追加すると、タイムオーバーレイも表示されます。"ghost pads" については後ほど詳しく説明します。(※コンストラクタの中に追加すると、画面の左上にタイムコードが表示されるようになりました。)

動作テスト - playbin - ビデオ

ここに音楽を再生するCLIの例が追加されました。これを実行するには:

python cliplayer.py /path/to/file1.mp3 /path/to/file2.ogg
#playbin-example-cliplayer.py

import sys, os, time
import threading

import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib, GObject

class CLI_Main(object):

    def __init__(self):
        self.player = Gst.ElementFactory.make("playbin", "player")
        fakesink = Gst.ElementFactory.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 on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.playmode = False
        elif t == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            err, debug = message.parse_error()
            print("Error: %s" % err, debug)
            self.playmode = False

    def start(self):
        for filepath in sys.argv[1:]:
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.playmode = True
                self.player.set_property("uri", "file://" + filepath)
                self.player.set_state(Gst.State.PLAYING)
                while self.playmode:
                    time.sleep(1)
        time.sleep(1)
        loop.quit()

GObject.threads_init()
Gst.init(None)
mainclass = CLI_Main()

t1 = threading.Thread(target=mainclass.start)
t1.start()

loop = GLib.MainLoop()
loop.run()

試しにBensound.comからmp3で1曲、デスクトップテーマからoggで1曲プログラムにかけてみます。下記の曲が順に演奏されました。

python3 playbin-example-cliplayer.py bensound-littleidea.mp3 desktop-logout.ogg

playbin は Gst.Pipeline 要素を実装していますが、それは次の章で詳しく説明します。


関連記事