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

Notify Signal

すっかり写真ブログ化しているこのブログですが。
今月末には新しい Fedora が出るはずなのでそろそろ本筋でも。
GTK4 のサンプルコードもそろそろ出そろってきたかなって。

GitHub – johnfactotum/quick-lookup: Simple GTK dictionary application powered by Wiktionary

このあたりが解りやすいかな。
省略表記多すぎ、てか文末セミコロンすら絶対に書かない派なのね。
まあ Gjs で GTK4 はこんなふうに書けばいいのかって参考にはなる。

気になったのは notify::is-active シグナル。
アクティブ化の真偽値をこのシグナルで捕まえられるのかな。

#!/usr/bin/gjs

imports.gi.versions.Gtk = '4.0'
const {GObject, Gtk} = imports.gi;

var TestWindow = GObject.registerClass({
    GTypeName: 'TestWindow'
}, class TestWindow extends Gtk.ApplicationWindow {
    _init(app) {
        super._init({
            application: app,
            title: '1'
        });
        try {
            this.connect('notify::is-active', () => {
                if (this.is_active) this.title += '1';
            });
        } catch(e) {
            printerr(`@@@Error@@@@:\n${e.message}`);
            app.quit();
        }
    }
});

var TestApplication = GObject.registerClass({
    GTypeName: 'TestApplication'
}, class TestApplication extends Gtk.Application {
    _init() {
        super._init({
            application_id: 'org.lumix.gh6'
        });
    }
    vfunc_activate() {
        let w = new TestWindow(this);
        w.present();
    }
});

let app = new TestApplication();
app.run(null);

おぉコレは使いどころがあるぞ。
昔筆者がやってた初回起動時間のセコい短縮なんかにも使えるね。
ウインド表示直後に処理 | Paepoi Blog

ところでこの Notify というシグナルって何だろう?

GObject.Object::notify

どうやら GObject のシグナルらしい。
そして is-active は GtkWindow のプロパティ。

Gtk.Window

つまりこのシグナルは何かプロパティをセットした時に吐きだすようだ。
別のプロパティで試してみよう、PyGObject でもイケるかな?

#!/usr/bin/env python3

import gi, sys
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk

class TestWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        try:
            self.connect('notify::default-width', self.on_width_change)
        except Exception as e:
            print(e, file=sys.stderr)
            app.quit()

    def on_width_change(self, this, pspec):
        self.set_title(f'width: {self.props.default_width}')

class TestApplication(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self, application_id='org.olympus.pen')

    def do_activate(self):
        w = TestWindow(self)
        w.present()

app = TestApplication()
app.run()

width

イケた、ウインドウの横サイズを変更すると即座にタイトルが変更される。
Notify::** シグナルでプロパティを監視することが可能なんですね。
引数の GParamSpec から値を得ることもできると思うけど手段がワカラン。
プロパティを見ればいいだけなので別にいいか。

mpv @ Next File (Nautilus Sort)

しつこいようだけど mpv で次のファイルを再生させるスクリプト。
順番が Nautilus と同じでないのでイマイチ不便だなって。

だったらコレも作ってしまえ!
Gjs なり PyGObject なりでソートさせて出力するコマンドを。
mpv の拡張スクリプトでは無理なのでコマンドを自作する。
それを Lua で呼び出せばなんとかなるはず。

ついでに、いちいち拡張子を指定するのも面倒臭い。
てか Lua がショボいなら自作コマンドのほうで正規表現を使えばいい。
で、GNOME では動画ファイルの ContentType は以下のようになっている。

video/mp4
video/quicktime
video/x-matroska

ContentType なら先頭の video/ だけで動画ファイルを見分けできる。
ということでこんなスクリプトを書いてみた。

#!/usr/bin/gjs

if (ARGV.length == 0) {
    print('usage: nautilus_ls {DIRNAME}');
} else {
    const GLib = imports.gi.GLib;
    const Gio = imports.gi.Gio;
    const re = /^video\//;

    let files = [];
    let d = Gio.file_new_for_path(ARGV[0]);
    let ls = d.enumerate_children('standard::content-type', 0, null);
    for (;;) {
        let info = ls.next_file(null);
        if (info == null)
            break;
        let t = info.get_content_type();
        if (re.test(t))
            files.push(info.get_name());
    }
    files.sort((s1, s2)=> {
        let cmpstr1 = GLib.utf8_collate_key_for_filename(s1, -1);
        let cmpstr2 = GLib.utf8_collate_key_for_filename(s2, -1);
        if (cmpstr1 < cmpstr2)
            return -1;
        return 1;
    });
    print(files.join('\n'));
}

// ex: ft=js

