""" A minimal, self-contained Gradio video editor that allows: 1. Uploading one or more videos. 2. Trimming each video to a start/end time. 3. Concatenating the trimmed clips in the order supplied. 4. Downloading the final edited video. Requirements (install once): pip install gradio moviepy==1.0.3 Run: python video_editor.py """ import tempfile import os from typing import List, Tuple import gradio as gr from moviepy.editor import VideoFileClip, concatenate_videoclips def _time_to_seconds(time_str: str) -> float: """Convert HH:MM:SS[.ms] → seconds (float).""" parts = list(map(float, reversed(time_str.split(":")))) return sum(v * 60 ** idx for idx, v in enumerate(parts)) def edit_videos( videos: List[str], trims: List[Tuple[str, str]], ) -> str: """ Create a single edited video from the list of uploaded videos and corresponding (start, end) times. Returns the file path of the final video. """ if not videos: raise ValueError("At least one video is required.") if len(videos) != len(trims): raise ValueError("Each video must have a start/end time pair.") clips = [] for vid_path, (start_str, end_str) in zip(videos, trims): start = _time_to_seconds(start_str) end = _time_to_seconds(end_str) clip = VideoFileClip(vid_path).subclip(start, end) clips.append(clip) final = concatenate_videoclips(clips) # Save to a temporary file with a unique name out_path = os.path.join( tempfile.gettempdir(), "edited_video.mp4" ) final.write_videofile(out_path, codec="libx264", audio_codec="aac") # Close clips to release file handles for c in clips: c.close() final.close() return out_path def gradio_interface(): with gr.Blocks(title="Video Editor") as demo: gr.Markdown("## Simple Gradio Video Editor") gr.Markdown( "Upload one or more videos. \n" "Provide the **start** and **end** time (HH:MM:SS) for each clip. \n" "The clips will be concatenated in the order given." ) with gr.Row(): with gr.Column(scale=2): video_files = gr.File( label="Upload videos", file_count="multiple", file_types=["video"], type="filepath", ) # Dynamic UI for trims trim_rows = gr.State([]) def _update_trim_inputs(videos): rows = [] for idx, v in enumerate(videos or []): with gr.Row(): start = gr.Textbox( label=f"Start time #{idx+1}", value="00:00:00", scale=1, ) end = gr.Textbox( label=f"End time #{idx+1}", value="00:00:05", scale=1, ) rows.append((start, end)) return rows trim_inputs_placeholder = gr.HTML() # dummy; we will render rows # Render trim inputs after file upload video_files.change( fn=_update_trim_inputs, inputs=video_files, outputs=trim_inputs_placeholder, ) with gr.Column(scale=1): output_video = gr.Video(label="Edited Video") edit_btn = gr.Button("Edit & Download", variant="primary") # Submit def _on_edit(videos): # Re-create trim tuples based on current videos rows = _update_trim_inputs(videos) trims = [ (start.value, end.value) for start, end in rows ] out_path = edit_videos(videos, trims) return out_path edit_btn.click(_on_edit, inputs=[video_files], outputs=output_video) demo.launch() if __name__ == "__main__": gradio_interface()