是非ともやりたかった、てわけではなくて。
PyAV の検証 しながら、「作れちゃうよな」と思っただけ。今10分くらいで作った:
1 import signal
2 import time
3 import av
4 from PIL import ImageGrab
5
6 class _Interrupt(Exception):
7 pass
8
9 def _IntHandler(signum, frame):
10 raise _Interrupt()
11
12 signal.signal(signal.SIGINT, _IntHandler)
13 # ---
14 time.sleep(3)
15
16 ocont = av.open("dr.mp4", "w")
17 vstream = None
18 vrate = 24 #
19 while True:
20 try:
21 dimg = ImageGrab.grab()
22 #dimg = dimg.resize((dimg.width // 4 * 3, dimg.height // 4 * 3))
23 if vstream is None:
24 vstream = ocont.add_stream('h264', rate=vrate)
25 vstream.width = dimg.width
26 vstream.height = dimg.height
27 vstream.pix_fmt = 'yuv420p'
28
29 vframe = av.VideoFrame.from_image(dimg)
30 for p in vstream.encode(vframe):
31 ocont.mux(p)
32 time.sleep(1.0 // vrate)
33 except (_Interrupt, KeyboardInterrupt):
34 print("done.")
35 break
36 ocont.close() # MUST!
録画終了は Ctrl-C、のつもりなので INT ハンドラってるが、こんなんで良かったっけか? で、今録画したヤツ:
なお、最初
1 vstream = ocont.add_stream('mpeg4', rate=vrate)
としててあまりの画質の悪さに参った。サンプルコード猿真似してた間は黙殺してたが、よく考えたら「エンコーダが mpeg4」てのもあるんだな。とりあえず見慣れた:
1 vstream = ocont.add_stream('h264', rate=vrate)
に変えて、挙げた動画になった。
にしてもまともな分解能の動画を作りには、細かくするほど(ソースコードの vrate)プログラムの性能が問題になるかもしれん(multiprocessing のお世話になるかも)、と想像して始めたが、アニメ並みの 24 fps でご覧のとおりの実用になるものになったし、性能も全然問題ないのね、意外だった。
普段はスクリーンキャストには Hangouts on Air を使ってるけど、ところどころ不満はあるのね、やっぱり。操作が面倒でもあるし。けどこの ImageGrab + PyAV は楽だし自由度もあって、案外日常使いにもいいかもなぁ。(無論そのためにはもちっと便利にせんといけんけれど。)
21日追記:
INT ハンドラの振る舞いが不定で使いづらいんで、ちょっとだけマジメなものに書き直してみた:
1 #! /bin/env python
2 import time
3 import signal
4 import argparse
5 from multiprocessing import Process, Queue
6 from Queue import Empty
7
8 import av
9 from PIL import ImageGrab
10
11 def _run_capture(outfile, q):
12 def _IntHandler(signum, frame):
13 q.put("done")
14
15 signal.signal(signal.SIGINT, _IntHandler)
16
17 ocont = av.open(outfile, "w")
18 vstream = None
19 vrate = 24 #
20
21 print("start capturing.")
22 while True:
23 dimg = ImageGrab.grab()
24 #dimg = dimg.resize((dimg.width // 4 * 3, dimg.height // 4 * 3))
25 if vstream is None:
26 vstream = ocont.add_stream('h264', rate=vrate)
27 vstream.width = dimg.width
28 vstream.height = dimg.height
29 vstream.pix_fmt = 'yuv420p'
30
31 vframe = av.VideoFrame.from_image(dimg)
32 for p in vstream.encode(vframe):
33 ocont.mux(p)
34 try:
35 r = q.get(block=False, timeout=1.0 // vrate)
36 if r:
37 break
38 except Empty as e:
39 pass
40
41 print("done.")
42 ocont.close() # MUST!
43
44 if __name__ == '__main__':
45 parser = argparse.ArgumentParser()
46 parser.add_argument("--recordfile", default="recorded.mp4")
47 parser.add_argument("--countdown", help="countdown for starting, in secs.", type=float, default=0.5)
48 args = parser.parse_args()
49
50 time.sleep(args.countdown)
51
52 q = Queue()
53 p = Process(target=_run_capture, args=(args.recordfile, q,))
54 p.start()
55 p.join()
multiprocessing な意味があるのかはわからんけれど、このやり方が安定して Ctrl-C を扱える、と思う。ちとダルいけど。