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

PyGObject SendMessage and PostMessage

GTK+ で PostMessage をやりたい。
ようするに関数を抜けてからシグナルの処理をしたい。
g_signal_emit では関数がそこで止まってしまう。

g_idle_add を使って似たようなことはできるんだけど。
Gedit for Windows part3 | PaePoi
やはり正攻法でやりたいジャン。

gdk_event_put という関数を見つけた。
この引数に GdkEvent を渡せばイケるかも。

Gjs で書いていたけど GdkEvent がどうやっても作れない。
手段を知らないだけか実装されていないのかは不明。
ということで久々に PyGObject でやってみる。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0') 
from gi.repository import Gtk, Gdk

class EmitTest (Gtk.Window):
    """
        SendMessage, PostMessage
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        b1 = Gtk.Button(label="g_signal_emit")
        b2 = Gtk.Button(label="gdk_event_put")
        b1.connect("clicked", self.on_emit_clicked)
        b2.connect("clicked", self.on_put_clicked)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(b1, False, False, 0)
        vbox.pack_start(b2, False, False, 0)
        self.add(vbox)
        self.show_all()

    def on_emit_clicked(self, button):
        ''' # output
            delete-event
            [g_signal_emit] Clicked
        '''
        self.emit("delete-event", None)
        print("[g_signal_emit] Clicked")

    def on_put_clicked(self, button):
        ''' # output
            [gdk_event_put] Clicked
            delete-event
        '''
        event = Gdk.Event.new(Gdk.EventType.DELETE)
        event.any.window = self.props.window
        event.put()
        print("[gdk_event_put] Clicked")

    def do_delete_event(self, event):
        print("delete-event")
        Gtk.main_quit()
        return True

EmitTest()
Gtk.main()

gdk_event_put ならハンドラを抜けた後に delete-event が発行される。
これなら PostMessage と同じように使えそうだ。
ただ本当は Gjs でやりたいんですけど。

Fedora 24 に入る Gjs なら同様に使えるのかな?
と楽しみにしていたら又一週間リリースが延びたようだ。
ま、いつものことか。

Video Cut Nautilus Script

デジカメ動画の編集に筆者は Avidemux を使っていた。
凝ったことはしないし残したい部分を切り張りできれば充分ということで。

しかし古い AMD から Skylake に環境を移したら何故か起動できなくなった。
未対応なのか?てか変に高機能にされると迷惑なだけなんだが。
しかたがないので代わりに ffmpeg コマンドを使う。

FFmpegで素早く正確に動画をカットする自分的ベストプラクティス – Qiita

うーん面倒だ。
やはりこういう編集作業は GUI でやったほうが楽に決まっている。

我が再生アプリに ffmpeg コマンドを送る機能の追加とか…
って同じことを考える人がいるもんだ。

Rosa Media Player 動画の切り取りやMP3の抽出が簡単にできる動画プレイヤー | Ubuntuアプリのいいところ

スクショを見ると編集は編集アプリで分けたほうがやはりよさそう。
そもそも再生が GStreamer で切り出しが ffmpeg って変なのでパス。
GStreamer でも当然編集はできるんだけど。

Fun with videomixer

GStreamer Editing Services 自体が PiTiVi の付属品でありまして。
だったら PiTiVi を使えばいいじゃん、ですよね。

ges

でも PiTiVi じゃ大袈裟なんだよなぁ。
Hello World に Visual Studio や Anjuta を使うくらいアホ臭い。

ということで。
Nautilus Script として使える超簡易な GUI カットアプリを作ってみた。

#!/usr/bin/gjs

// -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-

const Gtk = imports.gi.Gtk;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const System = imports.system;

const NumEntry = new Lang.Class({
    Name: 'NumEntry',
    Extends: Gtk.Entry,

    _init: function() {
        this.parent({
            xalign: 1.0
        });
        let buf = this.get_buffer();
        buf.connect("inserted-text", Lang.bind(this, function(buf, position, chars, n_chars) {
            if (isNaN(chars))
                buf.delete_text(position, n_chars);
        }));
    }
});

const FFCut = new Lang.Class({
    Name: 'FFCut',
    Extends: Gtk.ApplicationWindow,

    _init: function(app, basename) {
        this.parent({
            application: app,
            title: basename
        });
        // Grid
        let texts = ["Start", ":", "Stop", ":"];
        let labels = [];
        this.entries = [];
        for (let i=0; i<4; i++) {
            labels[i] = new Gtk.Label({label: texts[i]});
            this.entries[i] = new NumEntry();
        }
        let grid = new Gtk.Grid();
        grid.attach(labels[0], 0, 0, 1, 1);
        grid.attach(labels[1], 2, 0, 1, 1);
        grid.attach(labels[2], 0, 1, 1, 1);
        grid.attach(labels[3], 2, 1, 1, 1);
        grid.attach(this.entries[0], 1, 0, 1, 1);
        grid.attach(this.entries[1], 3, 0, 1, 1);
        grid.attach(this.entries[2], 1, 1, 1, 1);
        grid.attach(this.entries[3], 3, 1, 1, 1);
        // Button
        let button = new Gtk.Button({label: "Cut"});
        button.connect("clicked", Lang.bind(this, function() {
            let ss1 = Number(this.entries[0].get_text()) * 60 + Number(this.entries[1].get_text());
            let ss2 = Number(this.entries[2].get_text()) * 60 + Number(this.entries[3].get_text()) - ss1;
            let cmd = "ffmpeg -ss " + ss1 + " -i " + this.title + " -t " + ss2 + " -vcodec copy -acodec copy out_" + this.title;
            GLib.spawn_command_line_async(cmd);
        }));
        // Pack
        let vbox = new Gtk.Box({
            orientation: Gtk.Orientation.VERTICAL,
            spacing: 5
        });
        vbox.pack_start(grid, true, true, 0);
        vbox.pack_start(button, true, true, 0);
        this.add(vbox);
        this.show_all();
    }
});

const FFApp = new Lang.Class({
    Name: 'FFApp',
    Extends: Gtk.Application,

    _init: function() {
        this.parent({
            application_id: 'org.sasakima.ffcut',
            flags: Gio.ApplicationFlags.HANDLES_OPEN
        });
    },
    vfunc_open: function(files, hint) {
        let basename = files[0].get_basename();
        let w = new FFCut(this, basename);
    },
    vfunc_activate: function() {
        print("Usage: ffcut FILENAME");
    }
});

let argv = [System.programInvocationName];
ARGV.forEach(function(element) {
    if (element.indexOf("//") == -1) {
        argv.push(decodeURIComponent(escape(element)));
    } else {
        argv.push(element);
    }
});
let application = new FFApp();
application.run(argv);

ffcut

コレに ffcut という名前で +x 属性を付けて
~/.local/share/nautilus/scripts に突っ込んで動画ファイルを送る。
切り出し開始と終了時間を入力して [Cut] ボタン。
すると [out_ファイル名] のカットされたファイルが同一ディレクトリに作られる。
~/bin に入れてコマンドでも多分使える。

Gjs は拡張子を付けないと Gedit で色分けしてくれないのでモードラインを入れた。
GNOME さん、そこは見分けてくださいよ。

もう少し改良の余地があるけど私的にはコレで充分だ。
気が向いたらもう少し弄ってアプリとして公開、しないと思うけど。

プログラミング初心者はこんなのから初めたらいいと思う。

Clutter Box and Scroll

Clutter をチマチマやっているけど絶対値配置はやはり古い!
やはり BoxLayout で配置したい、スクロール機能も欲しい。

色々試して驚いた。
なんと Clutter はスクロールバーに相当するものが無い!

どうしても使いたいなら自分で作れってことでしょうか。
いや違う、そもそも Button Widget 相当すら無いではないか。
スマートフォンではいらないよね、そういうこと。

古臭い考え方をバッサリ捨てないと今の Mac と GNOME は使えない。
つか昔風に戻るなんてありえない、パソコン離れは加速する一方だし。

もしかしてスマートフォンのようなスクロールを想定しているのかも。
だったらやったろうジャン!

#!/usr/bin/gjs

const Clutter = imports.gi.Clutter;
const Lang = imports.lang;

const ListTest = new Lang.Class({
    Name: 'ListTest',
    Extends: Clutter.Stage,

    _init: function() {
        this.parent();
        let layout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.VERTICAL,
            spacing: 2
        });
        // ScrollActor
        this.scroll = new Clutter.ScrollActor({
            layout_manager: layout,
            scroll_mode: Clutter.ScrollMode.VERTICALLY,
            x_expand: true
        });
        // append
        for (let i=0; i<100; i++) {
            let m = new Clutter.Text({
                text: "TextLine: " + i,
                x_expand: true
            });
            m.set_background_color(Clutter.Color.new(255, 0, 0, 125));
            this.scroll.add_child(m);
        }
        // Scroll Action
        this.x_point = 0;
        this.y_point = 0;
        this.x_diff = 0;
        this.y_diff = 0;
        let gesture = new Clutter.GestureAction();
        gesture.connect("gesture-begin", Lang.bind(this, function(action, actor) {
            this.x_point = action.get_press_coords(0)[0] + this.x_diff;
            this.y_point = action.get_press_coords(0)[1] + this.y_diff;
            return true;
        }));
        gesture.connect("gesture-progress", Lang.bind(this, function(action, actor) {
            let x = action.get_motion_coords(0)[0];
            let y = action.get_motion_coords(0)[1];
            this.x_diff = this.x_point - x;
            this.y_diff = this.y_point - y
            let point = new Clutter.Point({
                x: this.x_diff,
                y: this.y_diff
            });
            this.scroll.scroll_to_point(point);
            return true;
        }));
        this.scroll.add_action(gesture);
        this.scroll.set_reactive(true);
        // this
        this.add_child(this.scroll);
        this.set_layout_manager(new Clutter.BoxLayout()); // Child Fill
        this.connect("hide", Clutter.main_quit);
        this.show_all();
    }
});

Clutter.init(null);
new ListTest();
Clutter.main();

scroll_actor

ClutterBoxLayout を使うことで GtkBox 相当のようだ。
これで fill, expand を利用したレイアウトができる。
しかし ClutterActor に割り当てなのか、GTK+ と随分違うなと。

子 Actor を親サイズに広げたい時は Child Fill の部分のように。
これだけで GtkWindow を使うのとと同じ感覚になる。

スクロールは上記で上手くいった。
スマートフォンみたいにマウスで掴んで動かした分だけスクロール。
やろうと思えば iPhone のような慣性やポヨンを入れることもできる。
でもあの動きは特許だったような…

このスクロールをパソコンで使うかというと疑問もあるけれど。
コマンドが優秀な Linux もタブレットの時代が、ってワカンネーけど。
GNOME プロジェクトはガッツリ備えているということでしょう。

ClutterImage

今回は Clutter で画像をクルクル回してみよう。
3D ライブラリの定番ですね、ClutterImage を使うようだ。

ClutterImage: Clutter Reference Manual

画像ファイルをセットする方法は公式のサンプルコードがある。
コレを clutter_actor_set_content すればテクスチャになるってことかな。
画像の原寸と ClutterActor のサイズはどんな関係なんだろう。
チャチャッと実験コードを書いて試してみよう。

このサンプルを作っている時に気が付いたけど ClutterScore は deprecated だ。
CLutterTimeline に同様の property がある時点で気が付けよって感じですが。

#!/usr/bin/gjs

const Clutter = imports.gi.Clutter;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Cogl = imports.gi.Cogl;
const Lang = imports.lang;

// Own Rewrite
const FILENAME = "/home/sasakima-nao/pic/game/gf/nae_yuki_ssr/ [ハロウィン13]優木苗.jpg";

const ImageTest = new Lang.Class({
    Name: 'ImageTest',
    Extends: Clutter.Stage,

    _init: function() {
        this.parent();
        // Image
        let image = new Clutter.Image();
        let pixbuf = GdkPixbuf.Pixbuf.new_from_file(FILENAME);
        let alpha = pixbuf.get_has_alpha() ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888;
        image.set_data(
            pixbuf.get_pixels(),
            alpha,
            pixbuf.get_width(),
            pixbuf.get_height(),
            pixbuf.get_rowstride
        );
        // Rotate Actor
        this.actor = new Clutter.Actor();
        this.actor.set_pivot_point(0.5, 0.5);
        let xy = pixbuf.get_height() / pixbuf.get_width();
        this.actor.set_size(200, 200 * xy);
        this.actor.set_position(70, 50);
        this.actor.set_content(image);
        // Timer
        this.timeline = new Clutter.Timeline({
            duration: 100,
            loop: true
        });
        this.rotation = 0;
        this.timeline.connect("new-frame", Lang.bind(this, function() {
            this.rotation += 0.3
            this.actor.set_rotation_angle(Clutter.RotateAxis.Y_AXIS, this.rotation);
            this.actor.set_rotation_angle(Clutter.RotateAxis.Z_AXIS, this.rotation);
        }));
        this.timeline.start();
        // this
        this.add_child(this.actor);
        this.connect("hide", Clutter.main_quit);
        //this.set_user_resizable(true);
        this.show_all();
    }
});

Clutter.init(null);
new ImageTest();
Clutter.main();

clutter_image

公式サンプルの方法で画像全体のデータは取り込めるようだ。
ClutterActor の大きさに合わせてアスペクト比無視で拡縮される。
上記は一応大雑把にアスペクト比を合わせるようにしている。

PivotPoint や回転は全部 ClutterActor 側の仕事。
ClutterImage は ClutterActor の角度に合わせてレンダリングされる。
簡単すぎて拍子抜け、3D ってもっと面倒なイメージがあったのにな。

ClutterGestureAction

Clutter @ Mouse Drag | PaePoi
にてシークバーを motion-event で動かすようにした。

しかしこの方法は問題があった。
マウスカーソルが ClutterActor から外れるとシグナルが無効化される。
カーソルのキャプチャが必要だが手段が解らない。

いくら手段を探しても見つからない、もしかして無いのかも。
もう別のシーク方法を考えたほうが良さそうだ。

ClutterDragAction でなく ClutterGestureAction ならどうだ?
タッチパネル向けアクションだと思って無視していたけど調べてみよう。

ClutterGestureAction: Clutter Reference Manual

clutter_gesture_action_get_motion_coords
で普通に x, y の値が抜けるみたいだね。
問題はマウスカーソルが外れてもキャプチャするかどうかだ、実験。

#!/usr/bin/gjs

const Clutter = imports.gi.Clutter;
const Lang = imports.lang;

const GestureTest = new Lang.Class({
    Name: 'GestureTest',
    Extends: Clutter.Stage,

    _init: function() {
        this.parent();
        // var
        this.is_drag = false;
        // text
        this.text = new Clutter.Text();
        this.text.set_text("x = NULL; y = NULL");
        this.add_child(this.text);
        // Action
        let gesture = new Clutter.GestureAction();
        gesture.connect("gesture-begin", Lang.bind(this, function(action, actor) {
            let x = action.get_press_coords(0)[0] / this.width;
            let y = action.get_press_coords(0)[1] / this.height;
            this.text.set_text("x = " + x + "; y = " + y);
            return true;
        }));
        gesture.connect("gesture-progress", Lang.bind(this, function(action, actor) {
            let x = action.get_motion_coords(0)[0] / this.width;
            let y = action.get_motion_coords(0)[1] / this.height;
            this.text.set_text("x = " + x + "; y = " + y);
            return true;
        }));
        gesture.connect("gesture-end", Lang.bind(this, function(action, actor) {
            this.text.set_text("x = NULL; y = NULL");
        }));
        this.add_action(gesture);
        this.set_reactive(true);
        // this
        this.connect("hide", Clutter.main_quit);
        this.show_all();
    }
});

Clutter.init(null);
new GestureTest();
Clutter.main();

にてマウスをウインドウの外まで移動

clutter_gesture_action

しっかりマイナス値も取得できていますね。
ウインドウ外でマウスボタンを離すとしっかり NULL に戻る。

なんだよ、こんなに簡単な手段があったじゃないか。
昔の手段で思考停止していたら駄目だね、どんどん新しいことをやらないと。
ついでに。

this.player = new ClutterGst.Playback();
this.player.set_seek_flags(ClutterGst.SeekFlags.ACCURATE);

これだけでシークバーの追従がナイスになる。
思い通りに動かなかったものがキチンと動くようになっていくのはマジ楽しい。