Python」タグアーカイブ

GRegex

正規表現に実は今まで興味が無かった。
実際に知らなくても特に何も困らなかった。

でもパターンの書き方を何かの言語で覚えてしまえば他で使い回せる。
他の言語はもとより grep コマンドでさえ、と今頃知った。
これは勉強しといたほうが良いかも。

でも使う手段は言語によって違うんだよなぁ。
いっそ GLib で使えればどんな言語でも gir で同じ手段でイケるのに。

Perl-compatible regular expressions: GLib Reference Manual

って普通にあるじゃん、perl と同じってことね。
Gjs でコイツと JavaScript の RegExp を比べてみよう。

正規表現 – JavaScript | MDN

画像の拡張子が最後に付いているかを大小文字区別なく探すコード。

#!/usr/bin/gjs

const GLib = imports.gi.GLib;

const ARRAY = ["001.jpg", "002.JPEG", "003.Png", "004.gif", "jpeg.gif"];
const PATTERN = "\.(jpe?g|png)$";

// JavaScript RegExp
ARRAY.forEach(function(s) {
    let re = new RegExp(PATTERN, "i");
    print(re.test(s));
});

print("----------");

// GLib Regex
ARRAY.forEach(function(s) {
    print(GLib.Regex.match_simple(PATTERN, s, GLib.RegexCompileFlags.CASELESS, 0));
});

/* @ Python
import re
for s in ARRAY:
    res = True if re.search(PATTERN, s, re.I) else False
    print(res)
*/

/* output
true
true
true
false
false
----------
true
true
true
false
false
*/

うん、見事に同じパターンが使えるのね。
コレで Vala だろうが何だろうが同様に使える。

Python は真偽値を戻さず mach Object 自体が True って変。
そりゃ Python なんだから変だと言われても何を今更と思えるのが凄い。

GSubprocess and Python subprocess

GSubprocess: GIO Reference Manual
あれ、こんなのあったんだ。

これを使えば Gjs からでも Python の subprocess と同様なことができるかも。
subprocess を使って筆者が作っている Comipoli beta9 で一部を書き換えしてみた。

class ComipoliWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        GtkClutter.init();
        # etc...

    def iter_7z(self):
        result, output, error, status = GLib.spawn_command_line_sync('7za l -slt "{0}"'.format(self.cbzpath))
        lines = output.decode("utf-8").split("\n")
        for line in lines:
            if line.startswith('Path'):
                name = line[7:]
                ext = os.path.splitext(name)[1].lower()
                if ext == ".jpg" or ext == ".jpeg" or ext == ".png":
                    #with subprocess.Popen(["7za", "x", "-so", self.cbzpath, name], stdout=subprocess.PIPE) as proc:
                    #    data = proc.stdout.read()
                    #    yield self.data_to_pixbuf(data)
                    sp = Gio.Subprocess.new(["7za", "x", "-so", self.cbzpath, name], Gio.SubprocessFlags.STDOUT_PIPE)
                    stream = sp.get_stdout_pipe()
                    p = GdkPixbuf.Pixbuf.new_from_stream(stream)
                    if p.get_height() > 1080:
                        rate = p.get_width() / p.get_height()
                        p = GdkPixbuf.Pixbuf.scale_simple(p, 1080 * rate, 1080, GdkPixbuf.InterpType.BILINEAR)
                    stream.close()
                    yield p

    def iter_zip(self):
        with zipfile.ZipFile(self.cbzpath) as o:
            l = o.namelist()
            l.sort()
            self.datas.clear()
            for name in l:
                ext = os.path.splitext(name)[1].lower()
                if ext == ".jpg" or ext == ".jpeg" or ext == ".png":
                    data = o.read(name)
                    yield self.data_to_pixbuf(data)

    def data_to_pixbuf(self, data):
        stream = Gio.MemoryInputStream.new_from_data(data)
        p = GdkPixbuf.Pixbuf.new_from_stream(stream)
        if p.get_height() > 1080:
            rate = p.get_width() / p.get_height()
            p = GdkPixbuf.Pixbuf.scale_simple(p, 1080 * rate, 1080, GdkPixbuf.InterpType.BILINEAR)
        stream.close()
        return p