Gjs での例。
nautilus_ls という拡張子無しファイル名でパスの通った場所に保存。
実行パーミッション追加でコマンドの出来上がり。
このコマンドを Lua で呼び出しする。

-- ~/.config/mpv/scripts/next_prev.lua

local utils = require 'mp.utils'

-- Ctrl+DOWN @ Next File Play
function on_nextfile()
    local hit = false
    local directory, fn = utils.split_path(mp.get_property('path'))
    if directory == '.' then
        directory = utils.getcwd()
    end
    local pfile = io.popen('nautilus_ls  "'..directory..'"')
    for filename in pfile:lines() do
        if hit then
            mp.commandv('loadfile', utils.join_path(directory, filename))
            if mp.get_property_bool('pause') then
                mp.set_property_bool('pause', false)
            end
            break
        end
        hit = filename == fn
    end
    pfile:close()
end
mp.add_key_binding('Ctrl+DOWN', 'nextfile_func', on_nextfile)

-- Ctrl+UP Previous File Play
function on_prevfile()
    local prevfn = ''
    local directory, fn = utils.split_path(mp.get_property('path'))
    if directory == '.' then
        directory = utils.getcwd()
    end
    local pfile = io.popen('nautilus_ls  "'..directory..'"')
    for filename in pfile:lines() do
        if filename == fn and prevfn ~= '' then
            mp.commandv('loadfile', utils.join_path(directory, prevfn))
            if mp.get_property_bool('pause') then
                mp.set_property_bool('pause', false)
            end
            break
        end
        prevfn = filename
    end
    pfile:close()
end
mp.add_key_binding('Ctrl+UP', 'prevfile_func', on_prevfile)

イケた!
GNOME 限定です、他の環境の人は参考程度に。

mpv @ Lua to Javascript

mpv を Lua で拡張するのが楽しい皆様。
こんなトコを見つけました。

GitHub – samhippo/mpv-scripts

参考になります、いやコピペでそのまま使ってもいいんだけど。
Windows 限定のコードが所々あるので macOS や Fedora の人は注意です。

そういえばこの人は複数拡張子のマッチをどうやっているんだろう?

mpv-scripts/next-file.lua at master ? samhippo/mpv-scripts ? GitHub

拡張子文字列で連想配列を作り値を全部 true に。

lua_nil

存在しなければエラーではなく nil になる、なるほど。
酷い手段だけど Lua 自体が色々とショボいからしかたがないか。

いやまて、この方法でディレクトリ内を列挙できるんだよな。
ls コマンドが不要なら正規表現が強力な Javascript を使うという手が。
とりあえず mp.utils を Javascript ではどう呼び出すか試す。

// ~/.config/mpv/scripts/test.js

function on_test() {
    //let dir = mp.utils.split_path(mp.get_property('path')); // ES5...
    var dir = mp.utils.split_path(mp.get_property('path'));
    mp.osd_message('Playng File: ' + dir[1]);
}
mp.add_key_binding('Ctrl+3', 'test_func', on_test);

なんだ、ドットで普通に使えるヤン。
しかし let 宣言でエラー、まさか今更 ECMAScript 5…

mpv.io

mpv.io にも書いていた、for-of とかも当然使えないな。
ES5 でも正規表現は同じはずだ、使ってみる。
以前書いたディレクトリ内で次のファイルを js で丸ごと書き換え。

// ~/.config/mpv/scripts/next_prev.js

var re = /\.(mov|m4v|mp4)$/i;

// Ctrl+DOWN でディレクトリ内の次ファイルを再生
function on_nextfile() {
    var dir = mp.utils.split_path(mp.get_property('path'));
    // カレントディレクトリだとドットになるので
    if (dir[0] == '.') dir[0] = mp.utils.getcwd();
    // ls
    var ls = mp.utils.readdir(dir[0], 'files');
    ls.sort();
    if (mp.last_error() == '') {
        // for-of が使いたい...
        var len = ls.length;
        var ex = false;
        for (var i=0; i<len; i++) {
            var f = ls[i];
            if (re.test(f) && ex) {
                mp.commandv('loadfile', mp.utils.join_path(dir[0], f));
                if (mp.get_property_bool('pause'))
                    mp.set_property_bool('pause', false);
                break;
            }
            ex = f == dir[1];
        }
    }
}
mp.add_key_binding('Ctrl+DOWN', 'nextfile_func', on_nextfile);

