「たくさんの動画ファイル、フレームレートも解像度もバラバラで管理しづらい…」
「特定のプロジェクトで使う動画の仕様を統一したい!」
そんな悩みを抱えていませんか?動画編集ソフトで一つ一つ設定を変更するのは大変な作業です。この記事では、強力なメディア処理ツールである ffmpeg とプログラミング言語 Python を組み合わせて、複数の動画ファイルのフレームレートと解像度を効率的に一括で統一する方法を、具体的なスクリプトと共に解説します。
動画プロジェクトやアーカイブ、オンラインでの共有など、様々な場面で動画の仕様を統一することにはメリットがあります。
手作業での変換は時間がかかりますが、Pythonスクリプトとffmpegを使えば、このプロセスを自動化できます。
以下のPythonスクリプトを、例えば convert_videos.py という名前で保存し、変換したい動画が入っているフォルダと同じ階層に置くか、スクリプト内でフォルダパスを正しく指定してください。
import os
import subprocess
import shutil
import json
# --- ☆設定項目☆ ---
# 変換したい動画ファイルが入っているフォルダの名前
INPUT_FOLDER = "source_videos"
# 変換後のファイル名に付ける接尾辞(元のファイル名に追加されます)
# 例: "my_video.mp4" -> "my_video_unified.mp4"
OUTPUT_SUFFIX = "_unified"
# 目標とするフレームレート (1秒あたりのコマ数)
TARGET_FPS = 30.0
# 目標とする動画の幅 (ピクセル単位)
TARGET_WIDTH = 1920 # 例: フルHD
# 目標とする動画の高さ (ピクセル単位)
TARGET_HEIGHT = 1080 # 例: フルHD
# 注意: 上記は横長動画(16:9)の例です。
# 縦長動画(9:16)の場合は TARGET_WIDTH = 1080, TARGET_HEIGHT = 1920 のように設定してください。
# 処理対象とする動画ファイルの拡張子 (タプル形式で指定)
VIDEO_EXTENSIONS = ('.mp4', '.mov', '.avi', '.mkv', '.flv', '.webm')
# 変換に成功した場合に、元のファイルを削除するかどうか
# Trueにすると削除します。非常に注意して使用してください!
# 安全のため、最初は False でテストすることを強く推奨します。
DELETE_ORIGINAL_ON_SUCCESS = False
# --- 設定項目ここまで ---
def check_ffmpeg_tools():
"""ffmpegとffprobeが利用可能か確認する"""
if shutil.which("ffmpeg") is None:
print("エラー: ffmpegが見つかりません。システムにインストールし、パスを通してください。")
return False
if shutil.which("ffprobe") is None:
print("エラー: ffprobeが見つかりません。システムにインストールし、パスを通してください。")
return False
return True
def get_video_properties_json(filepath):
"""ffprobeのJSON出力を使って動画の現在のプロパティを取得する"""
try:
command = [
'ffprobe', '-v', 'quiet', '-print_format', 'json',
'-show_streams', filepath
]
result = subprocess.run(command, capture_output=True, text=True, check=True, encoding='utf-8')
data = json.loads(result.stdout)
framerate, width, height = None, None, None
for stream in data.get('streams', []):
if stream.get('codec_type') == 'video': # ビデオストリームを探す
# フレームレートの取得 (avg_frame_rateまたはr_frame_rate)
rate_str = stream.get('avg_frame_rate', stream.get('r_frame_rate'))
if rate_str and '/' in rate_str: # "25/1"のような形式
num_str, den_str = rate_str.split('/')
if den_str != '0': # 0除算を避ける
num, den = int(num_str), int(den_str)
framerate = float(num) / den
elif rate_str: # "29.97"のような小数形式
try:
framerate = float(rate_str)
except ValueError:
pass # 変換できない場合はNoneのまま
width = stream.get('width') # 幅の取得
height = stream.get('height') # 高さの取得
# 最初のビデオストリームの情報が見つかればループを抜ける
if framerate is not None and width is not None and height is not None:
break
if framerate is None or width is None or height is None:
print(f" 警告: 動画情報(JSON)の取得失敗、またはビデオストリームが見つかりません。ファイル: {os.path.basename(filepath)}")
return None, None, None # 何か一つでも取得できない場合はNoneを返す
return framerate, int(width), int(height) # 整数で返す
except subprocess.CalledProcessError as e:
print(f" ffprobe実行エラー ({os.path.basename(filepath)}): {e.stderr.strip() if e.stderr else 'N/A'}")
return None, None, None
except json.JSONDecodeError as e_json:
print(f" ffprobe JSONパースエラー ({os.path.basename(filepath)}): {e_json}")
if 'result' in locals() and hasattr(result, 'stdout'):
print(f" ffprobe RAW出力: {result.stdout[:500]}...") # 長い出力は一部表示
return None, None, None
except Exception as e:
print(f" 動画情報取得中に予期せぬエラー ({os.path.basename(filepath)}): {type(e).__name__} - {e}")
return None, None, None
def convert_video_and_optionally_delete(input_path, output_path):
"""動画を指定のFPSと解像度に変換し、オプションで元ファイルを削除する"""
print(f" 変換実行中: {os.path.basename(input_path)} -> {TARGET_WIDTH}x{TARGET_HEIGHT}, {TARGET_FPS:.2f} FPS")
try:
# ffmpegのビデオフィルター設定
# scaleフィルターで解像度を変更し、fpsフィルターでフレームレートを変更
video_filters = [
f"scale={TARGET_WIDTH}:{TARGET_HEIGHT}",
f"fps=fps={TARGET_FPS}"
]
# ffmpegコマンドの組み立て
ffmpeg_command = [
'ffmpeg',
'-i', input_path, # 入力ファイル
'-vf', ",".join(video_filters), # 上記で定義したビデオフィルターを適用
'-c:v', 'libx264', # ビデオコーデック: H.264 (高品質で広く互換性あり)
'-preset', 'medium', # エンコード速度と品質のバランス (ultrafast, fast, medium, slow, veryslow)
'-crf', '23', # 品質設定 (Constant Rate Factor): 0-51の範囲。低いほど高品質・高ファイルサイズ。18-28が一般的。
'-c:a', 'aac', # オーディオコーデック: AAC (広く互換性あり)
'-b:a', '192k', # オーディオビットレート: 192kbps (標準的な品質)
'-y', # 出力ファイルが既に存在する場合、確認なしで上書き
output_path # 出力ファイルパス
]
# print(f" 実行コマンド: {' '.join(ffmpeg_command)}") # デバッグ用にコマンドを表示したい場合
# ffmpegコマンドを実行
result = subprocess.run(ffmpeg_command, capture_output=True, text=True, check=True, encoding='utf-8')
# print(f" ffmpegログ (stderr):\n{result.stderr.strip() if result.stderr else 'N/A'}") # 詳細ログを見たい場合
# 変換成功の確認: 出力ファイルが存在し、かつファイルサイズが0より大きい
if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
print(f" 変換成功: {os.path.basename(output_path)} が作成されました。")
# 元ファイルを削除するオプションが有効な場合
if DELETE_ORIGINAL_ON_SUCCESS:
try:
os.remove(input_path)
print(f" 元ファイル '{os.path.basename(input_path)}' を削除しました。")
except OSError as e_remove:
print(f" 警告: 元ファイル '{os.path.basename(input_path)}' の削除に失敗しました: {e_remove}")
return True
else:
# 変換は成功したように見えても、出力ファイルがおかしい場合
print(f" 変換後処理エラー: 出力ファイルが見つからないか、サイズが0です: {os.path.basename(output_path)}")
if os.path.exists(output_path) and os.path.getsize(output_path) == 0:
# サイズ0のファイルは問題なので削除
os.remove(output_path)
print(f" サイズ0の出力ファイル '{os.path.basename(output_path)}' を削除しました。")
return False
except subprocess.CalledProcessError as e: # ffmpegがエラーコードを返した場合
print(f" ffmpeg変換エラーが発生しました ({os.path.basename(input_path)}):")
print(f" ffmpegからのエラーメッセージ (STDERR):\n{e.stderr.strip() if e.stderr else 'N/A'}")
return False
except Exception as e: # その他のPythonエラー
print(f" 変換処理中に予期せぬエラーが発生しました ({os.path.basename(input_path)}): {type(e).__name__} - {e}")
return False
def main():
"""メイン処理"""
print("動画ファイルのフレームレートと解像度を統一するバッチ処理を開始します。")
print(f"目標設定: 解像度={TARGET_WIDTH}x{TARGET_HEIGHT}, フレームレート={TARGET_FPS:.2f} FPS")
if DELETE_ORIGINAL_ON_SUCCESS:
print("\n警告: 変換に成功した元の動画ファイルは削除されます!")
print("この操作は元に戻せません。続行する前に必ずバックアップを確認してください。")
# 自動実行の場合は以下の確認をコメントアウトまたは削除
# confirmation = input("本当に元のファイルを削除してもよろしいですか? (yes と入力して続行): ")
# if confirmation.lower() != 'yes':
# print("処理を中止しました。")
# return
else:
print("\n情報: 元の動画ファイルは削除されません。")
# ffmpegとffprobeが利用可能かチェック
if not check_ffmpeg_tools():
return
# 入力フォルダが存在するかチェック
if not os.path.isdir(INPUT_FOLDER):
print(f"エラー: 入力フォルダ '{INPUT_FOLDER}' が見つかりません。スクリプト内のINPUT_FOLDERの設定を確認してください。")
return
print(f"\n'{INPUT_FOLDER}' フォルダ内の動画を処理します...")
# カウンター初期化
processed_count = 0
converted_count = 0
skipped_count = 0
error_count = 0
# 処理対象のファイルリストを先に取得 (ループ中にファイルを削除する場合があるため)
# 既に変換済みのファイル(名前にサフィックスが付いているもの)は処理対象から除外
files_in_folder = os.listdir(INPUT_FOLDER)
filenames_to_process = []
for f in files_in_folder:
filepath = os.path.join(INPUT_FOLDER, f)
if os.path.isfile(filepath) and \
f.lower().endswith(VIDEO_EXTENSIONS) and \
not (OUTPUT_SUFFIX in f and os.path.splitext(f.replace(OUTPUT_SUFFIX, ''))[0] + os.path.splitext(f)[1] in files_in_folder):
filenames_to_process.append(f)
if not filenames_to_process:
print("処理対象の動画ファイルが見つかりませんでした。")
print(f"'{INPUT_FOLDER}' フォルダに '{VIDEO_EXTENSIONS}' の拡張子の動画があるか、")
print("または既に全ての名前にサフィックスが付いているか確認してください。")
return
# 各ファイルを処理
for filename in filenames_to_process:
input_filepath = os.path.join(INPUT_FOLDER, filename)
print(f"\n--- ファイル処理中: {filename} ---")
processed_count += 1
# 動画の現在のプロパティを取得
current_fps, current_width, current_height = get_video_properties_json(input_filepath)
if current_fps is None or current_width is None or current_height is None:
print(f" 動画プロパティの取得に失敗したため、このファイルはスキップします。")
error_count += 1
continue # 次のファイルへ
print(f" 現在の設定: 解像度={current_width}x{current_height}, フレームレート={current_fps:.2f} FPS")
# 目標設定と一致するかどうかをチェック
is_fps_target = abs(current_fps - TARGET_FPS) < 0.01 # フレームレート (浮動小数点の比較のため許容誤差)
is_resolution_target = (current_width == TARGET_WIDTH and current_height == TARGET_HEIGHT) # 解像度
if is_fps_target and is_resolution_target:
print(f" 既に目標設定 ({TARGET_WIDTH}x{TARGET_HEIGHT}, {TARGET_FPS:.2f} FPS) と一致しています。スキップします。")
skipped_count += 1
else:
# 出力ファイル名の生成 (例: video.mp4 -> video_unified.mp4)
base, ext = os.path.splitext(filename)
output_filename = f"{base}{OUTPUT_SUFFIX}{ext}"
output_filepath = os.path.join(INPUT_FOLDER, output_filename) # 元と同じフォルダに出力
# 万が一、入力ファイルと出力ファイル名が同じになってしまう事故を防ぐ
if input_filepath == output_filepath:
print(f" 致命的エラー: 入力ファイルと出力ファイル名が同じです ({input_filepath})。")
print(f" OUTPUT_SUFFIX ('{OUTPUT_SUFFIX}') の設定を確認してください。このファイルはスキップします。")
error_count +=1
continue
# 動画変換処理の実行
if convert_video_and_optionally_delete(input_filepath, output_filepath):
converted_count += 1
else:
error_count += 1
print("--- ファイル処理終了 ---")
# 最終結果の表示
print("\n--- 全ての処理が完了しました ---")
print(f"処理対象ファイル総数: {processed_count}")
print(f"変換成功ファイル数: {converted_count}")
print(f"スキップファイル数 (既に目標設定通りだったもの): {skipped_count}")
print(f"エラー発生ファイル数: {error_count}")
if __name__ == "__main__":
main()
content_copydownloadUse code with caution.Python
Pythonとffmpegを組み合わせることで、多数の動画ファイルのフレームレートと解像度を効率的に統一する作業を自動化できます。手作業による繰り返しの手間を省き、動画素材の管理や利用をよりスムーズに行いましょう。