Python」タグアーカイブ

Human sort 2

仕事関連でドタバタしており Blog の更新が止まりがちになっています。
今月は落ち着きそうな希望が少々持てるのでちょっぴりがんばってみる予定。
ということで

前々回(って半月前か…)

の自然順ソート仕様で我が Y901x のリストアップを色々試していた。
サボっていたのではなく地味にテストはしていた、とにかく解ったのは問題だらけ!

最大の問題は aa.mp4 aa1.mp4 等というファイルがあったとき。
Nautilus では aa.mp4 が当然のように上になる、前々回の関数では逆になってしまう。

原因が解らなくてしばらく悩んだけど解ってしまえば単純。
前々回のコードではドットは単なる文字列の一部としか認識しないからだ。
分離され整数変換された値とドットという文字列を比較ならば当然ドットのほうが大きな値と認識する。
結果 aa.mp4 のほうが aa1.mp4 より大きいと認識するファイル名になってしまう。

なんだそれ…
これじゃ拡張子が付いたファイル名では全部同じようになってしまう。
いや Linux の場合はそれだけじゃない、ドットファイルという文化があるのだから。
以下のようなファイル名を用意してテスト、nautilus は隠しファイルを表示設定に。

Python の文字列比較は先頭から一文字単位で照合しているだけ、当然の結果。
整数文字列の比較は確かに行っているけどこうなってしまう、落とし穴が深すぎだった。
Windows の Explorer 名前順ではドットファイルは先頭になるが Nautilus は日本語より下。
同じ自然順ソートでもドットをどう扱うかが違うようで。

