Python」タグアーカイブ

gi.repository GTK+ Version

Fedora 15 と Ubuntu 11.04 の PyGtk について。

その前に VirtualBox ose 上で Ubuntu を使っているけど \ や | が打てない。
Ubuntu日本語フォーラム / Virtualbox 4.0 で一部のキーが使えない?
VirtualBox ose の Update を待とう…
今回はホストの fedora からクリップボードコピペでなんとかした。

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

from gi.repository import Gtk

message = "「はい」を押すと終了"

while 1:
    dlg = Gtk.MessageDialog(
            None,  
            Gtk.DialogFlags.MODAL,  
            Gtk.MessageType.WARNING, 
            Gtk.ButtonsType.YES_NO,  
            message)  
    r = dlg.run()  
    dlg.destroy()
    if r == Gtk.ResponseType.YES:
        break
    elif r == Gtk.ResponseType.NO:
        message = "それは「いいえ」だろ!"
    elif r == Gtk.ResponseType.DELETE_EVENT:
        message = "閉じるボタンじゃネェ!"
    else:
        message = "不明な動作..."

このコードで Fedora 15, Ubuntu 11.04 のどちらでも動く。
Ubuntu 10.10 以前では例外になる、つか gi から無いわけで。
Fedora のダイアログは「閉じる」ボタンが出ないので右クリックで「閉じる」とやる。

import が変更になり gtk を Gtk に全置換。
以前は定数だったものの大半が enum 名のアトリビュートに変わっている。
それ以外はほとんど同様と思っていいみたい。

つまり Ubuntu 11.04 も GTK3 なのか。
と思ったけどインタラクティブシェル上でコレをやってみる。

from gi.repository import Gtk
Gtk._version

Ubuntu 11.04 の場合はこの形式で書いても GTK2 になるってことなのか。
/usr/lib64/python2.7/site-packages/gi/overrides/Gtk.py
がバージョン違いを振り分けているみたい、とにかくそういうこと。
ソースコードを見てもなんだかよくワカンネェ!

なんにしても早めにこの形式なコードへ書き換えしたほうがいいみたい。
API ドキュメントは既に GTK+ 3.0 になっているのだし。
PyGtk ドキュメントが遅いのはいつものこと。
GNOME 開発センター

ついでに

EyeOfGnome/Plugins – GNOME Live!

このとうりにやっても全然 eog が自作プラグインを認識しない…
~/.local/share/eog/plugins や ~/.local/lib64/eog/plugins
にも試しに置いてみたけど無意味だった。
/usr/lib64/eog/plugins に置くしか無いのだろうか。
ついでに IAge 指定が 2 のままじゃん、Gedit と違ってヤル気が無い感じ。
おかげで自作 eog プラグインの更新ができないよ、まいった。

Fedora 15 006

Fedora 15 (GNOME 3) 64bit 生活六日目。

※ Gedit Plugin

Gedit/PythonPluginHowTo – GNOME Live!

Gedit 3 のプラグインは妙に複雑になった感じだけどやってみると簡単。
Gedit プラグインの作り方 – L’Isola di Niente
私の公開しているこのページを Gedit 3 用に改造してみる。

*.gedit-plugin 拡張子であったファイルは *.plugin と拡張子を変更。
セクション名を [Plugin] に、IAge を 3 に変更、v2 との違いはコレだけだ。

肝心のコード

#-*- coding:utf-8 -*-

import gedit
import gtk

class TestPligin(gedit.Plugin):
    def __init__(self):
        gedit.Plugin.__init__(self)

    def activate(self, window):
        self.window = window

    def deactivate(self, window):
        pass

    def update_ui(self, window):
        pass

#-*- coding:utf-8 -*-

from gi.repository import GObject, Gedit, Gtk

class TestPligin(GObject.Object, Gedit.WindowActivatable):
    __gtype_name__ = "TestPligin"
    window = GObject.property(type=Gedit.Window)
    def __init__(self):
        GObject.Object.__init__(self)

    def do_activate(self):
        pass
        
    def do_deactivate(self):
        pass

    def do_update_state(self):
        pass

ということみたい、コールバックに引数は無くなったんだね。
__gtype_name__ は class 名と同じにしなければいけないみたい。

多重継承の敬称元を Gedit.AppActivatable とか3つから選べになっている。
普通なら Gedit.WindowActivatable だけでいいだろう。
下のほうにあるサンプルコードは継承元を書き忘れているじゃん…

