import gradio as gr import ffmpeg import os import tempfile import re import shutil def sanitize_filename(name): name = os.path.splitext(os.path.basename(name))[0] return re.sub(r'[^一-龥\w\-_.]', '_', name) def compress_video(file, resize_mode, resize_value, mute, audio_codec, audio_bitrate, audio_rate, audio_channels, video_codec, video_bitrate, framerate, pix_fmt): if not file: return None, "ファイルが選択されていません" input_path = file.name safe_name = sanitize_filename(input_path) SAVE_DIR = os.path.join(os.getcwd(), "compressed_videos") os.makedirs(SAVE_DIR, exist_ok=True) output_path = os.path.join(SAVE_DIR, f"{safe_name}_compressed.mp4") try: probe = ffmpeg.probe(input_path) video_stream = next((s for s in probe['streams'] if s['codec_type'] == 'video'), None) if not video_stream: return None, "映像ストリームが見つかりません" width = int(video_stream['width']) height = int(video_stream['height']) # リサイズ設定 resize_args = {} if resize_mode == "パーセンテージ": try: percent = float(resize_value.strip('%')) / 100.0 assert 0 < percent <= 1 new_width = int(width * percent) new_height = int(height * percent) resize_args = {'vf': f'scale={new_width}:{new_height}'} except Exception: return None, "リサイズ値が無効です。例: 80%" elif resize_mode == "ピクセル指定": match = re.match(r"(\d+)[xX](\d+)", resize_value) if not match: return None, "リサイズ値が無効です。例: 1280x720" new_width, new_height = map(int, match.groups()) resize_args = {'vf': f'scale={new_width}:{new_height}'} # 音声設定 if mute: audio_args = {'an': True} else: audio_args = {} if audio_codec: audio_args['c:a'] = audio_codec if audio_bitrate: audio_args['b:a'] = audio_bitrate if audio_rate: audio_args['ar'] = audio_rate if audio_channels: audio_args['ac'] = audio_channels # 映像設定 video_args = { 'c:v': video_codec, 'crf': 18, 'preset': 'slow', 'r': framerate, 'pix_fmt': pix_fmt } if video_bitrate: video_args['b:v'] = video_bitrate stream = ffmpeg.input(input_path) stream = ffmpeg.output( stream, output_path, movflags='+faststart', **resize_args, **audio_args, **video_args ) ffmpeg.run(stream, overwrite_output=True) return (output_path, None) if os.path.exists(output_path) else (None, "圧縮に失敗しました") except Exception as e: return None, f"エラーが発生しました: {str(e)}" with gr.Blocks() as demo: gr.Markdown("## 🎬 高画質を維持した動画圧縮ツール (カスタム設定付き)") with gr.Row(): with gr.Column(): video_input = gr.File(label="動画ファイルを選択", file_types=[".mp4", ".mov", ".mkv", ".avi"]) resize_mode = gr.Radio(choices=["パーセンテージ", "ピクセル指定"], value="パーセンテージ", label="リサイズ方法") resize_value = gr.Textbox(label="リサイズ値(例: 80% または 1280x720)", value="100%") mute = gr.Checkbox(label="音声を消す(ミュート)", value=False) gr.Markdown("### 音声設定") audio_codec = gr.Textbox(label="音声コーデック (-c:a)", value="aac") audio_bitrate = gr.Textbox(label="音声ビットレート (-b:a)", value="128k") audio_rate = gr.Textbox(label="サンプリングレート (-ar)", value="44100") audio_channels = gr.Textbox(label="チャンネル数 (-ac)", value="2") gr.Markdown("### 映像設定") video_codec = gr.Textbox(label="映像コーデック (-c:v)", value="libx264") video_bitrate = gr.Textbox(label="映像ビットレート (-b:v、省略可)", value="") framerate = gr.Textbox(label="フレームレート (-r)", value="30") pix_fmt = gr.Textbox(label="ピクセルフォーマット (-pix_fmt)", value="yuv420p") compress_btn = gr.Button("圧縮開始") with gr.Column(): result_file = gr.File(label="圧縮された動画ファイル(ダウンロード)") error_output = gr.Textbox(label="エラーメッセージ", lines=2) compress_btn.click( compress_video, inputs=[video_input, resize_mode, resize_value, mute, audio_codec, audio_bitrate, audio_rate, audio_channels, video_codec, video_bitrate, framerate, pix_fmt], outputs=[result_file, error_output] ) if __name__ == "__main__": demo.launch()