LoginSignup
3
2

Python用 高速動画入出力クラス FFStream

Last updated at Posted at 2024-02-03

はじめに

FFStreamはPython上で動画を高速に入出力する機能をまとめたクラスです。
主に動画から画像を取得して AI等で処理した結果を再び動画にする目的で使います。
最初はOpenCVやPyAV等を使っていましたが、動画の処理がボトルネックになる(AIの処理より動画の処理に時間がかかる)ケースが出てきたのでffmpeg-pythonを使う方法を試みたところ大幅な改善がみられたので、クラス化しました。

特徴

  • 処理が速い
  • 動画の音声をコピーできる
  • 画質やファイルサイズの調整ができる
  • PyTorchで扱いやすい Tensor[C,H,W]形式のRGBフレームに対応

benchi_13700k.jpg

ダウンロード

使い方

ffstream.zipを解凍してできる'ffstream'フォルダをプロジェクトへコピー

ffstream_sample.py
# ffstream.pyの使用例
# 入力動画からRGB画像を受け取り 簡単な処理を加えて 音声付きの動画を作成します
import torch
from tqdm import tqdm
from ffstream.ffstream import FFStream

input_path  = 'input.mp4'                   # 入力する 動画ファイル
output_path = 'output.mp4'                  # 作成する 動画ファイル

device = torch.device('cuda')               # ビデオメモリを使って CUDAで処理する(早い)
#device = torch.device('cpu')               # メインメモリを使って CPU で処理する(遅い)

# 入力パス, 出力パス, 画質20, ピクセルフォーマットyuv420p
# 音声がある場合はコピーする, フレーム処理用のデバイスを指定する
ff = FFStream(input_path, output_path, crf=20, pix_fmt='yuv420p',
              copy_audio_stream=True, device=device)

w = ff.width        # 入力動画の 画像サイズ
h = ff.height       # 入力動画の 画像サイズ
frames = ff.frames  # 入力動画の 総フレーム数

bar = tqdm(total=frames, dynamic_ncols=True)
while True:
    # FFmpegからRGBフレームを受け取る
    frame_rgb = ff.get_rgb()
    if (frame_rgb == None) :
        break                               # 動画の最後に達した

    # RGBの処理
    frame_rgb[2]=0                          # 青成分を0にする  [C,H,W]

    # FFmpegの出力ストリームへRGBフレームを送る
    ff.put_rgb(frame_rgb)

    bar.update(1)

bar.close()
ff.close()

リファレンス

class FFStream() v1.3.0

コンストラクタ

項目 説明
書式 ff = FFStream(’input.mp4’, ’output.mp4’...)
引数 下記
戻り値 なし
使用例 下記
引数
    input_path:str=None		# 入力パス、省略可
    
    output_path:str=None	# 出力パス、省略可
    						# 入力パスと出力パスの両方または一方を指定して使う
          
	mbps:int=None			# 画質指定(ビットレート) 10なら10Mbpsでエンコード
 
	crf:float=None			# 画質指定(品質) 20が標準
 							# 数値が低いほど 高画質でファイルサイズが大きくなる
        
	pix_fmt:str='yuv420p'	# ピクセルフォーマット
 							# 主に 動画なら'yuv420p', jpgなら'rgb24', pngなら'rgba'
        
	copy_audio_stream:bool=False
 							# 入力動画の音声を出力動画へコピーする場合True
        					# 音声がない場合はコピーしません
             
	ss:str=None				# 開始位置
 							# '60'				60秒
        					# f'{1000/60}'		60fpsの動画の場合に1000フレーム当たり
             				# '1:30:00'			1時間30分0秒
                 
	ow:int=None				# 出力画像サイズを指定する
 							# 入力と異なる画像サイスにする場合や
        					# 入力を使わず出力だけする場合に指定する
             
	oh:int=None				# 出力画像サイズを指定する
 
	ofps:float=None			# 出力フレームレートを指定する
 							# 入力と異なる場合や 入力を使わず出力だけする場合に指定する
        					# 29.97の場合は 30000/1001
             
	image_scale:float=1		# 出力画像サイズの倍率
 
	fps_scale:float=1		# 出力フレームレートの倍率
 
	no_probe:bool=False		# プローブを使わない
 							# 画像サイズ等の情報を取得する処理は遅い
        					# 連番動画などを大量に読み込む場合Trueにする
             				# 代わりに画像サイズをow,ohで指定する
                 
	get_info:bool=False		# 入力ファイルの情報を取得するだけで ffmpegを実行しない
 
	device:str=None			# フレーム処理をビデオメモリにするかメインメモリにするか指定する
 							# 省略した場合は メインメモリ
        
	input_options:str=None	# ffmpegの入力ストリームへ設定するオプション文字列
 							# ダブルクォーテーションは任意
        					# ハードウエアデコーダーを使う例 "-hwaccel cuda"
             
    output_options:str=None	# ffmpegの出力ストリームへ設定するオプション文字列
 							# ダブルクォーテーションは任意
        					# プロファイルを指定する例 "-profile:v baseline -level:v 4.1"
             
    mt_buffer:int=0			# 1以上の場合 転送処理を別スレッドで行う
    						# その時使うバッファ数を指定する
          					# フレームの入出力は get_rgb(), put_rgb()を使う
          					# 最後は必ず def close(self):を使う               
          					# 0の場合はメインスレッドで転送する

