import argparse
import json
import os
import platform
import subprocess
from pathlib import Path
def get_ffmpeg_path():
"""FFmpegの実行ファイルのパスを取得する"""
# デフォルトのFFmpegパス
if platform.system() == "Windows":
# Windowsの場合、一般的なインストール場所を確認
common_paths = [
r"C:\Program Files\FFmpeg\bin\ffmpeg.exe",
r"C:\FFmpeg\bin\ffmpeg.exe",
os.path.join(
os.getenv("USERPROFILE"), "Downloads", "ffmpeg", "bin", "ffmpeg.exe"
),
]
for path in common_paths:
if os.path.exists(path):
return path
return "ffmpeg" # システムパスに設定されている場合はそのまま'ffmpeg'を返す
def get_ffprobe_path(ffmpeg_path: str = None) -> str:
"""FFprobeの実行ファイルのパスを取得する"""
if ffmpeg_path:
# FFmpegのパスからFFprobeのパスを推測
ffprobe_path = ffmpeg_path.replace("ffmpeg", "ffprobe")
if os.path.exists(ffprobe_path):
return ffprobe_path
return "ffprobe" # システムパスに設定されている場合
def get_video_duration(input_path: str, ffmpeg_path: str = None) -> float:
"""動画の長さを秒単位で取得"""
ffprobe_path = get_ffprobe_path(ffmpeg_path)
# FFprobeの存在確認
try:
subprocess.run([ffprobe_path, "-version"], capture_output=True, check=True)
except (subprocess.SubprocessError, FileNotFoundError):
raise RuntimeError(
"FFprobeが見つかりません。FFmpegが正しくインストールされているか確認してください。"
)
command = [
ffprobe_path,
"-v",
"error",
"-show_entries",
"format=duration",
"-of",
"json",
input_path,
]
try:
result = subprocess.run(command, capture_output=True, text=True, check=True)
info = json.loads(result.stdout)
if "format" not in info or "duration" not in info["format"]:
raise ValueError("動画の長さ情報が取得できません")
return float(info["format"]["duration"])
except subprocess.CalledProcessError as e:
print(f"FFprobeエラー出力: {e.stderr}")
raise RuntimeError(f"動画情報の取得に失敗しました: {e.stderr}")
except json.JSONDecodeError:
raise RuntimeError("動画情報のJSON解析に失敗しました")
def convert_video_fps(
input_path: str,
output_path: str,
target_fps: int = 30,
speed: float = 1.0,
target_duration: float = None,
ffmpeg_path: str = None,
) -> None:
"""
動画のフレームレートと再生速度を変換する関数
Parameters:
input_path (str): 入力動画のパス
output_path (str): 出力動画のパス
target_fps (int): 目標のフレームレート(デフォルト: 30)
speed (float): 再生速度の倍率(デフォルト: 1.0、1未満で遅く、1より大きいと速く)
target_duration (float): 目標とする動画の長さ(秒)。指定すると--speedは無視されます
ffmpeg_path (str): FFmpegの実行ファイルのパス(省略可)
"""
if not Path(input_path).exists():
raise FileNotFoundError(f"入力ファイルが見つかりません: {input_path}")
ffmpeg_cmd = ffmpeg_path if ffmpeg_path else get_ffmpeg_path()
# 目標時間が指定されている場合、speedを計算
if target_duration is not None:
original_duration = get_video_duration(input_path, ffmpeg_cmd)
speed = original_duration / target_duration
print(f"元の長さ: {original_duration:.1f}秒")
print(f"目標時間に合わせた速度: {speed:.2f}倍")
try:
# 速度調整用のフィルター設定
# setptsフィルターで速度を調整(1/speed)
speed_filter = f"setpts={1/speed}*PTS"
command = [
ffmpeg_cmd,
"-i",
input_path,
"-fps_mode",
"cfr",
"-filter:v",
f"{speed_filter},fps={target_fps}", # 速度調整とFPS変換を連結
"-c:v",
"libx264",
"-preset",
"medium",
"-crf",
"23",
"-filter:a",
f"atempo={speed}", # 音声の速度も調整
"-y",
output_path,
]
process = subprocess.run(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
if process.returncode != 0:
raise RuntimeError(f"FFmpegエラー: {process.stderr}")
print(f"変換が完了しました。出力ファイル: {output_path}")
print(f"設定: FPS={target_fps}, 速度={speed:.2f}倍")
except Exception as e:
print(f"エラーが発生しました: {str(e)}")
raise
def main():
parser = argparse.ArgumentParser(
description="動画のフレームレートと再生速度を変換します"
)
parser.add_argument("input", help="入力動画のパス")
parser.add_argument("output", help="出力動画のパス")
parser.add_argument(
"--fps", type=int, default=30, help="目標フレームレート(デフォルト: 30)"
)
parser.add_argument(
"--speed",
type=float,
default=1.0,
help="再生速度の倍率(デフォルト: 1.0、1未満で遅く、1より大きいと速く)",
)
parser.add_argument(
"--target-duration",
type=float,
help="目標とする動画の長さ(秒)。指定すると--speedは無視されます",
)
parser.add_argument("--ffmpeg-path", help="FFmpegの実行ファイルのパス(省略可)")
args = parser.parse_args()
try:
convert_video_fps(
args.input,
args.output,
args.fps,
args.speed,
args.target_duration,
args.ffmpeg_path,
)
except Exception as e:
print(f"プログラムの実行中にエラーが発生しました: {str(e)}")
if __name__ == "__main__":
main()