Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import subprocess | |
| import os | |
| import uuid | |
| import json | |
| # Auto-install Node modules | |
| if not os.path.exists("node_modules"): | |
| subprocess.run(["npm", "install"], check=True) | |
| def get_video_dimensions(url): | |
| try: | |
| cmd = [ | |
| "ffprobe", "-v", "error", "-select_streams", "v:0", | |
| "-show_entries", "stream=width,height", "-of", "json", url | |
| ] | |
| result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) | |
| data = json.loads(result.stdout) | |
| return data['streams'][0]['width'], data['streams'][0]['height'] | |
| except: | |
| return 0, 0 | |
| def process_video(query, start_time, end_time, mute_audio, progress=gr.Progress()): | |
| if not query: | |
| return None, "β οΈ Enter a keyword." | |
| progress(0, desc="π Searching Pinterest...") | |
| search_query = query if "video" in query.lower() else f"{query} video" | |
| try: | |
| # 1. Scrape URLs | |
| result = subprocess.run( | |
| ["node", "scraper.js", search_query], | |
| capture_output=True, text=True, timeout=240 | |
| ) | |
| all_urls = [line.strip() for line in result.stdout.split('\n') if line.strip().startswith("http")] | |
| if not all_urls: | |
| return None, "β No videos found." | |
| selected_url = None | |
| needs_crop = False | |
| # --- PHASE 1: SEARCH FOR PORTRAIT FIRST --- | |
| progress(0.2, desc="π Phase 1: Looking for Portrait video...") | |
| for url in all_urls: | |
| w, h = get_video_dimensions(url) | |
| if h > w: # Found Portrait | |
| selected_url = url | |
| needs_crop = False | |
| break | |
| # --- PHASE 2: SEARCH FOR LANDSCAPE FALLBACK --- | |
| if not selected_url: | |
| progress(0.5, desc="π Phase 2: No Portrait found. Looking for Landscape to crop...") | |
| for url in all_urls: | |
| w, h = get_video_dimensions(url) | |
| if w > h: # Found Landscape | |
| selected_url = url | |
| needs_crop = True | |
| break | |
| if not selected_url: | |
| return None, "β No videos found in any orientation." | |
| # 3. Process Video | |
| progress(0.8, desc="βοΈ Final Processing...") | |
| output_filename = f"output_{uuid.uuid4().hex[:5]}.mp4" | |
| # Define Filter Logic | |
| if needs_crop: | |
| # Crop middle to 9:16 (ih*9/16 is the width based on height) | |
| # We then scale it to a standard height like 1280 to keep it clean | |
| vf_filter = "crop=ih*9/16:ih,scale=-1:1280,unsharp=3:3:1.2:3:3:0.0,format=yuv420p" | |
| status_msg = "β Found Landscape: Cropped to 9:16" | |
| else: | |
| # Already Portrait: Just sharpen and standardize | |
| vf_filter = "scale=-1:1280,unsharp=3:3:1.2:3:3:0.0,format=yuv420p" | |
| status_msg = "β Found Portrait: Keeping original ratio" | |
| ffmpeg_cmd = [ | |
| "ffmpeg", | |
| "-ss", str(start_time), | |
| "-to", str(end_time) if end_time > 0 else "999", | |
| "-i", selected_url, | |
| "-vf", vf_filter, | |
| "-c:v", "libx264", | |
| "-crf", "18", | |
| "-preset", "veryfast", | |
| "-y", output_filename | |
| ] | |
| if mute_audio: | |
| ffmpeg_cmd.insert(-1, "-an") | |
| else: | |
| ffmpeg_cmd.insert(-1, "-c:a") | |
| ffmpeg_cmd.insert(-1, "aac") | |
| subprocess.run(ffmpeg_cmd, check=True) | |
| return output_filename, status_msg | |
| except Exception as e: | |
| return None, f"β Error: {str(e)}" | |
| # --- UI --- | |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# π Pinterest Smart 9:16 Downloader") | |
| gr.Markdown("Prioritizes Vertical videos. If only Horizontal are found, it crops the center to 9:16.") | |
| with gr.Row(): | |
| with gr.Column(): | |
| q = gr.Textbox(label="Topic", placeholder="e.g. Minecraft Gameplay") | |
| with gr.Row(): | |
| s = gr.Number(label="Start (sec)", value=0) | |
| e = gr.Number(label="End (sec)", value=5) | |
| mute = gr.Checkbox(label="Mute Audio", value=False) | |
| btn = gr.Button("π Smart Search & Process", variant="primary") | |
| with gr.Column(): | |
| out_v = gr.Video(label="9:16 Result") | |
| out_s = gr.Textbox(label="Status") | |
| btn.click(process_video, [q, s, e, mute], [out_v, out_s]) | |
| if __name__ == "__main__": | |
| demo.launch() |