※当サイトではアフィリエイト広告を利用しています。

GUI開発

pythonで画面録画(キャプチャ)アプリを作ってみた

こんにちは、タナカです。

この記事では、pythonで画面録画する方法を紹介します。

普段、私はwindowsを使っており、windowsキー + Gで画面録画をするのですが、そのosの機能では、デスクトップやエクスプローラーといった画面を録画することができないため、pythonで実装しました。

ただただ画面を録画するためのアプリを作ったので紹介します。

1. 画面録画アプリのイメージ

画面録画アプリのイメージはこちらになります。

かなりシンプルな作りで、startとstopしか機能はありません。デザインセンスも皆無なのでご了承ください。。。

startを押せば画面録画を開始し、stopを押すと録画を停止して、mp4形式で保存します。

2. 画面録画アプリのコード

使用しているライブラリはこちらになります。

pyautogui = 0.9.53
opencv = 4.5.4
numpy = 1.21.2
PyQt5 = 5.15.4

 

上記はすべて外部ライブラリになるので、状況に応じてpip等でインストールしてください。

早速、実際のコードを記載します。

import cv2
import pyautogui as pg
import numpy as np
import sys
from PyQt5 import QtGui
from PyQt5 import QtCore
from PyQt5 import QtWidgets
import threading
import time

class Config():
    def __init__(self):
        # パラメータ管理
        self.start_flag = 0
        self.stop_flag = 0
        self.fps = 15
        self.m_time = 10 # s
        self.h, self.w = np.array(pg.screenshot()).shape[:2]
        self.img_list = []

class RecordThread():
    def __init__(self, config):
        super().__init__()
        self.config = config

    def loop(self):
        self.config.img_list = []
        while self.config.stop_flag != 1:
            time.sleep(0.01)
            img = pg.screenshot()
            img = np.array(img)
            img = cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
            self.config.img_list.append(img)

class CaptureApp(QtWidgets.QWidget):
    def __init__(self):
        super(CaptureApp, self).__init__()
        self.config = Config()

        # layout
        self.hbox_layout = QtWidgets.QHBoxLayout()
        self.setLayout(self.hbox_layout)

        # widgetの配置
        self.createWidget()

    # 閉じるボタンをしたときの処理
    def closeEvent(self, event):
        if self.config.start_flag == 1:
            self.config.stop_flag = 1
            self.record_thread.join()

    def createWidget(self):
        # button
        self.start_button = QtWidgets.QPushButton('start')
        self.stop_button = QtWidgets.QPushButton('stop')
        self.label = QtWidgets.QLabel('please start')
        self.hbox_layout.addWidget(self.start_button)
        self.hbox_layout.addWidget(self.stop_button)
        self.hbox_layout.addWidget(self.label)

        # function
        self.start_button.clicked.connect(self.recordStart)
        self.stop_button.clicked.connect(self.recordStop)

    def updateLabel(self):
        if self.config.stop_flag == 0:
            self.label.setText('rec now')
        elif self.config.stop_flag == 1:
            self.label.setText('please start')

    def recordStart(self):
        self.config.start_flag = 1
        self.record_thread = threading.Thread(target = record_control, args = (self.config, ))
        self.record_thread.start()

        # label表示名の更新
        self.updateLabel()

    def recordStop(self):
        # start flagが1の場合
        if self.config.start_flag == 1:
            # stop flagを1にする
            self.config.stop_flag = 1
            # thread処理解除
            self.record_thread.join()
            # 画面録画を保存
            self.recordSave()

            # label表示名の更新
            self.updateLabel()

            # stop flagを0にする
            self.config.stop_flag = 0
            # start flagを0にする
            self.config.start_flag = 0
        else:
            print('please rec start')

    def recordSave(self):
        self.save_path = QtWidgets.QFileDialog.getSaveFileName(self, 'save file', filter = '*.mp4')[0]

        if self.save_path == '':
            return
        fourcc = cv2.VideoWriter_fourcc('m','p','4', 'v')
        video = cv2.VideoWriter(self.save_path, fourcc, self.config.fps, (self.config.w, self.config.h))
        for img in self.config.img_list:
            video.write(img)
        video.release()

def record_control(config):
    rec = RecordThread(config)
    rec.loop()

def main():
    app = QtWidgets.QApplication(sys.argv)
    win = CaptureApp()
    win.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

 

startを押した後にstopを押せるようにするために、スレッド処理で書いてます。

スレッド処理にしないと録画処理(while文)がずっと回ってしまい、ループ処理から抜け出せなくなってしまいます。startとstopを別のスレッドで行えるようにすることでその問題を回避するようにしています。

 

下記のloop処理では、pyautoguiのscreenshotメソッドを使って、0.01s間隔で画面をキャプチャしており、そのキャプチャした画像をimg_listに格納しています。

    def loop(self):
        self.config.img_list = []
        while self.config.stop_flag != 1:
            time.sleep(0.01)
            img = pg.screenshot()
            img = np.array(img)
            img = cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
            self.config.img_list.append(img)

 

その格納した画像をopencvのVideoWriterで動画を作成し、画面録画ができるといった仕組みなっています。

        fourcc = cv2.VideoWriter_fourcc('m','p','4', 'v')
        video = cv2.VideoWriter(self.save_path, fourcc, self.config.fps, (self.config.w, self.config.h))
        for img in self.config.img_list:
            video.write(img)
        video.release()

 

1つ注意点としては、録画した動画は非常に容量が重くなることです。長時間の録画は避けた方がいいでしょう。

 

もっといい方法があれば教えてもらえると嬉しいです。

3. 画面録画した動画を圧縮する方法

こちらはpythonとは関係ないですが、画面録画した動画の容量が非常に重いため、ffmpegを使って圧縮することをおすすめします。

使い方を簡単に説明するとダウンロードしたffmpegのフォルダを任意の場所に移し、コマンドプロンプトを開きます。

作業ディレクトリをダウンロードしたffmpegフォルダ内のbinに移動(cdコマンド)します。

ffmpeg-cmd

そこで下のコマンドを入力します。

ffmpeg -i 圧縮したい動画パス 圧縮後の動画パス

 

圧縮したい動画パスには、画面録画したときに生成された動画のパスを指定し、圧縮後のパスは任意の名前を付けます。

私の環境で約10分の1くらい圧縮できました。

 

ちなみに今回は作業ディレクトリにffmpegが保存されている場所を指定して実行しましたが、環境変数にffmpegを設定しておけば、作業ディレクトリを変更する必要はありません。

 

ffmpegは他にも機能があるそうなので、色々活用の幅は広そうです。

 

pythonのプログラムとsubprocess関数を組み合わせてffmpeg自体を走らせることができれば、より汎用性が高まるのではと思いつつ、そこまではやっていません。

まとめ

pythonで画面録画する簡易アプリの紹介をしました。

pythonを使わなくても、画面録画するためのアプリは山ほど転がっていますが、コーディングの練習としてはいいのではないでしょうか。

何かアイディアがあれば、自分で実際に実装してみることを継続していれば自然とpythonが身につくと思います。

pythonのアウトプット環境としてブログを書くことをお勧めしています。こちらも参考にしてもらえると嬉しいです。

pythonの習得にブログがおすすめな5つの理由 pythonを効率的に習得したい pythonを継続的に勉強したい こんなお悩みにお答えします。 ...