Kindle for PCのスクショを撮る

新しいバージョンがあります

https://qiita.com/dengax/items/6fb1adf9bf3dd5fc6b2b


# Kindle for PCを画像にしたい
Kindle for PCのUIは正直クソなので、画像にして保存したいです。
幸いなことにスクリーンショットは撮れるので、Pythonを使えば楽に画像に出来るでしょう。
# と、言うわけでweb検索した
その結果、pyautoguiを使って連続してスクショを撮っているPythonスクリプトがありました。でも、正直あまり気に入らなかったのです。
と、言うのも、実行したら5秒以内にKindle for PCを表に持ってきて、フルスクリーンにして、で、5秒ごとにページを進めてスクショを撮って保存する。と言うものでした。
しかも切り取る座標は自分で入力しなければいけませんでした。
# もっと楽に、高速化出来るはず
Windows限定になってしまいますが、ウィンドウ一覧から特定のウィンドウをアクティブにするのは出来るはずです、もちろん、フルスクリーンにも出来るはずです。
と、言うわけで大改造しました。もう元の面影もないです。

```Python:kindless.py
import pyautogui as pag
import os, os.path as osp
import datetime , time
from PIL import ImageGrab
from tkinter import messagebox , simpledialog
import cv2
import numpy as np

from ctypes import *
from ctypes.wintypes import *

kindle_window_title = 'Kindle'  #Kindle for PCに含まれるウインドウタイトル
page_change_key = 'left'    #次ページに移動するときのキー

kindle_fullscreen_key = 'f11'   #フルスクリーンにするときのキー
kindle_fullscreen_wait = 5      #フルスクリーンにした後の待ち時間(秒)

l_margin = 1                    #サイズ自動設定のときの左側マージン
r_margin = 1                    #サイズ自動設定のときの右側マージン
waitsec = 0.15                  #キーを押してからスクリーンショットを撮る待ち時間(秒)

base_save_folder = 'e:\\kss\\'           #保存する場所 タイトルの前に入れられる

title = str(datetime.datetime.now().strftime("%Y%m%d%H%M%S"))

EnumWindows = windll.user32.EnumWindows
GetWindowText = windll.user32.GetWindowTextW
GetWindowTextLength = windll.user32.GetWindowTextLengthW
GetWindowRect = windll.user32.GetWindowRect
SetForegroundWindow = windll.user32.SetForegroundWindow

WNDENUMPROC = WINFUNCTYPE(c_bool, POINTER(c_int), POINTER(c_int))

ghwnd = None

def EnumWindowsProc(hwnd, lParam):
    global ghwnd
    length = GetWindowTextLength(hwnd)
    buff = create_unicode_buffer(length + 1)
    GetWindowText(hwnd, buff, length + 1)
    # print(buff.value)
    if buff.value.find(kindle_window_title) != -1:
        ghwnd = hwnd
        return False
    return True

EnumWindows(WNDENUMPROC(EnumWindowsProc), 0)
if ghwnd == None:
    messagebox.showerror("エラー","Kindleが見つからない")
    import sys
    sys.exit()

SetForegroundWindow(ghwnd)
rect = RECT()
GetWindowRect(ghwnd, pointer(rect))
pag.moveTo(rect.left+60, rect.top + 10)
pag.click()
time.sleep(1)

pag.press(kindle_fullscreen_key)
sc_w, sc_h = pag.size()

tt = simpledialog.askstring('タイトルを入力','タイトルを入力して下さい(空白の場合現在の時刻)')
if tt != '':
    title = tt

pag.moveTo(sc_w -200 , sc_h -1)

time.sleep(kindle_fullscreen_wait)


start = time.perf_counter()
img = ImageGrab.grab()
img = np.array(img)
imp = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

print(time.perf_counter()-start)
start = time.perf_counter()


def cmps(img,rng):
    for i in rng:
        if np.all(img[20][i] != img[19][0]):
            return i

lft = cmps(imp,range(l_margin,imp.shape[1]-r_margin))
rht = cmps(imp,reversed(range(l_margin,imp.shape[1]-r_margin)))

imp1 = imp[ :, lft : rht]
print(time.perf_counter()-start)

old = np.zeros((sc_h , rht-lft, 3), np.uint8)
page = 1
print(old.shape)
cd = os.getcwd()
os.mkdir(osp.join(base_save_folder, title))
os.chdir(osp.join(base_save_folder, title))

while True:
    filename = str(page).zfill(3) + '.png'
    change = False
    start = time.perf_counter()
    while True:
        time.sleep(waitsec)
        s = ImageGrab.grab()
        s = np.array(s)
        ss = cv2.cvtColor(s, cv2.COLOR_RGB2BGR)
        ss = ss[:, lft: rht]

        if not np.array_equal(old, ss):
            break
        if time.perf_counter()- start > 5.0:
            pag.press(kindle_fullscreen_key)
            os.chdir(cd)
            import sys
            sys.exit()
    cv2.imwrite(filename,ss)
    old = ss
    print('Page:', page, ' ', ss.shape, time.perf_counter() - start, 'sec')
    page += 1
    pag.keyDown(page_change_key)
```
# 使い方
適当にソースのコメントを見て書き換えて下さい。
キーを押してからスクリーンショットを撮るまでの待ち時間が0.15秒とかなり短いですが、同じ画像が撮れてしまった場合、違う画面になるまで0.15秒ごとにスクリーンショットをとり続けおなじ物は保存をスキップするので同じものが幾つも保存されることはありませんが、環境によっては、書き換え中のSSが撮られる事があるので、その場合はwaitsecを調整して下さい。

