リーディング時のWPM向上を支援するスクリプト書いてみた

自分は英文を読むのがクソ遅いんですが,知らないと損する英語の速読方法(1) - 一法律学徒の英語と読書な日々に感化されてちょっとWord Per Minute(WPM),つまり分あたりに読む単語数を改善しようと思い立った.
で,それっぽい支援ソフトウェアを探したんだがあまり自分の好みの物は見つからなかったので,pythonで適当に書いてみた.ウィンドウのフォーカスあたりでちょいちょいしてるので多分windowsのみサポート.そこを消せば他でも動くはずだけど実用レベルではない.
いやね,なんかフラッシュしながら表示するのはあるんだけど,ハイライトが見つからなかったんです.フラッシュ版は,Spreeder CX | なんかはある程度注目されてる模様.

で,ハイライトが好みの人向けのこのスクリプトの内容は,

  1. 設定したWPMに応じて,Ctrl+Shift+→ キーを送って,選択中の領域を単語ごとに拡大する

だけ.
要するに,表示テキストを選択できてCtrl+Shift+→ が有効なアプリケーションならなんでもよくて*1,ウィンドウを登録した後に開始付近の単語をマウスで選択状態にしてからスタートすると指定したWPMで単語がどんどんハイライトされる.



一応ウィンドウのフォーカスをとっているので暴走はそこまでしないハズだけど,これを実行したことによって生じる不利益はなんの保証もできません.

いろいろと恥ずかしいコードだけど,まぁ自分の欲求は最低限満たしているし,時間が勿体無いから書き直すこともないだろうということで投げやりに記載*2.適当に改良してください.いや,本当はchromeのextensionでできたらよかった*3んだけど,今は新しいものに手を出す余裕が無い.まぁこのスクリプトはPDFファイルにも応用できるのでこれはこれでよしとする.

実行には,以下のインストールが必要.

  1. python(2.6推奨)
  2. pyWin32
  3. SendKeys

あとは,下のスクリプトをコピペして"speedReader.py"とでも名前をつけて保存して,ダブルクリック.

#!usr/bin/python
# coding: utf-8
# 使い方:まず使用の対象となる英文を表示しているウィンドウを登録
# マウスなどを使って開始したい位置の文字を選択状態(ハイライト)にし,
# スタートすると指定したWPMで選択領域が拡大.原理的には Ctrl+Shift+->を送っているだけ
# 止めたいときは,いったんフォーカスを他のものに移す:通常はspeedReaderをクリック
# キー出力を乗っ取っているので,こちらからの入力がたまに被さる可能性がある
# 自動スクロールは実用的でないので,キー入力などを使って手動でスクロール推奨.

import sys
import time
import SendKeys
import msvcrt
import win32gui
import win32con

 
gGoalWPM = 250
gUnitTime = 0.0
gAutoScrollNum = 10     # auto scroll with this word count
gThisWindow = win32gui.GetForegroundWindow()
gTargetWindow = gThisWindow

def setWPM(w):
    global gGoalWPM, gUnitTime
    if w < 10:
        w = 10
    elif w > 1500:
        w = 1500
    gGoalWPM = w
    gUnitTime = 1.0 / (float(gGoalWPM)/60.0)
    print "Goal WPM = %d" % gGoalWPM

def selectWindow():
    global gTargetWindow
    print "select target window"
    while True:
        gTargetWindow = win32gui.GetForegroundWindow()
        if gTargetWindow != gThisWindow:
            print "[", win32gui.GetWindowText(gTargetWindow), "]"
            return True
        if msvcrt.kbhit():
            print "cancel"
            return False
        time.sleep(0.1)
    
def printHelp():
    print "s:start, o:stop, u/d:up/down speed"
    print "c:start in 3sec, 1-9:preset speed,"
    print "w:register target window, r:reset, "
    print "h:help, p:print target window, q:quit"
        
        
