| 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 |
|
|
| |
| OPENAI_API_KEY = os.getenv("open_key") |
| client = OpenAI(api_key=OPENAI_API_KEY) |
|
|
| |
| models_loaded = False |
| story_model = None |
| story_tokenizer = None |
| tts_model = None |
| tts_tokenizer = None |
| image_pipeline = None |
| device = None |
|
|
| |
| 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: |
| |
| print("📢 Loading Hindi TTS model...") |
| tts_model = VitsModel.from_pretrained("facebook/mms-tts-hin") |
| tts_tokenizer = VitsTokenizer.from_pretrained("facebook/mms-tts-hin") |
| |
| |
| 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) |
| |
| |
| 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: |
| |
| scenes_needed = max(8, duration_minutes // 2) |
| |
| 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", |
| 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 |
| |
| |
| try: |
| story_data = json.loads(story_content) |
| return story_data |
| except json.JSONDecodeError: |
| |
| 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"]) |
| |
| |
| 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: |
| |
| 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() |
| |
| |
| audio_np = self.apply_audio_effects(audio_np, mood) |
| |
| |
| 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) |
| |
| except Exception as e: |
| print(f"TTS Error: {e}") |
| |
| 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""" |
| |
| 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": |
| |
| audio_np = audio_np * 0.9 |
| elif mood == "happy": |
| |
| audio_np = audio_np * 1.05 |
| elif mood == "action": |
| |
| 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: |
| |
| 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 |
| """ |
| |
| |
| image = image_pipeline( |
| prompt=enhanced_prompt, |
| negative_prompt=negative_prompt, |
| num_inference_steps=40, |
| guidance_scale=8.5, |
| width=1920, |
| height=1080, |
| generator=torch.Generator(device=device).manual_seed(scene_num * 123 + hash(visual_prompt) % 1000) |
| ).images[0] |
| |
| |
| 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 |
| |
| |
| image = image.resize((1920, 1080), Image.Resampling.LANCZOS) |
| |
| |
| enhancer = ImageEnhance.Contrast(image) |
| image = enhancer.enhance(1.1) |
| |
| |
| enhancer = ImageEnhance.Sharpness(image) |
| image = enhancer.enhance(1.1) |
| |
| |
| 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: |
| |
| mood_colors = { |
| "happy": [(255, 223, 0), (255, 94, 77)], |
| "sad": [(74, 144, 226), (80, 80, 80)], |
| "suspense": [(30, 30, 30), (70, 20, 70)], |
| "action": [(255, 0, 0), (255, 165, 0)], |
| "peaceful": [(135, 206, 235), (144, 238, 144)], |
| "mysterious": [(25, 25, 112), (72, 61, 139)] |
| } |
| |
| colors = mood_colors.get(mood, [(100, 100, 150), (150, 100, 100)]) |
| |
| img = Image.new('RGB', (1920, 1080), color=colors[0]) |
| draw = ImageDraw.Draw(img) |
| |
| |
| 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)) |
| |
| |
| try: |
| |
| font_size = 72 |
| font = ImageFont.load_default() |
| |
| |
| 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 |
| |
| |
| draw.text((x + 3, y + 3), text, fill=(0, 0, 0), font=font) |
| draw.text((x, y), text, fill=(255, 255, 255), font=font) |
| |
| 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: |
| |
| temp_video = output_path.replace('.mp4', '_temp.mp4') |
| |
| |
| 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") |
| |
| |
| ffmpeg_cmd = [ |
| 'ffmpeg', '-y', |
| '-loop', '1', '-i', image_path, |
| '-i', audio_path, |
| '-c:v', 'libx264', |
| '-preset', 'slow', |
| '-crf', '18', |
| '-pix_fmt', 'yuv420p', |
| '-profile:v', 'high', |
| '-level:v', '4.1', |
| '-vf', f'{zoom_filter},fps=30', |
| '-c:a', 'aac', |
| '-b:a', '320k', |
| '-ac', '2', |
| '-ar', '44100', |
| '-shortest', |
| '-movflags', '+faststart', |
| 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) |
| |
| |
| self.add_professional_subtitles(temp_video, text, output_path, duration, mood) |
| |
| |
| 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: |
| |
| subtitle_file = output_path.replace('.mp4', '.srt') |
| |
| |
| words = text.split() |
| chunks = [] |
| chunk_size = 8 |
| |
| for i in range(0, len(words), chunk_size): |
| chunk = ' '.join(words[i:i + chunk_size]) |
| chunks.append(chunk) |
| |
| |
| 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" |
| |
| |
| with open(subtitle_file, 'w', encoding='utf-8') as f: |
| f.write(srt_content) |
| |
| |
| 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") |
| |
| |
| 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) |
| |
| |
| if os.path.exists(subtitle_file): |
| os.remove(subtitle_file) |
| |
| if result.returncode != 0: |
| print(f"Subtitle error: {result.stderr}") |
| |
| import shutil |
| shutil.copy(video_path, output_path) |
| |
| except Exception as e: |
| print(f"Subtitle addition error: {e}") |
| |
| 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 |
| |
| |
| image_clip = ImageClip(image_path, duration=duration) |
| audio_clip = AudioFileClip(audio_path) |
| |
| |
| final_clip = image_clip.set_audio(audio_clip) |
| final_clip.write_videofile( |
| output_path, |
| codec='libx264', |
| audio_codec='aac', |
| fps=30, |
| bitrate='8M' |
| ) |
| |
| |
| 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} |
| |
| |
| output_dir = f"generated_videos/{video_id}" |
| os.makedirs(output_dir, exist_ok=True) |
| |
| |
| 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 |
| |
| |
| for i, scene in enumerate(scenes): |
| print(f"🎬 Processing scene {i+1}/{total_scenes}: {scene['hindi_text'][:50]}...") |
| |
| |
| 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 |
| } |
| |
| |
| 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" |
| |
| |
| duration_sec = self.generate_enhanced_scene_audio( |
| scene['hindi_text'], |
| audio_path, |
| scene.get('mood', 'neutral') |
| ) |
| |
| |
| success = self.generate_high_quality_scene_image( |
| scene['visual_prompt'], |
| scene.get('mood', 'neutral'), |
| image_path, |
| i |
| ) |
| |
| |
| 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") |
| |
| |
| video_status[video_id] = {"status": "combining_videos", "progress": 90} |
| |
| if scene_videos: |
| final_path = f"{output_dir}/final_hindi_story_hd.mp4" |
| |
| |
| self.combine_videos_with_ffmpeg(scene_videos, final_path, story_title) |
| |
| |
| 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: |
| |
| 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_cmd = [ |
| 'ffmpeg', '-y', |
| '-f', 'concat', |
| '-safe', '0', |
| '-i', file_list_path, |
| '-c:v', 'libx264', |
| '-preset', 'slow', |
| '-crf', '18', |
| '-c:a', 'aac', |
| '-b:a', '320k', |
| '-movflags', '+faststart', |
| output_path |
| ] |
| |
| result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, timeout=600) |
| |
| |
| 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 |
|
|
| |
| generator = AdvancedHindiVideoGenerator() |
|
|
| |
| 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> |
| """ |
|
|
| @app.route('/') |
| def index(): |
| return render_template_string(HTML_TEMPLATE) |
|
|
| @app.route('/generate', methods=['POST']) |
| 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', '') |
| |
| |
| if duration > 25: |
| return jsonify({"error": "Duration must be 25 minutes or less"}), 400 |
| |
| |
| 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 |
| |
| |
| video_id = str(uuid.uuid4())[:8] |
| |
| |
| def generate_async(): |
| load_models() |
| generator.generate_complete_video(video_id, theme, duration) |
| |
| thread = threading.Thread(target=generate_async) |
| thread.daemon = True |
| 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 |
|
|
| @app.route('/status/<video_id>') |
| def get_status(video_id): |
| status = video_status.get(video_id, {"status": "not_found", "progress": 0}) |
| return jsonify(status) |
|
|
| @app.route('/videos') |
| 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) |
| }) |
| |
| |
| videos.sort(key=lambda x: x["created"], reverse=True) |
| return jsonify(videos) |
|
|
| @app.route('/download/<video_id>') |
| 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 |
|
|
| @app.route('/health') |
| 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) |
| }) |
|
|
| |
| 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 |
| """) |
| |
| |
| threading.Thread(target=load_models, daemon=True).start() |
| |
| |
| app.run( |
| debug=False, |
| host='0.0.0.0', |
| port=int(os.environ.get('PORT', 7860)), |
| threaded=True, |
| use_reloader=False |
| ) |