Spaces:
Build error
Build error
| # app.py | |
| import streamlit as st | |
| import os, traceback | |
| from clipper import ( | |
| process_uploaded_file, | |
| process_youtube_link, | |
| NetworkBlockedError, | |
| ) | |
| st.set_page_config(page_title="Smart Clipper", layout="centered") | |
| st.title("Smart Clipper β Upload MP4 (test) & optional YouTube link") | |
| st.markdown( | |
| "Upload MP4 to auto-create 40β60s vertical Shorts with zoom, auto-captions (Whisper if installed), " | |
| "and a viral-likelihood score. After MP4 works, try the YouTube link if your environment allows downloads." | |
| ) | |
| # Sidebar controls | |
| st.sidebar.header("Clip settings") | |
| min_clip = st.sidebar.number_input("Min clip length (s)", value=50, min_value=30, max_value=120, step=1) | |
| max_clip = st.sidebar.number_input("Max clip length (s)", value=60, min_value=30, max_value=300, step=1) | |
| if min_clip > max_clip: | |
| min_clip, max_clip = max_clip, min_clip | |
| num_max = st.sidebar.slider("Maximum candidate clips", 1, 6, 5) | |
| st.sidebar.markdown("---") | |
| st.sidebar.header("YouTube (optional)") | |
| yt_enabled = st.sidebar.checkbox("Enable YouTube download (only use if environment allows)", value=False) | |
| st.sidebar.caption("If network blocked (Spaces often block), use Upload MP4 instead.") | |
| st.sidebar.markdown("---") | |
| st.sidebar.header("Advanced") | |
| use_whisper = st.sidebar.checkbox("Use Whisper for captions (if installed)", value=False) | |
| st.sidebar.caption("If Whisper not installed, app uses a lightweight fallback for captions.") | |
| st.sidebar.markdown("---") | |
| st.info("Tip: Start by uploading an MP4 (Upload MP4). If that works, try YouTube link if you know your env allows downloads.") | |
| OUT_DIR = "outputs" | |
| os.makedirs(OUT_DIR, exist_ok=True) | |
| # --- Upload MP4 flow (recommended to test first) | |
| st.header("1) Upload MP4 (recommended for testing)") | |
| uploaded = st.file_uploader("Choose an MP4/MOV/MKV file", type=["mp4", "mov", "mkv"]) | |
| srt = st.file_uploader("Optional: Upload SRT (will be used as captions)", type=["srt"]) | |
| manual_caption = st.text_input("Optional: A short caption (will show on each clip if provided)") | |
| if uploaded: | |
| save_folder = "uploads" | |
| os.makedirs(save_folder, exist_ok=True) | |
| video_path = os.path.join(save_folder, uploaded.name) | |
| with open(video_path, "wb") as f: | |
| f.write(uploaded.getbuffer()) | |
| st.success(f"Saved upload to: {video_path}") | |
| if st.button("Generate clips from uploaded video"): | |
| status = st.empty() | |
| try: | |
| status.info("Processing β detecting highlights and generating clips. This can take a while.") | |
| clips = process_uploaded_file( | |
| video_path, | |
| out_dir=OUT_DIR, | |
| min_clip=min_clip, | |
| max_clip=max_clip, | |
| max_candidates=num_max, | |
| srt_path=(srt.name if srt else None), | |
| manual_caption=(manual_caption.strip() if manual_caption else None), | |
| use_whisper=use_whisper, | |
| ) | |
| if not clips: | |
| status.warning("No clips generated. Try a different video or expand max candidates.") | |
| else: | |
| status.success(f"Generated {len(clips)} clips.") | |
| for info in clips: | |
| # info is dict: {'path','score','start','end','duration'} | |
| st.subheader(os.path.basename(info["path"])) | |
| try: | |
| st.video(info["path"]) | |
| except: | |
| st.write("Preview not available for this clip.") | |
| st.write(f"Start: {info['start']}s End: {info['end']}s Duration: {info['duration']}s") | |
| st.write(f"Viral-likelihood: **{info['score']}%**") | |
| with open(info["path"], "rb") as fh: | |
| st.download_button("Download clip", fh.read(), file_name=os.path.basename(info["path"])) | |
| except NetworkBlockedError as ne: | |
| status.error("Network blocked for downloads: " + str(ne)) | |
| st.info("Use Upload MP4 or run locally/VPS for YouTube downloads.") | |
| except Exception as e: | |
| status.error("Processing failed: " + repr(e)) | |
| st.code(traceback.format_exc(), language="python") | |
| st.markdown("---") | |
| # --- YouTube link flow (optional) | |
| st.header("2) YouTube link (if your environment can download)") | |
| yt_url = st.text_input("Paste full YouTube URL here") | |
| proxy = st.text_input("Optional proxy for yt-dlp (e.g. socks5://user:pass@host:port)", value="") | |
| if st.button("Download & Generate from YouTube") and yt_url: | |
| status = st.empty() | |
| try: | |
| status.info("Attempting to download video; checking network...") | |
| clips = process_youtube_link( | |
| yt_url, | |
| out_dir=OUT_DIR, | |
| min_clip=min_clip, | |
| max_clip=max_clip, | |
| max_candidates=num_max, | |
| ytdlp_opts={"proxy": proxy} if proxy else None, | |
| use_whisper=use_whisper, | |
| ) | |
| if not clips: | |
| status.warning("No clips generated from YouTube.") | |
| else: | |
| status.success(f"Generated {len(clips)} clips from YouTube.") | |
| for info in clips: | |
| st.subheader(os.path.basename(info["path"])) | |
| try: | |
| st.video(info["path"]) | |
| except: | |
| st.write("Preview not available.") | |
| st.write(f"Start: {info['start']}s End: {info['end']}s Duration: {info['duration']}s") | |
| st.write(f"Viral-likelihood: **{info['score']}%**") | |
| with open(info["path"], "rb") as fh: | |
| st.download_button("Download clip", fh.read(), file_name=os.path.basename(info["path"])) | |
| except NetworkBlockedError as ne: | |
| status.error("Network appears blocked for downloads: " + str(ne)) | |
| st.info("Use Upload MP4 option instead.") | |
| except Exception as e: | |
| status.error("Failed: " + repr(e)) | |
| st.code(traceback.format_exc(), language="python") | |
| st.markdown("---") | |
| st.caption("Viral-likelihood is a heuristic (audio/motion/face presence). It is not a guarantee β but it helps rank candidate clips.") | |