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

PyGIWarning

Fedora を 23 にして環境の再構築しているのですが。
今回は GStreamer デコーダーが gnome-softwere に出てこない。
RPM Fusion を毎度のようにリポジトリに追加して

sudo dnf install gstreamer1-libav gstreamer1-plugins-bad-free gstreamer1-plugins-base gstreamer1-plugins-good gstreamer1-plugins-ugly

ぶっちゃけ必要なものは何も変わっていないようだ。
今回は”コマンドを打ってね”ということらしい。

ただ我が Y901x 等の GStreamer フロントエンドを使おうとすると

y901x_warn

PyGIWarning という今まで出なかった警告が出るようになった。
一応書くけど PyGObject と PyGI は同じ。
とにかく gi.require_version をする必要があるみたい。

require_version

これで警告は出なくなる。
実は GTK+ 3.16 でリサイズグリップが無くなったのを無視していた。
ついでに一部動画形式でサイズ取得を失敗する不具合が改善されているようだ。
よし一年ぶりに Y901x を更新だ、需要なんてないけど。

ただ最近は動画って Web ブラウザ上でしか見なくなったのよね。
最新動画が次々と出て来るのでダウンロードしようとか全然思わない。
まだデジカメ動画なんかで使うけどその需要もそのうち失くなりそう。

それはいいとして。
Gedit, Eog 自作プラグインにも PyGIWarning が出るや。
多分こっちにも gi.require_version が必要なんだろう。

警告が出るだけで普通に使えてはいるんだが。
いや、Eog のほうは少しおかしいみたい。
こっちの更新は明日、ウチ姫やんなきゃ(ぉい!

Similar image @ Gjs

あるアプリを作るのに近似画像を探す処理が必要になった。
Lab色空間 – Wikipedia
を得て元画像の全体ピクセル近似値を調べればいいと解った。

[PHP]似た画像を検索して近い順番に並べる(類似画像検索) | PHP Archive
手段を見つけた、PHP だけど計算方法はどの言語でも同じだろう。
ただ GD を利用しているので他言語では別の画像解析手段が必要。

ということで、同様な処理を Gjs(Linux) でやってみる。
後で osascript(Mac) 版や Web 版を作りたくなった場合に流用できるので。

Gjs だから画像は当然 GdkPixbuf を GD の代わりに使う。
とはいえ GD と同じ関数があるはずもなく違う手段で同様な処理を作る。

画像処理のサンプル一覧
imagecolorat は gdk_pixbuf_get_pixels からオフセット。
imagecolorsforindex は rowstride 値を使って取得。
実は随分前からコレで詰まっていたおかげでブログの更新が…

正直上手く変換できた自信は無いけどなんとかなったっぽいコード。

#!/usr/bin/gjs

const GdkPixbuf = imports.gi.GdkPixbuf;

// 比較元ファイル
let sample = GdkPixbuf.Pixbuf.new_from_file("nae.jpg");
let sample_lab = getImageLab(sample);

// 比較対象ファイル名の配列
let datas = ["nae01.jpg","nae02.jpg","nae03.jpg","nae04.jpg","nae05.jpg"];

let diff = [];
datas.forEach(function(element, index) {
    let data = GdkPixbuf.Pixbuf.new_from_file(element);
    let lab = getImageLab(data);
    // 比較
    let distance = 0;
    sample_lab.forEach(function(element, index) {
        distance += getLabDistance(element, lab[index]);
    });
    diff.push({n:distance, name:datas[index]});
});

// sort
diff.sort(function(a, b) {
    return a.n > b.n;
});

// 確認
diff.forEach(function(element, index) {
    print(element.name, element.n);
});

// 画像をリサイズしピクセルごとのLab色空間上の座標を取得する
function getImageLab(image){
    let thumb = image.scale_simple(4, 4, GdkPixbuf.InterpType.BILINEAR)
    let pixeldata = thumb.get_pixels();
    let rowstride = thumb.get_rowstride();
    let lab = [];
    for(let x=0; x<4; x++){
        for(let y=0; y<4; y++){
            let i = y * rowstride + x * 3;
            lab.push(rgb2lab([pixeldata[i], pixeldata[i+1], pixeldata[i+2]]));
        }
    }
    return lab;
}

// xyz色空間上の座標をlab色空間上の座標に変換する
function xyz2lab(xyz) {
    let threshold = 0.008856;
      
    //Chromatic Adaptation Matrices
    // D50
    let ref_x = 0.96422;
    let ref_y = 1.0000;
    let ref_z = 0.82521;
 
    let var_x = xyz[0] / (ref_x * 100);
    let var_y = xyz[1] / (ref_y * 100);      
    let var_z = xyz[2] / (ref_z * 100);
      
    var_x = (var_x > threshold) ? var_x = Math.pow(var_x, 1/3 ) : (7.787 * var_x) + (16 / 116);
    var_y = (var_y > threshold) ? var_y = Math.pow(var_y, 1/3 ) : (7.787 * var_y) + (16 / 116);
    var_z = (var_z > threshold) ? var_z = Math.pow(var_z, 1/3 ) : (7.787 * var_z) + (16 / 116);
      
 
    let l = ( 116 * var_y ) - 16;
    let a = 500 * ( var_x - var_y );
    let b = 200 * ( var_y - var_z );
    return [l, a, b];
}
 
// rgb値をxyz色空間上の座標に変換する
function rgb2xyz(rgb) {
    let r = rgb[0] / 255;
    let g = rgb[1] / 255;
    let b = rgb[2] / 255;
 
    r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
    g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
    b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
 
    r = r * 100;
    g = g * 100;
    b = b * 100;
      
    let xyz = new Array();
      
    //sRGB D50
    xyz.push(r * 0.4360747 + g * 0.3850649 + b * 0.1430804);
    xyz.push(r * 0.2225045 + g * 0.7168786 + b * 0.0606169);
    xyz.push(r * 0.0139322 + g * 0.0971045 + b * 0.7141733);
    return xyz;
}
 
// rgb値をlab色空間上の座標に変換する
function rgb2lab(rgb) {
    let xyz = rgb2xyz(rgb);
    let lab = xyz2lab(xyz);
    return lab;
}
 
// 2つの座標を比較し距離を返す
function getLabDistance(p1, p2){
    let dist = Math.sqrt( Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[2] - p1[2], 2) );
    return dist;
}

