import os import sys import gradio as gr import logging import tempfile import uuid from pathlib import Path import time from dotenv import load_dotenv import numpy as np # Configure logging and environment logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) load_dotenv() # Load environment variables from .env file # Define content types with descriptions and emojis CONTENT_TYPES = { "interesting": {"emoji": "🧠", "desc": "Intellectually engaging and thought-provoking moments"}, "funny": {"emoji": "😂", "desc": "Humorous moments that will make viewers laugh"}, "dramatic": {"emoji": "🎭", "desc": "Powerful emotional moments with high tension or impact"}, "educational": {"emoji": "📚", "desc": "Moments that teach valuable information clearly"}, "surprising": {"emoji": "😮", "desc": "Unexpected twists or shocking revelations"}, "inspiring": {"emoji": "✨", "desc": "Motivational or uplifting moments"} } # Add current directory to Python path for imports current_dir = os.path.dirname(os.path.abspath(__file__)) if current_dir not in sys.path: sys.path.insert(0, current_dir) # Check for Modal availability - prefer local processing on Spaces USE_MODAL = False try: import modal # Only set USE_MODAL if we're not running on HF Spaces if not os.environ.get("SPACE_ID"): USE_MODAL = os.environ.get("USE_MODAL", "False").lower() == "true" except ImportError: USE_MODAL = False # Custom CSS for better styling custom_css = """ .gradio-container { max-width: 1200px !important; margin-left: auto !important; margin-right: auto !important; background-color: #f9f9f9 !important; } h1, h2, h3 { font-family: 'Poppins', sans-serif !important; color: #222222 !important; } .content-type-btn { border-radius: 10px !important; border: 2px solid transparent !important; transition: all 0.3s ease !important; } .content-type-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 4px 8px rgba(0,0,0,0.1) !important; } .content-type-btn.selected { border: 2px solid #ff4b4b !important; background-color: rgba(255, 75, 75, 0.1) !important; } .container { margin: 0; padding: 0; } .tips-box { border-left: 4px solid #ff4b4b; padding: 10px 15px; background-color: rgba(255, 75, 75, 0.05); border-radius: 0 8px 8px 0; margin: 10px 0; } .progress-container { border-radius: 12px; border: 1px solid #ddd; padding: 15px; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.05); transition: all 0.3s ease; } .progress-container:hover { box-shadow: 0 4px 10px rgba(0,0,0,0.1); } .results-gallery img { border-radius: 8px; transition: all 0.3s ease; } .results-gallery img:hover { transform: scale(1.02); box-shadow: 0 4px 15px rgba(0,0,0,0.1); } footer { margin-top: 20px; text-align: center; font-size: 0.9em; color: #666; } """ def process_video(youtube_url, num_clips, min_duration, max_duration, content_type, progress=gr.Progress()): """Process video to generate shorts using either Modal or local processing""" result_clips = [] result_titles = [] result_captions = [] result_hashtags = [] try: # Update progress progress(0, desc="Starting processing...") time.sleep(0.5) # Small delay for better UX if USE_MODAL: # Use Modal processing if available try: import modal from modal_deploy import app progress(0.1, desc=f"Connecting to Modal cloud services...") # Download video progress(0.2, desc="Downloading YouTube video...") result = app.download_youtube_video.call(youtube_url) video_path = result[0] if isinstance(result, tuple) else result.get("path") video_title = result[1] if isinstance(result, tuple) else result.get("title", "Unknown Title") if not video_path: return [], "Download failed: Video could not be accessed", "", "", "" # Transcribe video progress(0.3, desc="Transcribing video audio...") transcript_result = app.transcribe_video_enhanced.call(video_path) # Select highlights based on content type progress(0.5, desc=f"Selecting {content_type} highlights...") try: highlights = app.smart_highlight_selector.call( transcript_result, video_title, num_clips, min_duration, max_duration, content_type) except: highlights = app.select_highlights.call( transcript_result, video_title, num_clips, max_duration) # Create clips progress(0.7, desc="Creating video clips...") try: clips = app.create_smart_clips.call( video_path, transcript_result, min_duration, max_duration, num_clips) except: clips = app.clip_video.call(video_path, highlights) # Generate captions progress(0.9, desc="Generating captions and hashtags...") for clip_info in clips: caption = app.generate_caption.call(clip_info, transcript_result, video_title) result_clips.append(clip_info.get("path")) result_titles.append(caption.get("title", "No title")) result_captions.append(caption.get("caption", "No caption")) result_hashtags.append(caption.get("hashtags", "#shorts")) progress(1.0, desc="Processing complete!") return result_clips, f"Successfully created {len(result_clips)} clips!", \ "\n\n".join(result_titles), "\n\n".join(result_captions), "\n\n".join(result_hashtags) except Exception as e: logger.error(f"Modal processing error: {str(e)}") return [], f"Error with Modal processing: {str(e)}", "", "", "" else: # Use local processing progress(0.1, desc="Initializing local processing...") # Import required utilities for local processing from utils.video import download_video_from_url from local_processors import ( transcribe_video_locally, select_highlights_locally, clip_video_locally, generate_caption_locally ) # Download video progress(0.2, desc="Downloading YouTube video...") try: video_path, video_title = download_video_from_url(youtube_url) except Exception as e: logger.error(f"Error downloading video: {str(e)}") return [], f"Download failed: {str(e)}", "", "", "" # Transcribe video progress(0.4, desc="Transcribing video...") transcript_data = transcribe_video_locally(video_path) # Select highlights progress(0.6, desc=f"Selecting {content_type} highlights...") highlights = select_highlights_locally(transcript_data, video_title, num_clips, max_duration) # Adjust highlight durations for highlight in highlights: if highlight["end_time"] - highlight["start_time"] < min_duration: highlight["end_time"] = highlight["start_time"] + min_duration if highlight["end_time"] - highlight["start_time"] > max_duration: highlight["end_time"] = highlight["start_time"] + max_duration # Create clips progress(0.8, desc=f"Creating {content_type} clips...") clip_infos = clip_video_locally(video_path, highlights, content_type) # Generate captions progress(0.9, desc="Generating captions...") for clip_info in clip_infos: caption_data = generate_caption_locally(clip_info, transcript_data, video_title) # Add to results result_clips.append(clip_info["path"]) result_titles.append(caption_data["title"]) result_captions.append(caption_data["caption"]) result_hashtags.append(caption_data["hashtags"]) progress(1.0, desc="Processing complete!") return result_clips, f"Successfully created {len(result_clips)} clips!", \ "\n\n".join(result_titles), "\n\n".join(result_captions), "\n\n".join(result_hashtags) except Exception as e: logger.error(f"Error in processing: {str(e)}") return [], f"Error: {str(e)}", "", "", "" def create_app(): """Create and configure the Gradio application""" # Define theme and layout theme = gr.themes.Soft( primary_hue="red", secondary_hue="blue", ).set( body_background_fill="#f9f9f9", block_radius="8px", button_primary_background_fill="#ff4b4b", button_primary_background_fill_hover="#ff6b6b", ) with gr.Blocks(css=custom_css, theme=theme, title="YouTube Shorts Generator") as app: # Header with branding with gr.Row(elem_id="header"): with gr.Column(scale=1): gr.Image("./assets/logo.png", show_label=False, height=80, width=80) with gr.Column(scale=5): gr.Markdown("# 🎬 YouTube Shorts Generator") gr.Markdown("Transform long videos into engaging social media shorts with AI") # Tabs for different input methods with gr.Tabs() as tabs: # YouTube URL Tab with gr.TabItem("YouTube URL 🔗", id="youtube_tab"): with gr.Row(): with gr.Column(scale=2): # Input options youtube_url = gr.Textbox( label="YouTube Video URL", placeholder="https://www.youtube.com/watch?v=...", info="Paste a YouTube video URL to process", show_label=True ) # Configuration panel with gr.Box(): gr.Markdown("### 🛠️ Configure Your Shorts") with gr.Row(): num_clips = gr.Slider( minimum=1, maximum=5, value=2, step=1, label="Number of clips to generate", info="How many separate shorts to create" ) with gr.Group(): gr.Markdown("##### ⏱️ Duration Settings (seconds)") with gr.Row(): min_duration = gr.Slider( minimum=10, maximum=30, value=15, step=5, label="Minimum duration" ) max_duration = gr.Slider( minimum=30, maximum=90, value=45, step=5, label="Maximum duration" ) gr.Markdown("##### 🎯 Content Type") with gr.Row(): content_type = gr.Dropdown( choices=list(CONTENT_TYPES.keys()), value="interesting", label="Content Style", info="Select what kind of moments to extract", multiselect=False ) with gr.Row(): gr.Markdown( value=f"**{CONTENT_TYPES['interesting']['emoji']} Type Info:** {CONTENT_TYPES['interesting']['desc']}", elem_id="content_description" ) # Process button process_btn = gr.Button( "🚀 Generate Shorts", variant="primary", size="lg" ) gr.Markdown( """