speed-chager / app.py
soiz1's picture
Update app.py
4587291 verified
import gradio as gr
from pydub import AudioSegment
from moviepy.editor import VideoFileClip
import tempfile
import mimetypes
import os
import subprocess
import json
import moviepy.video.fx.all as vfx
# FFmpegの音声速度変更用のatempoフィルター生成
def build_atempo_filters(speed):
if not 0.5 <= speed <= 2.0:
factors = []
while speed > 2.0:
factors.append(2.0)
speed /= 2.0
while speed < 0.5:
factors.append(0.5)
speed /= 0.5
factors.append(speed)
return ",".join(f"atempo={round(f, 5)}" for f in factors)
else:
return f"atempo={round(speed, 5)}"
# FFmpegで動画の速度変更
def change_video_speed_ffmpeg(video_file, speed, export_format):
result = subprocess.run(
[
"ffprobe", "-v", "error",
"-select_streams", "a", "-show_entries", "stream=index",
"-of", "json", video_file
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=True
)
audio_info = json.loads(result.stdout)
has_audio = bool(audio_info.get("streams"))
format_ext = os.path.splitext(video_file)[-1].lstrip(".") if export_format == "元のまま" else export_format
output_file = tempfile.NamedTemporaryFile(suffix=f".{format_ext}", delete=False).name
setpts_filter = f"setpts={1/speed}*PTS"
atempo_filter = build_atempo_filters(speed)
if has_audio:
filter_complex = f"[0:v]{setpts_filter}[v];[0:a]{atempo_filter}[a]"
map_args = ["-map", "[v]", "-map", "[a]"]
codec_args = ["-c:v", "libx264", "-c:a", "aac"]
else:
filter_complex = f"[0:v]{setpts_filter}[v]"
map_args = ["-map", "[v]"]
codec_args = ["-c:v", "libx264"]
cmd = [
"ffmpeg", "-y", "-i", video_file,
"-filter_complex", filter_complex,
*map_args, *codec_args, output_file
]
subprocess.run(cmd, check=True)
return output_file
# FFmpegで音声の速度変更
def change_audio_speed_ffmpeg(audio_file, speed, export_format):
atempo_filter = build_atempo_filters(speed)
format_ext = os.path.splitext(audio_file)[-1].lstrip(".") if export_format == "元のまま" else export_format
output_file = tempfile.NamedTemporaryFile(suffix=f".{format_ext}", delete=False).name
cmd = [
"ffmpeg", "-y", "-i", audio_file,
"-filter:a", atempo_filter,
"-c:a", "aac" if format_ext in ["mp4", "mov"] else "libmp3lame",
output_file
]
subprocess.run(cmd, check=True)
return output_file
# Pydubで音声の速度変更
def change_audio_speed_pydub(audio_file, speed, export_format):
audio = AudioSegment.from_file(audio_file)
new_frame_rate = int(audio.frame_rate * speed)
faster_audio = audio._spawn(audio.raw_data, overrides={'frame_rate': new_frame_rate})
faster_audio = faster_audio.set_frame_rate(audio.frame_rate)
format_ext = os.path.splitext(audio_file)[-1].lstrip(".") if export_format == "元のまま" else export_format
with tempfile.NamedTemporaryFile(suffix=f".{format_ext}", delete=False) as tmpfile:
faster_audio.export(tmpfile.name, format=format_ext)
return tmpfile.name
# moviepyで動画の速度変更
def change_video_speed_moviepy(video_file, speed, export_format):
clip = VideoFileClip(video_file)
new_clip = clip.fx(vfx.speedx, speed)
format_ext = os.path.splitext(video_file)[-1].lstrip(".") if export_format == "元のまま" else export_format
output_file = tempfile.NamedTemporaryFile(suffix=f".{format_ext}", delete=False).name
new_clip.write_videofile(
output_file,
codec="libx264" if format_ext in ["mp4", "mov"] else None,
audio_codec="aac",
verbose=False,
logger=None
)
return output_file
# 全体のラッパー関数(処理方法の分岐)
def change_speed(file_path, speed, export_format, method):
mime, _ = mimetypes.guess_type(file_path)
print(speed)
if mime and mime.startswith("audio"):
if method == "FFmpeg":
return change_audio_speed_ffmpeg(file_path, speed, export_format)
else:
return change_audio_speed_pydub(file_path, speed, export_format)
elif mime and mime.startswith("video"):
if method == "FFmpeg":
return change_video_speed_ffmpeg(file_path, speed, export_format)
else:
return change_video_speed_moviepy(file_path, speed, export_format)
else:
raise gr.Error("対応していないファイル形式です。音声または動画をアップロードしてください。")
# Gradio UI
with gr.Blocks() as demo:
gr.Markdown("# 🎬 音声・動画の再生速度を細かく調整して変換")
gr.Markdown("""
| 項目 | Gradio(入力) | Pydub/MoviePy | FFmpeg(フィルター) |
| -------- | --------------------------------- | --------------------------------------------- | ----------------------------------- |
| 入力形式 | `Slider(step=0.0001)` → 小数点以下4桁以上 | Python `float` → `AudioSegment` / `speedx` | `float` → `atempo` / `setpts` |
| 表現精度(理論) | 小数点以下4桁程度 | 倍精度浮動小数点 → 最大小数点以下15桁 | 同左(内部処理は浮動小数点) |
| 実効分解能 | 0.0001単位 | 音声: サンプリングレート依存(約20μs)、動画: フレーム単位 | 音声: `atempo` (0.5–2.0連結対応)、動画: µs単位 |
| 出力方式 | `float` → `ChangeSpeed(speed)` | `AudioSegment.export()` / `write_videofile()` | `ffmpeg` CLI を直接呼び出し |
""")
with gr.Row():
with gr.Column():
file_input = gr.File(label="音声または動画をアップロード", file_types=[".mp3", ".wav", ".mp4", ".mov"])
speed_input = gr.Slider(
minimum=0.1, maximum=4.0, value=1.0, step=0.0001,
label="再生速度(例:0.75 = 25%遅く、1.25 = 25%速く)"
)
export_format_input = gr.Dropdown(
choices=["元のまま", "wav", "mp3", "mp4", "mov"],
value="元のまま",
label="出力フォーマット"
)
method_input = gr.Dropdown(
choices=["FFmpeg", "Pydub/MoviePy"],
value="FFmpeg",
label="処理方法"
)
btn = gr.Button("変換")
with gr.Column():
file_output = gr.File(label="変換後のファイルをダウンロード")
btn.click(fn=change_speed, inputs=[file_input, speed_input, export_format_input, method_input], outputs=file_output)
if __name__ == "__main__":
demo.launch()