similar_pic

苗ちゃんがカワイイのは当然でどうでもよくて。
同一画像なら完全にゼロ、後は色が似た順に取得できた。
GdkPixbuf なので C や Python でも同じことができるはず。

後は osascript はともかく Web で画像解析はどうするかだ。
というか Web アプリでやりたいんですけどね。

PyObjC

PyObjC で Python から Cocoa にアクセスしてみよう (フェンリル | デベロッパーズブログ)

なんだよ、Mac で Python は全然役立たずじゃないヤン!
ただ Python2 専用だろうな、次期版は Python3 になるかな。

Simple PyObjC Example ? Python recipes ? ActiveState Code

Window も作れ、更にサブクラスも当然のように使えるようだ。
しかし日本語情報が全然見つからない。
日本人は Mac で Python でも計算ばかりやって喜んでいるようだ。
何が面白くてプログラミングしているか理解できないが人それぞれかと。

よし上記をコピーして動かしてみよう。

拡張子を外して実行パーミッションを付けて。
シバンで pythonw を指定してと。

#!/usr/bin/pythonw
#-*- coding:utf8 -*-

これで Finder から command+O してみる。

mac_pythonw

端末エミュレーターが開くジャン!
pythonw は Windows と仕様が違うの?

Macintosh で Python を使う ? Python 2.7ja1 documentation
Windows で Python を使う ? Python 2.7ja1 documentation

確かに Mac のドキュメントには”ターミナル無し”とは書いていない。
ヤラレタって感じ、結局アップルスクリプトと変わらない結果に。
オマケに初期化が凄く遅い、おまえは IronPython か!

やはりスクリプトで GUI はあきらめたほうが良さげ。
でも Objective-C 関数が Python で使えるのは嬉しいかも。
いや、初期化が遅いと結局使わなくなるのは経験済みで。
あー”コレだ!”てのがまだ見つからないYO!

GtkPopoverMenu

Fedora 22 にしてから一ヶ月近いが GTK3 関連でヤル気が出ない。
新規 Widget が GtkPopoverMenu くらいしかないので。
GtkPopover とたいして違わなそうだし。

多分 Nautilus のメニューがコレだと思うけど。
GTK+ Inspector で確認してみるか。

nautilus316menu

やはりっっってあれ、GtkModelButton って何?
devhelp で確認するとコレも 3.16 からの新規 Widget のようだ。
何故こんなにヒッソリと追加なのよ、よし調べて使ってみよう。

gtk3-demo には無い、海外を検索してもまだ誰もやっていない。
また devhelp のみが頼りか、いつものことだ。

GtkPopoverMenu: GTK+ 3 Reference Manual

GtkPopover のサブクラスなのでやはり GtkBox に積み重ねる必要あり。
action-name プロパティに GAction のアクション名指定だけでイケるっぽい。
今回は devhelp のように GtkBuilder でやってみました。

#!/usr/bin/env python3
 
from gi.repository import Gtk, Gio