とにかくファイル名の場合は前々回のソート関数では Nautilus とは同じにはならない。
というかドットファイルどころかバックアップファイルにも無考慮だと今頃気がつく私であった。
後日に続く(終わりカヨ!

Human sort

Python の List で sort() しても Nautilus のファイル名順にならない。
Windows XP の時のように数値優先な自然順ソートをしなければならない。

我がアプリ Y901x のリストはデフォルトのソートをしているだけ。
気にしていたけど無名なのをいいことに今まで知らん顔していた(ぉい!

Windows XP には shlwapi に StrCmpLogicalW という API 関数があったんだが…
Linux なら glib や gio に関数がありそうなんだけどな…

PyGObject Reference Manual

見つからないし。
Nautilus のソースも落としたけど何が何やらサッパリワカンネ。
自力で自然順ソート関数を作っている人のコードを参考にしてみよう。

Ned Batchelder: Human sorting

コレやったらとりあえずうまくいった。

Python sorts “u11-Phrase 1000.wav” before “u11-Phrase 101.wav”; how can I overcome this? – Stack Overflow

コッチは isdigit を使っている、その手もあるか。
というか整数の正規表現ってこのどっちでもいいんだなとか変な発見。

return [ tryint(c) for c in re.split('([0-9]+)', s) ]
or
return [ tryint(c) for c in re.split(r'(\d+)', s) ]

しかしコレで完全に Nautilus と同じになるのだろうか?
とりあえず自分でもう少し試してみる。

それと playbin2 化したらよく落ちるようになったなぁ…
切り替え事の gtk.main_iteration を消したらかなり良くなったけどこれでいいのかな…

GtkInfoBar

Part III. GTK+ Widgets and Objects

何か Linux とか GTK+ とかで新しいプログラミングのネタは無いものか…
なんて考えながら上記ををポッケーと眺めていた。

いきなり見つかったぞ、GtkInfoBar って何?

調べてみると例えば Gedit に動画ファイルをドロップしたエラーで出るあの少し大きなバー。
やれば解るので細かいことは書かないけど、ソレをわざわざ GTK+ 部品の一つにしたようだ。

GTK+ – Wikipedia, the free encyclopedia

GTK+ 2.18 で追加されたようだけど PyGtk から使えるのかな?
自前環境にて PyGtk から使えるか調べてみる。

Mandriva 2010.0 の GTK+ 2.20.0 と PyGtk 2.16 では使えない。
Ubuntu 10.04 の GTK+ 2.21.1 と PyGtk 2.17 では使える。

PyGTK 2.0 Reference Manual

こっちには今現在はマニュアルは無い、てかまだ version 2.15.2 のままのようで。
ドキュメントが遅れるのは Linux ではしかたがない、商売でやっている MSDN とは違うのだから。

しかしこの程度のインフォメーション表示バーなんて自作しても別に難しくない。
でも GTK+ 自体は GUI 部品の見た目統一を目指しているはずだし(違ったかな?
テーマ切り替え時の追従なんかも期待できる、Opera がまったく追従しないのが気になって…
とにかくせっかくベンダーが用意してくれたものだから使ってみよう。

GtkDialog の vbox アトリビュートのようなものは存在しない。
これ自体が GtkHBox サブクラスであらかじめボタン配置用スペースを予約しているみたい。
とりあえずボタンを押すと InfoBar が出てレスポンスでボタン文字を変更する例を。

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

import gtk

class InfoBarTest(gtk.Window):
    """
        GtkInfoBar Test
        PyGtk 2.17 or later.
    """
    def __init__(self):
        gtk.Window.__init__(self)
        vbox = gtk.VBox()
        vbox.show()
        self.button = gtk.Button("Click!")
        self.button.show()
        # GtkInfoBar
        self.infobar = gtk.InfoBar()
        self.infobar.pack_start(gtk.Label("Hi!\nButton Click"))
        self.infobar.add_button("_Cancel", gtk.RESPONSE_CANCEL)
        self.infobar.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
        # Signal
        self.infobar.connect("response", self.on_infobar_response)
        self.button.connect("button-press-event", self.on_click)
        self.connect("delete-event", gtk.main_quit)
        # append
        vbox.pack_start(self.infobar, False)
        vbox.pack_start(self.button)
        self.add(vbox)
        self.resize(300, 300)
        self.show()
        
    def on_click(self, widget, event=None):
        self.infobar.show_all()

    def on_infobar_response(self, widget, response_id):
        if response_id == gtk.RESPONSE_ACCEPT:
            self.button.set_label("OK!")
        else:
            self.button.set_label("Cancel...")
        self.infobar.hide()

if __name__ == "__main__":
    w = InfoBarTest()
    gtk.main()

バインディングは他の GTK+ 部品と同様みたい、これなら得にマニュアルは不要か。
後はチマチマ弄くってみればもっと見た目とかもよくなると思う。

うん、StatusBar や TitleBar では面積が足りない場合なんかに有効だ。
上手く使えば Debug にも利用できそう、自作すればいいとはいわない。

というか自作すればいいというのなら GtkMessageDialog なんかもいらないわけで。
「ナンデモアリ」を徹底的にやった結果として複雑怪奇になった WPF は見習ってほしいよ。

GStreamer Change Volume

GStreamer Player のボリュームを直線的に変更したい。
GtkHScale の値を直で渡すと曲線になって使いにくい、DirectShow はもっと酷かったけど。

dBデシベルの話し 音の大きさ

音量のデシベルは非常にややこしい。
GStreamer マニュアルの GstStreamVolumeFormat 以下にも 20 * log10 (val) とある。

gststreamvolume

実は私の Cinema という Windows 用 DirectShow プレイヤーは手抜きをしていて…
DirectShow ボリュームは最大 0、無音 -10,000 をデシベル単位でということなのですが…

void CDirectA::SetVolume(int nVolume)
{
	double d = pow((double)(nVolume * 2), 2.0);
	m_nVolume =  static_cast(-d);
	if (pBasicAudio)
		pBasicAudio->put_Volume(m_nVolume);
}

というベキ乗を利用してなんとなく似たようなカーブにしている。
ぶっちゃけ WMP とは全然カーブが違うんだが面倒だということで(ぉい!

GStreamer ボリュームは最大 100.0、無音 0.0 なのでコレを Y901x で真似るには

def on_volume_value_changed(self, widget, event=None):
    val = widget.get_value()**2 / 10000.0
    self.player.set_property("volume", val)

とやってみたけど完全に違う、GStreamer では同じ手は使えないようだ。
キチンと調べないといけないみたい、Totem はどうやっているかコードを漁る。

bacon-video-widget-gst-0.10.c の bacon_video_widget_set_volume 関数で
GST_STREAM_VOLUME_FORMAT_CUBIC を指定している、この型と相互変換が必要か。

gststreamvolume を pygst から利用するには c ヘッダが gst/interfaces 以下にあるので

import gst
dir(gst.interfaces)

みたいにやれば関数が一覧されるので探ってみる。
stream_volume_set_volume は流石に使えないようだけど convert はできるようだ。
CLAMP 関数も必要かな C言語でCLAMP(a,b,c) | OKWave
一行なのでラムダ式を使えと言われそうだが CLAMP という関数名も覚書に使いたい。

def set_volume(self, value):
    v = gst.interfaces.stream_volume_convert_volume(
            gst.interfaces.STREAM_VOLUME_FORMAT_CUBIC,
            gst.interfaces.STREAM_VOLUME_FORMAT_LINEAR,
            value)
    def clamp(a,b,c): return min(max(a,b),c)
    v = clamp(v, 0.0, 1.0)
    self.player.set_property("volume", v)

def on_volume_value_changed(self, widget, event=None):
    self.set_volume(widget.get_value() / 100.0)

おっし、これで Totem とまったく同じ音量カーブになった。
CUBIC を LINEAR に変換すればいいのね、実はいまいちよく解っていなかったり(ぉい!

もう少し Debug して Y901x の Playbin2 化 version 0.3 はなんとかなりそう。

playbin2

Totem は

gst_stream_volume_set_volume

でボリューム調節を行っているということだけ理解した。
その関数ってもしかして playbin の volume Property とリンクしているんじゃないの?

米国の Google でコードを探し試しまくったけどさっぱり解らない…
3. Pipeline
自前でパイプラインを構築するか、しかし情報少なすぎ!

いや、そういえば playbin は古いので新しい PulseAudio に対応しているはずがない。
だから playbin2 に移行しろということなのか…

playbin2

playbin

Y901x の GstPipeline を playbin2 に書き換えて試す。

self.player = gst.element_factory_make("playbin2", "player")

エラーが出る場所をとにかくコメントアウトしてボリュームを試す。

あーあ、マジでそんだけだった、しかも mute Property にて簡単にミュート。
しかし DirectShow 同様に非線形カーブなので GtkHScale 値をそのまま使えないみたい。
ミュートも「サウンドの設定」の「アプリケーション」タブと見事連動。

これではもう playbin2 に移行するしか選択肢が無いようだ。

# Get GstStreamInfo List
info_arr = self.player.get_property("stream-info-value-array")

の所で例外になる、そりゃ playbin2 にそんなプロパティは無い。
本当はもの凄い遠回りをしたけど一応こう書き換えたら上手く動いた。

def gst_on_message(self, bus, message):
    t = message.type
    if t == gst.MESSAGE_EOS:
        self.clear()
        # Repeat
        if self.settingwin["repeat"] > 1:
            self.on_next(self)
        elif self.settingwin["repeat"] == 1:
            self.set_play_position(0)
            self.player.set_state(gst.STATE_PLAYING)
    elif t == gst.MESSAGE_ERROR:
        self.clear()
        err, debug = message.parse_error()
        self.messagebox(str(err) + "\n" + str(debug))  
        self.toolbox.change_img(0)
    elif t == gst.MESSAGE_STATE_CHANGED:
        if gst.STATE_PLAYING == message.parse_state_changed()[1]:
            self.toolbox.change_img(0)
            # This is First Play
            if not self.is_playing:
                try:
                    self.p_duration = self.player.query_duration(gst.FORMAT_TIME)[0]
                except:
                    self.p_duration = gst.CLOCK_TIME_NONE
                if self.p_duration != gst.CLOCK_TIME_NONE:
                    self.toolbox.seekadj.upper = self.p_duration
                    self.is_playing = True
                    # toolbox Buttons Enabled
                    self.toolbox.set_enable_ctrl(True)
                    # Timer on
                    self.timer_id = gobject.timeout_add(200, self.on_timer)
                    # File Names Listup
                    self.listup_change()
                    #
                    # playbin2
                    #
                    vsink = self.player.get_property("video-sink")
                    # vsink == None @ Music File
                    if vsink:
                        for pad in vsink.pads():
                            # print pad.get_caps()[0].get_name() # is Check
                            # W Buffer Off
                            self.video_window.unset_flags(gtk.DOUBLE_BUFFERED)
                            # caps[0] @ GstStructure
                            caps = pad.get_negotiated_caps()
                            self.video["width"] = caps[0]["width"]
                            self.video["height"] = caps[0]["height"]
                            #
                            flt = float(self.video["width"]) / float(self.video["height"])
                            self.aframe.set(xalign=0.5, yalign=0.5, ratio=flt, obey_child=False)
                            self.change_video_size()
                    """ under Old PlayBin src
                    # Get GstStreamInfo List
                    info_arr = self.player.get_property("stream-info-value-array")
                    if len(info_arr) == 1:
                        # Music
                        pass
                    else:
                        # W Buffer Off
                        self.video_window.unset_flags(gtk.DOUBLE_BUFFERED)
                    for info in info_arr:
                        # Get Video Size
                        pad = info.get_property("object")
                        #print pad
                        caps = pad.get_negotiated_caps()
                        if caps != None and "video" in caps.to_string():
                            # caps[0] @ GstStructure
                            self.video["width"] = caps[0]["width"]
                            self.video["height"] = caps[0]["height"]
                            #
                            flt = float(self.video["width"]) / float(self.video["height"])
                            self.aframe.set(xalign=0.5, yalign=0.5, ratio=flt, obey_child=False)
                            self.change_video_size()"""
        else:
            self.toolbox.change_img(1)

GstStreamInfo のリストからの情報で設定するより短くなってもーたw
video-sink なんてテキトーだったのにビンゴだったのは自分で驚いたマジで。

これで playbin2 化はなんとかなりそうだ、後はボリューム値の線形化だ。
って以前のように動かせるだけで見た目は何も進化していないのが少し悲しい…