akhaliq's picture
akhaliq HF Staff
Upload app.py with huggingface_hub
f7a8ccc verified
"""
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()