Spaces:
Running
Running
""" | |
Upload β Shorts (Free, Phone-only) β Hugging Face Space App | |
""" | |
import os | |
import tempfile | |
from typing import List | |
import gradio as gr | |
from moviepy.editor import VideoFileClip | |
def make_916(clip: VideoFileClip, target_h: int = 1920, target_w: int = 1080) -> VideoFileClip: | |
"""Resize + crop to 9:16 vertical format.""" | |
resized = clip.resize(height=target_h) | |
w, h = resized.size | |
if w == target_w: | |
return resized | |
x1 = max(0, (w - target_w) // 2) | |
x2 = x1 + target_w | |
return resized.crop(x1=x1, y1=0, x2=x2, y2=target_h) | |
def slice_evenly(duration: float, clip_len: float, n_clips: int) -> List[float]: | |
"""Get evenly spaced start times for subclips.""" | |
starts = [] | |
if duration <= 0 or clip_len <= 0 or n_clips <= 0: | |
return starts | |
last_valid = max(0.0, duration - clip_len) | |
for i in range(n_clips): | |
t = (last_valid * i) / max(1, n_clips - 1) | |
starts.append(float(t)) | |
return starts | |
def video_to_shorts(video_path: str, clip_len: int, n_clips: int, fps: int, quality: str): | |
temp_root = tempfile.mkdtemp(prefix="shorts_") | |
outputs = [] | |
error_msg = None | |
try: | |
base_name = os.path.splitext(os.path.basename(video_path))[0] | |
with VideoFileClip(video_path) as vid: | |
duration = float(vid.duration or 0) | |
if duration <= 1: | |
raise ValueError("Video too short or failed to load.") | |
max_clips = max(1, int(duration // max(1, clip_len))) | |
n_use = min(n_clips, max_clips) | |
starts = slice_evenly(duration, clip_len, n_use) | |
if quality == "High": | |
bitrate = "5000k" | |
elif quality == "Medium": | |
bitrate = "3500k" | |
else: | |
bitrate = "2000k" | |
for idx, st in enumerate(starts, start=1): | |
sub = vid.subclip(st, min(st + clip_len, duration)) | |
sub916 = make_916(sub) | |
out_path = os.path.join(temp_root, f"{base_name}_short_{idx:02d}.mp4") | |
sub916.write_videofile( | |
out_path, | |
codec="libx264", | |
audio_codec="aac", | |
fps=fps, | |
bitrate=bitrate, | |
threads=2, | |
verbose=False, | |
logger=None, | |
) | |
outputs.append(out_path) | |
if not outputs: | |
error_msg = "No clips generated. Try shorter length or another video." | |
except Exception as e: | |
error_msg = f"Error: {str(e)}" | |
if error_msg: | |
return None, error_msg | |
return outputs, None | |
def run_app(): | |
with gr.Blocks(title="Upload β Shorts (Free, Phone)") as demo: | |
gr.Markdown( | |
""" | |
# π¬ Upload β Shorts Converter | |
Upload a long video and get vertical (9:16) Shorts. | |
β Works on phone (no PC needed). | |
""" | |
) | |
video_in = gr.File(label="Upload your video", type="filepath") | |
clip_len = gr.Slider(10, 60, value=25, step=1, label="Clip length (sec)") | |
n_clips = gr.Slider(1, 10, value=3, step=1, label="Number of clips") | |
fps = gr.Slider(24, 60, value=30, step=1, label="FPS") | |
quality = gr.Radio(["High", "Medium", "Low"], value="Medium", label="Quality") | |
btn = gr.Button("π₯ Make Shorts") | |
gallery = gr.Gallery(label="Your Shorts", columns=3) | |
status = gr.Markdown() | |
def submit(video_in, clip_len, n_clips, fps, quality): | |
if not video_in: | |
return [], "β Please upload a video first." | |
files, err = video_to_shorts(video_in, int(clip_len), int(n_clips), int(fps), quality) | |
if err: | |
return [], f"β {err}" | |
return files, f"β Done! {len(files)} short(s) generated." | |
btn.click(submit, [video_in, clip_len, n_clips, fps, quality], [gallery, status]) | |
return demo | |
if __name__ == "__main__": | |
app = run_app() | |
app.launch() |