Python」タグアーカイブ

get_size, resize

GtkHeaderBar 付きの GtkWindow のリサイズ。
以前こんなことを書いた。

GTK+ 3.20 GtkHeaderBar and CSS | PaePoi

もう少し細かく判った。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Win(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # Headerbar
        self.headerbar = Gtk.HeaderBar(show_close_button=True)
        self.set_titlebar(self.headerbar)
        # Button
        button = Gtk.Button(label="Click !!!")
        button.connect("clicked", self.on_button_clicked)
        self.add(button)

    def on_button_clicked(self, button):
        print(self.get_allocated_width(), self.get_allocated_height())
        w, h = self.get_size()
        print(w, h)

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

    def do_activate(self):
        w = Win(self)
        w.show_all()

app = App()
app.run()

aloc_size

get_allocated_* と get_size で値が違う。
ちなみに GtkHeaderbar を消すと同一の値になる。

見た目で解るけどクライアント領域のサイズですね。
window.get_size と window.resize が共通サイズになるようだ。
ということでサイズの保存にはコチラを使いましょう。

ついでに、GVariant.deep_unpack は Gjs でないと使えないのね。
Gjs も地味に便利にしているところがあったのね。

Google Chrome 51 Chach

Google Chrome はキャッシュの仕様が変わってしまったね。
FLV や MP4 の動画キャッシュにヘッダが着くようになった。

Nautilus をスクリプトで拡張 – L’Isola di Niente
つまり上記に書いた手段が使えない。
YouTube は随分前から駄目だけどニコニコなんかはまだキャッシュに残るし。

いやまあ強引にダウンロードする手段はいくらでもあるんだけど。
せっかくキャッシュにあるのにもう一回通信なんてアホ臭い。
GHex 等で地味に余計なヘッダを削るなんて面倒臭い。

ならば余計なヘッダを取っ払ってからコピーすればいいじゃない!
Nautilus スクリプトです、Linux じゃない人は参考程度に。

FLV が取得できなかったので小変更

#!/usr/bin/env python3

"""
    chrome_catch (~/.local/share/nautilus/scripts)
    Copy Chrome Cache File to Nautilus Current Directry
    Google Chrome 51 Version
"""

import os
from gi.repository import GLib

FLV_FILE = "FLV".encode("utf-8")
MP4_FILE = "ftyp".encode("utf-8")

# Src Directory is Google Chrome Cache Path
src_path = os.path.expanduser("~/.cache/google-chrome/Default/Cache")
# Copy Directory is Nautilus current directory
dst_path = GLib.filename_from_uri(GLib.getenv("NAUTILUS_SCRIPT_CURRENT_URI"))[0]

ls = os.listdir(src_path)
for f in ls:
    src = os.path.join(src_path, f)
    # size < 1MB is continue:
    if os.path.getsize(src) < 1048576:
        continue
    with open(src, "rb") as o:
        data = o.read()
        data2 = data
        try:
            i = data.find(MP4_FILE)
            o.seek(i-4)
            with open("{0}/{1}.mp4".format(dst_path, f), "wb") as ff:
                ff.write(o.read())
        except Exception as e:
            pass
        #data2 = o.read()
        try:
            i = data2.find(FLV_FILE)
            o.seek(i)
            with open("{0}/{1}.flv".format(dst_path, f), "wb") as ff:
                ff.write(o.read())
        except Exception as e:
            pass

find で見つからないと例外なのでコレでいいと思う。
早速ニコニコでバイク動画を再生して、よしよし拾うことができた。
いやバイク動画目的ですよ、決してえっち(以下略

PyGObject do_key_press_event

PyGObject でのオーバーライドでハマったので覚書。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, Gdk

class NoActionWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        # connect
        #self.connect("key_press_event", self.on_key_press_event)
        self.show_all()

    def do_key_press_event(self, event):
    #def on_key_press_event(self, widget, event):
        """
            Call the method of the parent
            'gtk_application_set_accels_for_action' does not work.
        """
        # Important!
        Gtk.ApplicationWindow.do_key_press_event(self, event)
        #
        if event.keyval == Gdk.KEY_Escape:
            self.close()
        return False

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

    def do_activate(self):
        # ApplicationMenu
        menu = Gio.Menu()
        menu.append("_Quit", "app.quit_action")
        self.set_app_menu(menu)
        self.set_accels_for_action("app.quit_action", ["<Control>Q"])
        # Action
        quit_action = Gio.SimpleAction(name="quit_action")
        quit_action.connect("activate", self.on_quit_action)
        self.add_action(quit_action)
        #
        self.window = NoActionWindow(self)

    def on_quit_action(self, action, parameter):
        self.quit()

app = NoActionApplication()
app.run()

上記 Important 部を無効にすると Ctrl+Q で終了できない。
このオーバーライドは親の do_key_press_event を呼ぶ必要がある。
connect メソッドならその必要は無いようだ。

こんなしょーもないことを解決するのに三時間くらい使っちまったぜ。
GtkApplication 側に問題があると思いこんでしまうもの、こういう場合。
この連休でアレを完成させるつもりだったけど無理かも。

Python3 yield

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

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

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

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 はメソッドではなく関数になっているのね。
それさえ解ればなんとかなりそうだ。

#!/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 ってこんなに便利だったのか、何を今更だが。

gzip, zip

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

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

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

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

Projects/Vala/GIOCompressionSample – GNOME Wiki!

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

#!/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 を使ってしまえ!
スクリプト言語の速度で大丈夫かな?試してみるべ。

#!/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 風になってしまった。