Spaces:
Runtime error
Runtime error
""" | |
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() |