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

PyObjC がトンデモなく遅い

今日は久しぶりに PyObjC で小物を作ろうとした。
いやまあ macOS プログラミングは完全に放置気味でしたね。
ということでまずアップグレードを行なってと。

# pip Upgrade
pip3 install --upgrade pip

# PyObjC Install or Upgrade
pip3 install -U pyobjc

で本題、PyObjC コードの初期化がありえないほど遅い。
NSWindow を作ったら表示されるまで十秒くらい待たされる。
Python 標準モジュールのみのコードなら普通に一瞬だ。
しばらく色々試したらこういうことだった。

#!/usr/bin/env python3

'''
    app.old.py
'''

import time
start = time.time()

from AppKit import *
import objc

class AppMenu(NSMenu):
    def init(self):
        objc.super(AppMenu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        # command+Q で閉じるメニュー
        item_quit = NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit App', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

# NSApp を作る
NSApplication.sharedApplication()
# command+Q で終了するメニューを入れる
NSApp.setMainMenu_(AppMenu.new())
# コレをしないと最前面に出てこない
NSApp.activateIgnoringOtherApps_(True)
# メインループを回さない
#NSApp.run()

# 経過時間 ms
print(f'{time.time() - start} ms')

from を使う今までやっていたコード。

#!/usr/bin/env python3

'''
    aoo.new.py
'''

import time
start = time.time()

import AppKit, objc

class AppMenu(AppKit.NSMenu):
    def init(self):
        objc.super(AppMenu, self).init()
        item_app  = AppKit.NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = AppKit.NSMenu.new()
        item_app.setSubmenu_(menu_app)
        # command+Q で閉じるメニュー
        item_quit = AppKit.NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit App', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

# NSApp を作る
AppKit.NSApplication.sharedApplication()
# command+Q で終了するメニューを入れる
AppKit.NSApp.setMainMenu_(AppMenu.new())
# コレをしないと最前面に出てこない
AppKit.NSApp.activateIgnoringOtherApps_(True)
# メインループを回さないさない
#AppKit.NSApp.run()

# 経過時間 ms
print(f'{time.time() - start} ms')

都度プリフィクスを書く面倒くさいコード。

PyObjC

from で * を使うとトンデモネェ遅さになってしまった。
以前は from でも同じような速度で初期化されていたんですが。
NSApp だけでコレ、NSWindow まで作るとすげぇ悲惨。

Python 3.11 からの高速化の弊害なのかな。
from は全部辿ってキャッシュとかになっていたらまあこうなるよな。
いや PyObjC 側の不具合かもしれないけどプリフィクス化で解決するし。
Cocoa のメソッド名長いんだよな、GTK+ みたくできないのかと。
アスタリスクを使っている人は調べてみたほうがいいかも。

ついでに、objc は import しなくてもよかったのに必須になった。
activateIgnoringOtherApps が動作しない、何故?
ちょっと放置しすぎたな、もう少し調べよう。
秋の渡り鳥が来る前にやらないとまた放置しそうだし。

MPV AspectRate Change (2024)

いつのまにか MPV にてアスペクト比変更ができなくなっていた。
いや Shift+a での変更は可能、自作した拡張 Lua スクリプトが動かない。

How to add 21:9 Aspect ratio option in MPV Android? : r/mpv

色々探して上記を見つける。
video-aspect-override という property があるのね。

--mp.set_property('video-aspect', aspects[aspect_num])
mp.set_property('video-aspect-override', aspects[aspect_num])

これで動いた、思っていたより簡単でよかった。
Fedora の MPV は更新されていないし ffmpeg 側の仕様変更かな?
なので環境によって変わると思う、Fedora 40 な人は書き換え必須です。

せっかくなので Shift 追加で逆順変更機能も付けてみよう。

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

-- Shift+a でアスペクト比は変更できますけど
-- スマホの縦動画用が足りないので独自に作ってみました

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

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

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

こんな感じになった。

しかし Kate は今だに使い辛いなぁ。
コードやブログネタを書いて余計な補完機能を見つけて無効化するばかり。
macOS で Sublime Text は Gedit でやっていたことを再現できたんだけど。
Kate は無理っぽい、でも Gedit は死亡したも同然だし代わりも見つからない。

Node.js: ESM

Node.js って令和になってから全然話題にならないよね。
と思っていたら v20 になっていた。

Node.js ? Run JavaScript Everywhere

ちょっとまて、require は import に変わっていたの?
拡張子が mjs みたいなんですけど、これ何だ?

CJS, UMD, ESMとは?その違い。 | milestones

変わったのではなく標準に合わせて拡張したってことみたい。
でもトップページが ESM ってことは今後はこっちを使えなんだろうな。
ちなみに以下のコードを書いた時はたしか v6 だか v8 だったような。

Gjs, JXA, Node.js – Paepoi

標準である import に合わせたのは Gjs だけじゃなかったのね。
JXA は Apple がやる気ゼロだから今後も放置だろうけど。
書き換えしなきゃ、使わないと解っているけど確認のため導入。

node

うん Fedora 29 の dnf も v20 ですね。
とりあえず readline を試してみよう。

Readline | Node.js v21.7.1 Documentation

スイッチを CJS から ESM に切り替えると import のコードに変わる。
モジュール変数は UpperCamelCase 推奨だったけど小文字推奨に変わった?
無名関数ではなく promises を使えなの?とにかく書いてみる。

#!/usr/bin/env node

/** CJS
const readline = require('readline');
const tty = require('tty');

let rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal: false
});
let [prpt, prefix] = tty.isatty(0) ? ['何か入力して return :', '端末入力'] : ['', 'パイプ'];
rl.question(prpt, (line)=> {
    console.log(`${prefix}から ${line} が渡されました`);
    rl.close();
});
*/
// ESM (extention as 'mjs')
import * as readline from 'node:readline/promises';
import * as tty from 'node:tty';
import { stdin as input, stdout as output } from 'node:process';

let rl = readline.createInterface({ input, output, terminal:false });

let [prpt, prefix] = tty.isatty(0) ? ['何か入力して return :', '端末入力'] : ['', 'パイプ'];

let line = await rl.question(prpt);
console.log(`${prefix}から ${line} が渡されました`);
rl.close();

test.mjs

拡張子は mjs にしないと動かなかった。
Gjs は -m オプションだけど合わせてくるかな。
シバンを書いても問題なく動作しますね。

node

ぶっちゃけ ESM でも process は import せず使えるし無名関数でも動く。
とはいえ公式サンプルががこうなので今後はこうさせたいのだろうと思う。
promises を使うと上級者って感じでかっこいい、ような気がする。
もう少し調べて四月までに tips ページは書き換えよう。

JavaScript prototype: static method

JavaScript Tips を更新しました。
JavaScript Tips – Paepoi

古くさいコードを書き直しだけならすぐ終わるんだけど。
無意味に class のコードを prototype で書いてみたりとか。

// ベースクラスのようなもの、ES5 表記なのは意図的です
var TestClassBase = function(num) {
    this._num = num;
}
TestClassBase.staticFunc = function() {
    console.log('staticFunc 呼び出し');
}
TestClassBase.prototype.instanceFunc = function() {
    console.log('num は '+ this._num + ' です');
}
 
// 継承のようなもの
var TestClass = function(num) {
    TestClassBase.call(this, num);
}
TestClass.prototype = Object.create(TestClassBase.prototype);
TestClass.prototype.constructor = TestClass;
// 静的メソッドは無理やり継承
for (var p in TestClassBase) {
    if (TestClass[p] === undefined) {
        TestClass[p] = TestClassBase[p];
    }
}
// 新たな定義
TestClass.prototype.getNum = function() {
    return '百倍すると ' + this._num * 100 + ' です';
}
  
// 直接定義したものはそのまま呼び出す
TestClass.staticFunc();
  
// prototype で定義したものは object 化してから呼び出す
var test = new TestClass(7);
test.instanceFunc();
//tc.staticFunc(); // インスタンス化すると静的関数は呼び出しできない
  
console.log(test.getNum());

こんなの。

MDN で見つけたコードでは静的メソッドが継承できなくて。
ネットを探しても「できません」としか見つからなくて。
実現させろよ、プログラミングってそういうものだろみたいな。

意地になって色々試してやっと実現、安全は保証しないけど。
別にいいよね、今は普通に class を使うはずだもんね。
わざわざ ES5 縛りでやった意味は別に無い、楽しかったわ。

Gjs setInterval

先日 Gjs で TextDecoder が使えると書きましたが。

for (let s in this) print(s);

this

グローバル関数だったんですね、無理やりな実装だなって。
てか setTimeout や setInterval も追加されているじゃないの。
console.log も使えるようになったし、Web に合わせてきたのね。
Web や Node.js と同じなのかな、チト試してみよう。

#!/usr/bin/gjs -m

import GLib from 'gi://GLib';

const mainloop = new GLib.MainLoop(null, false);

let count = 1;
let id = setInterval(()=> {
    if (count == 5) {
        clearInterval(id);
        mainloop.quit();
    }
    console.log(count);
    count++;
}, 1000);

print('__do__');

mainloop.run();

print('__done__');

log

基本 Web と同じですけど、やっぱりメインループが必要ですね。
Web の場合はブラウザがメインループを回しているから不要なだけで。
Node.js が不要だからややこしい、繰り返し処理にはループが必要。
GtkApplication から使う等の場合は当然別途のループはいりません。

console.log は print と同じでいいのに、スタンドアロンでこの表示いる?
とにかく Web 標準と同様に使えます、ということで。

というか、Gjs って三年前とは全然変わってしまったなと。
tips ページはそろそろ丸ごと書き換えしなきゃ、面倒くさいなぁ。。。。。