# main
def main():
    global gGoalWPM, gUnitTime, gTargetWindow, gCurrentWindow
    # variables
    mCurrentTime = time.clock()
    mLastTime = mCurrentTime
    mTotalTime = 0.1                 # temp

    mShouldStop = True
    mDefocus = False
    mWordCount = 0
    # variables
    
    # always on top, window size, position
    win32gui.SetWindowPos(gThisWindow,win32con.HWND_TOPMOST,500,200,350,200,0)

    if(len(sys.argv) > 1):
        gGoalWPM = sys.argv[1]
    setWPM(gGoalWPM)
    mTotalTime = gUnitTime
    printHelp()
    
    while True:
        firstLoop = True

        # wait untile goal WPM rate
        while firstLoop or (mCurrentTime - mLastTime) < gUnitTime:
            firstLoop = False
            time.sleep(0.001)
            mCurrentTime = time.clock()
            # keyboard interrupt
            if msvcrt.kbhit():
                hitkey = msvcrt.getch()
                if hitkey == '\000':
                    pass        # Send key
                elif hitkey == 'q':
                    print "exit"
                    sys.exit()
                elif hitkey == 'u':
                    setWPM(gGoalWPM + 10)
                elif hitkey == 'd':
                    setWPM(gGoalWPM - 10)
                elif hitkey == '1':    # preset 1
                    setWPM(150)
                elif hitkey == '2':    # preset
                    setWPM(200)
                elif hitkey == '3':    # preset
                    setWPM(250)
                elif hitkey == 'w':
                    selectWindow()
                    # win32gui.SetForegroundWindow()
                elif hitkey == 'h':
                    printHelp()
                elif hitkey == 'p':
                    print "[", win32gui.GetWindowText(gTargetWindow), "]"
                elif hitkey == 's':
                    print "start"
                    if gTargetWindow == gThisWindow:
                        if selectWindow():
                            win32gui.SetForegroundWindow(gTargetWindow)
                            mShouldStop = False
                    else:
                        win32gui.SetForegroundWindow(gTargetWindow)
                        mShouldStop = False
                elif hitkey == 'o':
                    print "stop"
                    mShouldStop = True
                elif hitkey == 'c':
                    if mShouldStop:
                        print "wait 3 seconds..."
                        time.sleep(3)
                        win32gui.SetForegroundWindow(gTargetWindow)
                        mShouldStop = False
                elif hitkey == 'r':
                    print "reset"
                    printHelp()
                    setWPM(gGoalWPM)
                    mLastTime = mCurrentTime
                    mTotalTime = gUnitTime
                    mShouldStop = True
                    mWordCount = 0

        if not mShouldStop:
            if not mDefocus:
                curWindow = win32gui.GetForegroundWindow()
                if curWindow != gTargetWindow:
                    print "defocus"
                    print "[", win32gui.GetWindowText(curWindow), "]"
                    mDefocus = True
                else:
                    SendKeys.SendKeys("""^+{RIGHT}""", 0.0)
                    minutes = int(mTotalTime) / 60
                    seconds = int(mTotalTime) - minutes * 60
                    decisec = int((mTotalTime - float(minutes*60 + seconds)) * 100.0)

                    print "%d/%d[wpm], " % (float(mWordCount+1)/mTotalTime*60.0, gGoalWPM),
                    print "%3d[w], %02d:%02d:%02d" % (mWordCount, minutes, seconds, decisec)
                    if gAutoScrollNum > 0 and mWordCount%gAutoScrollNum == 0:
                        # I don't know why this doesn't work
                        # win32gui.SendMessage(gTargetWindow, win32con.WM_VSCROLL, win32con.SB_LINEDOWN, 0)
                        pass

                    mTotalTime += gUnitTime
                    mWordCount = mWordCount + 1

            else:
                if gTargetWindow != gThisWindow and gTargetWindow == win32gui.GetForegroundWindow():
                    print "get focus"
                    mDefocus = False
                    mShouldStop = False

        mLastTime = mCurrentTime


# main
if __name__ == "__main__":
    main()
メモ

始めてwin32API使ったけど,なんかよくわからんことが多いのでメモ.

  • ScrollWindowEx()で他のアプリケーションをスクロールさせるのは,スクロール領域がわからないので無理?
  • 正当法であろう SendMessage() はなぜかうまく動かなかった
  • SetForegroundWindow() は,自分自身を非アクティブからアクティブにできないらしい
追記

11/14 最大wpmが低かったのでコードを修正.

*1:メモ帳,firefox, Adobe Readerとか

*2:最近こんなんばっかだな

*3:単語でなく文字ごとにハイライトとか,適度に自動スクロールとか,領域先端の単語の意味表示とか.どなたかぜひ作ってください