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 側に問題があると思いこんでしまうもの、こういう場合。
この連休でアレを完成させるつもりだったけど無理かも。

United RPMS

Fedora に新しいリポジトリができたんだね。
数はまだ少ないけど。

United RPMS by UnitedRPMs

RPM Fusion から何故か無くなった unrar がこっちにはある。
Atom や Opera やよくワカランのやら、ふむふむ。
mplayer 等のマルチメディア系は RPM Fusion と被りまくっているような。

unitedrpms.github.io/README.md at master ? UnitedRPMs/unitedrpms.github.io ? GitHub

インストールは上記、GPG キーのインストールを忘れずに。
gnome-softwere のソフトウエアソースを確認。

unitedrpms1

うん登録されているね。
これで Atom とかはインストールできるけど被っているものはどうなるの?

gaburi

普通にどちらか一方が入るということのようだ。
これなら導入しても特に問題はなさそうだ。

今後どうなるか解らないけど面白いものが登録されるといいな。
まあ筆者の場合は使うアプリがほぼ固定しちゃってるんで意味ないんですけどね。

Fedora 24 rar, cbr

CBZ ビューアを作っているけど CBR はどうしよう?
筆者自信は cbr ファイルを一つも持っていないし。
まあそりゃ rar 自体が超マイナーだし。

てゆーか Fedora 24 にしてから RPM Fusion で unrar が見つからない。
と思ったけど手持ちにあった rar ファイルを file-roller で開くことができる。
これって自前対応になったってこと?
いや圧縮はできないし、今度ソースでも落として調べてみよう。

自前になったとしても MComix 等は unrar を使うんだよなぁ。
筆者はいらないけど rar で圧縮したい人もいるだろう。

WinRAR archiver, a powerful tool to process RAR and ZIP files

なんだ、RARLAB で普通に Linux x64 バイナリが落とせるじゃん。
せっかくなので現在最新の 5.40 beta3 を落として

sudo make install

rar

あれ、Makefile は先頭を大文字にしなくても使えるのね。
まあいい、バイナリだからこれだけでインストール完了。
早速 Nautilus のコンテキストメニューから[圧縮]をば。

rar_cbr

おお増えた、と思ったら cbr, cbz まで何故か増えた!
これで ZIP 圧縮した後で拡張子を変更する手間が省ける、少し嬉しい。

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