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

Python3 yield

前回のコードはダメダメだった、ZIP 展開が遅すぎる!
やはり Python の zipfile は使い物にならないのだろうか。

いやまて、ZIP の展開は非同期にすればよくね?
g_idle_add を使って一枚目以降はバックグラウンド読み込みにすればイケるかも。

そういえば yield ってどう使うんだっけ。
clipoli では for 文で使っていたけど今回の場合はそれじゃダメだし。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Clipoli(Gtk.Menu):
    def __init__(self, confpath):
        # etc...
 
    def read_lines(self, filename):
        """
            ini file Read Function
        """
        f = Gio.file_new_for_path(filename)
        fstream = f.read(None)
        dstream = Gio.DataInputStream.new(fstream)
        while 1:
            line, length = dstream.read_line_utf8(None)
            if line == None: break
            if line == "": continue
            if line[0] == ";": continue
            if len(line) > 2 and line[0] =="[" and line[-1] == "]":
                section = line[1:-1]
            elif section == "":
                pass # Nothing
            elif "=" in line:
                pos = line.index("=")
                yield section, line[:pos], line[pos+1:]
        fstream.close(None)
 
    def load_inifile(self):
        """
            Inifile Read and Create Menu
        """
        for section, key, value in self.read_lines(self.confpath):
            # etc...

Pythonのイテレータとジェネレータ – Qiita

ってジェネレーターを作って next() を呼んだら例外だ。
って上記は Python2 だし、Python3 では変わっているのかな?

Pythonのジェネレータ、コルーチン、ネイティブコルーチン、そしてasync/await | プログラミング | POSTD

next はメソッドではなく関数になっているのね。
それさえ解ればなんとかなりそうだ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/env python3
 
import sys, zipfile, gi
gi.require_version('Gtk', '3.0')
gi.require_version('Clutter', '1.0')
gi.require_version('GtkClutter', '1.0')
from gi.repository import Gtk, Gio, GLib, Gdk, GdkPixbuf, Clutter, GtkClutter, Cogl
 
PATH = "gf(kari).cbz";
 
class ComipoliWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # var
        self.num = 0
        self.datas = []
        self.is_fullscreen = False
        # Dark Theme
        settings = Gtk.Settings.get_default()
        settings.props.gtk_application_prefer_dark_theme = True
        # DnD
        self.drag_dest_set(
            Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
            [Gtk.TargetEntry.new("text/uri-list", 0, 0)],
            Gdk.DragAction.MOVE
        )
        # Clutter
        embed = GtkClutter.Embed()
        self.add(embed)
        self.stage = embed.get_stage()
        #
        self.actor1 = Clutter.Actor()
        self.stage.add_child(self.actor1)
        self.image1 = Clutter.Image()
        self.actor1.set_content(self.image1)
        # signal
        self.stage.connect("allocation-changed", self.on_stage_allocation_changed)
        #
        self.show_all()
 
    def set_uri(self, uri):
        if uri == None:
            return
        # Check CBZ
        self.zip = Gio.file_new_for_uri(uri)
        info = self.zip.query_info("standard::content-type", Gio.FileQueryInfoFlags.NONE)
        if info.get_content_type() == "application/x-cbz":
            self.gen = self.iter_zip()
            GLib.idle_add(self.iter_idle)
 
    def iter_idle(self):
        try:
            p = next(self.gen)
            #print(p) # check
            self.datas.append(p)
            if len(self.datas) == 1:
                self.set_pixbuf(0)
            return True
        except Exception as e:
            return False
 
    def iter_zip(self):
        # unzip
        with zipfile.ZipFile(self.zip.get_path()) as o:
            l = o.namelist()
            l.sort()
            self.datas.clear()
            for name in l:
                try:
                    data = o.read(name)
                    stream = Gio.MemoryInputStream.new_from_data(data)
                    p = GdkPixbuf.Pixbuf.new_from_stream(stream)
                    yield p
                except Exception as e:
                    pass
 
    def set_pixbuf(self, num):
        pixbuf = self.datas[num]
        self.image1.set_data(
            pixbuf.get_pixels(),
            Cogl.PixelFormat.RGB_888,
            pixbuf.get_width(),
            pixbuf.get_height(),
            pixbuf.get_rowstride()
        )
        self.num = num
 
    def change_pixbuf(self, bool_next):
        """
            TODO: Spread display
        """
        if bool_next:
            if len(self.datas) > self.num + 1:
                self.set_pixbuf(self.num + 1)
        else:
            if self.num > 0:
                self.set_pixbuf(self.num - 1)
 
    def on_stage_allocation_changed(self, actor, box, flags):
        """
            TODO: Aspect ratio
        """
        self.actor1.set_size(box.x2 , box.y2)
 
    def do_key_press_event(self, event):
        """
            eog like Key Bind
        """
        if event.keyval == Gdk.KEY_Down:
            self.change_pixbuf(True)
        elif event.keyval == Gdk.KEY_Right:
            self.change_pixbuf(True)
        elif event.keyval == Gdk.KEY_space:
            self.change_pixbuf(True)
 
        elif event.keyval == Gdk.KEY_Up:
            self.change_pixbuf(False)
        elif event.keyval == Gdk.KEY_Left:
            self.change_pixbuf(False)
        elif event.keyval == Gdk.KEY_BackSpace:
            self.change_pixbuf(False)
 
        elif event.keyval == Gdk.KEY_F11:
            if self.is_fullscreen:
                self.unfullscreen()
                self.is_fullscreen = False
            else:
                self.fullscreen()
                self.is_fullscreen = True
        elif event.keyval == Gdk.KEY_Escape:
            if self.is_fullscreen:
                self.unfullscreen()
                self.is_fullscreen = False
            else:
                self.close()
 
    def do_drag_data_received(self, drag_context, x, y, data, info, time):
        drop = data.get_uris()[0]
        self.set_uri(drop)
        self.present()
 
class ComipoliApp(Gtk.Application):
    def __init__(self):
        GLib.set_prgname("Comipoli");
        Gtk.Application.__init__(
            self,
            application_id="apps.sasakima.comipoli",
            flags=Gio.ApplicationFlags.FLAGS_NONE )
 
    def do_activate(self):
        ComipoliWindow(self)
 
GtkClutter.init();
app = ComipoliApp()
app.run(sys.argv)

GLib.idle_add でムリムリに非同期にして。
yield を利用して self.datas 配列になるべく早く GdkPixbuf を詰め込む。
next() は yield が終ると例外なのを利用して idle 監視を止める。

とやっています、物凄く解り辛いと思うけど。
しかし class 内で使うとガベージコレクション回避で self だらけに。

試す、よし実用で問題ない程度には使えるようになった。
実際は絶望的に遅いんですけどね。
バックグラウンド処理が追いつかない速度でマンガを読む人はいない、かも。

yield ってこんなに便利だったのか、何を今更だが。

PyGObject CBZ Viewer

MComix のデザインや操作性が古すぎて GNOME3 に合わない。
Python2 は諦めるけど PyGtk はもう入れたくない。

キーバインドが eog と同じように使える cbz コミックビューアが欲しい。
特に [→] キーで改ページがやりたい、筆者が eog で一番使うキーである。

Evince でも見開き表示にはできるが左ページから始まるようにしか設定できない。
次ページが [→] キーなのはこのアプリも同じ。

etc…

だったら自分で作ればいいじゃないか!

今から作るなら当然 ClutterImage を使って OpenGL 表示だよね。
昔ながらのノウハウが使えないという意味でもあるけど。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/env python3
 
import sys, zipfile, gi
gi.require_version('Gtk', '3.0')
gi.require_version('Clutter', '1.0')
gi.require_version('GtkClutter', '1.0')
from gi.repository import Gtk, Gio, GLib, Gdk, GdkPixbuf, Clutter, GtkClutter, Cogl
 
