Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import tempfile | |
| import os | |
| import subprocess | |
| from imageio_ffmpeg import get_ffmpeg_exe | |
| def rotate_video(input_file, rotation): | |
| if not input_file: | |
| return None, None | |
| angles = { | |
| "90° clockwise": -90, | |
| "90° counter-clockwise": 90, | |
| "180°": 180, | |
| } | |
| angle = angles.get(rotation, -90) | |
| # Resolve input path from various possible formats (str path, dict with path/name, or file-like) | |
| input_path = None | |
| if isinstance(input_file, str) and os.path.exists(input_file): | |
| input_path = input_file | |
| elif isinstance(input_file, dict): | |
| # Try common keys first | |
| for k in ("path", "name", "video", "file"): | |
| v = input_file.get(k) | |
| if isinstance(v, str) and os.path.exists(v): | |
| input_path = v | |
| break | |
| # Fallback: scan all values for a valid path string | |
| if not input_path: | |
| for v in input_file.values(): | |
| if isinstance(v, str) and os.path.exists(v): | |
| input_path = v | |
| break | |
| elif hasattr(input_file, "name"): | |
| v = getattr(input_file, "name", None) | |
| if v and os.path.exists(v): | |
| input_path = v | |
| if not input_path: | |
| return None, None | |
| tmpdir = tempfile.mkdtemp(prefix="rotate_") | |
| out_path = os.path.join(tmpdir, "rotated.mp4") | |
| # Build FFmpeg transpose filter (no borders for multiples of 90) | |
| if angle == -90: # clockwise | |
| vf_filter = "transpose=1" | |
| elif angle == 90: # counter-clockwise | |
| vf_filter = "transpose=2" | |
| elif angle == 180 or angle == -180: | |
| vf_filter = "transpose=1,transpose=1" # 180° | |
| else: | |
| vf_filter = "transpose=1" # default safeguard | |
| ffmpeg_exe = get_ffmpeg_exe() | |
| base_cmd = [ | |
| ffmpeg_exe, | |
| "-y", | |
| "-i", input_path, | |
| "-vf", vf_filter, | |
| "-c:v", "libx264", | |
| "-pix_fmt", "yuv420p", | |
| "-preset", "medium", | |
| "-crf", "20", | |
| "-movflags", "+faststart", | |
| "-c:a", "aac", | |
| out_path, | |
| ] | |
| try: | |
| subprocess.run(base_cmd, check=True) | |
| except subprocess.CalledProcessError: | |
| # Fallback: try without audio if AAC not available | |
| cmd_no_audio = base_cmd.copy() | |
| # replace audio settings with -an | |
| if "-c:a" in cmd_no_audio: | |
| idx = cmd_no_audio.index("-c:a") | |
| # remove -c:a and its value | |
| del cmd_no_audio[idx:idx+2] | |
| cmd_no_audio.insert(-1, "-an") | |
| subprocess.run(cmd_no_audio, check=True) | |
| return out_path, out_path | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Video Rotate") | |
| with gr.Row(): | |
| input_video = gr.Video(label="Input") | |
| rotation = gr.Radio(choices=["90° clockwise", "90° counter-clockwise", "180°"], value="90° counter-clockwise", label="Rotation") | |
| out_preview = gr.Video(label="Preview") | |
| out_file = gr.File(label="Download MP4") | |
| run_btn = gr.Button("Rotate") | |
| run_btn.click(rotate_video, inputs=[input_video, rotation], outputs=[out_preview, out_file]) | |
| if __name__ == "__main__": | |
| demo.launch() | |