があらんどう

伽藍洞です。

MENU

pythonでシンプルなシンプルなシンプルなAVIプレイヤー

AVI形式の動画をループ再生するだけのプレイヤー
将来的には拡張して実験に使いたいけどとりあえずは動画再生するだけものすごくシンプルなもの。
以前記事にしたUSBカメラで録画した内容を再生するというような時に使える。
USBカメラを動作させる記事はこちら。
calciummm.hatenablog.com


コードは以下。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Simple AVI video player with OpenCV.
- Plays the specified file in a loop
- Press 'q' to quit
"""
import argparse
import sys
import cv2

def main():
    parser = argparse.ArgumentParser(description="Simple AVI video player (loops, press 'q' to quit)")
    parser.add_argument("video_path", help="Path to the AVI (or other supported) video file")
    parser.add_argument("--window", default="AVI Player", help="Window name (default: AVI Player)")
    args = parser.parse_args()

    cap = cv2.VideoCapture(args.video_path)
    if not cap.isOpened():
        print(f"Error: failed to open video: {args.video_path}", file=sys.stderr)
        sys.exit(1)

    # Try to get FPS to sync playback speed; fall back to ~30fps if unknown.
    fps = cap.get(cv2.CAP_PROP_FPS)
    if not fps or fps <= 1e-3:
        fps = 30.0
    delay_ms = max(1, int(round(1000.0 / fps)))

    try:
        while True:
            ret, frame = cap.read()
            if not ret:
                # Reached end: loop back to start
                cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
                continue

            cv2.imshow(args.window, frame)

            # waitKey returns -1 if no key pressed; lower 8 bits are the actual key code.
            key = cv2.waitKey(delay_ms) & 0xFF
            if key == ord('q'):
                break
    finally:
        cap.release()
        cv2.destroyAllWindows()

if __name__ == "__main__":
    main()


以下では使い方と行ごとにどのような意味を持っているかを観ていく。

使い方

上のコードをコピペして、例えばplayer.pyの名前で保存する。
ターミナルやプロンプトで保存した場所まで移動してから

python player.py sample.avi

と入力する。

ただし、今はプログラムと同じ場所に動画sample.aviが存在していることを前提にしている。
ウィンドウ名はデフォルトにしている。

上を実行するとウィンドウが開き、動画が再生される。
再生は繰り返されて無限ループ。
再生を終了したい場合はキーボードからqを入力。

もしもmp4ファイルで、開くウィンドウのタイトルをMY playerにしたい場合は

python player.py sample.mp4 --window "My Player"

と書けばよい。


行ごと意味を解説

#!/usr/bin/env python3

shebang(シバン)というらしい。よくわからんけど、Windowsでは不要。

# -*- coding: utf-8 -*-

文字コードの宣言。Python3の既定はUTF-8。日本語コメントいれるときはあった方が◯。

import argparse

コマンドライン引数を扱う標準ライブラリのインポート。argparse(アーグパース)。
argumentつまり引数のparse(解析)という意味。

import sys

標準入出力や 終了コード用の標準モジュールをインポート。

import cv2

OpenCVライブラリのインポート。

def main():

メイン関数の定義。



ここから4行は上でインポートしたargparseを使う部分。
ここからの内容は
qiita.com
がわかりやすかった。

parser = argparse.ArgumentParser(description="Simple AVI video player (loops, press 'q' to quit)")

引数を解析するための パーサを作成。かっこの中で description =説明文と書くと-h/--helpを表示したときに書いた説明文が出るので実行時に便利。

parser.add_argument("video_path", ...)

位置引数を1つ定義。動画ファイルへのパスを受け取るために必須。
今回は実行時にsample.aviを再生するとすれば、
python player.py sample.avi
と書くことになるけど、このsample.aviのpathが格納されるということだと思う。

parser.add_argument("--window", default="AVI Player", ...)

windowの後に文字列を渡すとウィンドウのタイトル変えられる。
指定がない場合は `"AVI Player"`。

args = parser.parse_args()

実際にコマンドラインを解析して、結果を `args` というオブジェクトに格納。`args.video_path` や `args.window` で取りだす。

cap = cv2.VideoCapture(args.video_path)

動画読み込みのハンドルを作る。ファイルを開く。
args.video_path引数から取り出した再生する動画ファイルのパス。

if not cap.isOpened():
         print(..., file=sys.stderr)
         sys.exit(1)

動画が正常に開けたかチェックする箇所。
何らかの事情で開なかったらif文の中に入り、
エラーメッセージを標準エラー出力にprintする。
標準エラー出力はエラーがあったときに表示するでデフォルトの出力。
今回は画面上のプロンプトへの出力になる。
また、終了コード1でプログラムを終了する。
成功時は0、失敗時は1に対応。

fps = cap.get(cv2.CAP_PROP_FPS)

動画再生のFPS(1秒あたりのフレーム数)を動画のメタデーアから取得する。
ファイルの種類やコーデックによっては 値が0 になる(取得できない)ことがある。

if not fps or fps <= 1e-3:
      fps = 30.0

FPSが取得できなかった(0あるいはNone)もしくは異常なほど小さい場合はif文の中に入りFPSを30(よくある値)に指定。

delay_ms = max(1, int(round(1000.0 / fps)))`

フレーム間の 待ち時間をミリ秒を計算する。
後に出てくるwaitKeyに渡す値。
短いほど速く再生されるため、最低でも1ms。標準的な30fpsなら約33msになる。

try:

pythonの例外処理のうちのtry-except文の一種。
こちらのサイトが参考になった。
note.nkmk.me
今回は43行目にfinallyと対になっている。
try文の途中で何らかのエラーが起きて終了せざる得なくても、最後の終了処理(後始末)がfinally以降の部分に書かれている。

while True:

無限ループ。ひたすら動画再生するため。42行目のbreakで抜ける。

ret, frame = cap.read()

openCVの次のフレームを読み込むコマンド
ret は成功したかの真偽値。
frameは読み込んだ画像(NumPy配列/BGR色順)。
www.shangtian.tokyo

if not ret:

retはフレームが読めたがどうかの真偽値なので、フレームが動画が最後まで再生されて読めなかったなどの理由で偽になった場合にif文の中に入る。

cap.set(cv2.CAP_PROP_POS_FRAMES, 0)`

フレームが読めなかったら再生位置を先頭に戻す。
先頭フレームは0に対応。

continue

continue文。
42行目のbreakはループを途中で中断してループを抜けるが、continue文は書いたところでループの残りを飛ばして最初に戻り、次の集会に入る。
ループをもう一回始めたいときに使う。

cv2.imshow(args.window, frame)

pythonのopenCVライブラリのフレームのウィンドウへの表示をするコマンド。
第一引数のarg.windowはwindowの名前になる。
つまり、動画再生するにあたって表示されるウィンドウの名前がこの値になる。
上で書いたargparseで指定している部分なのでデフォルトではAVI Playerと表示される。もしもここに別の値が入ると別ウィンドウとしてフレームが表示されるが、同じ名前ならば上書きされるのでフレームが順次表示されることになる。
第二引数は七行前にret, frame = cap.read()にて取得した次に表示するフレーム。

key = cv2.waitKey(delay_ms) & 0xFF`

この箇所は大きく二つの構造からなる。
cv2.waitKey(delay_ms) はdelay_msの時間だけキーボードからのキー入力を待つというコマンド。
delay_msは12行前で再生速度のFPS(1秒あたりのフレーム数)から計算した。
標準的な30FPSならば33msになる。
この時間が再生の間隔になる。
この待ち時間にキーボードに何かしらの入力があれば押されたキーのASCIIコードが返り値になる。
一方で何も押されなかったら-1が返り値。

& 0xFFの部分はcv2.waitKey(delay_ms)の返り値の下位8bitを抜き出すという意味。
これはcv2.waitKey(delay_ms)の返り値が通常のASCIIコードのビット値である8bitを超えた返り値を出力することがあるための対策。
下位8bitだけ抜き出す、つまり入力されたASCIIコードだけを取り出している。

if key == ord('q'):
     break

ord関数は1文字の文字列を引数にとって、その文字に対応するASCIIコードを返す。
そのため先ほど押されたキーがq(動画再生終了のコマンド)と一致しているかチェック。
一致していたらif文の中に入りbreakで無限ループから脱出し、再生を終了する。

finally:

前述の通りtry に対応する。エラーがあってもなくても最後に必ず実行される。

cap.release()

18行目にてcap = cv2.VideoCapture(args.video_path)として動画ファイルを開いた。この動画ファイル(あるいはカメラデバイス)を開放する。ファイルロックを外す意味もある。

cv2.destroyAllWindows()

OpenCVが作ったウィンドウをすべて閉じる。ここまでがfinallyの内容。

if __name__ == "__main__":

このファイルが直接実行されたときだけ、次の行を実行するためのお決まりの書き方
別ファイルからインポートされた場合はmain()を呼ばない(ライブラリ的に使える)。

main()

ここからプログラムが実際に開始。上で定義した `main` 関数を呼んでいる。