PATH = "gf(kari).cbz";
 
class ComipoliWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # var
        self.num = 0
        self.datas = []
        self.is_fullscreen = False
        # Clutter
        embed = GtkClutter.Embed()
        self.add(embed)
        self.stage = embed.get_stage()
        #
        self.actor1 = Clutter.Actor()
        self.stage.add_child(self.actor1)
        self.image1 = Clutter.Image()
        self.actor1.set_content(self.image1)
        # signal
        self.stage.connect("allocation-changed", self.on_stage_allocation_changed)
        # unzip
        with zipfile.ZipFile(PATH) as f:
            l = f.namelist()
            l.sort()
            for name in l:
                try:
                    data = f.read(name)
                    stream = Gio.MemoryInputStream.new_from_data(data)
                    p = GdkPixbuf.Pixbuf.new_from_stream(stream)
                    self.datas.append(p)
                except Exception as e:
                    pass
        # set
        self.set_pixbuf(0)
        #
        self.show_all()
 
    def set_pixbuf(self, num):
        pixbuf = self.datas[num]
        self.image1.set_data(
            pixbuf.get_pixels(),
            Cogl.PixelFormat.RGB_888,
            pixbuf.get_width(),
            pixbuf.get_height(),
            pixbuf.get_rowstride()
        )
        self.num = num
 
    def change_pixbuf(self, bool_next):
        """
            TODO: Spread display
        """
        if bool_next:
            if len(self.datas) > self.num + 1:
                self.set_pixbuf(self.num + 1)
        else:
            if self.num > 0:
                self.set_pixbuf(self.num - 1)
 
    def on_stage_allocation_changed(self, actor, box, flags):
        """
            TODO: Aspect ratio
        """
        self.actor1.set_size(box.x2 , box.y2)
 
    def do_key_press_event(self, event):
        """
            eog like Key Bind
        """
        if event.keyval == Gdk.KEY_Down:
            self.change_pixbuf(True)
        elif event.keyval == Gdk.KEY_Right:
            self.change_pixbuf(True)
        elif event.keyval == Gdk.KEY_space:
            self.change_pixbuf(True)
 
        elif event.keyval == Gdk.KEY_Up:
            self.change_pixbuf(False)
        elif event.keyval == Gdk.KEY_Left:
            self.change_pixbuf(False)
        elif event.keyval == Gdk.KEY_BackSpace:
            self.change_pixbuf(False)
 
        elif event.keyval == Gdk.KEY_F11:
            if self.is_fullscreen:
                self.unfullscreen()
                self.is_fullscreen = False
            else:
                self.fullscreen()
                self.is_fullscreen = True
        elif event.keyval == Gdk.KEY_Escape:
            if self.is_fullscreen:
                self.unfullscreen()
                self.is_fullscreen = False
            else:
                self.close()
 
class ComipoliApp(Gtk.Application):
    def __init__(self):
        GLib.set_prgname("Comipoli");
        Gtk.Application.__init__(
            self,
            application_id="apps.sasakima.comipoli",
            flags=Gio.ApplicationFlags.FLAGS_NONE )
 
    def do_activate(self):
        ComipoliWindow(self)
 
#Clutter.init(); @ Error
GtkClutter.init();
app = ComipoliApp()
app.run(sys.argv)

comipoli000

とりあえず読み込みと表示関連はなんとかなった。
後はアスペクト比の保持と見開き表示等々。

実は、キーバインドが eog と同じだと少々問題が。

