月別アーカイブ: 2016年6月

JXA NSString

久々に Mac で JavaScript(JXA) を。
JavaScript 文字列と NSString の相互変換が今まで解らなかった。

OS?X 10.10 Release Notes

公式の解説にて $() だけで NSString に変換できるのは理解。
でも逆がドコにも書いていないんですけど。
散々探してやっと以下を見つける。

Home ? dtinth/JXA-Cookbook Wiki ? GitHub
Shell and CLI Interactions ? dtinth/JXA-Cookbook Wiki ? GitHub

んと、ObjC.unwrap で NSArray を JavaScript 形式に変換できるのか。
ならば NSString もイケるかな?と適当にやったら出来ちゃった。
ということで JXA, NSString 関連の覚書。

ObjC.import("Cocoa");

console.log("日本語も大丈夫");
$.NSLog("%@ %@", $("NSString"), $("はこうする"));

var jsStr = "変数も $() で変換できる\n";
var nsStr = $(jsStr);
$.NSLog("%@", nsStr);

nsStr = $("NS* から JavaScript 形式へは ObjC.unwrap()");
jsStr = ObjC.unwrap(nsStr);
console.log(jsStr);

var saveStr = "保存します\n";
saveStr += "戻り値が表示されるので変数に入れています\n";
var data = $(saveStr).dataUsingEncoding($.NSUTF8StringEncoding);
var res = data.writeToFileAtomically("output.txt", true);

if (res) {
    var terminal = Application("Terminal");
    var fm = $.NSFileManager.defaultManager;
    var cwd = fm.currentDirectoryPath;
    var nil = terminal.doScript("cat " + ObjC.unwrap(cwd) + "/output.txt");
}

jxa_nsstring

よし日本語もこれでバッチリ(死語)
Cookbook は他にも色々試したいコードが沢山あって素晴らしい。

しかし Cookbook も var とセミコロンを全部書いているんだね。
JXA では不要と言われても無いとキモいのは皆同じようである。

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 さん、そこは見分けてくださいよ。

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

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