目次
動画ファイルのフレームレートと解像度を統一!Python + ffmpegで簡単バッチ処理

「たくさんの動画ファイル、フレームレートも解像度もバラバラで管理しづらい…」
「特定のプロジェクトで使う動画の仕様を統一したい!」
そんな悩みを抱えていませんか?動画編集ソフトで一つ一つ設定を変更するのは大変な作業です。この記事では、強力なメディア処理ツールである ffmpeg とプログラミング言語 Python を組み合わせて、複数の動画ファイルのフレームレートと解像度を効率的に一括で統一する方法を、具体的なスクリプトと共に解説します。
この記事でできること
- フォルダ内にある複数の動画ファイルのフレームレートを指定した値に統一する。
- フォルダ内にある複数の動画ファイルの解像度を指定した値に統一する。
- これらの処理をバッチ(一括)で自動的に行う。
- 変換後のファイルは元ファイルと同じフォルダに、識別しやすい名前で保存される(元ファイル削除オプションも有り)。
なぜフレームレートと解像度の統一が必要?
動画プロジェクトやアーカイブ、オンラインでの共有など、様々な場面で動画の仕様を統一することにはメリットがあります。
- 再生環境の互換性向上: 特定のデバイスやプラットフォームで最適な再生品質を確保できます。
- 編集作業の効率化: 編集ソフトに取り込む際に、仕様が統一されていると処理がスムーズに進みます。
- ファイル管理の容易化: アーカイブする際に、仕様が揃っていると管理しやすくなります。
- 一貫した視聴体験の提供: 複数の動画を連続して見せる場合、仕様が統一されていると視聴者に違和感を与えにくいです。
解決策: Pythonとffmpegで自動変換
手作業での変換は時間がかかりますが、Pythonスクリプトとffmpegを使えば、このプロセスを自動化できます。
準備するもの
- ffmpeg と ffprobe:
これらは動画・音声のエンコード、デコード、変換などを行うための非常に強力なコマンドラインツールです。お使いのPCにインストールし、コマンドプロンプトやターミナルから直接実行できるように「パスを通す」設定が必要です。- ffmpeg公式サイト: https://ffmpeg.org/
- インストール方法はOSによって異なります。「ffmpeg インストール [お使いのOS名]」などで検索してください。
- Python:
スクリプトを実行するためにPythonの実行環境が必要です。- Python公式サイト: https://www.python.org/
- 変換用Pythonスクリプト:
後述するコードをテキストエディタにコピーし、convert_videos.pyのような名前でPCに保存します。
処理の流れ
- 準備: 変換したい動画ファイルをPC上の一つのフォルダ(例: source_videos フォルダ)にまとめます。
- スクリプト設定: Pythonスクリプト内の設定項目(目標フレームレート、目標解像度など)を編集します。
- 実行: コマンドプロンプトやターミナルからPythonスクリプトを実行します。
- 確認: 処理が完了すると、元のフォルダ内に変換された動画ファイルが指定した名前で作成されます。
コピペで使える!動画統一Pythonスクリプト
以下の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
スクリプトの使い方
- 動画の準備:
変換したい動画ファイルを、PC上の任意のフォルダに入れます。スクリプト冒頭の INPUT_FOLDER = “source_videos” の “source_videos” 部分を、そのフォルダ名に合わせて書き換えてください(または、スクリプトと同じ階層に source_videos というフォルダを作成し、そこに動画を入れます)。 - 目標設定の編集:
スクリプト冒頭の — ☆設定項目☆ — セクションで、以下の値を自分の目的に合わせて変更します。- TARGET_FPS: 目標とするフレームレート(例: 30.0)。
- TARGET_WIDTH: 目標とする動画の幅(例: 1920)。
- TARGET_HEIGHT: 目標とする動画の高さ(例: 1080)。
- OUTPUT_SUFFIX: 変換後のファイル名に付加される文字列(例: _unified)。
- DELETE_ORIGINAL_ON_SUCCESS: True にすると、変換が成功した元のファイルが削除されます。最初は False のままにして、動作をよく確認することを強く推奨します。
- スクリプトの実行:
コマンドプロンプト(Windowsの場合)またはターミナル(macOSやLinuxの場合)を開きます。
cd コマンドで、保存したPythonスクリプト (convert_videos.py) があるディレクトリに移動します。
そして、python convert_videos.py と入力してEnterキーを押し、スクリプトを実行します。 - 結果の確認:
処理が完了すると、INPUT_FOLDER で指定したフォルダ内に、名前に OUTPUT_SUFFIX が追加された変換済みの動画ファイルが作成されているはずです。
(例: my_clip.mp4 が変換された場合、my_clip_unified.mp4 ができる)
元のファイルが削除される設定にした場合は、元ファイルがなくなっていることも確認してください。
重要な注意点
- 元ファイルの削除オプション (DELETE_ORIGINAL_ON_SUCCESS) は非常に強力です。 True に設定すると、変換が成功したとプログラムが判断した場合、元のファイルは完全に削除され、通常の方法では元に戻せません。必ず、このオプションを有効にする前に、重要なファイルはバックアップを取るか、少数のテストファイルで動作を十分に確認してください。
- ffmpegの変換処理は、動画の長さやPCのスペックによっては時間がかかることがあります。
- このスクリプトは基本的な変換を行います。より高度なエンコード設定(特定のビットレート指定、2パスエンコードなど)が必要な場合は、ffmpeg_command のリストを適宜カスタマイズしてください。
まとめ
Pythonとffmpegを組み合わせることで、多数の動画ファイルのフレームレートと解像度を効率的に統一する作業を自動化できます。手作業による繰り返しの手間を省き、動画素材の管理や利用をよりスムーズに行いましょう。