""" 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()