vanshcodeworks's picture
Upload 18 files
5168d07 verified
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(
"""
<div class="tips-box">
<strong>๐Ÿ’ก Pro Tip:</strong> For best results, choose a content type that
matches your video's style and select an appropriate duration for your target platform.
</div>
""",
elem_classes=["tips"]
)
with gr.Column(scale=2, elem_classes=["progress-container"]):
gr.Markdown("### ๐Ÿ“Š Processing Status")
# Status and results display
status = gr.Textbox(label="Status", interactive=False)
# Results gallery
results_gallery = gr.Gallery(
label="Generated Shorts",
columns=2,
object_fit="contain",
height="auto",
show_label=True,
elem_classes=["results-gallery"]
)
# Results details collapsible
with gr.Accordion("๐Ÿ“ Generated Content", open=False):
title_results = gr.Textbox(label="๐Ÿ”ค Generated Titles", lines=3)
caption_results = gr.Textbox(label="๐Ÿ“„ Generated Captions", lines=3)
hashtag_results = gr.Textbox(label="๐Ÿท๏ธ Generated Hashtags", lines=3)
# Upload Tab (for future implementation)
with gr.TabItem("Upload Video ๐Ÿ“ค", id="upload_tab"):
gr.Markdown("## ๐Ÿšง Coming Soon!")
gr.Markdown("Direct video upload functionality will be available in a future update.")
gr.Markdown("Please use the YouTube URL tab for now.")
# How to use section
with gr.Accordion("๐Ÿ“š How to Use", open=False):
gr.Markdown("""
### How to Create YouTube Shorts
1. **Input a YouTube URL**: Enter the link to any YouTube video you want to turn into shorts
2. **Configure settings**:
- Number of Clips: How many short clips to generate
- Duration Range: Set min/max duration (15-90 seconds)
- Content Type: Choose what kind of moments to look for (funny, educational, etc.)
3. **Click Generate**: The system will:
- Download and transcribe the video
- Find the best moments based on your content preference
- Create variable-length clips optimized for engagement
- Generate titles, captions and hashtags
4. **Use the Results**: Download clips and copy the generated text for your posts
**Processing Time**: Depending on video length, processing can take 2-5 minutes
""")
# Footer with credits
gr.Markdown("""
<footer>
<p>Created for the MCP Hackathon 2025 | <a href="https://github.com/VanshGoyal000/shorts-generator" target="_blank">GitHub</a></p>
</footer>
""")
# Update content description when type changes
def update_description(content_type):
emoji = CONTENT_TYPES[content_type]['emoji']
desc = CONTENT_TYPES[content_type]['desc']
return f"**{emoji} Type Info:** {desc}"
content_type.change(update_description, inputs=[content_type], outputs="content_description")
# Process when button is clicked
process_btn.click(
process_video,
inputs=[youtube_url, num_clips, min_duration, max_duration, content_type],
outputs=[results_gallery, status, title_results, caption_results, hashtag_results]
)
return app
# Run the app directly when this file is executed
if __name__ == "__main__":
# Set up logging
logging.basicConfig(level=logging.INFO)
logger.getLogger().setLevel(logging.INFO)
# Check for Windows-specific fixes
if os.name == 'nt':
try:
from utils.windows_fix import apply_windows_asyncio_fixes
apply_windows_asyncio_fixes()
except ImportError:
logger.warning("Windows-specific fixes not available, some features may not work correctly.")
# Create and launch app
app = create_app()
app.queue(concurrency_count=1).launch(share=True)