soxogvv commited on
Commit
b3a477d
·
verified ·
1 Parent(s): 3eae762

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +1284 -0
app.py ADDED
@@ -0,0 +1,1284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, send_file, render_template_string
2
+ import os
3
+ import torch
4
+ import numpy as np
5
+ from transformers import AutoTokenizer, AutoModelForCausalLM, VitsModel, VitsTokenizer
6
+ from diffusers import StableDiffusionPipeline
7
+ import cv2
8
+ from PIL import Image, ImageDraw, ImageFont
9
+ import soundfile as sf
10
+ import subprocess
11
+ import threading
12
+ import uuid
13
+ import json
14
+ import time
15
+ import random
16
+ from datetime import datetime
17
+ import asyncio
18
+ from concurrent.futures import ThreadPoolExecutor
19
+ import openai
20
+ from openai import OpenAI
21
+ import re
22
+
23
+ app = Flask(__name__)
24
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max
25
+
26
+ # Configuration
27
+ OPENAI_API_KEY = os.getkey("open_key") # Add your OpenAI API key
28
+ client = OpenAI(api_key=OPENAI_API_KEY)
29
+
30
+ # Global variables for models
31
+ models_loaded = False
32
+ story_model = None
33
+ story_tokenizer = None
34
+ tts_model = None
35
+ tts_tokenizer = None
36
+ image_pipeline = None
37
+ device = None
38
+
39
+ # Video generation status tracking
40
+ video_status = {}
41
+ video_files = {}
42
+
43
+ def load_models():
44
+ """Load all AI models at startup"""
45
+ global models_loaded, story_model, story_tokenizer, tts_model, tts_tokenizer, image_pipeline, device
46
+
47
+ if models_loaded:
48
+ return
49
+
50
+ device = "cuda" if torch.cuda.is_available() else "cpu"
51
+ print(f"🚀 Loading models on {device}...")
52
+
53
+ try:
54
+ # Hindi TTS Model
55
+ print("📢 Loading Hindi TTS model...")
56
+ tts_model = VitsModel.from_pretrained("facebook/mms-tts-hin")
57
+ tts_tokenizer = VitsTokenizer.from_pretrained("facebook/mms-tts-hin")
58
+
59
+ # Image Generation Model - Higher quality settings
60
+ print("🎨 Loading image generation model...")
61
+ image_pipeline = StableDiffusionPipeline.from_pretrained(
62
+ "runwayml/stable-diffusion-v1-5",
63
+ torch_dtype=torch.float16 if device == "cuda" else torch.float32,
64
+ safety_checker=None,
65
+ requires_safety_checker=False
66
+ )
67
+ image_pipeline.to(device)
68
+
69
+ # Optimize for speed and quality
70
+ if hasattr(image_pipeline, 'enable_xformers_memory_efficient_attention'):
71
+ image_pipeline.enable_xformers_memory_efficient_attention()
72
+ if hasattr(image_pipeline, 'enable_model_cpu_offload'):
73
+ image_pipeline.enable_model_cpu_offload()
74
+
75
+ models_loaded = True
76
+ print("✅ All models loaded successfully!")
77
+
78
+ except Exception as e:
79
+ print(f"❌ Error loading models: {e}")
80
+
81
+ class AdvancedHindiVideoGenerator:
82
+ def __init__(self):
83
+ self.device = device
84
+ self.executor = ThreadPoolExecutor(max_workers=4)
85
+
86
+ def generate_dynamic_hindi_story(self, theme, duration_minutes, style="adventure"):
87
+ """Generate dynamic Hindi story using ChatGPT"""
88
+ try:
89
+ # Calculate number of scenes based on duration
90
+ scenes_needed = max(8, duration_minutes // 2) # ~2 minutes per scene
91
+
92
+ prompt = f"""
93
+ Create a captivating {duration_minutes}-minute Hindi story about {theme} with exactly {scenes_needed} scenes.
94
+
95
+ Requirements:
96
+ - Theme: {theme}
97
+ - Style: {style}
98
+ - Each scene should be 1-2 minutes long
99
+ - Include dialogue and descriptive narration
100
+ - Make it engaging for YouTube audience
101
+ - Ensure cultural authenticity
102
+
103
+ Format your response as JSON with this structure:
104
+ {{
105
+ "title": "Story title in Hindi and English",
106
+ "scenes": [
107
+ {{
108
+ "scene_number": 1,
109
+ "hindi_text": "Hindi narration text (2-3 sentences)",
110
+ "english_description": "Scene description for image generation",
111
+ "visual_prompt": "Detailed visual description for AI image generation",
112
+ "mood": "happy/sad/suspense/action/peaceful",
113
+ "duration_estimate": "seconds"
114
+ }}
115
+ ]
116
+ }}
117
+
118
+ Make each scene vivid and cinematic. The story should flow naturally and be suitable for all ages.
119
+ """
120
+
121
+ response = client.chat.completions.create(
122
+ model="gpt-4",
123
+ messages=[
124
+ {"role": "system", "content": "You are a master storyteller specializing in Hindi stories. Create engaging, family-friendly content perfect for YouTube videos."},
125
+ {"role": "user", "content": prompt}
126
+ ],
127
+ max_tokens=2000,
128
+ temperature=0.8
129
+ )
130
+
131
+ story_content = response.choices[0].message.content
132
+
133
+ # Parse JSON response
134
+ try:
135
+ story_data = json.loads(story_content)
136
+ return story_data
137
+ except json.JSONDecodeError:
138
+ # Fallback if JSON parsing fails
139
+ return self.create_fallback_story(theme, scenes_needed)
140
+
141
+ except Exception as e:
142
+ print(f"ChatGPT Error: {e}")
143
+ return self.create_fallback_story(theme, scenes_needed)
144
+
145
+ def create_fallback_story(self, theme, scenes_needed):
146
+ """Fallback story generation if ChatGPT fails"""
147
+ fallback_stories = {
148
+ "adventure": {
149
+ "title": "साहसिक यात्रा - The Great Adventure",
150
+ "scenes": [
151
+ {
152
+ "scene_number": 1,
153
+ "hindi_text": "एक बार एक बहादुर युवक था जो नए रोमांच की तलाश में निकला।",
154
+ "english_description": "A brave young man starting an adventure",
155
+ "visual_prompt": "heroic young Indian man with backpack standing at mountain edge, sunrise, cinematic wide shot, epic landscape",
156
+ "mood": "inspiring",
157
+ "duration_estimate": "120"
158
+ },
159
+ {
160
+ "scene_number": 2,
161
+ "hindi_text": "जंगल में उसे एक रहस्यमय गुफा दिखाई दी जिसमें से अजीब रोशनी आ रही थी।",
162
+ "english_description": "Mysterious glowing cave in forest",
163
+ "visual_prompt": "mysterious cave entrance glowing with magical blue light, dense forest, atmospheric lighting, fantasy style",
164
+ "mood": "mysterious",
165
+ "duration_estimate": "110"
166
+ }
167
+ ]
168
+ }
169
+ }
170
+
171
+ base_story = fallback_stories.get(theme, fallback_stories["adventure"])
172
+
173
+ # Extend scenes to match required length
174
+ scenes = base_story["scenes"]
175
+ while len(scenes) < scenes_needed:
176
+ scenes.extend(base_story["scenes"])
177
+
178
+ return {
179
+ "title": base_story["title"],
180
+ "scenes": scenes[:scenes_needed]
181
+ }
182
+
183
+ def generate_enhanced_scene_audio(self, text, output_path, mood="neutral"):
184
+ """Generate high-quality Hindi audio with mood-based adjustments"""
185
+ try:
186
+ # Preprocess text for better TTS
187
+ processed_text = self.preprocess_hindi_text(text)
188
+
189
+ inputs = tts_tokenizer(processed_text, return_tensors="pt")
190
+ with torch.no_grad():
191
+ output = tts_model(**inputs).waveform
192
+
193
+ audio_np = output.squeeze().cpu().numpy()
194
+
195
+ # Apply mood-based audio processing
196
+ audio_np = self.apply_audio_effects(audio_np, mood)
197
+
198
+ # Save high-quality audio
199
+ sf.write(output_path, audio_np, tts_model.config.sampling_rate)
200
+
201
+ duration = len(audio_np) / tts_model.config.sampling_rate
202
+ return max(duration, 4.0) # Minimum 4 seconds per scene
203
+
204
+ except Exception as e:
205
+ print(f"TTS Error: {e}")
206
+ # Create silence as fallback
207
+ duration = max(len(text.split()) * 0.7, 4.0)
208
+ silence = np.zeros(int(duration * 22050))
209
+ sf.write(output_path, silence, 22050)
210
+ return duration
211
+
212
+ def preprocess_hindi_text(self, text):
213
+ """Preprocess Hindi text for better TTS pronunciation"""
214
+ # Add pauses for better speech rhythm
215
+ text = re.sub(r'([।!?])', r'\1 ', text)
216
+ text = re.sub(r'([,])', r'\1 ', text)
217
+ return text.strip()
218
+
219
+ def apply_audio_effects(self, audio_np, mood):
220
+ """Apply mood-based audio effects"""
221
+ try:
222
+ if mood == "suspense":
223
+ # Lower pitch slightly for suspense
224
+ audio_np = audio_np * 0.9
225
+ elif mood == "happy":
226
+ # Slight pitch increase for happiness
227
+ audio_np = audio_np * 1.05
228
+ elif mood == "action":
229
+ # Increase volume and add slight compression
230
+ audio_np = np.tanh(audio_np * 1.2)
231
+
232
+ return np.clip(audio_np, -1.0, 1.0)
233
+ except:
234
+ return audio_np
235
+
236
+ def generate_high_quality_scene_image(self, visual_prompt, mood, output_path, scene_num):
237
+ """Generate ultra-high quality images for YouTube"""
238
+ try:
239
+ # Enhanced prompt based on mood and YouTube requirements
240
+ mood_styles = {
241
+ "happy": "bright colors, warm lighting, cheerful atmosphere",
242
+ "sad": "muted colors, soft lighting, emotional depth",
243
+ "suspense": "dramatic shadows, mysterious atmosphere, dark tones",
244
+ "action": "dynamic angles, intense lighting, high energy",
245
+ "peaceful": "soft pastels, natural lighting, serene atmosphere",
246
+ "mysterious": "dim lighting, fog, ethereal atmosphere"
247
+ }
248
+
249
+ style_addition = mood_styles.get(mood, "cinematic lighting, professional quality")
250
+
251
+ enhanced_prompt = f"""
252
+ {visual_prompt}, {style_addition},
253
+ ultra high quality, 8K resolution, professional photography,
254
+ cinematic composition, perfect lighting, sharp focus,
255
+ detailed textures, rich colors, masterpiece quality,
256
+ YouTube thumbnail worthy, award winning photography
257
+ """
258
+
259
+ negative_prompt = """
260
+ blurry, low quality, distorted, ugly, bad anatomy, pixelated,
261
+ watermark, text, signature, amateur, poorly lit, overexposed,
262
+ underexposed, noise, artifacts, jpeg artifacts, compression
263
+ """
264
+
265
+ # Generate high-resolution image
266
+ image = image_pipeline(
267
+ prompt=enhanced_prompt,
268
+ negative_prompt=negative_prompt,
269
+ num_inference_steps=40, # Higher steps for better quality
270
+ guidance_scale=8.5,
271
+ width=1920, # Full HD width
272
+ height=1080, # Full HD height
273
+ generator=torch.Generator(device=device).manual_seed(scene_num * 123 + hash(visual_prompt) % 1000)
274
+ ).images[0]
275
+
276
+ # Post-process for maximum quality
277
+ image = self.enhance_image_quality(image)
278
+ image.save(output_path, "PNG", quality=100, optimize=False)
279
+
280
+ return True
281
+
282
+ except Exception as e:
283
+ print(f"Image generation error for scene {scene_num}: {e}")
284
+ return self.create_fallback_image(output_path, scene_num, mood)
285
+
286
+ def enhance_image_quality(self, image):
287
+ """Enhance image quality using PIL"""
288
+ try:
289
+ from PIL import ImageEnhance, ImageFilter
290
+
291
+ # Resize to exact YouTube dimensions
292
+ image = image.resize((1920, 1080), Image.Resampling.LANCZOS)
293
+
294
+ # Enhance contrast slightly
295
+ enhancer = ImageEnhance.Contrast(image)
296
+ image = enhancer.enhance(1.1)
297
+
298
+ # Enhance sharpness
299
+ enhancer = ImageEnhance.Sharpness(image)
300
+ image = enhancer.enhance(1.1)
301
+
302
+ # Enhance colors
303
+ enhancer = ImageEnhance.Color(image)
304
+ image = enhancer.enhance(1.05)
305
+
306
+ return image
307
+
308
+ except Exception as e:
309
+ print(f"Image enhancement error: {e}")
310
+ return image.resize((1920, 1080), Image.Resampling.LANCZOS)
311
+
312
+ def create_fallback_image(self, output_path, scene_num, mood):
313
+ """Create high-quality fallback image"""
314
+ try:
315
+ # Create gradient background based on mood
316
+ mood_colors = {
317
+ "happy": [(255, 223, 0), (255, 94, 77)], # Yellow to red
318
+ "sad": [(74, 144, 226), (80, 80, 80)], # Blue to gray
319
+ "suspense": [(30, 30, 30), (70, 20, 70)], # Dark to purple
320
+ "action": [(255, 0, 0), (255, 165, 0)], # Red to orange
321
+ "peaceful": [(135, 206, 235), (144, 238, 144)], # Sky blue to light green
322
+ "mysterious": [(25, 25, 112), (72, 61, 139)] # Dark blue to purple
323
+ }
324
+
325
+ colors = mood_colors.get(mood, [(100, 100, 150), (150, 100, 100)])
326
+
327
+ img = Image.new('RGB', (1920, 1080), color=colors[0])
328
+ draw = ImageDraw.Draw(img)
329
+
330
+ # Create gradient effect
331
+ for y in range(1080):
332
+ ratio = y / 1080
333
+ r = int(colors[0][0] * (1 - ratio) + colors[1][0] * ratio)
334
+ g = int(colors[0][1] * (1 - ratio) + colors[1][1] * ratio)
335
+ b = int(colors[0][2] * (1 - ratio) + colors[1][2] * ratio)
336
+ draw.line([(0, y), (1920, y)], fill=(r, g, b))
337
+
338
+ # Add scene information
339
+ try:
340
+ # Use a larger font
341
+ font_size = 72
342
+ font = ImageFont.load_default()
343
+
344
+ # Add scene number
345
+ text = f"Scene {scene_num}"
346
+ bbox = draw.textbbox((0, 0), text, font=font)
347
+ text_width = bbox[2] - bbox[0]
348
+ text_height = bbox[3] - bbox[1]
349
+
350
+ x = (1920 - text_width) // 2
351
+ y = (1080 - text_height) // 2
352
+
353
+ # Add text with shadow
354
+ draw.text((x + 3, y + 3), text, fill=(0, 0, 0), font=font) # Shadow
355
+ draw.text((x, y), text, fill=(255, 255, 255), font=font) # Main text
356
+
357
+ except Exception as font_error:
358
+ print(f"Font error: {font_error}")
359
+
360
+ img.save(output_path, "PNG", quality=100)
361
+ return True
362
+
363
+ except Exception as e:
364
+ print(f"Fallback image creation error: {e}")
365
+ return False
366
+
367
+ def create_professional_video_with_ffmpeg(self, image_path, audio_path, text, output_path, duration, mood):
368
+ """Create professional quality video using FFmpeg with advanced effects"""
369
+ try:
370
+ # FFmpeg command for high-quality video with effects
371
+ temp_video = output_path.replace('.mp4', '_temp.mp4')
372
+
373
+ # Ken Burns effect parameters based on mood
374
+ zoom_effects = {
375
+ "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",
376
+ "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",
377
+ "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"
378
+ }
379
+
380
+ 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")
381
+
382
+ # Create video from image with advanced effects
383
+ ffmpeg_cmd = [
384
+ 'ffmpeg', '-y',
385
+ '-loop', '1', '-i', image_path,
386
+ '-i', audio_path,
387
+ '-c:v', 'libx264',
388
+ '-preset', 'slow', # Better quality
389
+ '-crf', '18', # High quality (18 is very high quality)
390
+ '-pix_fmt', 'yuv420p',
391
+ '-profile:v', 'high',
392
+ '-level:v', '4.1',
393
+ '-vf', f'{zoom_filter},fps=30', # 30 FPS for smooth playback
394
+ '-c:a', 'aac',
395
+ '-b:a', '320k', # High quality audio
396
+ '-ac', '2', # Stereo
397
+ '-ar', '44100', # Standard sample rate
398
+ '-shortest',
399
+ '-movflags', '+faststart', # Optimize for web streaming
400
+ temp_video
401
+ ]
402
+
403
+ result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, timeout=300)
404
+
405
+ if result.returncode != 0:
406
+ print(f"FFmpeg error: {result.stderr}")
407
+ return self.fallback_video_creation(image_path, audio_path, output_path, duration)
408
+
409
+ # Add subtitles using FFmpeg
410
+ self.add_professional_subtitles(temp_video, text, output_path, duration, mood)
411
+
412
+ # Cleanup
413
+ if os.path.exists(temp_video):
414
+ os.remove(temp_video)
415
+
416
+ return True
417
+
418
+ except subprocess.TimeoutExpired:
419
+ print("FFmpeg timeout - falling back to basic method")
420
+ return self.fallback_video_creation(image_path, audio_path, output_path, duration)
421
+ except Exception as e:
422
+ print(f"FFmpeg video creation error: {e}")
423
+ return self.fallback_video_creation(image_path, audio_path, output_path, duration)
424
+
425
+ def add_professional_subtitles(self, video_path, text, output_path, duration, mood):
426
+ """Add professional subtitles using FFmpeg"""
427
+ try:
428
+ # Create subtitle file
429
+ subtitle_file = output_path.replace('.mp4', '.srt')
430
+
431
+ # Split text into chunks for better readability
432
+ words = text.split()
433
+ chunks = []
434
+ chunk_size = 8 # Words per subtitle
435
+
436
+ for i in range(0, len(words), chunk_size):
437
+ chunk = ' '.join(words[i:i + chunk_size])
438
+ chunks.append(chunk)
439
+
440
+ # Create SRT content
441
+ srt_content = ""
442
+ chunk_duration = duration / len(chunks) if chunks else duration
443
+
444
+ for i, chunk in enumerate(chunks):
445
+ start_time = i * chunk_duration
446
+ end_time = min((i + 1) * chunk_duration, duration)
447
+
448
+ start_srt = self.seconds_to_srt_time(start_time)
449
+ end_srt = self.seconds_to_srt_time(end_time)
450
+
451
+ srt_content += f"{i + 1}\n{start_srt} --> {end_srt}\n{chunk}\n\n"
452
+
453
+ # Write subtitle file
454
+ with open(subtitle_file, 'w', encoding='utf-8') as f:
455
+ f.write(srt_content)
456
+
457
+ # Subtitle style based on mood
458
+ subtitle_styles = {
459
+ "action": "FontSize=24,PrimaryColour=&H00FFFF&,OutlineColour=&H000000&,Outline=2",
460
+ "peaceful": "FontSize=22,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&,Outline=1",
461
+ "suspense": "FontSize=23,PrimaryColour=&H00FFFF&,OutlineColour=&H800000&,Outline=2"
462
+ }
463
+
464
+ style = subtitle_styles.get(mood, "FontSize=22,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&,Outline=2")
465
+
466
+ # Add subtitles to video
467
+ ffmpeg_subtitle_cmd = [
468
+ 'ffmpeg', '-y',
469
+ '-i', video_path,
470
+ '-vf', f"subtitles={subtitle_file}:force_style='{style}'",
471
+ '-c:a', 'copy',
472
+ '-c:v', 'libx264',
473
+ '-crf', '18',
474
+ output_path
475
+ ]
476
+
477
+ result = subprocess.run(ffmpeg_subtitle_cmd, capture_output=True, text=True, timeout=120)
478
+
479
+ # Cleanup subtitle file
480
+ if os.path.exists(subtitle_file):
481
+ os.remove(subtitle_file)
482
+
483
+ if result.returncode != 0:
484
+ print(f"Subtitle error: {result.stderr}")
485
+ # Just copy the video without subtitles
486
+ import shutil
487
+ shutil.copy(video_path, output_path)
488
+
489
+ except Exception as e:
490
+ print(f"Subtitle addition error: {e}")
491
+ # Fallback: copy video without subtitles
492
+ try:
493
+ import shutil
494
+ shutil.copy(video_path, output_path)
495
+ except:
496
+ pass
497
+
498
+ def seconds_to_srt_time(self, seconds):
499
+ """Convert seconds to SRT time format"""
500
+ hours = int(seconds // 3600)
501
+ minutes = int((seconds % 3600) // 60)
502
+ secs = int(seconds % 60)
503
+ millis = int((seconds % 1) * 1000)
504
+ return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"
505
+
506
+ def fallback_video_creation(self, image_path, audio_path, output_path, duration):
507
+ """Fallback video creation method"""
508
+ try:
509
+ from moviepy.editor import VideoFileClip, AudioFileClip, ImageClip
510
+
511
+ # Create video from image
512
+ image_clip = ImageClip(image_path, duration=duration)
513
+ audio_clip = AudioFileClip(audio_path)
514
+
515
+ # Combine
516
+ final_clip = image_clip.set_audio(audio_clip)
517
+ final_clip.write_videofile(
518
+ output_path,
519
+ codec='libx264',
520
+ audio_codec='aac',
521
+ fps=30,
522
+ bitrate='8M' # High bitrate for quality
523
+ )
524
+
525
+ # Cleanup
526
+ image_clip.close()
527
+ audio_clip.close()
528
+ final_clip.close()
529
+
530
+ return True
531
+
532
+ except Exception as e:
533
+ print(f"Fallback video creation error: {e}")
534
+ return False
535
+
536
+ def generate_complete_video(self, video_id, theme, duration):
537
+ """Generate complete high-quality video"""
538
+ try:
539
+ video_status[video_id] = {"status": "initializing", "progress": 5}
540
+
541
+ # Create output directory
542
+ output_dir = f"generated_videos/{video_id}"
543
+ os.makedirs(output_dir, exist_ok=True)
544
+
545
+ # Generate dynamic story using ChatGPT
546
+ video_status[video_id] = {"status": "generating_story_with_chatgpt", "progress": 10}
547
+ story_data = self.generate_dynamic_hindi_story(theme, duration)
548
+
549
+ scenes = story_data["scenes"]
550
+ total_scenes = len(scenes)
551
+ story_title = story_data.get("title", f"Hindi {theme.title()} Story")
552
+
553
+ video_status[video_id] = {
554
+ "status": "processing_scenes",
555
+ "progress": 20,
556
+ "total_scenes": total_scenes,
557
+ "story_title": story_title
558
+ }
559
+
560
+ scene_videos = []
561
+ total_duration = 0
562
+
563
+ # Process each scene
564
+ for i, scene in enumerate(scenes):
565
+ print(f"🎬 Processing scene {i+1}/{total_scenes}: {scene['hindi_text'][:50]}...")
566
+
567
+ # Update progress
568
+ progress = 20 + (i / total_scenes) * 65
569
+ video_status[video_id] = {
570
+ "status": f"processing_scene_{i+1}",
571
+ "progress": int(progress),
572
+ "current_scene": i+1,
573
+ "total_scenes": total_scenes,
574
+ "story_title": story_title
575
+ }
576
+
577
+ # File paths
578
+ audio_path = f"{output_dir}/scene_{i}_audio.wav"
579
+ image_path = f"{output_dir}/scene_{i}_image.png"
580
+ video_path = f"{output_dir}/scene_{i}_video.mp4"
581
+
582
+ # Generate enhanced audio
583
+ duration_sec = self.generate_enhanced_scene_audio(
584
+ scene['hindi_text'],
585
+ audio_path,
586
+ scene.get('mood', 'neutral')
587
+ )
588
+
589
+ # Generate high-quality image
590
+ success = self.generate_high_quality_scene_image(
591
+ scene['visual_prompt'],
592
+ scene.get('mood', 'neutral'),
593
+ image_path,
594
+ i
595
+ )
596
+
597
+ # Create professional video
598
+ if self.create_professional_video_with_ffmpeg(
599
+ image_path, audio_path, scene['hindi_text'],
600
+ video_path, duration_sec, scene.get('mood', 'neutral')
601
+ ):
602
+ scene_videos.append(video_path)
603
+ total_duration += duration_sec
604
+ print(f"✅ Scene {i+1} completed ({duration_sec:.1f}s)")
605
+ else:
606
+ print(f"❌ Scene {i+1} failed")
607
+
608
+ # Combine all scenes with professional transitions
609
+ video_status[video_id] = {"status": "combining_videos", "progress": 90}
610
+
611
+ if scene_videos:
612
+ final_path = f"{output_dir}/final_hindi_story_hd.mp4"
613
+
614
+ # Use FFmpeg for professional video concatenation
615
+ self.combine_videos_with_ffmpeg(scene_videos, final_path, story_title)
616
+
617
+ # Store video info
618
+ video_files[video_id] = {
619
+ "path": final_path,
620
+ "title": story_title,
621
+ "duration": total_duration,
622
+ "created": datetime.now().isoformat(),
623
+ "theme": theme,
624
+ "quality": "1080p",
625
+ "scenes": total_scenes
626
+ }
627
+
628
+ video_status[video_id] = {
629
+ "status": "completed",
630
+ "progress": 100,
631
+ "duration": total_duration,
632
+ "title": story_title
633
+ }
634
+ print(f"🎉 High-quality video {video_id} generated successfully!")
635
+ return final_path
636
+
637
+ except Exception as e:
638
+ video_status[video_id] = {"status": "failed", "progress": 0, "error": str(e)}
639
+ print(f"❌ Video generation failed: {e}")
640
+ return None
641
+
642
+ def combine_videos_with_ffmpeg(self, scene_videos, output_path, title):
643
+ """Combine videos using FFmpeg with professional quality"""
644
+ try:
645
+ # Create file list for FFmpeg
646
+ file_list_path = output_path.replace('.mp4', '_filelist.txt')
647
+
648
+ with open(file_list_path, 'w') as f:
649
+ for video in scene_videos:
650
+ f.write(f"file '{os.path.abspath(video)}'\n")
651
+
652
+ # FFmpeg concatenation command
653
+ ffmpeg_cmd = [
654
+ 'ffmpeg', '-y',
655
+ '-f', 'concat',
656
+ '-safe', '0',
657
+ '-i', file_list_path,
658
+ '-c:v', 'libx264',
659
+ '-preset', 'slow',
660
+ '-crf', '18', # High quality
661
+ '-c:a', 'aac',
662
+ '-b:a', '320k',
663
+ '-movflags', '+faststart',
664
+ output_path
665
+ ]
666
+
667
+ result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True, timeout=600)
668
+
669
+ # Cleanup
670
+ if os.path.exists(file_list_path):
671
+ os.remove(file_list_path)
672
+
673
+ if result.returncode != 0:
674
+ print(f"FFmpeg concatenation error: {result.stderr}")
675
+ return False
676
+
677
+ print(f"✅ Video combined successfully: {output_path}")
678
+ return True
679
+
680
+ except Exception as e:
681
+ print(f"Video combination error: {e}")
682
+ return False
683
+
684
+ # Initialize generator
685
+ generator = AdvancedHindiVideoGenerator()
686
+
687
+ # Enhanced HTML template
688
+ HTML_TEMPLATE = """
689
+ <!DOCTYPE html>
690
+ <html lang="en">
691
+ <head>
692
+ <meta charset="UTF-8">
693
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
694
+ <title>🎬 Professional Hindi Story Video Generator</title>
695
+ <style>
696
+ * { margin: 0; padding: 0; box-sizing: border-box; }
697
+ body {
698
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
699
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
700
+ min-height: 100vh;
701
+ padding: 20px;
702
+ }
703
+ .container { max-width: 900px; margin: 0 auto; }
704
+ .header {
705
+ text-align: center;
706
+ color: white;
707
+ margin-bottom: 30px;
708
+ }
709
+ .header h1 {
710
+ font-size: 3em;
711
+ margin-bottom: 10px;
712
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
713
+ }
714
+ .header p {
715
+ font-size: 1.2em;
716
+ opacity: 0.9;
717
+ }
718
+ .card {
719
+ background: white;
720
+ border-radius: 20px;
721
+ padding: 30px;
722
+ margin: 20px 0;
723
+ box-shadow: 0 15px 35px rgba(0,0,0,0.3);
724
+ }
725
+ .form-group { margin-bottom: 25px; }
726
+ label {
727
+ display: block;
728
+ margin-bottom: 8px;
729
+ font-weight: 600;
730
+ color: #555;
731
+ font-size: 1.1em;
732
+ }
733
+ select, input, textarea {
734
+ width: 100%;
735
+ padding: 15px;
736
+ border: 2px solid #ddd;
737
+ border-radius: 10px;
738
+ font-size: 16px;
739
+ transition: border-color 0.3s;
740
+ }
741
+ select:focus, input:focus, textarea:focus {
742
+ outline: none;
743
+ border-color: #667eea;
744
+ box-shadow: 0 0 10px rgba(102, 126, 234, 0.3);
745
+ }
746
+ .btn {
747
+ background: linear-gradient(45deg, #667eea, #764ba2);
748
+ color: white;
749
+ padding: 18px 30px;
750
+ border: none;
751
+ border-radius: 12px;
752
+ cursor: pointer;
753
+ font-size: 18px;
754
+ font-weight: bold;
755
+ width: 100%;
756
+ transition: all 0.3s;
757
+ }
758
+ .btn:hover {
759
+ transform: translateY(-3px);
760
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
761
+ }
762
+ .btn:disabled {
763
+ opacity: 0.6;
764
+ cursor: not-allowed;
765
+ transform: none;
766
+ }
767
+ .progress-container {
768
+ margin-top: 30px;
769
+ display: none;
770
+ }
771
+ .progress-bar {
772
+ width: 100%;
773
+ height: 30px;
774
+ background: #f0f0f0;
775
+ border-radius: 15px;
776
+ overflow: hidden;
777
+ position: relative;
778
+ }
779
+ .progress-fill {
780
+ height: 100%;
781
+ background: linear-gradient(45deg, #667eea, #764ba2);
782
+ transition: width 0.5s ease;
783
+ border-radius: 15px;
784
+ position: relative;
785
+ }
786
+ .progress-fill::after {
787
+ content: '';
788
+ position: absolute;
789
+ top: 0;
790
+ left: 0;
791
+ right: 0;
792
+ bottom: 0;
793
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
794
+ animation: shimmer 2s infinite;
795
+ }
796
+ @keyframes shimmer {
797
+ 0% { transform: translateX(-100%); }
798
+ 100% { transform: translateX(100%); }
799
+ }
800
+ .status {
801
+ text-align: center;
802
+ margin-top: 15px;
803
+ font-weight: 600;
804
+ color: #555;
805
+ font-size: 1.1em;
806
+ }
807
+ .video-list {
808
+ margin-top: 30px;
809
+ }
810
+ .video-item {
811
+ background: linear-gradient(135deg, #f8f9fa, #e9ecef);
812
+ padding: 25px;
813
+ border-radius: 15px;
814
+ margin: 15px 0;
815
+ display: flex;
816
+ justify-content: space-between;
817
+ align-items: center;
818
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
819
+ }
820
+ .video-info {
821
+ flex-grow: 1;
822
+ }
823
+ .video-title {
824
+ font-size: 1.3em;
825
+ font-weight: bold;
826
+ color: #333;
827
+ margin-bottom: 5px;
828
+ }
829
+ .video-details {
830
+ color: #666;
831
+ font-size: 0.95em;
832
+ }
833
+ .download-btn {
834
+ background: linear-gradient(45deg, #28a745, #20c997);
835
+ color: white;
836
+ padding: 12px 24px;
837
+ border: none;
838
+ border-radius: 8px;
839
+ text-decoration: none;
840
+ font-weight: bold;
841
+ transition: all 0.3s;
842
+ }
843
+ .download-btn:hover {
844
+ transform: translateY(-2px);
845
+ box-shadow: 0 5px 15px rgba(40, 167, 69, 0.4);
846
+ }
847
+ .feature-list {
848
+ background: linear-gradient(135deg, #e3f2fd, #f3e5f5);
849
+ padding: 20px;
850
+ border-radius: 15px;
851
+ margin: 20px 0;
852
+ }
853
+ .feature-list h3 {
854
+ color: #333;
855
+ margin-bottom: 15px;
856
+ text-align: center;
857
+ }
858
+ .features {
859
+ display: grid;
860
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
861
+ gap: 15px;
862
+ }
863
+ .feature {
864
+ text-align: center;
865
+ padding: 10px;
866
+ }
867
+ .feature-icon {
868
+ font-size: 2em;
869
+ margin-bottom: 10px;
870
+ }
871
+ .api-key-section {
872
+ background: #fff3cd;
873
+ padding: 20px;
874
+ border-radius: 10px;
875
+ margin-bottom: 20px;
876
+ border: 2px solid #ffeaa7;
877
+ }
878
+ .alert {
879
+ background: #d1ecf1;
880
+ border: 1px solid #bee5eb;
881
+ color: #0c5460;
882
+ padding: 15px;
883
+ border-radius: 8px;
884
+ margin-bottom: 20px;
885
+ }
886
+ </style>
887
+ </head>
888
+ <body>
889
+ <div class="container">
890
+ <div class="header">
891
+ <h1>🎬 Professional Hindi Story Generator</h1>
892
+ <p>Create High-Quality YouTube Videos with AI-Powered Stories</p>
893
+ </div>
894
+
895
+ <div class="card api-key-section">
896
+ <h3>🔑 OpenAI API Configuration</h3>
897
+ <div class="alert">
898
+ <strong>Important:</strong> Add your OpenAI API key to the Python file (line 23) before running the generator.
899
+ <br><strong>OPENAI_API_KEY = "your-api-key-here"</strong>
900
+ </div>
901
+ </div>
902
+
903
+ <div class="card feature-list">
904
+ <h3>✨ Professional Features</h3>
905
+ <div class="features">
906
+ <div class="feature">
907
+ <div class="feature-icon">🤖</div>
908
+ <strong>ChatGPT Stories</strong><br>
909
+ Dynamic, unique stories every time
910
+ </div>
911
+ <div class="feature">
912
+ <div class="feature-icon">🎥</div>
913
+ <strong>1080p Quality</strong><br>
914
+ Professional YouTube-ready videos
915
+ </div>
916
+ <div class="feature">
917
+ <div class="feature-icon">🎨</div>
918
+ <strong>AI Visuals</strong><br>
919
+ High-quality generated images
920
+ </div>
921
+ <div class="feature">
922
+ <div class="feature-icon">🔊</div>
923
+ <strong>Hindi Audio</strong><br>
924
+ Natural text-to-speech
925
+ </div>
926
+ <div class="feature">
927
+ <div class="feature-icon">⚡</div>
928
+ <strong>FFmpeg Power</strong><br>
929
+ Professional video processing
930
+ </div>
931
+ <div class="feature">
932
+ <div class="feature-icon">📱</div>
933
+ <strong>YouTube Ready</strong><br>
934
+ Optimized for social media
935
+ </div>
936
+ </div>
937
+ </div>
938
+
939
+ <div class="card">
940
+ <form id="videoForm">
941
+ <div class="form-group">
942
+ <label for="theme">🎭 Story Theme:</label>
943
+ <select id="theme" name="theme" required>
944
+ <option value="adventure">🗡️ Adventure (साहसिक कहानी)</option>
945
+ <option value="mystery">🔍 Mystery (रहस्यमय कहानी)</option>
946
+ <option value="family">👨‍👩‍👧‍👦 Family (पारिवारिक कहानी)</option>
947
+ <option value="friendship">🤝 Friendship (दोस्ती की कहानी)</option>
948
+ <option value="moral">📚 Moral Story (नैतिक कहानी)</option>
949
+ <option value="fantasy">🧚‍♀️ Fantasy (काल्पनिक कहानी)</option>
950
+ <option value="historical">🏛️ Historical (ऐतिहासिक कहानी)</option>
951
+ </select>
952
+ </div>
953
+
954
+ <div class="form-group">
955
+ <label for="duration">⏱️ Video Duration (minutes):</label>
956
+ <input type="number" id="duration" name="duration" min="10" max="25" value="15" required>
957
+ <small style="color: #666;">Recommended: 15-20 minutes for YouTube</small>
958
+ </div>
959
+
960
+ <div class="form-group">
961
+ <label for="style">🎨 Story Style:</label>
962
+ <select id="style" name="style">
963
+ <option value="cinematic">🎬 Cinematic</option>
964
+ <option value="animated">🎭 Animated Style</option>
965
+ <option value="realistic">📷 Realistic</option>
966
+ <option value="artistic">🎨 Artistic</option>
967
+ </select>
968
+ </div>
969
+
970
+ <div class="form-group">
971
+ <label for="custom_prompt">📝 Custom Story Elements (Optional):</label>
972
+ <textarea id="custom_prompt" name="custom_prompt" rows="3"
973
+ placeholder="Add specific characters, settings, or plot elements you want in your story..."></textarea>
974
+ </div>
975
+
976
+ <button type="submit" class="btn" id="generateBtn">
977
+ 🚀 Generate Professional Hindi Video
978
+ </button>
979
+ </form>
980
+
981
+ <div class="progress-container" id="progressContainer">
982
+ <div class="progress-bar">
983
+ <div class="progress-fill" id="progressFill"></div>
984
+ </div>
985
+ <div class="status" id="status">Initializing...</div>
986
+ <div id="sceneProgress" style="text-align: center; margin-top: 10px; font-size: 0.9em; color: #666;"></div>
987
+ </div>
988
+ </div>
989
+
990
+ <div class="card video-list" id="videoList">
991
+ <h2>📥 Generated Videos</h2>
992
+ <div id="videos"></div>
993
+ </div>
994
+ </div>
995
+
996
+ <script>
997
+ let currentVideoId = null;
998
+
999
+ document.getElementById('videoForm').addEventListener('submit', async function(e) {
1000
+ e.preventDefault();
1001
+
1002
+ const theme = document.getElementById('theme').value;
1003
+ const duration = document.getElementById('duration').value;
1004
+ const style = document.getElementById('style').value;
1005
+ const customPrompt = document.getElementById('custom_prompt').value;
1006
+ const btn = document.getElementById('generateBtn');
1007
+ const progressContainer = document.getElementById('progressContainer');
1008
+
1009
+ btn.disabled = true;
1010
+ btn.textContent = '⏳ Starting Generation...';
1011
+ progressContainer.style.display = 'block';
1012
+
1013
+ try {
1014
+ const response = await fetch('/generate', {
1015
+ method: 'POST',
1016
+ headers: { 'Content-Type': 'application/json' },
1017
+ body: JSON.stringify({
1018
+ theme,
1019
+ duration: parseInt(duration),
1020
+ style,
1021
+ custom_prompt: customPrompt
1022
+ })
1023
+ });
1024
+
1025
+ const data = await response.json();
1026
+
1027
+ if (data.error) {
1028
+ throw new Error(data.error);
1029
+ }
1030
+
1031
+ currentVideoId = data.video_id;
1032
+
1033
+ // Start progress tracking
1034
+ trackProgress();
1035
+
1036
+ } catch (error) {
1037
+ alert('Error starting video generation: ' + error.message);
1038
+ resetForm();
1039
+ }
1040
+ });
1041
+
1042
+ async function trackProgress() {
1043
+ if (!currentVideoId) return;
1044
+
1045
+ try {
1046
+ const response = await fetch(`/status/${currentVideoId}`);
1047
+ const data = await response.json();
1048
+
1049
+ const progressFill = document.getElementById('progressFill');
1050
+ const status = document.getElementById('status');
1051
+ const sceneProgress = document.getElementById('sceneProgress');
1052
+
1053
+ progressFill.style.width = data.progress + '%';
1054
+
1055
+ // Format status message
1056
+ let statusText = data.status.replace(/_/g, ' ').toUpperCase();
1057
+ if (data.story_title) {
1058
+ statusText += ` - ${data.story_title}`;
1059
+ }
1060
+ status.textContent = statusText;
1061
+
1062
+ // Show scene progress
1063
+ if (data.current_scene && data.total_scenes) {
1064
+ sceneProgress.textContent = `Scene ${data.current_scene} of ${data.total_scenes}`;
1065
+ } else {
1066
+ sceneProgress.textContent = '';
1067
+ }
1068
+
1069
+ if (data.status === 'completed') {
1070
+ status.textContent = '✅ Professional Video Generated Successfully!';
1071
+ if (data.duration) {
1072
+ sceneProgress.textContent = `Final Duration: ${Math.round(data.duration/60)} minutes`;
1073
+ }
1074
+ loadVideos();
1075
+ setTimeout(resetForm, 3000);
1076
+ } else if (data.status === 'failed') {
1077
+ status.textContent = '❌ Generation Failed: ' + (data.error || 'Unknown error');
1078
+ sceneProgress.textContent = '';
1079
+ setTimeout(resetForm, 5000);
1080
+ } else {
1081
+ setTimeout(trackProgress, 3000); // Check every 3 seconds
1082
+ }
1083
+
1084
+ } catch (error) {
1085
+ console.error('Progress tracking error:', error);
1086
+ setTimeout(trackProgress, 5000);
1087
+ }
1088
+ }
1089
+
1090
+ function resetForm() {
1091
+ const btn = document.getElementById('generateBtn');
1092
+ const progressContainer = document.getElementById('progressContainer');
1093
+
1094
+ btn.disabled = false;
1095
+ btn.textContent = '🚀 Generate Professional Hindi Video';
1096
+ setTimeout(() => {
1097
+ progressContainer.style.display = 'none';
1098
+ }, 3000);
1099
+ currentVideoId = null;
1100
+ }
1101
+
1102
+ async function loadVideos() {
1103
+ try {
1104
+ const response = await fetch('/videos');
1105
+ const videos = await response.json();
1106
+
1107
+ const videosContainer = document.getElementById('videos');
1108
+ videosContainer.innerHTML = '';
1109
+
1110
+ if (videos.length === 0) {
1111
+ videosContainer.innerHTML = '<p style="text-align: center; color: #666; padding: 20px;">No videos generated yet. Create your first professional Hindi story video!</p>';
1112
+ return;
1113
+ }
1114
+
1115
+ videos.forEach(video => {
1116
+ const item = document.createElement('div');
1117
+ item.className = 'video-item';
1118
+
1119
+ const duration = video.duration ? Math.round(video.duration/60) : 0;
1120
+ const fileSize = video.quality === '1080p' ? '~500-800 MB' : '~200-400 MB';
1121
+
1122
+ item.innerHTML = `
1123
+ <div class="video-info">
1124
+ <div class="video-title">${video.title}</div>
1125
+ <div class="video-details">
1126
+ 📊 Duration: ${duration} minutes |
1127
+ 🎥 Quality: ${video.quality || 'HD'} |
1128
+ 🎬 Scenes: ${video.scenes || 'Multiple'} |
1129
+ 📅 Created: ${new Date(video.created).toLocaleDateString()}
1130
+ <br>💾 Size: ${fileSize} | 🎭 Theme: ${video.theme || 'Story'}
1131
+ </div>
1132
+ </div>
1133
+ <a href="/download/${video.id}" class="download-btn">📥 Download HD</a>
1134
+ `;
1135
+ videosContainer.appendChild(item);
1136
+ });
1137
+
1138
+ } catch (error) {
1139
+ console.error('Error loading videos:', error);
1140
+ }
1141
+ }
1142
+
1143
+ // Load videos on page load
1144
+ loadVideos();
1145
+
1146
+ // Auto-refresh videos every 30 seconds
1147
+ setInterval(loadVideos, 30000);
1148
+ </script>
1149
+ </body>
1150
+ </html>
1151
+ """
1152
+
1153
+ @app.route('/')
1154
+ def index():
1155
+ return render_template_string(HTML_TEMPLATE)
1156
+
1157
+ @app.route('/generate', methods=['POST'])
1158
+ def generate_video():
1159
+ try:
1160
+ data = request.json
1161
+ theme = data.get('theme', 'adventure')
1162
+ duration = data.get('duration', 15)
1163
+ style = data.get('style', 'cinematic')
1164
+ custom_prompt = data.get('custom_prompt', '')
1165
+
1166
+ # Validate duration for Hugging Face resources
1167
+ if duration > 25:
1168
+ return jsonify({"error": "Duration must be 25 minutes or less"}), 400
1169
+
1170
+ # Check if OpenAI API key is configured
1171
+ if not OPENAI_API_KEY or OPENAI_API_KEY == "your-openai-api-key-here":
1172
+ return jsonify({"error": "Please configure your OpenAI API key in the code"}), 400
1173
+
1174
+ # Generate unique video ID
1175
+ video_id = str(uuid.uuid4())[:8]
1176
+
1177
+ # Start video generation in background
1178
+ def generate_async():
1179
+ load_models() # Ensure models are loaded
1180
+ generator.generate_complete_video(video_id, theme, duration)
1181
+
1182
+ thread = threading.Thread(target=generate_async)
1183
+ thread.daemon = True # Dies when main thread dies
1184
+ thread.start()
1185
+
1186
+ return jsonify({
1187
+ "status": "started",
1188
+ "video_id": video_id,
1189
+ "message": "Professional video generation started with ChatGPT stories"
1190
+ })
1191
+
1192
+ except Exception as e:
1193
+ return jsonify({"error": str(e)}), 500
1194
+
1195
+ @app.route('/status/<video_id>')
1196
+ def get_status(video_id):
1197
+ status = video_status.get(video_id, {"status": "not_found", "progress": 0})
1198
+ return jsonify(status)
1199
+
1200
+ @app.route('/videos')
1201
+ def list_videos():
1202
+ videos = []
1203
+ for vid_id, info in video_files.items():
1204
+ videos.append({
1205
+ "id": vid_id,
1206
+ "title": info["title"],
1207
+ "duration": info.get("duration", 0),
1208
+ "created": info["created"],
1209
+ "theme": info.get("theme", ""),
1210
+ "quality": info.get("quality", "HD"),
1211
+ "scenes": info.get("scenes", 0)
1212
+ })
1213
+
1214
+ # Sort by creation time, newest first
1215
+ videos.sort(key=lambda x: x["created"], reverse=True)
1216
+ return jsonify(videos)
1217
+
1218
+ @app.route('/download/<video_id>')
1219
+ def download_video(video_id):
1220
+ try:
1221
+ if video_id in video_files:
1222
+ file_path = video_files[video_id]["path"]
1223
+ if os.path.exists(file_path):
1224
+ return send_file(
1225
+ file_path,
1226
+ as_attachment=True,
1227
+ download_name=f"hindi_story_hd_{video_id}.mp4",
1228
+ mimetype='video/mp4'
1229
+ )
1230
+
1231
+ return jsonify({"error": "Video not found"}), 404
1232
+
1233
+ except Exception as e:
1234
+ return jsonify({"error": str(e)}), 500
1235
+
1236
+ @app.route('/health')
1237
+ def health_check():
1238
+ """Health check endpoint"""
1239
+ return jsonify({
1240
+ "status": "healthy",
1241
+ "models_loaded": models_loaded,
1242
+ "device": str(device) if device else "not_initialized",
1243
+ "active_generations": len(video_status),
1244
+ "total_videos": len(video_files)
1245
+ })
1246
+
1247
+ # Create necessary directories
1248
+ os.makedirs('generated_videos', exist_ok=True)
1249
+
1250
+ if __name__ == '__main__':
1251
+ print("""
1252
+ 🎬 Professional Hindi Story Video Generator with ChatGPT & FFmpeg
1253
+
1254
+ 📦 Install requirements:
1255
+ pip install flask torch transformers diffusers opencv-python pillow soundfile openai ffmpeg-python
1256
+
1257
+ 🔧 System requirements:
1258
+ - FFmpeg installed on system
1259
+ - CUDA GPU recommended (18GB RAM, 2 vCPU minimum)
1260
+ - OpenAI API key required
1261
+
1262
+ ✨ Features:
1263
+ - ChatGPT-powered dynamic stories
1264
+ - 1080p professional video quality
1265
+ - FFmpeg-based video processing
1266
+ - YouTube-ready output format
1267
+ - Professional subtitles and effects
1268
+ - Optimized for Hugging Face Spaces
1269
+
1270
+ 🚀 Starting server...
1271
+ Visit: http://localhost:5000
1272
+ """)
1273
+
1274
+ # Load models in background
1275
+ threading.Thread(target=load_models, daemon=True).start()
1276
+
1277
+ # Run with production settings for Hugging Face
1278
+ app.run(
1279
+ debug=False, # Disable debug for production
1280
+ host='0.0.0.0',
1281
+ port=int(os.environ.get('PORT', 7860)), # Hugging Face Spaces default port
1282
+ threaded=True,
1283
+ use_reloader=False
1284
+ )