使用例

動画の入出力
from ffstream.ffstream import FFStream
input_path  = 'input.mp4'                   # 入力する 動画ファイル
output_path = 'output.mp4'                  # 作成する 動画ファイル
ff = FFStream(input_path, output_path)
for i in range(ff.frames):
    frame_rgb = ff.get_rgb()
    ff.put_rgb(frame_rgb)
    print(f'{i+1} / {ff.frames}')
ff.close()
動画の入力のみ
from ffstream.ffstream import FFStream
input_path  = 'input.mp4'                   # 入力する 動画ファイル
ff = FFStream(input_path, None)
frame_rgb = ff.get_rgb()
ff.close()
動画の出力のみ
from ffstream.ffstream import FFStream
import torch
w:int       = 1920
h:int       = 1080
fps:float   = 30000/1001
frame_rgb   = torch.ones(3,h,w)
output_path = 'output.mp4'                  # 作成する 動画ファイル
ff = FFStream(None, output_path, ow=w, oh=h, ofps=fps)
ff.put_rgb(frame_rgb)
ff.close()
動画や画像の情報だけ取得する
from ffstream.ffstream import FFStream
input_path  = 'input.mp4'                   # 入力する 動画ファイル
ff = FFStream(input_path, None, get_info=True)
w = ff.width        # 入力動画の 画像サイズ
h = ff.height       # 入力動画の 画像サイズ
frames = ff.frames  # 入力動画の 総フレーム数
print(f'{w}x{h}, {frames}frames')
画像を読み込む
from ffstream.ffstream import FFStream
input_path  = 'input.jpg'                   # 入力する 画像ファイル
#input_path = 'input.png'                   # 入力する 画像ファイル
ff = FFStream(input_path, None, pix_fmt='rgb24')
frame_rgb = ff.get_rgb()
ff.close()
アルファチャンネル付きpng画像を読み込む
from ffstream.ffstream import FFStream
import torch
input_path  = 'input.png'               # 入力する 画像ファイル
ff = FFStream(input_path, None, pix_fmt='rgba')
w, h = ff.width, ff.height              # 画像サイズ
frame_bytes = ff.recv_frame()           # RGBARGBARGBARGBA
img_src     = torch.frombuffer(frame_bytes, dtype=torch.uint8)
img_src     = img_src.cuda().reshape(h,w,4)
img_src     = img_src.permute(2, 0, 1)  # 配列を整形 [C,H,W] RRRRGGGGBBBBAAAA
frame_rgb   = img_src[0:3]              # RGBフレーム
frame_alpha = img_src[3:4]              # アルファチャンネル
ff.close()

recv_frame()

ローレベル入力。
FFmpegの入力ストリームから受け取ったフレームを返す。
フレームが送られてくるまで 処理は中断する。

項目 説明
書式 frame_bytes = recv_frame()
引数 なし
戻り値 受け取ったフレーム(1次元のByte配列)
動画の最後まで達した場合はNoneを返す

send_frame(bytes)

ローレベル出力。
FFmpegの出力ストリームへフレームを送る。

項目 説明
書式 send_frame(bytes)
引数 - bytes
送るフレーム(1次元のByte配列)
戻り値 なし

close()

起動しているFFmpegのプロセスを終了する。

項目 説明
書式 close()
引数 なし
戻り値 なし

get_fps()->float:

入力ストリームのフレームレートを float型で返す。

項目 説明
書式 fps = get_fps()
引数 なし
戻り値 フレームレート(float型)

get_rgb()->Tensor

入力ストリームからRGBフレームを取得する。
yuv420pまたは rgb24を受け取って RGBフレーム(Tensor[C,H,W])へ変換する。
動画の最後に達したら Noneを返す。

項目 説明
書式 frame_rgb = get_rgb()
引数 なし
戻り値 RGBフレーム(Tensor[C,H,W])
動画の最後まで達した場合はNoneを返す

put_rgb(frame_rgb:Tensor)

出力ストリームへRGBフレームを送る。
RGBフレーム(Tensor[C,H,W])をyuv420pまたは rgb24へ変換する。

項目 説明
書式 put_rgb(frame_rgb)
引数 - frame_rgb:Tensor
RGBフレーム(Tensor[C,H,W])
戻り値 なし

そのほか

  • yuv420pのストリーム用 入出力関数
    • get_yuv420_to_rgb()->Tensor
    • put_rgb_to_yuv420(frame_rgb:Tensor)
  • rgb24のストリーム用 入出力関数
    • get_rgb24_to_rgb()->Tensor
    • put_rgb_to_rgb24(frame_rgb:Tensor)
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2