Kindle for PCを起動して、撮りたい物の１ページ目を表示させて置いて下さい。
スクリプトを実行すると勝手にKindle for PCをアクティブにしてフルスクリーンにした上でタイトルを聞いてくるのでタイトルを入れて下さい。

5秒の待ち時間の後に全てのページを連続してスクリーンショットを撮ります。
最終ページまで保存したら、5秒間待ってフルスクリーンを解除して終了します。

この際、小説のようなものであれば、左右の余白は勝手にカットしてくれます。
マウスカーソルは右下の目立たない位置にいるので、マウスを動かさないで下さい。

>コミックもトリミングしたい
>
>画像のトリミングとモノクロ化(Kindle for PCのスクショを撮る2)
>https://qiita.com/dengax/items/5eb93560112da0a99131

なお、外部ライブラリとして`pyautogui`,`PIL`,`cv2`,`numpy`を使用しています。
`pip install pyautogui pillow opencv-contrib-Python numpy`等でインストールして下さい

# 著作権について
このスクリプトを使用して作成した画像ファイルは、私的複製として、個人的に使用する用途以外では使用できません。
# ここから開発者向け
単にKindleの書籍のスクショを撮りたいだけの人は、ここから下は読む必要はありません。
このスクリプトは結構突貫工事で作成したので、無駄な所が有ります

- pyautoguiのscreenshotではなく、PILのimagegrab.grabを使用している。
　もっとも、pyautoguiでは内部でPILを使用しているっぽいので、動作的には多分あんまり変わらない。

- スクリーンショットを全画面で取得している
　imagegrab.grabの引数調べるの面倒だったから

- opencvを使っている
　スクショ→変換→色変換→トリミング→比較→保存 と言う手順なので、無駄が多い、単に画像比較するだけなら 範囲スクショ→比較→保存 の方が多分高速

で、このへんを解消したテストコードは作ったけれど、このスクリプトはもうこれで不満がない程度の速度で動作するし、書き換えるのが手間なので放置しました。

改善候補のテストスクリプトはこちら
```Python:pagsstest.py
import pyautogui as pag
import time

def imgcmp(img1, img2) -> bool:
    width, height = img1.size
    if img1.size != img2.size:
        return False
    im1 = img1.getdata()
    im2 = img2.getdata()
    mx = height * width
    i = 0
    while i < mx:
        if list(im1[i]) != list(im2[i]):
            return False
        i += 1
    return True

s_rg = (0, 0, 1920, 1080)

start = time.time()
p1 = pag.screenshot(region=s_rg)
end = time.time()

print('screenshot', (end-start)*1000)

time.sleep(3)
p2 = pag.screenshot(region=s_rg)
# p2.save('filename.png')

start = time.time()
f = imgcmp(p1, p2)
end = time.time()

print('compare', (end-start)*1000)
print(f)
```
>numpyは高速なのですが、ndarrayで直接スクリーンショットが撮れず、PILやpyautogui経由で撮ることになり、ndarrayへの変換コストが馬鹿にならないので、PILの形式のまま比較しています。

実行時するとすぐに撮ったスクリーンショットと3秒後に撮ったスクリーンショットを比較して、TrueかFalseを表示します。

pyautoguiでは操作対象のアプリの画面変化の監視と言うのはそれなりに使いどころがありそうではあるので、何かの参考になれば・・・

imgcmpは、できる限り高速になるようにしましたが、所詮Pythonなので、速度はそこそこです。でも、pyautogui.screenshot自体がかなり低速なので、これ以上を望む場合このあたりから作り直さないとダメっぽいので、この程度で仕方ないでしょう。