動くじゃん。
しかもほとんど同じ!

ただ何故か g_subprocess_newv の引数が new メソッドに割り当てされている。
Gjs リファレンス同様に Property 割り当てで作ったほうが違和感が少ないかと。
PyGObject では JSON をイコールにするだけ。

// Gjs
let sp = new Gio.Subprocess({
    argv: ["7za", "x", "-so", self.cbzpath, name],
    flags: Gio.SubprocessFlags.STDOUT_PIPE
});

たぶんこれでいい。

一旦バイナリを取り出しせずに直接ストリームにできるからこのほうが効率いいかも。
でもそう書き換えると CBZ の処理側を作り替えしなきゃいけないけどどうしよう?

GLib, Gio はまだまだ知らないことがイッパイあると思い知る今日この頃です。

PyGObject Evince

遅ればせながら gir で evince を使う方法がやっと解った。
以下のファイル形式がこの手段で開けるようです。
ようするに Evince で開けるファイルということですけど。

PDF, CBR/CBZ, PostScript, TIFF, XPS, Djvu

#!/usr/bin/env python3

import gi
gi.require_version("EvinceDocument", "3.0")
gi.require_version("EvinceView", "3.0")
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, EvinceDocument, EvinceView

URI = "file:///home/sasakima-nao/doc/pdfs/gero-map.pdf"
#URI = "file:///home/sasakima-nao/doc/pdfs/postscript.ps"
#URI = "file:///home/sasakima-nao/doc/pdfs/kokomin.tiff"
#URI = "file:///home/sasakima-nao/doc/pdfs/erohon.cbz"

EvinceDocument.init()

doc = EvinceDocument.Document.factory_get_document(URI)
model = EvinceView.DocumentModel.new_with_document(doc)
view = EvinceView.View()
view.set_model(model)

scroll = Gtk.ScrolledWindow()
scroll.add(view)

win = Gtk.Window()
win.add(scroll)
win.connect("delete-event", Gtk.main_quit)
win.resize(600, 600)
win.show_all()

Gtk.main()

URI は各自の手持ちに書き換えてね。

init は必須、呼ばないとエラーになる。
EvinceDocument はファイル形式につては自動判別してくれるようです。
EvinceView は GtkScrolledWindow 以外には乗せることができないので注意。
マルチページ分はスクロールすると表示される。