POPOVER = '''<interface>
<object class="GtkPopoverMenu" id="menu">
  <child>
    <object class="GtkBox">
      <property name="visible">True</property>
      <property name="margin">10</property>
      <property name="orientation">vertical</property>
      <child>
        <object class="GtkModelButton">
          <property name="visible">True</property>
          <property name="active">True</property>
          <property name="action-name">win.hello</property>
          <property name="text" translatable="yes">Hello</property>
        </object>
      </child>
      <child>
        <object class="GtkModelButton">
          <property name="visible">True</property>
          <property name="action-name">win.world</property>
          <property name="text" translatable="yes">World</property>
        </object>
      </child>
    </object>
  </child>
</object>
</interface>'''
 
class PopoverMenu(Gtk.ApplicationWindow):
    """
        GtkPopoverMenu Test
    """
    def __init__(self):
        Gtk.ApplicationWindow.__init__(self)
        # DrawingAre
        area = Gtk.DrawingArea.new()
        # Popup Button
        button = Gtk.Button.new_with_label("Option")
        button.connect("clicked", self.on_option_button_clicked)
        # ActionBar
        bar = Gtk.ActionBar.new()
        bar.pack_end(button)
        #
        # Popup Contents
        builder = Gtk.Builder.new_from_string(POPOVER, -1)
        self.menu = builder.get_object("menu")
        self.menu.set_relative_to(button)
        action = Gio.SimpleAction.new("hello", None)
        action.connect("activate", self.hello_cb)
        self.add_action(action)
        action2 = Gio.SimpleAction.new("world", None)
        action2.connect("activate", self.world_cb)
        self.add_action(action2)
        #
        # pack
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(area, True, True, 0)
        vbox.pack_start(bar, False, False, 0)
        # self
        self.add(vbox)
        self.connect("delete-event", Gtk.main_quit)
        self.set_title("PopUp")
        self.resize(300, 200)
        self.show_all()

    def hello_cb(self, action, parameter):
        self.set_title("Hello")

    def world_cb(self, action, parameter):
        self.set_title("World")

    def on_option_button_clicked(self, widget):
        """
            Show Menu
        """
        self.menu.show_all()
 
PopoverMenu()
Gtk.main()

gtkpopovermenu

Gedit や eog のプラグインを作っておいて良かった。
多分やっていなければ action-name property でドンズマリしていた。

つまり action-name プロパティには win のプリフィクスが必要。
win なんて自分ではどこにも定義していないけど固定値みたい。
GtkApplication を使わなくてもいるのね、なんでだろう。

margin は 10 にすると Nautilus のメニューとほぼ一致する。
妙にデカい気がするけど多分タッチパネル化の準備だろう。

GtkBuilder の XML で vertical と書くことができるのか。
あまり使わないから知らなかった、整数や文字列だけかと思っていた。
つか devhelp のサンプルコードにコレ入れてくれよ。

この程度なら GtkBuilder よりコードで作ったほうが簡単そう。
他人が読むことを考えると XML のほうが理解しやすいだろうけどNE。

eog plugin 3.16

随分前に Fedora を 22 にアップデートしたような。
そうだ、自作 eog プラグインの更新を忘れていた!

私的に傑作プラグインだけど、そんなにリネームなんてしないジャン。
ということでとっとと更新しなければ。

Apps/EyeOfGnome/Plugins – GNOME Wiki!

公式は相変わらずヤル気ネェ…
とにかく Gedit 同様に GtkUIManager を排除する必要があるだろう。

GEdit 用に作った奴をコピペして Eog に書き換えてみた。
Eog.App なんて無いよと Python に怒られる、なんでじゃ!

dir(Eog) で調べると Eog.Application らしい、まぎらわしいわ!
AppActivatable も ApplicationActivatable だ、統一してよ。
同じ GNOME 標準アプリといっても企業ではなく GPL だからしかたがないが。

メニューを作ろうとしたけど extend_menu メソッドが使えない。
よく解らないのでソースを落とし reload プラグインを見てみる。
GtkApplicationWindow の activate で突っ込んでいた。
だから統一、、、まあいいか。

この方法だとメニュー排除が凄く面倒臭いようだ。
というか g_menu_item_set_attribute が何故か上手くいかない。
ええい面倒だ、メニューは廃止にしてしまえwww

昨今の GNOME に合わせシンプルにしたといえば皆納得するだろう。
実際筆者はメニューからリネームなんてしたことないもん。

Mac でも F2 をつい何度と押したことか、Mac は Enter なんだよね。
はどうでもよくて。
ということでこんなソースになりました。

renamedlg.py

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

#    Eye of GNOME renamedlg plugin version 3.16.0
#    Copyright © 2012 sasakima-nao <sasakimanao@gmail.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    Eye of GNOME Plugins
#    https://wiki.gnome.org/Apps/EyeOfGnome/Plugins
#    Eye of GNOME Reference Manual
#    http://developer.gnome.org/eog/stable/index.html
#
#   2015.06.16 3.16.0
#   Support eog 3.16 (Remove GtkUIManager)
#
#   2012.08.28 3.12.0
#   Support eog 3.12 (Python3)
#
#   2012.08.28 3.0.0

