Download app.py from Vvbbnnn/H: direct link, hf CLI and curl.
- Browser
- Download file 51.3 kB
-
https://huggingface.co/spaces/Vvbbnnn/H/resolve/main/app.py
- Command line
-
hf download hf://spaces/Vvbbnnn/H/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Vvbbnnn/H/resolve/main/app.py
51.3 kB
| from flask import Flask, request, jsonify, send_file, render_template_string | |
| import os | |
| import torch | |
| import numpy as np | |
| from transformers import AutoTokenizer, AutoModelForCausalLM, VitsModel, VitsTokenizer | |
| from diffusers import StableDiffusionPipeline | |
| import cv2 | |
| from PIL import Image, ImageDraw, ImageFont | |
| import soundfile as sf | |
| import subprocess | |
| import threading | |
| import uuid | |
| import json | |
| import time | |
| import random | |
| from datetime import datetime | |
| import asyncio | |
| from concurrent.futures import ThreadPoolExecutor | |
| import openai | |
| from openai import OpenAI | |
| import re | |
| app = Flask(__name__) | |
| app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max | |
| # Configuration | |
| OPENAI_API_KEY = os.getenv("open_key") # Add your OpenAI API key | |
| client = OpenAI(api_key=OPENAI_API_KEY) | |
| # Global variables for models | |
| models_loaded = False | |
| story_model = None | |
| story_tokenizer = None | |
| tts_model = None | |
| tts_tokenizer = None | |
| image_pipeline = None | |
| device = None | |
| # Video generation status tracking | |
| video_status = {} | |
| video_files = {} | |
| def load_models(): | |
| """Load all AI models at startup""" | |
| global models_loaded, story_model, story_tokenizer, tts_model, tts_tokenizer, image_pipeline, device | |
| if models_loaded: | |
| return | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| print(f"🚀 Loading models on {device}...") | |
| try: | |
| # Hindi TTS Model | |
| print("📢 Loading Hindi TTS model...") | |
| tts_model = VitsModel.from_pretrained("facebook/mms-tts-hin") | |
| tts_tokenizer = VitsTokenizer.from_pretrained("facebook/mms-tts-hin") | |
| # Image Generation Model - Higher quality settings | |
| print("🎨 Loading image generation model...") | |
| image_pipeline = StableDiffusionPipeline.from_pretrained( | |
| "runwayml/stable-diffusion-v1-5", | |
| torch_dtype=torch.float16 if device == "cuda" else torch.float32, | |
| safety_checker=None, | |
| requires_safety_checker=False | |
| ) | |
| image_pipeline.to(device) | |
| # Optimize for speed and quality | |
| try: | |
| if hasattr(image_pipeline, 'enable_xformers_memory_efficient_attention'): | |
| image_pipeline.enable_xformers_memory_efficient_attention() | |
| except Exception as e: | |
| print(f"xformers not available, using standard attention: {e}") | |
| try: | |
| if hasattr(image_pipeline, 'enable_model_cpu_offload'): | |
| image_pipeline.enable_model_cpu_offload() | |
| except Exception as e: | |
| print(f"CPU offload not available: {e}") | |
| models_loaded = True | |
| print("✅ All models loaded successfully!") | |
| except Exception as e: | |
| print(f"❌ Error loading models: {e}") | |
| class AdvancedHindiVideoGenerator: | |
| def __init__(self): | |
| self.device = device | |
| self.executor = ThreadPoolExecutor(max_workers=4) | |
| def generate_dynamic_hindi_story(self, theme, duration_minutes, style="adventure"): | |
| """Generate dynamic Hindi story using ChatGPT""" | |
| try: | |
| # Calculate number of scenes based on duration | |
| scenes_needed = max(8, duration_minutes // 2) # ~2 minutes per scene | |
| prompt = f""" | |
| Create a captivating {duration_minutes}-minute Hindi story about {theme} with exactly {scenes_needed} scenes. | |
| Requirements: | |
| - Theme: {theme} | |
| - Style: {style} | |
| - Each scene should be 1-2 minutes long | |
| - Include dialogue and descriptive narration | |
| - Make it engaging for YouTube audience | |
| - Ensure cultural authenticity | |
| Format your response as JSON with this structure: | |
| {{ | |
| "title": "Story title in Hindi and English", | |
| "scenes": [ | |
| {{ | |
| "scene_number": 1, | |
| "hindi_text": "Hindi narration text (2-3 sentences)", | |
| "english_description": "Scene description for image generation", | |
| "visual_prompt": "Detailed visual description for AI image generation", | |
| "mood": "happy/sad/suspense/action/peaceful", | |
| "duration_estimate": "seconds" | |
| }} | |
| ] | |
| }} | |
| Make each scene vivid and cinematic. The story should flow naturally and be suitable for all ages. | |
| """ | |
| response = client.chat.completions.create( | |
| model="gpt-3.5-turbo", # Changed from gpt-4 to gpt-3.5-turbo for better accessibility | |
| messages=[ | |
| {"role": "system", "content": "You are a master storyteller specializing in Hindi stories. Create engaging, family-friendly content perfect for YouTube videos."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| max_tokens=2000, | |
| temperature=0.8 | |
| ) | |
| story_content = response.choices[0].message.content | |
| # Parse JSON response | |
| try: | |
| story_data = json.loads(story_content) | |
| return story_data | |
| except json.JSONDecodeError: | |
| # Fallback if JSON parsing fails | |
| return self.create_fallback_story(theme, scenes_needed) | |
| except Exception as e: | |
| print(f"ChatGPT Error: {e}") | |
| return self.create_fallback_story(theme, scenes_needed) | |
| def create_fallback_story(self, theme, scenes_needed): | |
| """Fallback story generation if ChatGPT fails""" | |
| fallback_stories = { | |
| "adventure": { | |
| "title": "साहसिक यात्रा - The Great Adventure", | |
| "scenes": [ | |
| { | |
| "scene_number": 1, | |
| "hindi_text": "एक बार एक बहादुर युवक था जो नए रोमांच की तलाश में निकला।", | |
| "english_description": "A brave young man starting an adventure", | |
| "visual_prompt": "heroic young Indian man with backpack standing at mountain edge, sunrise, cinematic wide shot, epic landscape", | |
| "mood": "inspiring", | |
| "duration_estimate": "120" | |
| }, | |
| { | |
| "scene_number": 2, | |
| "hindi_text": "जंगल में उसे एक रहस्यमय गुफा दिखाई दी जिसमें से अजीब रोशनी आ रही थी।", | |
| "english_description": "Mysterious glowing cave in forest", | |
| "visual_prompt": "mysterious cave entrance glowing with magical blue light, dense forest, atmospheric lighting, fantasy style", | |
| "mood": "mysterious", | |
| "duration_estimate": "110" | |
| } | |
| ] | |
| } | |
| } | |
| base_story = fallback_stories.get(theme, fallback_stories["adventure"]) | |
| # Extend scenes to match required length | |
| scenes = base_story["scenes"] | |
| while len(scenes) < scenes_needed: | |
| scenes.extend(base_story["scenes"]) | |
| return { | |
| "title": base_story["title"], | |
| "scenes": scenes[:scenes_needed] | |
| } | |
| def generate_enhanced_scene_audio(self, text, output_path, mood="neutral"): | |
| """Generate high-quality Hindi audio with mood-based adjustments""" | |
| try: | |
| # Preprocess text for better TTS | |
| processed_text = self.preprocess_hindi_text(text) | |
| inputs = tts_tokenizer(processed_text, return_tensors="pt") | |
| with torch.no_grad(): | |
| output = tts_model(**inputs).waveform | |
| audio_np = output.squeeze().cpu().numpy() | |
| # Apply mood-based audio processing | |
| audio_np = self.apply_audio_effects(audio_np, mood) | |
| # Save high-quality audio | |
| sf.write(output_path, audio_np, tts_model.config.sampling_rate) | |
| duration = len(audio_np) / tts_model.config.sampling_rate | |
| return max(duration, 4.0) # Minimum 4 seconds per scene | |
| except Exception as e: | |
| print(f"TTS Error: {e}") | |
| # Create silence as fallback | |
| duration = max(len(text.split()) * 0.7, 4.0) | |
| silence = np.zeros(int(duration * 22050)) | |
| sf.write(output_path, silence, 22050) | |
| return duration | |
| def preprocess_hindi_text(self, text): | |
| """Preprocess Hindi text for better TTS pronunciation""" | |
| # Add pauses for better speech rhythm | |
| text = re.sub(r'([।!?])', r'\1 ', text) | |
| text = re.sub(r'([,])', r'\1 ', text) | |
| return text.strip() | |
| def apply_audio_effects(self, audio_np, mood): | |
| """Apply mood-based audio effects""" | |
| try: | |
| if mood == "suspense": | |
| # Lower pitch slightly for suspense | |
| audio_np = audio_np * 0.9 | |
| elif mood == "happy": | |
| # Slight pitch increase for happiness | |
| audio_np = audio_np * 1.05 | |
| elif mood == "action": | |
| # Increase volume and add slight compression | |
| audio_np = np.tanh(audio_np * 1.2) | |
| return np.clip(audio_np, -1.0, 1.0) | |
| except: | |
| return audio_np | |
| def generate_high_quality_scene_image(self, visual_prompt, mood, output_path, scene_num): | |
| """Generate ultra-high quality images for YouTube""" | |
| try: | |
| # Enhanced prompt based on mood and YouTube requirements | |
| mood_styles = { | |
| "happy": "bright colors, warm lighting, cheerful atmosphere", | |
| "sad": "muted colors, soft lighting, emotional depth", | |
| "suspense": "dramatic shadows, mysterious atmosphere, dark tones", | |
| "action": "dynamic angles, intense lighting, high energy", | |
| "peaceful": "soft pastels, natural lighting, serene atmosphere", | |
| "mysterious": "dim lighting, fog, ethereal atmosphere" | |
| } | |
| style_addition = mood_styles.get(mood, "cinematic lighting, professional quality") | |
| enhanced_prompt = f""" | |
| {visual_prompt}, {style_addition}, | |
| ultra high quality, 8K resolution, professional photography, | |
| cinematic composition, perfect lighting, sharp focus, | |
| detailed textures, rich colors, masterpiece quality, | |
| YouTube thumbnail worthy, award winning photography | |
| """ | |
| negative_prompt = """ | |
| blurry, low quality, distorted, ugly, bad anatomy, pixelated, | |
| watermark, text, signature, amateur, poorly lit, overexposed, | |
| underexposed, noise, artifacts, jpeg artifacts, compression | |
| """ | |
| # Generate high-resolution image | |
| image = image_pipeline( | |
| prompt=enhanced_prompt, | |
| negative_prompt=negative_prompt, | |
| num_inference_steps=40, # Higher steps for better quality | |
| guidance_scale=8.5, | |
| width=1920, # Full HD width | |
| height=1080, # Full HD height | |
| generator=torch.Generator(device=device).manual_seed(scene_num * 123 + hash(visual_prompt) % 1000) | |
| ).images[0] | |
| # Post-process for maximum quality | |
| image = self.enhance_image_quality(image) | |
| image.save(output_path, "PNG", quality=100, optimize=False) | |
| return True | |
| except Exception as e: | |
| print(f"Image generation error for scene {scene_num}: {e}") | |
| return self.create_fallback_image(output_path, scene_num, mood) | |
| def enhance_image_quality(self, image): | |
| """Enhance image quality using PIL""" | |
| try: | |
| from PIL import ImageEnhance, ImageFilter | |
| # Resize to exact YouTube dimensions | |
| image = image.resize((1920, 1080), Image.Resampling.LANCZOS) | |
| # Enhance contrast slightly | |
| enhancer = ImageEnhance.Contrast(image) | |
| image = enhancer.enhance(1.1) | |
| # Enhance sharpness | |
| enhancer = ImageEnhance.Sharpness(image) | |
| image = enhancer.enhance(1.1) | |
| # Enhance colors | |
| enhancer = ImageEnhance.Color(image) | |
| image = enhancer.enhance(1.05) | |
| return image | |
| except Exception as e: | |
| print(f"Image enhancement error: {e}") | |
| return image.resize((1920, 1080), Image.Resampling.LANCZOS) | |
| def create_fallback_image(self, output_path, scene_num, mood): | |
| """Create high-quality fallback image""" | |
| try: | |
| # Create gradient background based on mood | |
| mood_colors = { | |
| "happy": [(255, 223, 0), (255, 94, 77)], # Yellow to red | |
| "sad": [(74, 144, 226), (80, 80, 80)], # Blue to gray | |
| "suspense": [(30, 30, 30), (70, 20, 70)], # Dark to purple | |
| "action": [(255, 0, 0), (255, 165, 0)], # Red to orange | |
| "peaceful": [(135, 206, 235), (144, 238, 144)], # Sky blue to light green | |
| "mysterious": [(25, 25, 112), (72, 61, 139)] # Dark blue to purple | |
| } | |
| colors = mood_colors.get(mood, [(100, 100, 150), (150, 100, 100)]) | |
| img = Image.new('RGB', (1920, 1080), color=colors[0]) | |
| draw = ImageDraw.Draw(img) | |
| # Create gradient effect | |
| for y in range(1080): | |
| ratio = y / 1080 | |
| r = int(colors[0][0] * (1 - ratio) + colors[1][0] * ratio) | |
| g = int(colors[0][1] * (1 - ratio) + colors[1][1] * ratio) | |
| b = int(colors[0][2] * (1 - ratio) + colors[1][2] * ratio) | |
| draw.line([(0, y), (1920, y)], fill=(r, g, b)) | |
| # Add scene information | |
| try: | |
| # Use a larger font | |
| font_size = 72 | |
| font = ImageFont.load_default() | |
| # Add scene number | |
| text = f"Scene {scene_num}" | |
| bbox = draw.textbbox((0, 0), text, font=font) | |
| text_width = bbox[2] - bbox[0] | |
| text_height = bbox[3] - bbox[1] | |
| x = (1920 - text_width) // 2 | |
| y = (1080 - text_height) // 2 | |
| # Add text with shadow | |
| draw.text((x + 3, y + 3), text, fill=(0, 0, 0), font=font) # Shadow | |
| draw.text((x, y), text, fill=(255, 255, 255), font=font) # Main text | |
| except Exception as font_error: | |
| print(f"Font error: {font_error}") | |
| img.save(output_path, "PNG", quality=100) | |
| return True | |
| except Exception as e: | |
| print(f"Fallback image creation error: {e}") | |
| return False | |
| def create_professional_video_with_ffmpeg(self, image_path, audio_path, text, output_path, duration, mood): | |
| """Create professional quality video using FFmpeg with advanced effects""" | |
| try: | |
| # FFmpeg command for high-quality video with effects | |
| temp_video = output_path.replace('.mp4', '_temp.mp4') | |
| # Ken Burns effect parameters based on mood | |
| zoom_effects = { | |
| "action": "zoompan=z='min(zoom+0.001,1.5)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=1:s=1920x1080", | |
| "peaceful": "zoompan=z='min(zoom+0.0005,1.2)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=1:s=1920x1080", | |
| "suspense": "zoompan=z='if(lte(zoom,1.0),1.5,max(1.0,zoom-0.001))':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=1:s=1920x1080" | |
| } | |
| zoom_filter = zoom_effects.get(mood, "zoompan=z='min(zoom+0.0008,1.3)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=1:s=1920x1080") | |
| # Create video from image with advanced effects | |
| ffmpeg_cmd = [ | |
| 'ffmpeg', '-y', | |
| '-loop', '1', '-i', image_path, | |
| '-i', audio_path, | |
| '-c:v', 'libx264', | |
| '-preset', 'slow', # Better quality | |
| '-crf', '18', # High quality (18 is very high quality) | |
| '-pix_fmt', 'yuv420p', | |
| '-profile:v', 'high', | |
| '-level:v', '4.1', | |
| '-vf', f'{zoom_filter},fps=30', # 30 FPS for smooth playback | |
| '-c:a', 'aac', | |
| '-b:a', '320k', # High quality audio | |
| '-ac', '2', # Stereo | |
| '-ar', '44100', # Standard sample rate | |
| '-shortest', | |
| '-movflags', '+faststart', # Optimize for web streaming | |
| temp_video | |
| ] | |
| result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, timeout=300) | |
| if result.returncode != 0: | |
| print(f"FFmpeg error: {result.stderr}") | |
| return self.fallback_video_creation(image_path, audio_path, output_path, duration) | |
| # Add subtitles using FFmpeg | |
| self.add_professional_subtitles(temp_video, text, output_path, duration, mood) | |
| # Cleanup | |
| if os.path.exists(temp_video): | |
| os.remove(temp_video) | |
| return True | |
| except subprocess.TimeoutExpired: | |
| print("FFmpeg timeout - falling back to basic method") | |
| return self.fallback_video_creation(image_path, audio_path, output_path, duration) | |
| except Exception as e: | |
| print(f"FFmpeg video creation error: {e}") | |
| return self.fallback_video_creation(image_path, audio_path, output_path, duration) | |
| def add_professional_subtitles(self, video_path, text, output_path, duration, mood): | |
| """Add professional subtitles using FFmpeg""" | |
| try: | |
| # Create subtitle file | |
| subtitle_file = output_path.replace('.mp4', '.srt') | |
| # Split text into chunks for better readability | |
| words = text.split() | |
| chunks = [] | |
| chunk_size = 8 # Words per subtitle | |
| for i in range(0, len(words), chunk_size): | |
| chunk = ' '.join(words[i:i + chunk_size]) | |
| chunks.append(chunk) | |
| # Create SRT content | |
| srt_content = "" | |
| chunk_duration = duration / len(chunks) if chunks else duration | |
| for i, chunk in enumerate(chunks): | |
| start_time = i * chunk_duration | |
| end_time = min((i + 1) * chunk_duration, duration) | |
| start_srt = self.seconds_to_srt_time(start_time) | |
| end_srt = self.seconds_to_srt_time(end_time) | |
| srt_content += f"{i + 1}\n{start_srt} --> {end_srt}\n{chunk}\n\n" | |
| # Write subtitle file | |
| with open(subtitle_file, 'w', encoding='utf-8') as f: | |
| f.write(srt_content) | |
| # Subtitle style based on mood | |
| subtitle_styles = { | |
| "action": "FontSize=24,PrimaryColour=&H00FFFF&,OutlineColour=&H000000&,Outline=2", | |
| "peaceful": "FontSize=22,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&,Outline=1", | |
| "suspense": "FontSize=23,PrimaryColour=&H00FFFF&,OutlineColour=&H800000&,Outline=2" | |
| } | |
| style = subtitle_styles.get(mood, "FontSize=22,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&,Outline=2") | |
| # Add subtitles to video | |
| ffmpeg_subtitle_cmd = [ | |
| 'ffmpeg', '-y', | |
| '-i', video_path, | |
| '-vf', f"subtitles={subtitle_file}:force_style='{style}'", | |
| '-c:a', 'copy', | |
| '-c:v', 'libx264', | |
| '-crf', '18', | |
| output_path | |
| ] | |
| result = subprocess.run(ffmpeg_subtitle_cmd, capture_output=True, text=True, timeout=120) | |
| # Cleanup subtitle file | |
| if os.path.exists(subtitle_file): | |
| os.remove(subtitle_file) | |
| if result.returncode != 0: | |
| print(f"Subtitle error: {result.stderr}") | |
| # Just copy the video without subtitles | |
| import shutil | |
| shutil.copy(video_path, output_path) | |
| except Exception as e: | |
| print(f"Subtitle addition error: {e}") | |
| # Fallback: copy video without subtitles | |
| try: | |
| import shutil | |
| shutil.copy(video_path, output_path) | |
| except: | |
| pass | |
| def seconds_to_srt_time(self, seconds): | |
| """Convert seconds to SRT time format""" | |
| hours = int(seconds // 3600) | |
| minutes = int((seconds % 3600) // 60) | |
| secs = int(seconds % 60) | |
| millis = int((seconds % 1) * 1000) | |
| return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}" | |
| def fallback_video_creation(self, image_path, audio_path, output_path, duration): | |
| """Fallback video creation method""" | |
| try: | |
| from moviepy.editor import VideoFileClip, AudioFileClip, ImageClip | |
| # Create video from image | |
| image_clip = ImageClip(image_path, duration=duration) | |
| audio_clip = AudioFileClip(audio_path) | |
| # Combine | |
| final_clip = image_clip.set_audio(audio_clip) | |
| final_clip.write_videofile( | |
| output_path, | |
| codec='libx264', | |
| audio_codec='aac', | |
| fps=30, | |
| bitrate='8M' # High bitrate for quality | |
| ) | |
| # Cleanup | |
| image_clip.close() | |
| audio_clip.close() | |
| final_clip.close() | |
| return True | |
| except Exception as e: | |
| print(f"Fallback video creation error: {e}") | |
| return False | |
| def generate_complete_video(self, video_id, theme, duration): | |
| """Generate complete high-quality video""" | |
| try: | |
| video_status[video_id] = {"status": "initializing", "progress": 5} | |
| # Create output directory | |
| output_dir = f"generated_videos/{video_id}" | |
| os.makedirs(output_dir, exist_ok=True) | |
| # Generate dynamic story using ChatGPT | |
| video_status[video_id] = {"status": "generating_story_with_chatgpt", "progress": 10} | |
| story_data = self.generate_dynamic_hindi_story(theme, duration) | |
| scenes = story_data["scenes"] | |
| total_scenes = len(scenes) | |
| story_title = story_data.get("title", f"Hindi {theme.title()} Story") | |
| video_status[video_id] = { | |
| "status": "processing_scenes", | |
| "progress": 20, | |
| "total_scenes": total_scenes, | |
| "story_title": story_title | |
| } | |
| scene_videos = [] | |
| total_duration = 0 | |
| # Process each scene | |
| for i, scene in enumerate(scenes): | |
| print(f"🎬 Processing scene {i+1}/{total_scenes}: {scene['hindi_text'][:50]}...") | |
| # Update progress | |
| progress = 20 + (i / total_scenes) * 65 | |
| video_status[video_id] = { | |
| "status": f"processing_scene_{i+1}", | |
| "progress": int(progress), | |
| "current_scene": i+1, | |
| "total_scenes": total_scenes, | |
| "story_title": story_title | |
| } | |
| # File paths | |
| audio_path = f"{output_dir}/scene_{i}_audio.wav" | |
| image_path = f"{output_dir}/scene_{i}_image.png" | |
| video_path = f"{output_dir}/scene_{i}_video.mp4" | |
| # Generate enhanced audio | |
| duration_sec = self.generate_enhanced_scene_audio( | |
| scene['hindi_text'], | |
| audio_path, | |
| scene.get('mood', 'neutral') | |
| ) | |
| # Generate high-quality image | |
| success = self.generate_high_quality_scene_image( | |
| scene['visual_prompt'], | |
| scene.get('mood', 'neutral'), | |
| image_path, | |
| i | |
| ) | |
| # Create professional video | |
| if self.create_professional_video_with_ffmpeg( | |
| image_path, audio_path, scene['hindi_text'], | |
| video_path, duration_sec, scene.get('mood', 'neutral') | |
| ): | |
| scene_videos.append(video_path) | |
| total_duration += duration_sec | |
| print(f"✅ Scene {i+1} completed ({duration_sec:.1f}s)") | |
| else: | |
| print(f"❌ Scene {i+1} failed") | |
| # Combine all scenes with professional transitions | |
| video_status[video_id] = {"status": "combining_videos", "progress": 90} | |
| if scene_videos: | |
| final_path = f"{output_dir}/final_hindi_story_hd.mp4" | |
| # Use FFmpeg for professional video concatenation | |
| self.combine_videos_with_ffmpeg(scene_videos, final_path, story_title) | |
| # Store video info | |
| video_files[video_id] = { | |
| "path": final_path, | |
| "title": story_title, | |
| "duration": total_duration, | |
| "created": datetime.now().isoformat(), | |
| "theme": theme, | |
| "quality": "1080p", | |
| "scenes": total_scenes | |
| } | |
| video_status[video_id] = { | |
| "status": "completed", | |
| "progress": 100, | |
| "duration": total_duration, | |
| "title": story_title | |
| } | |
| print(f"🎉 High-quality video {video_id} generated successfully!") | |
| return final_path | |
| except Exception as e: | |
| video_status[video_id] = {"status": "failed", "progress": 0, "error": str(e)} | |
| print(f"❌ Video generation failed: {e}") | |
| return None | |
| def combine_videos_with_ffmpeg(self, scene_videos, output_path, title): | |
| """Combine videos using FFmpeg with professional quality""" | |
| try: | |
| # Create file list for FFmpeg | |
| file_list_path = output_path.replace('.mp4', '_filelist.txt') | |
| with open(file_list_path, 'w') as f: | |
| for video in scene_videos: | |
| f.write(f"file '{os.path.abspath(video)}'\n") | |
| # FFmpeg concatenation command | |
| ffmpeg_cmd = [ | |
| 'ffmpeg', '-y', | |
| '-f', 'concat', | |
| '-safe', '0', | |
| '-i', file_list_path, | |
| '-c:v', 'libx264', | |
| '-preset', 'slow', | |
| '-crf', '18', # High quality | |
| '-c:a', 'aac', | |
| '-b:a', '320k', | |
| '-movflags', '+faststart', | |
| output_path | |
| ] | |
| result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, timeout=600) | |
| # Cleanup | |
| if os.path.exists(file_list_path): | |
| os.remove(file_list_path) | |
| if result.returncode != 0: | |
| print(f"FFmpeg concatenation error: {result.stderr}") | |
| return False | |
| print(f"✅ Video combined successfully: {output_path}") | |
| return True | |
| except Exception as e: | |
| print(f"Video combination error: {e}") | |
| return False | |
| # Initialize generator | |
| generator = AdvancedHindiVideoGenerator() | |
| # Enhanced HTML template | |
| HTML_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>🎬 Professional Hindi Story Video Generator</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| } | |
| .container { max-width: 900px; margin: 0 auto; } | |
| .header { | |
| text-align: center; | |
| color: white; | |
| margin-bottom: 30px; | |
| } | |
| .header h1 { | |
| font-size: 3em; | |
| margin-bottom: 10px; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.5); | |
| } | |
| .header p { | |
| font-size: 1.2em; | |
| opacity: 0.9; | |
| } | |
| .card { | |
| background: white; | |
| border-radius: 20px; | |
| padding: 30px; | |
| margin: 20px 0; | |
| box-shadow: 0 15px 35px rgba(0,0,0,0.3); | |
| } | |
| .form-group { margin-bottom: 25px; } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| color: #555; | |
| font-size: 1.1em; | |
| } | |
| select, input, textarea { | |
| width: 100%; | |
| padding: 15px; | |
| border: 2px solid #ddd; | |
| border-radius: 10px; | |
| font-size: 16px; | |
| transition: border-color 0.3s; | |
| } | |
| select:focus, input:focus, textarea:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 10px rgba(102, 126, 234, 0.3); | |
| } | |
| .btn { | |
| background: linear-gradient(45deg, #667eea, #764ba2); | |
| color: white; | |
| padding: 18px 30px; | |
| border: none; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| font-size: 18px; | |
| font-weight: bold; | |
| width: 100%; | |
| transition: all 0.3s; | |
| } | |
| .btn:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); | |
| } | |
| .btn:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .progress-container { | |
| margin-top: 30px; | |
| display: none; | |
| } | |
| .progress-bar { | |
| width: 100%; | |
| height: 30px; | |
| background: #f0f0f0; | |
| border-radius: 15px; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(45deg, #667eea, #764ba2); | |
| transition: width 0.5s ease; | |
| border-radius: 15px; | |
| position: relative; | |
| } | |
| .progress-fill::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); | |
| animation: shimmer 2s infinite; | |
| } | |
| @keyframes shimmer { | |
| 0% { transform: translateX(-100%); } | |
| 100% { transform: translateX(100%); } | |
| } | |
| .status { | |
| text-align: center; | |
| margin-top: 15px; | |
| font-weight: 600; | |
| color: #555; | |
| font-size: 1.1em; | |
| } | |
| .video-list { | |
| margin-top: 30px; | |
| } | |
| .video-item { | |
| background: linear-gradient(135deg, #f8f9fa, #e9ecef); | |
| padding: 25px; | |
| border-radius: 15px; | |
| margin: 15px 0; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.1); | |
| } | |
| .video-info { | |
| flex-grow: 1; | |
| } | |
| .video-title { | |
| font-size: 1.3em; | |
| font-weight: bold; | |
| color: #333; | |
| margin-bottom: 5px; | |
| } | |
| .video-details { | |
| color: #666; | |
| font-size: 0.95em; | |
| } | |
| .download-btn { | |
| background: linear-gradient(45deg, #28a745, #20c997); | |
| color: white; | |
| padding: 12px 24px; | |
| border: none; | |
| border-radius: 8px; | |
| text-decoration: none; | |
| font-weight: bold; | |
| transition: all 0.3s; | |
| } | |
| .download-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(40, 167, 69, 0.4); | |
| } | |
| .feature-list { | |
| background: linear-gradient(135deg, #e3f2fd, #f3e5f5); | |
| padding: 20px; | |
| border-radius: 15px; | |
| margin: 20px 0; | |
| } | |
| .feature-list h3 { | |
| color: #333; | |
| margin-bottom: 15px; | |
| text-align: center; | |
| } | |
| .features { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 15px; | |
| } | |
| .feature { | |
| text-align: center; | |
| padding: 10px; | |
| } | |
| .feature-icon { | |
| font-size: 2em; | |
| margin-bottom: 10px; | |
| } | |
| .api-key-section { | |
| background: #fff3cd; | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin-bottom: 20px; | |
| border: 2px solid #ffeaa7; | |
| } | |
| .alert { | |
| background: #d1ecf1; | |
| border: 1px solid #bee5eb; | |
| color: #0c5460; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>🎬 Professional Hindi Story Generator</h1> | |
| <p>Create High-Quality YouTube Videos with AI-Powered Stories</p> | |
| </div> | |
| <div class="card api-key-section"> | |
| <h3>🔑 OpenAI API Configuration</h3> | |
| <div class="alert"> | |
| <strong>Important:</strong> Add your OpenAI API key to the Python file (line 23) before running the generator. | |
| <br><strong>OPENAI_API_KEY = "your-api-key-here"</strong> | |
| </div> | |
| </div> | |
| <div class="card feature-list"> | |
| <h3>✨ Professional Features</h3> | |
| <div class="features"> | |
| <div class="feature"> | |
| <div class="feature-icon">🤖</div> | |
| <strong>ChatGPT Stories</strong><br> | |
| Dynamic, unique stories every time | |
| </div> | |
| <div class="feature"> | |
| <div class="feature-icon">🎥</div> | |
| <strong>1080p Quality</strong><br> | |
| Professional YouTube-ready videos | |
| </div> | |
| <div class="feature"> | |
| <div class="feature-icon">🎨</div> | |
| <strong>AI Visuals</strong><br> | |
| High-quality generated images | |
| </div> | |
| <div class="feature"> | |
| <div class="feature-icon">🔊</div> | |
| <strong>Hindi Audio</strong><br> | |
| Natural text-to-speech | |
| </div> | |
| <div class="feature"> | |
| <div class="feature-icon">⚡</div> | |
| <strong>FFmpeg Power</strong><br> | |
| Professional video processing | |
| </div> | |
| <div class="feature"> | |
| <div class="feature-icon">📱</div> | |
| <strong>YouTube Ready</strong><br> | |
| Optimized for social media | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <form id="videoForm"> | |
| <div class="form-group"> | |
| <label for="theme">🎭 Story Theme:</label> | |
| <select id="theme" name="theme" required> | |
| <option value="adventure">🗡️ Adventure (साहसिक कहानी)</option> | |
| <option value="mystery">🔍 Mystery (रहस्यमय कहानी)</option> | |
| <option value="family">👨👩👧👦 Family (पारिवारिक कहानी)</option> | |
| <option value="friendship">🤝 Friendship (दोस्ती की कहानी)</option> | |
| <option value="moral">📚 Moral Story (नैतिक कहानी)</option> | |
| <option value="fantasy">🧚♀️ Fantasy (काल्पनिक कहानी)</option> | |
| <option value="historical">🏛️ Historical (ऐतिहासिक कहानी)</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="duration">⏱️ Video Duration (minutes):</label> | |
| <input type="number" id="duration" name="duration" min="10" max="25" value="15" required> | |
| <small style="color: #666;">Recommended: 15-20 minutes for YouTube</small> | |
| </div> | |
| <div class="form-group"> | |
| <label for="style">🎨 Story Style:</label> | |
| <select id="style" name="style"> | |
| <option value="cinematic">🎬 Cinematic</option> | |
| <option value="animated">🎭 Animated Style</option> | |
| <option value="realistic">📷 Realistic</option> | |
| <option value="artistic">🎨 Artistic</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="custom_prompt">📝 Custom Story Elements (Optional):</label> | |
| <textarea id="custom_prompt" name="custom_prompt" rows="3" | |
| placeholder="Add specific characters, settings, or plot elements you want in your story..."></textarea> | |
| </div> | |
| <button type="submit" class="btn" id="generateBtn"> | |
| 🚀 Generate Professional Hindi Video | |
| </button> | |
| </form> | |
| <div class="progress-container" id="progressContainer"> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="progressFill"></div> | |
| </div> | |
| <div class="status" id="status">Initializing...</div> | |
| <div id="sceneProgress" style="text-align: center; margin-top: 10px; font-size: 0.9em; color: #666;"></div> | |
| </div> | |
| </div> | |
| <div class="card video-list" id="videoList"> | |
| <h2>📥 Generated Videos</h2> | |
| <div id="videos"></div> | |
| </div> | |
| </div> | |
| <script> | |
| let currentVideoId = null; | |
| document.getElementById('videoForm').addEventListener('submit', async function(e) { | |
| e.preventDefault(); | |
| const theme = document.getElementById('theme').value; | |
| const duration = document.getElementById('duration').value; | |
| const style = document.getElementById('style').value; | |
| const customPrompt = document.getElementById('custom_prompt').value; | |
| const btn = document.getElementById('generateBtn'); | |
| const progressContainer = document.getElementById('progressContainer'); | |
| btn.disabled = true; | |
| btn.textContent = '⏳ Starting Generation...'; | |
| progressContainer.style.display = 'block'; | |
| try { | |
| const response = await fetch('/generate', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| theme, | |
| duration: parseInt(duration), | |
| style, | |
| custom_prompt: customPrompt | |
| }) | |
| }); | |
| const data = await response.json(); | |
| if (data.error) { | |
| throw new Error(data.error); | |
| } | |
| currentVideoId = data.video_id; | |
| // Start progress tracking | |
| trackProgress(); | |
| } catch (error) { | |
| alert('Error starting video generation: ' + error.message); | |
| resetForm(); | |
| } | |
| }); | |
| async function trackProgress() { | |
| if (!currentVideoId) return; | |
| try { | |
| const response = await fetch(`/status/${currentVideoId}`); | |
| const data = await response.json(); | |
| const progressFill = document.getElementById('progressFill'); | |
| const status = document.getElementById('status'); | |
| const sceneProgress = document.getElementById('sceneProgress'); | |
| progressFill.style.width = data.progress + '%'; | |
| // Format status message | |
| let statusText = data.status.replace(/_/g, ' ').toUpperCase(); | |
| if (data.story_title) { | |
| statusText += ` - ${data.story_title}`; | |
| } | |
| status.textContent = statusText; | |
| // Show scene progress | |
| if (data.current_scene && data.total_scenes) { | |
| sceneProgress.textContent = `Scene ${data.current_scene} of ${data.total_scenes}`; | |
| } else { | |
| sceneProgress.textContent = ''; | |
| } | |
| if (data.status === 'completed') { | |
| status.textContent = '✅ Professional Video Generated Successfully!'; | |
| if (data.duration) { | |
| sceneProgress.textContent = `Final Duration: ${Math.round(data.duration/60)} minutes`; | |
| } | |
| loadVideos(); | |
| setTimeout(resetForm, 3000); | |
| } else if (data.status === 'failed') { | |
| status.textContent = '❌ Generation Failed: ' + (data.error || 'Unknown error'); | |
| sceneProgress.textContent = ''; | |
| setTimeout(resetForm, 5000); | |
| } else { | |
| setTimeout(trackProgress, 3000); // Check every 3 seconds | |
| } | |
| } catch (error) { | |
| console.error('Progress tracking error:', error); | |
| setTimeout(trackProgress, 5000); | |
| } | |
| } | |
| function resetForm() { | |
| const btn = document.getElementById('generateBtn'); | |
| const progressContainer = document.getElementById('progressContainer'); | |
| btn.disabled = false; | |
| btn.textContent = '🚀 Generate Professional Hindi Video'; | |
| setTimeout(() => { | |
| progressContainer.style.display = 'none'; | |
| }, 3000); | |
| currentVideoId = null; | |
| } | |
| async function loadVideos() { | |
| try { | |
| const response = await fetch('/videos'); | |
| const videos = await response.json(); | |
| const videosContainer = document.getElementById('videos'); | |
| videosContainer.innerHTML = ''; | |
| if (videos.length === 0) { | |
| videosContainer.innerHTML = '<p style="text-align: center; color: #666; padding: 20px;">No videos generated yet. Create your first professional Hindi story video!</p>'; | |
| return; | |
| } | |
| videos.forEach(video => { | |
| const item = document.createElement('div'); | |
| item.className = 'video-item'; | |
| const duration = video.duration ? Math.round(video.duration/60) : 0; | |
| const fileSize = video.quality === '1080p' ? '~500-800 MB' : '~200-400 MB'; | |
| item.innerHTML = ` | |
| <div class="video-info"> | |
| <div class="video-title">${video.title}</div> | |
| <div class="video-details"> | |
| 📊 Duration: ${duration} minutes | | |
| 🎥 Quality: ${video.quality || 'HD'} | | |
| 🎬 Scenes: ${video.scenes || 'Multiple'} | | |
| 📅 Created: ${new Date(video.created).toLocaleDateString()} | |
| <br>💾 Size: ${fileSize} | 🎭 Theme: ${video.theme || 'Story'} | |
| </div> | |
| </div> | |
| <a href="/download/${video.id}" class="download-btn">📥 Download HD</a> | |
| `; | |
| videosContainer.appendChild(item); | |
| }); | |
| } catch (error) { | |
| console.error('Error loading videos:', error); | |
| } | |
| } | |
| // Load videos on page load | |
| loadVideos(); | |
| // Auto-refresh videos every 30 seconds | |
| setInterval(loadVideos, 30000); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| def index(): | |
| return render_template_string(HTML_TEMPLATE) | |
| def generate_video(): | |
| try: | |
| data = request.json | |
| theme = data.get('theme', 'adventure') | |
| duration = data.get('duration', 15) | |
| style = data.get('style', 'cinematic') | |
| custom_prompt = data.get('custom_prompt', '') | |
| # Validate duration for Hugging Face resources | |
| if duration > 25: | |
| return jsonify({"error": "Duration must be 25 minutes or less"}), 400 | |
| # Check if OpenAI API key is configured | |
| if not OPENAI_API_KEY or OPENAI_API_KEY == "your-openai-api-key-here": | |
| return jsonify({"error": "Please configure your OpenAI API key in the code"}), 400 | |
| # Generate unique video ID | |
| video_id = str(uuid.uuid4())[:8] | |
| # Start video generation in background | |
| def generate_async(): | |
| load_models() # Ensure models are loaded | |
| generator.generate_complete_video(video_id, theme, duration) | |
| thread = threading.Thread(target=generate_async) | |
| thread.daemon = True # Dies when main thread dies | |
| thread.start() | |
| return jsonify({ | |
| "status": "started", | |
| "video_id": video_id, | |
| "message": "Professional video generation started with ChatGPT stories" | |
| }) | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| def get_status(video_id): | |
| status = video_status.get(video_id, {"status": "not_found", "progress": 0}) | |
| return jsonify(status) | |
| def list_videos(): | |
| videos = [] | |
| for vid_id, info in video_files.items(): | |
| videos.append({ | |
| "id": vid_id, | |
| "title": info["title"], | |
| "duration": info.get("duration", 0), | |
| "created": info["created"], | |
| "theme": info.get("theme", ""), | |
| "quality": info.get("quality", "HD"), | |
| "scenes": info.get("scenes", 0) | |
| }) | |
| # Sort by creation time, newest first | |
| videos.sort(key=lambda x: x["created"], reverse=True) | |
| return jsonify(videos) | |
| def download_video(video_id): | |
| try: | |
| if video_id in video_files: | |
| file_path = video_files[video_id]["path"] | |
| if os.path.exists(file_path): | |
| return send_file( | |
| file_path, | |
| as_attachment=True, | |
| download_name=f"hindi_story_hd_{video_id}.mp4", | |
| mimetype='video/mp4' | |
| ) | |
| return jsonify({"error": "Video not found"}), 404 | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| def health_check(): | |
| """Health check endpoint""" | |
| return jsonify({ | |
| "status": "healthy", | |
| "models_loaded": models_loaded, | |
| "device": str(device) if device else "not_initialized", | |
| "active_generations": len(video_status), | |
| "total_videos": len(video_files) | |
| }) | |
| # Create necessary directories | |
| os.makedirs('generated_videos', exist_ok=True) | |
| if __name__ == '__main__': | |
| print(""" | |
| 🎬 Professional Hindi Story Video Generator with ChatGPT & FFmpeg | |
| 📦 Install requirements: | |
| pip install flask torch transformers diffusers opencv-python pillow soundfile openai | |
| # Optional for better performance (may require compilation): | |
| # pip install xformers | |
| 🔧 System requirements: | |
| - FFmpeg installed on system (sudo apt install ffmpeg) | |
| - CUDA GPU recommended (18GB RAM, 2 vCPU minimum) | |
| - OpenAI API key required (supports GPT-3.5-turbo) | |
| 📝 Setup: | |
| 1. Get OpenAI API key from https://platform.openai.com/ | |
| 2. Replace 'your-openai-api-key-here' in line 23 | |
| 3. Install FFmpeg: sudo apt install ffmpeg | |
| ✨ Features: | |
| - ChatGPT-3.5-powered dynamic stories | |
| - 1080p professional video quality | |
| - FFmpeg-based video processing | |
| - YouTube-ready output format | |
| - Professional subtitles and effects | |
| - Optimized for Hugging Face Spaces | |
| 🚀 Starting server... | |
| Visit: http://localhost:5000 | |
| """) | |
| # Load models in background | |
| threading.Thread(target=load_models, daemon=True).start() | |
| # Run with production settings for Hugging Face | |
| app.run( | |
| debug=False, # Disable debug for production | |
| host='0.0.0.0', | |
| port=int(os.environ.get('PORT', 7860)), # Hugging Face Spaces default port | |
| threaded=True, | |
| use_reloader=False | |
| ) |