後は self.window のメソッドを辿って弄くっていく。
v2 のコードから gtk → Gtk に全置換する必要がある。
それと activate のコールバックは引数を3つにしておく必要があった。
user_data を指定しなくても None が送られてくるみたい、注意ね。

ついでに GtkMessageDialog の引数は enum 形式に変更されていた。
指定がよく解らない人は dir() で辿って(私はそうやった)

testtest.plugin

[Plugin]
Loader=python
Module=testtest
IAge=3
Name=testtest
Name[ja]=テストテスト
Description=plugin test
Description[ja]=プラグインのテスト
Authors=sasakima-nao 
Copyright=Copyright © 2011 sasakima-nao 
Website=http://palepoli.skr.jp/

testtest.py

#-*- coding:utf-8 -*-

from gi.repository import GObject, Gedit, Gtk

ui_str = """<ui>
  <menubar name="MenuBar">
    <menu name="EditMenu" action="Edit">
      <placeholder name="EditOps_3">
        <menuitem name="testtest" action="testtest"/>
      </placeholder>
    </menu>
  </menubar>
</ui>
"""

class TestTest(GObject.Object, Gedit.WindowActivatable):
    __gtype_name__ = "TestTest"
    window = GObject.property(type=Gedit.Window)
    def __init__(self):
        GObject.Object.__init__(self)

    def do_activate(self):
        # GtkUIManager を得る
        manager = self.window.get_ui_manager()
        # GtkActionGroup を新規で作成
        self._action_group = Gtk.ActionGroup("TestTestActions")
        # GtkActionEntry を作成
        # name, stock_id, label, accelerator, tooltip, callback
        actions = [("testtest", None, "引用に変換", None, "すてーたすばー", self.on_testtest_activate)]
        # GtkActionGroup に挿入
        self._action_group.add_actions(actions)
        # GtkUIManager に追加
        manager.insert_action_group(self._action_group, -1)
        self._ui_id = manager.add_ui_from_string(ui_str)
        
    def do_deactivate(self):
        manager = self.window.get_ui_manager()
        manager.remove_ui(self._ui_id)
        manager.remove_action_group(self._action_group)
        manager.ensure_update()
        

    def do_update_state(self):
        pass
    
    def on_testtest_activate(self, action, data=None):
        view = self.window.get_active_view()
        buf = view.get_buffer()
        try:
            begin, end = buf.get_selection_bounds()
        except:
            self.messagebox("変換したいテキストを選択してください")
            return
        text = begin.get_text(end)
        lines = text.split("\n")
        # list の join に変更
        result = []
        for line in lines:
            result.append("> {0}".format(line))
        buf.delete_selection(True, True)
        buf.insert_at_cursor("\n".join(result))
            
        
    def messagebox(self, text):
        dlg = Gtk.MessageDialog(
                self.window,  
                Gtk.DialogFlags.MODAL,  
                Gtk.MessageType.WARNING, 
                Gtk.ButtonsType.OK,  
                text)  
        r = dlg.run()  
        dlg.destroy()

と2つのファイルを作成して ~/.local/share/gedit/plugins に置く。
Gedit を再起動して設定からこのプラグインを選択。

GtkUIManager のメソッドや XML UI 指定は v2 と変わっていないようだ。
これで Gedit 3 のプラグイン作りはなんとかなりそう。

ついでに、外部ツールで実行前にファイルを保存するように指定すると動作しない。
gedit って更新毎に必ず一ヶ所はおかしな所があるよね。

Dynamic Menu in GtkUIManager

PyGtk でメニュー項目の動的な追加と削除。
コレををやらないとマルチトラック音声の切り替え機能が実装できない。

GtkUIManager を使うのが一番簡単、てか私はこの方法しか知らない。
Gedit プラグインでメニューを追加するのと同じ方法でいいはず。

Gedit プラグインの作り方 – L’Isola di Niente

切り替え自体はラジオメニューに、Totem もそうなっているし。
ということは GtkActionGroup には以前書いたコレを指定すればいいかな。

GtkRadioAction でラヂオメニューとツールバーを同期させる

しらない間に自身で方法だけは書いていた。
この2つをまとめれば動的なメニュー項目の追加削除ができそうなのでやってみる。

class AudioMenu(object):
    __slots__ = ["uimanager", "ui_id", "action_group"]
    def __init__(self, uimanager):
        self.uimanager = uimanager
        self.ui_id = -1

    def add_menu(self, actions, xml, callback, value):
        self.action_group = gtk.ActionGroup("AudioMenuActions")
        self.action_group.add_radio_actions(actions, value, callback)
        self.uimanager.insert_action_group(self.action_group, -1)
        self.ui_id = self.uimanager.add_ui_from_string(xml)

    def remove_menu(self):
        if self.ui_id != -1:
            self.uimanager.remove_ui(self.ui_id)
            self.uimanager.remove_action_group(self.action_group)
            self.uimanager.ensure_update()
            self.ui_id = -1
            del self.action_group