// Ctrl+UP でディレクトリ内の手前ファイルを再生
function on_prevfile() {
    var prevfn = '';
    var dir = mp.utils.split_path(mp.get_property('path'));
    if (dir[0] == '.') dir[0] = mp.utils.getcwd();
    var ls = mp.utils.readdir(dir[0], 'files');
    ls.sort();
    if (mp.last_error() == '') {
        var len = ls.length;
        var ex = false;
        for (var i=0; i<len; i++) {
            var f = ls[i];
            if (re.test(f)) {
                if (dir[1] == f && prevfn != '') {
                    mp.commandv('loadfile', mp.utils.join_path(dir[0], prevfn));
                    if (mp.get_property_bool('pause'))
                        mp.set_property_bool('pause', false);
                    break;
                }
                prevfn = f;
            }
        }
    }
}
mp.add_key_binding('Ctrl+UP', 'prevfile_func', on_prevfile);

動くジャン。
ただ ls と違ってソートしないとよくワカラン順番になるみたい。

loadfile は半角スペース付きでもダブルクォートで囲む必要は無かったのね。
というか URI にする必要すら無かった、とほほ。

とにかくコレで完璧な拡張子判別ができるようになった。
慣れた言語はやっぱり楽だね、言語仕様を調べる必要が無いし。
自由に改造して使ってください、Windows でも多分動くと思う。

mpv scripts

今日も mpv を Lua で弄る。

で、例によってディレクトリ内巡回機能なんですが。
筆者は再生終了で本体を終了しないように指定している。
再生中に「次のファイル」とすると普通に上手く切り替わるんだけど。
EOF 状態からだとファイルは切り替わるけどポーズされた状態で始まる。

GStreamer は EOS(End of Stream) という表現だったけど。
mpv では EOF(End of File) なんですね、同じ意味だしどうでもいいけど。

試しに途中でポーズして切り替えだと完全に同じだ。
つまり mpv の EOF は単にポーズされた状態。
もしポーズだったら解除する、というコードを追加すればいい。

mp.command('loadfile "file://'..directory..filename..'"')
-- これでもいい、true|false を yes|no の文字列で
--if mp.get_property('pause') == 'yes' then
if mp.get_property_bool('pause') then
    mp.set_property_bool('pause', false)
    -- これでもいい、true|false を yes|no の文字列で
    --mp.set_property('pause', 'no')
end
break

mpv の property アクセスは全部文字列。
UNIX 系を知っているなら macOS を含んでありがちだと解りますね。
コレを直接 Lua から使える BOOL 値で取り出せるのが _bool 付き関数。

ところで。
~/.config/mpv/scripts
にソースを放り込めば全部が起動時に読み込みされ適用されていくんだね。
これは機能ごとに分割したほうが色々と都合がいい。

そういえばアスペクト比変更もデフォルトでは割り付けされていない。
色々割り付けても筆者はキーを忘れるので Ctrl+2 でサイクルがいいな。
-1 を突っ込むとデフォルトになるので一周させるようにすれば完璧。
OSD(On Screen Document) で表示もあると良さげ。

-- ~/.config/mpv/scripts/mpv_aspect_rate.lua

aspect_num = 0
aspects = {'4:3', '16:9', '1:1'}

function on_change_aspectrate()
    aspect_num = aspect_num + 1
    if aspect_num > #aspects then
        aspect_num = 0
        mp.set_property('video-aspect', '-1')
        mp.osd_message('Aspect Rate @ Default')
    else
        mp.set_property('video-aspect', aspects[aspect_num])
        mp.osd_message('Aspect Rate @ '..aspects[aspect_num])
    end
end
mp.add_key_binding('Ctrl+2', 'aspectrate_func', on_change_aspectrate)

これを放り込んで、よし動いた。
グローバル変数はインスタンス毎に記憶してくれるようだ、助かる。
以降 mpv プログラミングはまとめたページを作るか。

いやープログラミングって面白いですね。
Lua は別に面白くないけど書いたら思った通りに動いてくれるのが面白い。
言語はただの道具で重要なのは完成品、実社会と同じ。

not =

Lua で凄い勘違いをしていた!
~= の演算子を bash の =~ と同じものだと思いこんでいた。
bash の != がコレなのね、何を今頃。

ちなみに。

#!/bin/sh

if [[ aaa.mp4 =~ \.(mov|mp4)$ ]]; then
    echo 動画ファイルです
fi

こんな感じで使います。
bash ですら複数拡張子のマッチを調べられるのに Lua ときたら…

いやまあ。
「Lua って != が無いのか、変わった言語だなぁ」
って思いこんで今まで not を使ってたよ。
それどころか。

#!/usr/bin/lua

if 1 ~= 2 then
    print('lua の ~= は bash の != と同じ意味です')
end

if not(nil) then
    print('nil は否です')
end

-- よく考えたら下記で良かった

--if not(extstr:match(ext) == nil) then
if extstr:match(ext) then

スゲェ無駄なコードを昨日まで書いていた。
もう少し勉強しなきゃとも思うけど。
mpv の拡張以外でこの言語を使うか疑問なんだよな。