Programming」カテゴリーアーカイブ

Python with GTK3 and Gst 1.0

GStreamer 1.0 を PyGI で試してみた。
Seekbar は面倒くさいので付けていない、Play/Pause ボタンのみ。
動画ファイルをドラッグアンドドロップすれば再生できるサンプル。

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

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

"""
    GdkX11   @ get_xid()
    GstVideo @ xvimagesink
"""

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

class Player(Gtk.Window):
    """
        Simple DnD Video Player
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", self.on_quit)
        # Status
        self.status = Gst.State.NULL
        # Video Area
        self.video_area = Gtk.DrawingArea()
        # Disable Double Buffered
        self.video_area.set_double_buffered(False)
        # Play/Pause Button
        self.button = Gtk.Button.new_with_label("Null")
        self.button.connect("clicked", self.on_button_clicked)
        # playbin
        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.on_message)
        bus.connect("sync-message::element", self.on_sync_message)
        # 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)
        # pack
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) 
        vbox.pack_start(self.video_area, True, True, 0)
        vbox.pack_start(self.button, False, False, 0)
        self.add(vbox)
        self.resize(640, 360)
        self.show_all()

    def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
        uris = data.get_uris()
        self.player.set_state(Gst.State.NULL)
        self.player.props.uri = uris[0]
        self.player.set_state(Gst.State.PLAYING)

    def on_quit(self, widget, data=None):
        self.player.set_state(Gst.State.NULL)
        Gtk.main_quit()

    def on_sync_message(self, bus, message):
        if message.get_structure().get_name() == "prepare-window-handle":
            xid = self.video_area.props.window.get_xid()
            imagesink = message.src
            #imagesink.props.force_aspect_ratio = False
            imagesink.set_window_handle(xid)

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.status = Gst.State.NULL
            self.button.set_label("Null")
        elif t == Gst.MessageType.ERROR:
            self.messagebox(message.parse_error())
            self.emit("delete-event", None)
        elif t == Gst.MessageType.STATE_CHANGED:
            status = message.parse_state_changed()[1]
            if status == Gst.State.PLAYING: 
                if not self.status == Gst.State.PLAYING:
                    self.status = Gst.State.PLAYING
                    self.button.set_label("Pause")
            elif status == Gst.State.PAUSED: 
                if not self.status == Gst.State.PAUSED:
                    self.status = Gst.State.PAUSED
                    self.button.set_label("Play")

    def on_button_clicked(self, widget, data=None):
        if not self.player.props.uri == "":
            if self.status == Gst.State.PLAYING:
                self.player.set_state(Gst.State.PAUSED)
            elif self.status == Gst.State.PAUSED:
                self.player.set_state(Gst.State.PLAYING)

    def messagebox(self, text):
        dlg = Gtk.MessageDialog(
                self,
                Gtk.DialogFlags.MODAL,
                Gtk.MessageType.ERROR,
                Gtk.ButtonsType.OK,
                text)
        dlg.set_title("Error")  
        r = dlg.run()  
        dlg.destroy()
        return r

if __name__ == "__main__":
    GObject.threads_init()
    Gst.init(None)
    Player()
    Gtk.main()

GdkX11 は xid を得るのに必要、GstVideo は DrawingArea に貼り付けるのに必要。
つまり音楽プレイヤーを作るなら別にインポートする必要は無い。

以前リンクしたところは GstPipeline に playbin をセットしていた。
けど普通に playbin 自体を GstPipeline として使えるということみたい。

expose_event が無くなったおかげか xid を得るのに一手間がいらなくなった。
アスペクト比を保持させない方法も PyGtk と同じだった。

STATE_CHANGED メッセージで再生中に Gst.State.PLAYING が延々流れてくるのだが…
PyGtk ではこんなだったかな、覚えていないや。

というか PyGtk と Gst 0.10 で作るのと全然変わっていなかったという。

GStreamer のバージョンが上がったメリットは今の私には解らない。
多分そのうち気がつくだろう、多分。

しかし fedora 18 で検索しても皆 GStreamer なんて一言も触れていない。
Nautilus でサムネイルできなくて皆阿鼻叫喚してるかと思ったけど実際の話メインで使っている人がほとんどいないってことなのね。

しかし今日だけで五回もフリーズした、早く安定しないかなぁ。

Python subprocess

何を今更だけど os.system や os.popen はもう「使うな!」なんだね。
17.1. subprocess ? サブプロセス管理 ? Python 2.7ja1 documentation

それどころか Python3 には commands が無い。
subprocess の使いかたを覚えておかなければ。

# command

import os
import subprocess

#os.system("gvfs-open hoge.txt")
subprocess.call(["gvfs-open", "hoge.txt"])

# With spaces in the file names
#os.system('gvfs-open "on space.txt"')
subprocess.call(["gvfs-open", "on space.txt"])

引数の最初が args だから list に全部入れなきゃだめってことみたい。
ファイル名にスペースがある場合に楽になった、ような…

それからえっと
os.system(“gedit &”)
みたいなバックグラウンド実行はどうするのかな。

# Background

#subprocess.call(["gedit", "&"]) # Error
subprocess.Popen(["gedit"])

Popen を選ぶだけみたい、ふむふむ。

でコマンドのアウトプットを得るには

# Get Output

#output = os.popen("pwd").read()
#output = commands.getoutput("pwd")
output = subprocess.check_output(["pwd"])

なるほど。

ということでこんなページを作ってみた。
Python Tips – L’Isola di Niente

でも結局 GLib を使ったほうが楽だったりして。
全部 Python でやることにこだわるなんて正直アホみたい。

from gi.repository import GLib

# Execute
GLib.spawn_command_line_async("gvfs-open hoge.txt")
# Get Output
result, output, error, status = GLib.spawn_command_line_sync("ls -l")
print output

Gtk+ DataBinding

明日から仕事始めなのでと覚書ページ書き換えラストスパート。
GtkSwitch を試していて困った。

GtkSwitch

activate シグナルがあるけど connect しても発行されない。
but use the notify::active signal.
なんて書いてあるから当然なんだけど、どう使うの?
さて今日も英語と格闘が始まるな。

Short Example of GSettings Bindings with Python using a Gtk Switch ? Mariano Chavero

なるほど、こうやって使うのか。
GNOME3 環境の人しか実験できないサンプルだけど on off でデスクトップアイコンの表示切り替えができるのを確認した。
しかも dconf-editor で切り替えたら GtkSwitch も切り替わる双方向。

つまり Widget のプロパティと GSettings はバインディングできるんだね。
それなら Widget 同士でもやれそうだと思ったので調べてみる。
というか GSettings のサンプルだと GNOME3 以外を使っている人には解らないし。

GBinding

g_object_bind_property という関数で Widget 同士のバインドもできそう。
PyGI で GObject.Binding 内を探すが見当たらない。

GObject-2.0 Python API Documentation

うーんココには有ると書いているんだけど。
って URL をよく見たら ubuntu-12.10 だ、つまり Fedora17 より新しい。
仮想 Ubuntu 12.10 を立ち上げ dir してみる。

bind_property

あぁやっぱり。
しかたがない、今は仮想 Ubuntu 上で試してみるか。

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

"""
    Widget Property Binding Sample