一ヶ所でしか使わないけど class にしたほうがメンテナンスが楽なので。

GtkRadioActionEntry List とメニュー XML とコールバックと選択するラジオメニュー値
を引数に渡してメニューを作成する。
メニューの削除は Gedit プラグインとまったく同じで大丈夫だろう。
GtkActionGroup は同じアクションを追記してしまうので都度新規作成と削除をする。

この引数に指定するコールバックを本体クラスに。

def on_soundmenu(self, action, current):
    """
        Multi Track Audio Select
    """
    n = action.get_current_value()
    self.player.set_property("current-audio", n)
    # 下記は本体側の設定保存用
    self.settingwin.n_audio = n

これだけでラジオメニューのどこが選択されているかで勝手にトラック選択になる。

後は GtkActionGroup の List と XML 文字列を黙々と完成させればいい。
とりあえず XML の大雑把なテンプレートを用意。

audio_str = """<ui>
    <menubar name="MenuBar">
        <menu action="File">
            <menu action="sound">
            {0}
            </menu>
        </menu>
    </menubar>
    <popup name="pop_main">
        <menu action="sound">
        {0}
        </menu>
    </popup>
</ui>
"""

やはり Gedit プラグイン同様にメニューの XML を用意する。
自分で作った XML ツリーだからまったく迷わないwww

str.format() なら一つの引数で {0} に入るから間違えない。
新しいフォーマッタはこういう場合には便利だね。

asink = self.player.get_property("audio-sink")
if asink:
    # versioon 0.3.6
    # 一度メニューを空にする
    self.audiomenu.remove_menu()
    # オーディオトラック数で振り分ける
    n_audio = self.player.get_property("n-audio")
    if n_audio == 1:
        self.player.set_property("current-audio", 0)
    else:
        actions = []
        x = ""
        for i in range(n_audio):
            a = "sound{0}".format(i)
            s = "トラック #{0}".format(i)
            # GtkRadioActionEntry の List を作成
            # name, stock_id, label, accelerator, tooltip, value
            t = (a, None, s, None, s, i)
            actions.append(t)
            x += '<menuitem action="{0}"/>'.format(a)
        xml = audio_str.format(x)
        # メニューに追加
        self.audiomenu.add_menu(actions, xml, self.on_soundmenu, self.settingwin.n_audio)
        self.player.set_property("current-audio", self.settingwin.n_audio)

こんな感じで for ループでひたすら文字列を加工し作成。
GtkRadioActionEntry 作成は PyGtk の場合は単なるタプルでいいので簡単。
GtkMenuItem の XML も当然文字列なので同時に作って最後にテンプレートに流し込む。

新しいことは得に何もやっていないのでスンナリと動的メニューが完成した。
まぁ GTK+ と GStreamer に命令を送っているだけのチッポケなアプリですからね。

current-audio property

動画コンテナ(DVD や mp4 等)は音声や映像のトラックを複数格納できる。
主に字幕用途で考えられた構造だろう、と思う…

しかしこの構造を利用して昨今では様々な応用というか実験が進んでいる。
…っぽい、業界全般なんて現場のオッサンに解るはずが無いのだからしょーがない。

確実なのは特定用途限定かつ軽いことだけがウリのプレイヤーにはトドメでしかない。
たとえば私が公開しているプレイヤーアプリとかである。
こんなのってないよ…あんまりだよ…

つまり我が Y901x 0.3.5 はまったく複数トラックメディアに対応していない。
当然開発終了している Windows 用もそうだが開発は終了しているので放置。
DirectShow のコードなんて海外にゴロゴロ転がっているけどシラネ!

実は Totem で指定すれば Y901x にも反映されるのであまり気にしていなかった。
ぶっちゃけ Totem が終了時に X の設定を初期化しないというのを利用しているだけ。
だったけどいいかげんに自分自身が気になるようになってきたのでそろそろ対策しよう。

日本語版 Ubuntu てか GNOME 環境専用アプリで続けるなら別に…
って次で GNOME を排除すると明言している Ubuntu は終わった感が…
GNOME だから選んでいた人は多いと思うんですけど…
私も Fedora 15 待ちで移行は確定だけど Ubuntu 11.04 は全然話題になっていないね…

