Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

flaskで動画再生Webアプリを作ってみる

先日OpenCVを用いた動画再生や逆再生、早送り等を実装してみました。
elsammit-beginnerblg.hatenablog.com

今回はFlaskを用いてこれら逆再生や早送りなどが実装されたWebアプリについてまとめていきたいと思います!!



■完成形

完成したアプリはこちらのようになります。
f:id:Elsammit:20210509191521g:plain

■最終コード

最終的なコードはこちらに格納いたしましたのでご参考まで。
https://github.com/Elsammit/Flask-VideoApplication

■フォルダ構成

フォルダ構成は下記になります。
f:id:Elsammit:20210509180839p:plain

■Flaskでの動画表示

ではまずはFlaskで動画再生表示について実装していきたいと思います。
こちらのコードですが、下記を参考に作成しました。
https://github.com/miguelgrinberg/flask-video-streaming
FastAPIでOpenCV Streaming - Qiita

ここで動画表示について解説しても良いのですが、、、
こちらの記事がかなり詳しく記載されており、ここで書くのは冗長な気がするので割愛します。

実施している流れとしては、
・フロントエンドから動画再生要求
・バックエンドにてOpenCVのVideoCaptureで動画データのフレームを取得
・取得したフレームをバイト列に変換の上フロントエンドに返す
・フロントエンドにて受け取ったバイト列情報から動画を再生
となります。
マルチアクセスにも対応させるためにフレームの取得・操作をスレッドで行っております。

■動画早送り・巻き戻し機能追加(バックエンド)

では先ほどの動画表示から早送り・巻き戻しなどの機能を追加していきます。
まずはバックエンドから。

変更するファイルはcamera.py、main.pyの2つです。
それぞれ、
・main.py:フロントエンドとのIF追加
・camera.py:フロントエンドからの指示に応じて早送り・巻き戻し動作実行
になります。

まずはman.pyから。
追加コードはこちらになります。

@app.route('/start', methods=["POST"])
def start_movie():
    print("start")
    Camera.rewindFlg = False
    Camera.stop = False
    Camera.speed = 0
    return Response("OK", 200)

@app.route('/pause', methods=["POST"])
def pause_movie():
    print("pause")
    Camera.stop = True
    return Response("OK", 200)

@app.route('/stop', methods=["POST"])
def stop_movie():
    print("stop")
    Camera.cap = cv2.VideoCapture(Camera.MoviePath)
    Camera.stop = False
    video_feed()

@app.route('/speed', methods=["POST"])
def changeSpeed():
    if Camera.rewindFlg == True:
        Camera.speed = 1
        Camera.rewindFlg = False
    else:
        Camera.speed = Camera.speed % 3 + 1
    Camera.stop = False
    print("speed:"+str(Camera.speed))
    return Response("OK", 200)

@app.route('/rewind', methods=["POST"])
def rewind_movie():
    if Camera.rewindFlg == False:
        Camera.speed = 1
        Camera.rewindFlg = True
    else:
        Camera.speed = Camera.speed % 3 + 1
    Camera.stop = False
    print("rewind:"+str(Camera.rewindFlg))
    return Response("OK", 200)

@app.route('/progress', methods=["POST"])
def get_progress():
    return Response(str(Camera.progress), 200)

実施していることは、各動作に応じてpost requestを受け取り、受け取った内容に応じてフラグ管理を行っているのみになります。
管理するフラグですが、
・一時停止判定用フラグ:stop
・巻き戻し判定用フラグ:rewindFlg
・早送り時の速度:speed
の3種類です。
speedは3段階で切り替えられるようにいたしました。
合わせて再生バーを表示させたかったので、逐次呼び出すようのprogressも用意いたしました。


次にcamera.pyです。
変更部はこちら。

    def frames():
        while True:
            #↓ここから
            if Camera.stop == True:
                continue

            ret, frame2 = Camera.cap.read()
            frame = cv2.resize(frame2,(WIDTH,HEIGHT))
            
            if ret == True:
                Camera.counter = Camera.cap.get(cv2.CAP_PROP_POS_FRAMES)
                Camera.progress = int(Camera.counter / Camera.frame_count * 100)

                if Camera.speed != 0:
                    SpdNum = Camera.speed*30
                        
                    if Camera.rewindFlg==True:
                        Camera.counter-=SpdNum
                        if Camera.counter < 0:
                            Camera.counter = 0
                        Camera.cap.set(cv2.CAP_PROP_POS_FRAMES, Camera.counter)
                    else:
                        Camera.counter+=SpdNum
                        Camera.cap.set(cv2.CAP_PROP_POS_FRAMES, Camera.counter)
                else:
                    time.sleep(1/30)
            else:
                Camera.counter=0
            #↑ここまで
            yield cv2.imencode('.png', frame)[1].tobytes()

実施していることは、先ほどのフラグに応じて一時停止させたり動画の速度を速めたりしております。
早送り・巻き戻しの方法についてはこちらにまとめておりますので、よろしければこちらもどうぞ。
elsammit-beginnerblg.hatenablog.com

■動画早送り・巻き戻し機能追加(フロントエンド)

次にフロントエンドです。
こちらはjavascriptcss、htmlそれぞれ変更を加えております。
今回はjavascriptの一部のみ抜粋させていただきます。

まずflaskにてhtmlからjavascriptのコードを呼び出す際にはこちらのように、
scriptタグからstatic配下のファイルパスを指定すればOKです。

<script src="{{url_for('static', filename='js/script.js')}}"></script>

そして、script.jsにはPostリクエストを行うためのAjaxを記載いたしました。
スタート、ストップ等の動作に応じてそれぞれ定義しているのですが、
各関数はほぼ同じになっております。
一例として早送り押下時のコードを下記に載せます。

$( function() {
    $('#Speed').on('click',
    function() {
        var hostUrl= '/speed';
        $.ajax({
            url: hostUrl,
            type:'POST',
            timeout:3000,
        }).done(function(data) {
                          console.log("ok");
        }).fail(function(XMLHttpRequest, textStatus, errorThrown) {
                         console.log("error");
        })
    });
} );

内容としては、Speedと定義したidに対してボタン押下された際、
hostUrlで定義したパスに対してPOSTリクエストを行うのみになります。

■最後に

今見てて思ったのですが、なんかもっと効率よく書ける気がしてきました。。
改善版を再度上げられたらな!と思います。