from gi.repository import GObject, Gtk, Eog, Gio, GLib
import os

class RenameDlgAppActivatable(GObject.Object, Eog.ApplicationActivatable):
    """
        Set GMenu and Accelerator
    """
    app = GObject.property(type=Eog.Application)
 
    def __init__(self):
        GObject.Object.__init__(self)
 
    def do_activate(self):
        self.app.add_accelerator("F2", "win.rename", None)
 
    def do_deactivate(self):
        self.app.remove_accelerator("win.rename", None)

class RenameDlgPlugin(GObject.Object, Eog.WindowActivatable):
    """
        Rename Dialog Plugin for eog 3.6
        Actibate from F2 key
    """
    __gtype_name__ = "Rename"
    window = GObject.property(type=Eog.Window)
    def __init__(self):
        GObject.Object.__init__(self)

    def do_activate(self):
        # Add Action
        self.action = Gio.SimpleAction.new("rename", None)
        self.action.connect('activate', self.on_rename)
        self.window.add_action(self.action)

    def do_deactivate(self):
        # Remove Action
        self.window.remove_action("rename")

    def do_update_state(self):
        #self.action.set_enabled(not self.window.is_empty())
        self.action.set_enabled(True)
        pass

    def on_rename(self, action, data=None):
        img = self.window.get_image()
        if img == None:
            return
        fullname = img.get_uri_for_display()[7:]
        path, name = os.path.split(fullname)
        label = Gtk.Label(name)
        entry = Gtk.Entry()
        entry.set_text(name)
        d = Gtk.Dialog( "Rename",
                        self.window,
                        Gtk.DialogFlags.MODAL,
                        (Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
                        Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT) )
        vbox = d.get_content_area()
        try:
            vbox.pack_start(label, False, False, 0)
            vbox.pack_start(entry, False, False, 0)
            d.show_all()
            def dlg_ok(self):
                d.response(Gtk.ResponseType.ACCEPT)
            def on_focus_in(self, widget):
                try:
                    # Calculate the number of characters
                    # Converted to a Display Name
                    s = entry.get_text()
                    displayname = GLib.filename_display_name(s)
                    i = displayname.rindex(".")
                    entry.select_region(0, i)
                except ValueError:
                    # Full select
                    pass
            def on_focus_out(self, widget):
                entry.select_region(0, 0)
            entry.connect("activate", dlg_ok)
            entry.connect("focus-in-event", on_focus_in)
            entry.connect("focus-out-event", on_focus_out)
            # Loop until success or Cancel
            while 1:
                if d.run() == Gtk.ResponseType.ACCEPT:
                    text = entry.get_text()
                    if text == "":
                        self.messagebox("File name is empty")
                        entry.set_text(name)
                    elif text == name:
                        self.messagebox("Have not changed")
                    elif  text in os.listdir(path):
                        self.messagebox("Found the same file name")
                    else:
                        # Get the EogListStore
                        store = self.window.get_store()
                        # Rename
                        newname = os.path.join(path, text)
                        os.rename(fullname, newname)
                        # Turn the queue
                        while Gtk.events_pending():
                            Gtk.main_iteration()
                        # Create EogImage
                        f = Gio.file_new_for_path(newname)
                        newimg = Eog.Image.new_file(f)
                        # Insert EogListStore
                        store.append_image(newimg)
                        # EogThumbView
                        tv = self.window.get_thumb_view()
                        tv.set_current_image(newimg, True)
                        # Turn the queue
                        while Gtk.events_pending():
                            Gtk.main_iteration()
                        # Remove Image from EogListStore
                        store.remove_image(img)
                        break
                else:
                    # Cancel Button
                    break
        finally:
            d.destroy()

    def messagebox(self, text):
        dlg = Gtk.MessageDialog(
                self.window,
                Gtk.DialogFlags.MODAL,
                Gtk.MessageType.WARNING,
                Gtk.ButtonsType.OK,
                text)
        dlg.set_title("Eye of GNOME")  
        r = dlg.run()  
        dlg.destroy()
        return r

koruri

Gedit 及び Eye of Gnome プラグイン – L’Isola di Niente

よしよし、キチンとリネーム可能だぞと。
よく見ると update_state ハンドラが実験用のままじゃないか。
特に問題は無いから次の更新で修正しよう、オープンソースはそれでイイ。

*.plugin ファイルの仕様は 3.14 までと変わっていない。
IAge が今でも 2 のまま動く、多分ガン無視しているだけだろうけど。

ところで。
Gedit で今頃気が付いたが 3.16 は単語 W クリックの仕様が変わっていた。
do_update_state 等の W クリック単語選択はアンダーバーを含めるようになた。
以前は区切りになっていたはずなんだが、あの動作に慣れているのでとまどうYO!