いつものように長い戯れ言はこのくらいにして。

playbin2

n-audio プロパティでオーディオトラック数。
current-audio プロパティでオーディオトラックの指定。
が可能であるようだ。

Totem のソース bacon-video-widget-gst-0.10.c
にて parse_stream_info 関数がやっていることを参考にしただけなんだが。
C のコードを見て形を変えて Python で使うのは GPL には反していないよね。

映像やテキストのトラック関連もあるけど現状は「シラネ!」でいいだろう。
Y901x 0.3.5 の 1314 行目に以下を追記してみる。

# Audio
asink = self.player.get_property("audio-sink")
if asink:
    self.toolbox.volumebar.set_property("sensitive", True)
    v = self.toolbox.volumeadj.get_value() / 100.0
    self.set_volume(v)
    # 追加
    print self.player.get_property("n-audio")
    # デフォルトは -1 で先頭トラックはゼロ
    self.player.set_property("current-audio", 1)
else:
    self.toolbox.volumebar.set_property("sensitive", False)

なんだ、たったコレだけで 2 番目のオーディオトラックが再生できた。
playbin2 は本当に何でも簡単にやってくれる、頼るのもどうかとは解っているけど。
とにかく私程度のサンデープログラマーなオッサンにはありがたい存在である。

後は切り替えを実際に行うユーザーインターフェイスをどうするかだ。
やっぱりメニュー項目に加えるのが一番迷わない方法だと思うけど。
トラック数を調べて一度選択メニューを全部削除してそれからえっと、実装は面倒くさい…

{0:02d}

Y901x 0.3.5 公開。

Y901x はシークバーを弄くるのに gst.SEEK_FLAG_KEY_UNIT 指定で軽くしている。
けどたまに動かしたピチッとした位置で止めたい場合が多々ある。
面倒くさいので放置していたけどそろそろやろうかと。

def set_play_position(self, location):
    """
        Media Seeking
        SEEK_FLAG_KEY_UNIT -> LightWaight
    """
    if self.seekinfo.button == 1:
        seek = gst.SEEK_FLAG_KEY_UNIT
    else:
        seek = gst.SEEK_FLAG_ACCURATE
    event = gst.event_new_seek(
            1.0,
            gst.FORMAT_TIME,
            gst.SEEK_FLAG_FLUSH | seek,
            gst.SEEK_TYPE_SET,
            location,
            gst.SEEK_TYPE_NONE,
            0)
    self.player.send_event(event)

とシークバー上で押されたマウスボタンを記憶しておいて振り分けにしてみた。
GtkHScale はホイールクリックだと正確な位置に移動できるので合わせてみたんだが。
つーてもホイールクリックでシークはやりにくいので右ボタンでもオケにと。
こんなに簡単に振り分けできるならもっと早くやればよかった、我ながら。

それと一時間以上のファイルの場合は自動で
00:05/85:45 → 00:00:05/01:23:45
なんて切り替わるように表示したくなった。

こんな処理にしたけど、今見るともしかしてジャスト一時間だとアウト…
まぁ手持ちのは問題無いし次で直せばいいか。

def put_time_status(self, location):
    """
        self.player is PlayBin2
    """
    sec = location / 1000000000
    allsec = self.player.query_duration(gst.FORMAT_TIME)[0] / 1000000000
    if allsec > 3600:
        s = time.strftime("%H:%M:%S", time.gmtime(sec))
        t = time.strftime("%H:%M:%S", time.gmtime(allsec))
    else:
        s = time.strftime("%M:%S", time.gmtime(sec))
        t = time.strftime("%M:%S", time.gmtime(allsec))
    self.statusbar.label[0].set_text("{0}/{1}".format(s, t))
    """
    今までの処理
    secv = "%02d:%02d/%02d:%02d" % ((sec / 60), (sec % 60), (allsec / 60), (allsec % 60))
    self.statusbar.label[0].set_text(secv)"""

自分で計算しなくても time モジュールを使えばいいと今頃気がついた私って…

そういえば新しいフォーマッタで %02d みたくゼロ詰めするのはどうやるのだ?
どうせ V2 を使いつづけるのでほとんど気にしていなかった。

7.1. string ? Common string operations ? Python v2.7.1 documentation

# old
print "%02d:%02d" % (1, 2)
# new
print("{0:02d}:{1:02d}".format(1, 2))

スゲェ分かり辛いと思うんですけど…
せっかく C 言語とほぼ同じで覚えやすかったのに今後はこうなるんだよなぁ…