Ubuntu」タグアーカイブ

g_utf8_collate_key_for_filename of ctypes

g_utf8_collate_key_for_filename
がいったいどういう変換をしているかもう少し。

ところで端末に長々と打ち込むのが面倒なので GEdit 外部ツールに以下を指定。
GTK+ を使うには pkg-config 指定が必要なのよね、キーは F7 にした。

/*
gedit tool script

#!/bin/sh
gcc $GEDIT_CURRENT_DOCUMENT_PATH `pkg-config --cflags --libs gtk+-2.0`
*/

#include <stdio.h>
#include <gtk/gtk.h>
#include <glib.h>

int main() {
    gchar c[4];
    int i;
    for (i=0; i<105; i++) {
        sprintf(c, "%d", i);
        gchar *s = g_utf8_collate_key_for_filename (c, -1);
        printf("%s\n", s);
        g_free(s);
    }
    return 0;
}

端末だと解り難いにでリダイレクトしてみる。

十進で桁が増える毎にコロンが一つ付加されていくようだ。
なんだかよく解らない変換だけどこれで自然順ソート比較は上手くいくようだ。
色々やってみたけど結局 Python から ctypes を使う。

14.14.1 ctypesチュートリアル

ココに全部書いているんだが、Linux はちびっと面倒なのね。

glibc = ctypes.cdll.LoadLibrary('libglib-2.0.so.0')
cmpstr = glibc.g_utf8_collate_key_for_filename("a")

とやっても int が戻ってくる、実際にはポインタだが Python にはポインタ型が無いので。
つまり restype をキッチリ指定しないと全部 int になるってことですね。
ということで。

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

import ctypes

glibc = ctypes.cdll.LoadLibrary('libglib-2.0.so.0')
cmpstr = glibc.g_utf8_collate_key_for_filename
cmpstr.restype = ctypes.c_char_p
cmpstr.argtypes = [ctypes.c_char_p, ctypes.c_int]

def sort_nicely(l):
    l.sort(lambda x, y : cmp(cmpstr(x, -1), cmpstr(y, -1)))

たまには lambda を使ってみようと思ったので。
ガベージコレクションなのだからコレでいいはずだけど…
とにかくこれでどうだ?

よし Nautilus とはドットファイルを除けば一致するようになった。
隠しファイルはリストアップに含めないようにする予定なのでどうでもいいけど。

でも Mandriva KDE 上で動かしたら何故か数値ソートしてくれなかった。
って ./ を付け忘れで以前のバージョンを起動しただけだった、GNOME と同様になる。
つか Dolphin のソートって以前の関数とまったく同じ結果じゃん!

これじゃ設定でどちらかに振り分けしてもらうしか両対応の方法が無さそう。

utf8_collate_key

いいかげんに Y901x が落ちまくる件とソート問題を解決させねば。
なんとかさせないと追加機能もやれないよ。
落ちる件は色々試しているんだがまだ原因が解らない、困った…

ソートに関しては自力を諦め Nautilus のコードをひたすら追う。
libnautilus-private/nautilus-file.c
に compare_by_display_name というソレっぽい関数をやっと見つける。

display_name_collation_key を strcmp で比較しているだけなのか。
g_utf8_collate_key_for_filename という glib の関数で代入している。

Unicode Manipulation

あれ、もしかして数値もドットもこの関数一つで解決してまうの?

The Whole PyGTK FAQ

Python には実装されていないようで。
最近のバージョンではあるかもと dir() で探しまくるも見つからず。

とにかくこの関数でどう変換されるのか気になるので C でやってみる。
Ubuntu 10.04 デフォルトでは gcc はあるけど gtk や glib のヘッダは無い。
Glade を入れれば依存関係で Devhelp を含めまとめて入るので Glade を入れる。

/*
gcc  b.c `pkg-config --cflags --libs gtk+-2.0`
*/

#include <stdio.h>
#include <gtk/gtk.h>
#include <glib.h>

int main() {
    gchar *c;
    c = "a1.mp4";
    gchar *u;
    u = g_utf8_collate_key_for_filename (c, -1);
    printf("%s to %s\n", c, u);
    /*g_free(c); is Segmentation fault*/
    g_free(u);
    return 0;
}

久々の C なんだがこんな感じでよかったかなぁ…
strcpy で警告になったのが Visual Studio と同じだったが代替が解らない。
とにかく結果。

なんだかよく解らないのに変換されとる。
後は比較関数を作って実験して上手くいったら…
Python で使うんだが、ctypes しか手が無いかな?

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