"""

from gi.repository import GObject, Gtk

switch = Gtk.Switch()
check = Gtk.CheckButton("Check")
GObject.Binding.bind_property(
        switch, "active",
        check, "active",
        GObject.BindingFlags.BIDIRECTIONAL )

hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
hbox.pack_start(switch, False, False, 0)
hbox.pack_start(check, False, False, 0)

w = Gtk.Window()
w.add(hbox)
w.connect("delete-event", Gtk.main_quit)
w.show_all()
Gtk.main()

widget_property_binding

GBindingFlags は
GObject.BindingFlags.DEFAULT では source 側からの一方向バインド。
GObject.BindingFlags.BIDIRECTIONAL で双方向になる。
他2つはよく解らない、上記2つあれば充分だし。

こんな感じでデータと GUI を結びつければ面白くなるね。
WPF の ObservableCollection みたくできるかも、アレは混乱の元だが。

しかし早く fedora 18 出ないか、今日サイトを見たら又伸びているんだが…

Gtk SetForegroundWindow

年末年始の空き時間を使って覚書ページまとめと Tips 追加をやっている。
ついでに HDD 内の古いサンプルコードを整理。

ほぼ PyGtk コード、ほとんどもう役に立たない…
GTK3 には Pixmap なんてもう無いっつーのとか。
千以上あったけどやっと 2/3 くらいに減らした。

でも中には今まで気がつかなかったコードも見つかる。
多重起動防止でアプリを最前面にもっていく方法が今まで解らなかった。

gtk_window_activate_focus ()
gtk_window_present ()

どうやらこの二つを呼べばアクティブ化できるようだ。
以前書いた GtkApplication を使った Vala コードを書き換えて実験。

using Gtk;

/*
 * Prevent multiple window Sample
**/
public class TestWin : Window {

    private Notebook note;
    private TextView[] view;

    public TestWin ( Gtk.Application app ) {
        this.set_application ( app );
        this.set_title( "TestWin" );
        note = new Notebook ();
        this.add ( note );
        this.resize ( 320, 240 );
        this.show_all ();
    }
    public void CreateNew () {
        var tab_label = new Label ( "new.txt" );
        var text_view = new TextView ();
        view += text_view;
        note.append_page (text_view, tab_label);
        this.show_all ();
        // New Page Activate
        note.set_current_page ( view.length - 1 );
    }
    public void CreateTab ( File[] files ) {
        foreach ( var file in files ) {
            var tab_label = new Label( file.get_basename () );
            var text_view = new TextView ();
            view += text_view;
            note.append_page(text_view, tab_label);
        }
        this.show_all();
        // Last Page Activate
        note.set_current_page ( view.length - 1 );
    }
}

public class App : Gtk.Application {

    private TestWin win = null;

    public App () {
        Object (application_id:"apps.test.helloworld", flags:ApplicationFlags.HANDLES_OPEN );
    }
    public override void activate () {
        if ( win == null ) {
            win = new TestWin( this );
        }
        win.CreateNew ();
        // SetForegroundWindow
        win.activate_focus();
        win.present();
    }
    public override void open ( File[] files, string hint ) {
        if ( win == null ) {
            win = new TestWin( this );
        }
        win.CreateTab ( files );
        // SetForegroundWindow
        win.activate_focus();
        win.present();
    }
}

public class Main {
    public static int main ( string[] args ) {
        Gtk.init ( ref args );
        var app = new App ();
        app.run ( args );
        return 0;
    }
}

ついでに追加タブもアクティブにするコードも入れてみた。
うん、今のところこれでいけるようだ。
多重起動防止アプリ以外ではどうでもいいことなんだけどね。

コンパイルするの面倒くさいよ、早く PyGI で使えるようになってくれ。

Vala GtkDrawingArea

古いコンテンツの覚書のページ書き換えをヤルヤル詐欺していた。
放置しすぎてもう古さ全開なのでと先週から地味に整理中。
この年末年始休暇の内になんとか整理を終わらせたい。

ウインドウを作る – L’Isola di Niente
ダイアログを作る – L’Isola di Niente

コレのために Glade を何年かぶりに入れたりしたけど特にネタも無かった。
Glade を使ったページは削除で済ませることにしよう。

次は DrawingArea を、PyGtk と共通点がゼロなんだよなココ。
expose-event が draw に変わって cairo_t を使えだもんな。

しかし困った、Fedora 17 では下記 PyGI コードが動かない。
というか draw のシグナルが発生しない。

原因判明、同一ディレクトリに cairo.py なんて名前のファイルを入れていたので上書き扱いになり cairo.Context が見つからず変換できないエラーでシグナルが作成できなかっただけだった…
消したら普通に動いた…

Python with GTK+3.0 Create GtkPixbuf | PaePoi

GTK+ 3.4 自体がおかしいのか gir がおかしいのかな。
Boxes 上の仮想 Ubuntu は GTK+3.6 なのでソッチで動かしてみる。

gtk3_6

問題なく動く、こりゃココの整理は Fedora 18 待ちか。
せっかくなので Vala で同じコードを書いてみた。

PyGI では入れていなかった画像が無かった場合の例外処理も入れて。
以前書いた MessageBox.Show() の使い方サンプルも兼ねて。

using Gtk;
using Gdk; //Pixbuf

/*
 * build command
 * valac --pkg gtk+-3.0 test.vala
 * `--pkg gdk-2.0` Not required
 */

public class Win : Gtk.Window {

    private Pixbuf pixbuf;
    private DrawingArea drawing_area;

    public Win () {
        this.set_title("draw test");
        this.destroy.connect (Gtk.main_quit);
        // DrawingArea
        drawing_area = new DrawingArea();
        drawing_area.draw.connect(on_drawing_area_draw);
        try {
            pixbuf = new Pixbuf.from_file("madoka.jpg");
        } catch (Error e) {
            MessageBox.Show(e.message);
        }
        this.add(drawing_area);
        this.show_all();
    }

    private bool on_drawing_area_draw(Cairo.Context cr) {
        // set picture
        if (pixbuf != null)
            Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
        // Arc
        int width = drawing_area.get_allocated_width();
        int height = drawing_area.get_allocated_height();
        cr.arc( width / 2.0,
                height / 2.0,
                int.min(width, height) / 2.0,
                0,
                2 * Math.PI);
        cr.fill();
        return false;
    }
 
    public static int main (string[] args) {
        Gtk.init(ref args);
        new Win();
        Gtk.main();
        return 0;
    }
}

public class MessageBox {
    public static ResponseType Show (string text) {
        var dlg = new MessageDialog(
                null,
                DialogFlags.MODAL,
                MessageType.ERROR,
                ButtonsType.OK,
                text );
        dlg.set_title("Message");  
        var res = dlg.run();
        dlg.destroy();
        return (ResponseType)res;
    }
}

gtk3_4_vala

Vala では GdkPixbuf.Pixbuf ではなく Gdk.Pixbuf なんだね。
gir って実はよく解っていないけど動的と静的で違うのか。
PyGI では GLib.PI だったのが GLib.Math.PI だったりとか思ったより迷う。
min 関数は int.min() とかって何だよ…
ほとんど同じだけど実は細かく違うのね。

このコードは PyGI っぽくハンドラを書いたけどハンドラに GtkWidget 部を入れられないのでクラス変数を使うしかなく少し面倒、公式サンプルのように書いた方がローカル変数が使えて楽かと。
pkg 指定が Gtk+-3.0 だけでイケたのがちょっと奇妙。

とりあえず GTK+ 3.4 環境でも Vala なら問題なく draw シグナルが使えるってことですね。

しかし古いページはたった三年前に書いたとは思えない古臭さだ。
又あと三年たったら…考えないようにしよう。
最近の Linux は進化が早すぎて怖い。

アプリケーションの.NET Framework 4.5への移行: 廃止された型と新しい型

とはいえ .NET に比べたら楽なもんだ。
969 の新しい public 型なんて吐き気しかしない、Linux へ早めに逃げて良かった。
.NET 関連の tips も削除しといたほうがいいのかも。