Djvu なんて知らなかったし今後も普及するとは思えないけど。
CBR はもちろん unrar 必須です、HONDA のバイクじゃ(以下略

TIFF はマルチページを作る手段を知らないのでそれは試していない。
アーカイブして拡張子を変えるだけの CBR/CBZ のほうが使い勝手がいいしね。

ま、実際のところ PDF 以外での利用はほぼ無いと思う。

exit_status

さて前回やった cbr ファイルの展開を我がアプリに実装するのだが。
アレは完全なる unrar コマンド依存である。
つまり、使う側のマシンに unrar が入っているかどうかを調べなければいけない。

ライブラリやコマンドが入っていないマシンなら華麗に受け流すようにするべき。
かなり重要なコトなのにそういう Tips を書く人が少ないんだよなぁ。
まあソレは置いておいて。

16.16. ctypes ? Pythonのための外部関数ライブラリ ? Python 3.5.2 ドキュメント

ctypes.util.find_library(name)

でライブラリの有無を調べられるってことなんだけどコレって意味あるのか?
そもそも Python では使う機会が少ないしどうでもいいか。

いや、今回のはライブラリじゃなくてコマンドの有無。

Bashでコマンドの存在チェックはwhichよりhashの方が良いかも→いやtypeが最強 – Qiita

そうか type コマンドを使うのか。
ってソレよく考えたら自分で書いていた。
シェルスクリプトの覚書色々/コマンドの存在を確認してから実行

標準出力ではなく終了ステータスがゼロ(正常終了)かどうかを調べる。

g_spawn_command_line_sync なら第四引数がゼロになるかを調べればいい。
PyGObject だと戻り値タプルの四番目になるので

#!/usr/bin/env python3

from gi.repository import GLib

is_unrar = GLib.spawn_command_line_sync("type unrar")[3] == 0

if is_unrar:
    print("unrar is in your machine")
else:
    print("unrar not found...")

# test
print(GLib.spawn_command_line_sync("type unko")[3] == 0)

標準出力の値は勝手に捨ててくれる。

これで振り分けは大丈夫そうだ。
みなさん、ko というアーカイブは絶対に作ってはいけません。

ところで小牧駅周辺がカイロスの巣っぽくなっているのは何故だ?
用事ついでに歩いたらやたらイッパイ出たんだけーが。

Read application/x-cbr

やっと application/x-cbr を展開する方法が解った。
コマンドから取得するしかないんだから unrar のヘルプを見る。

unrar_help

引数無しで unrar と打つとヘルプが出るのね、知らなかった。
えっと、オプションの使い方がよくわかんないんですけど。

つーことで file-roller のソースを落として見る。
fr-command-rar.c を見つける。

<Commands> はハイフン無し、ファイル名の前にハイフン 2 つ。
なるほど、jjs と同じなんだね。

ファイルに展開はコイツで解ったけどメモリ上に展開させる手段がワカンネ。
ええい最後の手段だ、mcomix のソースを見る、筆者のも GPL だからいいよね。
見ないままココまでやってきたけど筆者の技術ではもう無理だ。
ああ Python2 だ、今となっては懐かしい。

unrar p -inul -@ -- test.cbr 111.jpg > a.jpg

なんだ、p オプションにするだけだった。
上記でメモリ展開されたファイルをリダイレクトするとファイルにもなる。

つまり最初にファイル名のリストを取得して一つずつ抜き出せばいいのね。
よしこんだけ理解すればなんとかなるぞ。

#!/usr/bin/env python3

import sys, os, gi, subprocess
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Gio, GdkPixbuf

class CbrTest(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.datas = []
        self.pixbuf = []
        # add
        button = Gtk.Button(label="show CBR")
        button.connect("clicked", self.on_button_clicked)
        self.fbox = Gtk.FlowBox(valign=Gtk.Align.START, max_children_per_line=6, min_children_per_line=6)
        sc = Gtk.ScrolledWindow()
        sc.add(self.fbox)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox.pack_start(button, False, False, 0)
        vbox.pack_start(sc, True, True, 0)
        self.add(vbox)
        self.resize(600, 300)
        self.show_all()

    def on_button_clicked(self, button):
        # listup filename
        result, output, error, status = GLib.spawn_command_line_sync("unrar vt -p- -- test.cbr")
        lines = output.decode("utf-8").split("\n")
        for line in lines:
            s = line.lstrip()
            if s.startswith('Name: '):
                name = s[6:]
                ext = os.path.splitext(name)[1].lower()
                if ext == ".jpg" or ext == ".jpeg" or ext == ".png":
                    # error: output is gchar*
                    #result, output, error, status = GLib.spawn_command_line_sync("unrar p -inul -@ -- test.cbr {0}".format(name))
                    with subprocess.Popen(["unrar", "p", "-inul", "-@", "--", "test.cbr", name], stdout=subprocess.PIPE) as proc:
                        output = proc.stdout.read()
                        self.append_data(output)

    def append_data(self, data):
        try:
            stream = Gio.MemoryInputStream.new_from_data(data)
            p = GdkPixbuf.Pixbuf.new_from_stream(stream)
            minp = p.scale_simple(80, 100, GdkPixbuf.InterpType.BILINEAR)
            image = Gtk.Image(pixbuf=minp)
            image.show()
            self.fbox.add(image)
        except Exception as e:
            print(e)
        return False

class CbrApp(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_activate(self):
        CbrTest(self)

app = CbrApp()
app.run(sys.argv)

おぉ。

naekore

なんとかなったけど GLib.spawn_command_line_sync には困った。
output に \0 が含まれているとソコでバイナリが途切れてエラーになる。
そりゃまあ C での定義は gchar* だし当然かもしれないけど。
しかたがないので subprocess を使う、Gjs だとお手上げかも。