GANMA! むさむらだけ読んでいる
となりのヤングジャンプ えびなちゃんだけ(同
ガンガンONLINE ぐるぐる(同

と筆者が利用しているマンガサイトはことごとく改ページが [←] キーなのだ。
縦書き文化の国で生まれたマンガなのだからそのほうが自然といえる。

横書きの英語文化で作られたアプリとキーバインドを共通にするべきかどうか。
もう完全に好みの問題、左右キーのみ設定で入れ替えできるようにするのが一番かなと。
タッチパネルでもどちらにフリックかで問題になりそう。

gzip, zip

zip 書庫の中にある画像を表示したい。
もちろん展開ファイルを作らずメモリ内でやりくり。

って Gjs ではどうすればいいんだ?
Java なら ZipInputStream という便利なクラスがあるんだが。

ホイール欲しい ハンドル欲しい ? データ圧縮 zlib と gzip と zip (zlib で zip にアクセスする)

zlib でできるっぽい、サイト名が関係なさすぎでワロタ。
Gio で zlib はアクセスできるはず。

Projects/Vala/GIOCompressionSample – GNOME Wiki!

Vala コードだけど gir なら同様に扱えるはず。
ということでこんなコードを書いてみた。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/gjs
 
const Gtk = imports.gi.Gtk;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const GdkPixbuf = imports.gi.GdkPixbuf;
 
//const PATH = "akazukin.zip";
const PATH = "akazukin.gz";
 
const UnzipTestWin = new Lang.Class({
    Name: 'UnzipTestWin',
    Extends: Gtk.ApplicationWindow,
 
    _init: function(app) {
        this.parent({
            application: app
        });
        // Read gzip file
        let source = Gio.File.new_for_path(PATH);
        let stream = source.read(null);
        let converter = new Gio.ZlibDecompressor({
            //format: Gio.ZlibCompressorFormat.RAW
            format: Gio.ZlibCompressorFormat.GZIP
        });
        let cnv_stream = new Gio.ConverterInputStream ({
            base_stream: stream,
            converter: converter
        });
        // Create Pixbuf
        let pixbuf = GdkPixbuf.Pixbuf.new_from_stream(cnv_stream, null);
        let image = new Gtk.Image({
            pixbuf: pixbuf
        });
        this.add(image);
        this.show_all();
    }
});
 
const UnzipApp = new Lang.Class({
    Name: 'UnzipApp',
    Extends: Gtk.Application,
 
    _init: function() {
        GLib.set_prgname("UnzipApp");
        this.parent({
            application_id: 'org.sasakima.unzip',
            flags: Gio.ApplicationFlags.FLAGS_NONE
        });
    },
    vfunc_activate: function() {
        new UnzipTestWin(this);
    }
});
let application = new UnzipApp();
application.run(null);

gzip は上手くいったけど zip はダメだ。
そんなに甘くはなかった、いや筆者が無知なだけかも。
てか gzip は普通 tar とセットだ、tar も展開しないと…

いや違うだろ、zip でなきゃ意味が無いんだ。
何を作ろうとしているかバレバレ臭いのは気にしない。

ええい面倒だ、Python3 の zipfile を使ってしまえ!
スクリプト言語の速度で大丈夫かな?試してみるべ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env python3
 
import sys, zipfile, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, GLib, GdkPixbuf
 
PATH = "なえコレ.zip";
 
class UnzipTestWin(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        fbox = Gtk.FlowBox(valign=Gtk.Align.START, min_children_per_line=5)
        datas = []
        # unzip
        with zipfile.ZipFile(PATH) as f:
            l = f.namelist()
            for name in l:
                d = f.read(name)
                datas.append(d)
        for data in datas:
            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)
            fbox.add(image)
        self.add(fbox)
        self.show_all()
 
 
class UnzipApp(Gtk.Application):
    def __init__(self):
        GLib.set_prgname("UnzipApp");
        Gtk.Application.__init__(
            self,
            application_id="apps.test.naecore",
            flags=Gio.ApplicationFlags.FLAGS_NONE )
 
    def do_activate(self):
        UnzipTestWin(self)
 
app = UnzipApp()
app.run(sys.argv)

naekore

なんだ一瞬だった。
これなら速度も問題ないし簡単だし Python で作ることにしよう。
しかし PyGObject でもプロパティ指定がすっかり Gjs 風になってしまった。