karthikeya1212 commited on
Commit
82f04d1
·
verified ·
1 Parent(s): d88d679

Update core/story_script.py

Browse files
Files changed (1) hide show
  1. core/story_script.py +594 -593
core/story_script.py CHANGED
@@ -1,593 +1,594 @@
1
- # # # story_script.py
2
- # # import asyncio
3
- # # import json
4
- # # import logging
5
- # # import random
6
-
7
- # # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
8
-
9
- # # async def generate_story(script: str) -> dict:
10
- # # """
11
- # # Convert the ad script into a structured storyboard JSON format:
12
- # # - Characters
13
- # # - Scenes with keyframes, camera instructions, and music
14
- # # """
15
- # # # Sample character extraction (for simplicity, can be improved)
16
- # # characters = [{"name": name, "seed": idx+1} for idx, name in enumerate(["MainGuy", "Friend1", "Friend2"])]
17
-
18
- # # # Split script into lines and create scenes (basic heuristic, can be improved)
19
- # # lines = [line.strip() for line in script.split("\n") if line.strip()]
20
- # # scenes = {}
21
- # # for idx, line in enumerate(lines, start=1):
22
- # # char = characters[idx % len(characters)]["name"]
23
- # # seed = characters[idx % len(characters)]["seed"]
24
- # # scenes[f"scene{idx}"] = {
25
- # # "character": char,
26
- # # "scene": line,
27
- # # "keyframes": [
28
- # # {
29
- # # "seed": seed,
30
- # # "keyframe1": f"{char} in action based on script line: '{line[:40]}...'",
31
- # # "keyframe2": f"{char} expressive close-up reacting to: '{line[:40]}...'"
32
- # # }
33
- # # ],
34
- # # "camera": "Medium shot with dynamic zoom-ins",
35
- # # "music": "Appropriate upbeat or dramatic tune based on action"
36
- # # }
37
-
38
- # # storyboard = {
39
- # # "characters": characters,
40
- # # **scenes
41
- # # }
42
-
43
- # # logging.info("Story script generated successfully")
44
- # # return storyboard
45
-
46
-
47
- # # # best
48
- # # # story_script.py
49
- # # import os
50
- # # import asyncio
51
- # # import httpx
52
- # # import logging
53
- # # from dotenv import load_dotenv
54
- # # from dotenv import load_dotenv
55
-
56
- # # dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
57
- # # load_dotenv(dotenv_path)
58
-
59
- # # load_dotenv()
60
- # # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
61
-
62
- # # OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
63
- # # MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
64
- # # OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
65
-
66
- # # async def generate_story(script: str) -> dict:
67
- # # """
68
- # # Convert the ad script into a structured storyboard JSON using AI.
69
- # # The JSON format includes:
70
- # # - characters (with seeds)
71
- # # - scenes (character, scene description, keyframes, camera, music)
72
- # # """
73
- # # prompt = f"""
74
- # # You are a professional ad storyboard generator.
75
- # # Take this ad script and convert it into a **storyboard JSON**.
76
- # # Follow this format exactly:
77
-
78
- # # ADD {{
79
- # # "characters": [
80
- # # {{"name": "MainGuy", "seed": 1}},
81
- # # {{"name": "Friend1", "seed": 2}},
82
- # # {{"name": "Friend2", "seed": 3}}
83
- # # ],
84
- # # "scene1": {{
85
- # # "character": "MainGuy",
86
- # # "scene": "Description of the scene",
87
- # # "keyframes": [
88
- # # {{
89
- # # "seed": 1,
90
- # # "keyframe1": "First keyframe description",
91
- # # "keyframe2": "Second keyframe description"
92
- # # }}
93
- # # ],
94
- # # "camera": "Camera instructions",
95
- # # "music": "Music instructions"
96
- # # }}
97
- # # ...
98
- # # }}
99
-
100
- # # Ensure:
101
- # # - Use the **script lines as scenes**.
102
- # # - Assign characters logically to actions.
103
- # # - Provide **keyframes, camera, and music**.
104
- # # - Return **valid JSON only**, no extra text.
105
- # # Script:
106
- # # \"\"\"{script}\"\"\"
107
- # # """
108
-
109
- # # headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
110
- # # payload = {
111
- # # "model": MODEL_NAME,
112
- # # "messages": [{"role": "user", "content": prompt}],
113
- # # "temperature": 0.8,
114
- # # "max_tokens": 1200
115
- # # }
116
-
117
- # # async with httpx.AsyncClient(timeout=120) as client:
118
- # # response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
119
- # # response.raise_for_status()
120
- # # data = response.json()
121
-
122
- # # # AI returns the JSON as a string
123
- # # story_json_str = data["choices"][0]["message"]["content"]
124
-
125
- # # # Remove possible extra text before/after JSON (some AI outputs might wrap with "ADD {...}")
126
- # # if story_json_str.startswith("ADD"):
127
- # # story_json_str = story_json_str[story_json_str.find("{"):]
128
-
129
- # # # Convert string to dict
130
- # # try:
131
- # # story_dict = eval(story_json_str) # safe because AI returns JSON-like dict
132
- # # except Exception as e:
133
- # # logging.error(f"Failed to parse story JSON: {e}")
134
- # # story_dict = {}
135
-
136
- # # logging.info("Story script generated successfully using AI")
137
- # # return story_dict
138
-
139
-
140
-
141
-
142
- # # import os
143
- # # import asyncio
144
- # # import httpx
145
- # # import logging
146
- # # import json
147
- # # from dotenv import load_dotenv
148
-
149
- # # dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
150
- # # load_dotenv(dotenv_path)
151
-
152
- # # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
153
-
154
- # # OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
155
- # # MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
156
- # # OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
157
-
158
- # # async def generate_story(script: str) -> dict:
159
- # # """
160
- # # Convert the ad script into a structured storyboard JSON using AI.
161
- # # The JSON format includes:
162
- # # - characters (with seeds)
163
- # # - scenes (character, scene description, keyframes, camera, music)
164
- # # """
165
- # # prompt = f"""
166
- # # You are a professional ad storyboard generator.
167
- # # Take this ad script and convert it into a **storyboard JSON**.
168
- # # Follow this format exactly:
169
-
170
- # # ADD {{
171
- # # "characters": [
172
- # # {{"name": "MainGuy", "seed": 1}},
173
- # # {{"name": "Friend1", "seed": 2}},
174
- # # {{"name": "Friend2", "seed": 3}}
175
- # # ],
176
- # # "scene1": {{
177
- # # "character": "MainGuy",
178
- # # "scene": "Description of the scene",
179
- # # "keyframes": [
180
- # # {{
181
- # # "seed": 1,
182
- # # "keyframe1": "First keyframe description",
183
- # # "keyframe2": "Second keyframe description"
184
- # # }}
185
- # # ],
186
- # # "camera": "Camera instructions",
187
- # # "music": "Music instructions"
188
- # # }}
189
- # # ...
190
- # # }}
191
-
192
- # # Ensure:
193
- # # - Use the **script lines as scenes**.
194
- # # - Assign characters logically to actions.
195
- # # - Provide **keyframes, camera, and music**.
196
- # # - Return **valid JSON only**, no extra text, no markdown, no ``` fences.
197
- # # Script:
198
- # # \"\"\"{script}\"\"\"
199
- # # """
200
-
201
- # # headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
202
- # # payload = {
203
- # # "model": MODEL_NAME,
204
- # # "messages": [{"role": "user", "content": prompt}],
205
- # # "temperature": 0.8,
206
- # # "max_tokens": 1500
207
- # # }
208
-
209
- # # async with httpx.AsyncClient(timeout=120) as client:
210
- # # response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
211
- # # response.raise_for_status()
212
- # # data = response.json()
213
-
214
- # # story_json_str = data["choices"][0]["message"]["content"]
215
-
216
- # # # Clean unwanted wrappers
217
- # # story_json_str = story_json_str.strip()
218
- # # if story_json_str.startswith("ADD"):
219
- # # story_json_str = story_json_str[story_json_str.find("{"):]
220
-
221
- # # # Remove markdown fences
222
- # # story_json_str = story_json_str.replace("```json", "").replace("```", "").strip()
223
-
224
- # # # Parse safely
225
- # # try:
226
- # # story_dict = json.loads(story_json_str)
227
- # # except json.JSONDecodeError as e:
228
- # # logging.error(f"❌ Failed to parse story JSON properly: {e}")
229
- # # story_dict = {"raw_output": story_json_str}
230
-
231
- # # logging.info("Story script generated successfully using AI")
232
- # # return story_dict
233
-
234
-
235
- # import os
236
- # import asyncio
237
- # import httpx
238
- # import logging
239
- # import json
240
- # import re
241
- # from dotenv import load_dotenv
242
-
243
- # dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
244
- # load_dotenv(dotenv_path)
245
-
246
- # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
247
-
248
- # OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
249
- # MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
250
- # OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
251
-
252
-
253
- # async def generate_story(script: str) -> dict:
254
- # """
255
- # Convert the ad script into a structured storyboard JSON using AI.
256
- # Returns:
257
- # {
258
- # "raw_output": "<original AI string>",
259
- # "parsed_output": <dict | None>
260
- # }
261
- # """
262
- # prompt = f"""
263
- # You are a professional ad storyboard generator.
264
- # Take this ad script and convert it into a **storyboard JSON**.
265
- # Follow this format exactly:
266
-
267
- # ADD {{
268
- # "characters": [
269
- # {{"name": "MainGuy-", "seed": 1}},
270
- # {{"name": "Friend1", "seed": 2}},
271
- # {{"name": "Friend2", "seed": 3}}
272
- # ],
273
- # "scene1": {{
274
- # "character": "MainGuy",
275
- # "scene": "Description of the scene",
276
- # "keyframes": [
277
- # {{
278
- # "seed": 1,
279
- # "keyframe1": "First keyframe description",
280
- # "keyframe2": "Second keyframe description"
281
- # }}
282
- # ],
283
- # "camera": "Camera instructions",
284
- # "music": "Music instructions"
285
- # }}
286
- # }}
287
-
288
- # Ensure:
289
- # - Each scene corresponds to a line in the script.
290
- # - Assign logical characters.
291
- # - Return only valid JSON (no markdown or explanations).
292
- # Script:
293
- # \"\"\"{script}\"\"\"
294
- # """
295
-
296
- # headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
297
- # payload = {
298
- # "model": MODEL_NAME,
299
- # "messages": [{"role": "user", "content": prompt}],
300
- # "temperature": 0.8,
301
- # "max_tokens": 1500
302
- # }
303
-
304
- # async with httpx.AsyncClient(timeout=120) as client:
305
- # response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
306
- # response.raise_for_status()
307
- # data = response.json()
308
-
309
- # story_json_str = data["choices"][0]["message"]["content"].strip()
310
- # raw_output = story_json_str
311
-
312
- # # --- Cleaning Stage ---
313
- # story_json_str = story_json_str.strip()
314
- # if story_json_str.startswith("ADD"):
315
- # story_json_str = story_json_str[story_json_str.find("{"):]
316
-
317
- # # Remove markdown code fences and artifacts
318
- # story_json_str = story_json_str.replace("```json", "").replace("```", "").strip()
319
-
320
- # # Remove unwanted triple quotes, trailing commas, and unescaped slashes
321
- # story_json_str = re.sub(r',\s*}', '}', story_json_str)
322
- # story_json_str = re.sub(r',\s*\]', ']', story_json_str)
323
- # story_json_str = story_json_str.replace('\\"', '"').replace("\\'", "'")
324
-
325
- # parsed_story = None
326
-
327
- # # --- Parsing Stage ---
328
- # try:
329
- # parsed_story = json.loads(story_json_str)
330
- # except json.JSONDecodeError:
331
- # try:
332
- # # Handle double-encoded or escaped JSON
333
- # cleaned_str = bytes(story_json_str, "utf-8").decode("unicode_escape")
334
- # parsed_story = json.loads(cleaned_str)
335
- # except Exception as e:
336
- # logging.error(f"❌ JSON parse failed after cleaning: {e}")
337
- # parsed_story = None
338
-
339
- # logging.info("✅ Storyboard generation completed")
340
- # return parsed_story
341
-
342
-
343
- ##best best
344
- # import os
345
- # import asyncio
346
- # import httpx
347
- # import logging
348
- # import json
349
- # import re
350
- # from dotenv import load_dotenv
351
-
352
- # dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
353
- # load_dotenv(dotenv_path)
354
-
355
- # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
356
-
357
- # OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
358
- # MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
359
- # OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
360
-
361
-
362
- # async def generate_story(script: str) -> dict:
363
- # """
364
- # Convert the ad script into a structured storyboard JSON using AI.
365
- # Always returns:
366
- # {
367
- # "raw_output": "<original text from AI>",
368
- # "parsed_output": <dict or None>
369
- # }
370
- # """
371
- # prompt = f"""
372
- # You are a professional ad storyboard generator.
373
- # Convert this ad script into a **storyboard JSON** only — no extra text.
374
-
375
- # Format example:
376
- # {{
377
- # "characters": [
378
- # {{"name": "MainGuy","Description":"complete decsripiton of the character", "seed": 1}},
379
- # {{"name": "Dog","Description":"complete decsripiton of the character", "seed": 2}}
380
- # ],
381
- # "scene1": {{
382
- # "character": "MainGuy",
383
- # "scene": "Man wakes up late and rushes outside",
384
- # "keyframes": [
385
- # {{"seed": 1, "keyframe1": "Man stepping in puddle", "keyframe2": "Reaction close-up"}}
386
- # ],
387
- # "camera": "Medium shot with soft lighting",
388
- # "music": "Playful upbeat tune"
389
- # }}
390
- # }}
391
- # Script:
392
- # \"\"\"{script}\"\"\"
393
- # Return **only valid JSON**, no markdown or commentary.
394
- # """
395
-
396
- # headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
397
- # payload = {
398
- # "model": MODEL_NAME,
399
- # "messages": [{"role": "user", "content": prompt}],
400
- # "temperature": 0.7,
401
- # "max_tokens": 1500
402
- # }
403
-
404
- # async with httpx.AsyncClient(timeout=120) as client:
405
- # response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
406
- # response.raise_for_status()
407
- # data = response.json()
408
-
409
- # story_json_str = data["choices"][0]["message"]["content"].strip()
410
- # raw_output = story_json_str
411
-
412
- # # --- Clean the output ---
413
- # story_json_str = re.sub(r"^ADD\s*", "", story_json_str)
414
- # story_json_str = story_json_str.replace("```json", "").replace("```", "")
415
- # story_json_str = story_json_str.replace("“", "\"").replace("”", "\"").replace("’", "'")
416
- # story_json_str = re.sub(r",\s*([}\]])", r"\1", story_json_str) # remove trailing commas
417
- # story_json_str = story_json_str.strip()
418
-
419
- # # --- Parse the JSON safely ---
420
- # parsed_story = None
421
- # try:
422
- # parsed_story = json.loads(story_json_str)
423
- # except json.JSONDecodeError as e:
424
- # logging.warning(f"JSON parse failed: {e}")
425
- # try:
426
- # # try to find JSON substring in case AI wrapped it with text
427
- # match = re.search(r"\{.*\}", story_json_str, re.DOTALL)
428
- # if match:
429
- # parsed_story = json.loads(match.group(0))
430
- # except Exception as e2:
431
- # logging.error(f"Final parsing failed: {e2}")
432
- # parsed_story = None
433
-
434
- # logging.info("✅ Storyboard generation completed")
435
- # return parsed_story
436
-
437
-
438
-
439
-
440
-
441
- import os
442
- import asyncio
443
- import httpx
444
- import logging
445
- import json
446
- import re
447
- from dotenv import load_dotenv
448
-
449
- # --- Load environment variables ---
450
- dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
451
- load_dotenv(dotenv_path)
452
-
453
- # --- Configure logging ---
454
- logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
455
-
456
- # --- API Constants ---
457
- OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
458
- MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
459
- OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
460
-
461
-
462
- async def generate_story(script: str) -> dict:
463
- """
464
- Converts ad script into a structured storyboard JSON.
465
- Each scene = only one action.
466
- Reuses character seeds for consistency.
467
- Always returns a non-null dictionary.
468
- """
469
-
470
- prompt = f"""
471
- You are a professional storyboard generator for AI video production.
472
-
473
- Convert the given ad script into a JSON storyboard format.
474
- Each scene must represent only ONE clear action or emotional beat.
475
- Do not include any explanations, markdown, or text outside JSON.
476
-
477
- ### STRICT JSON FORMAT:
478
- {{
479
- "characters": [
480
- {{"name": "Man","description":"average build, brown hair, casual outfit","seed":1}},
481
- {{"name": "Librarian","description":"stern woman, cat-eye glasses, neat bun","seed":2}}
482
- ],
483
- "scene1": {{
484
- "character": "Man",
485
- "scene": "Man enters the library holding a bag of potato chips",
486
- "keyframes": [
487
- {{
488
- "seed": 1,
489
- "keyframe1": "Man walking into a quiet library, holding a bag of potato chips, warm sunlight from windows, calm mood",
490
- "keyframe2": "Side shot of man sitting down at a wooden table, casual expression, sunlight glows behind him"
491
- }}
492
- ],
493
- "camera": "Medium wide shot with soft natural lighting",
494
- "music": "Gentle ambient tune"
495
- }}
496
- }}
497
-
498
- Rules:
499
- - Each scene = ONE clear action only.
500
- - Use each character’s 'seed' consistently across scenes.
501
- - Each keyframe describes two cinematic angles of that action.
502
- - Keep descriptions detailed but realistic.
503
- - Return valid JSON only — no extra text, comments, or markdown.
504
-
505
- Script:
506
- \"\"\"{script}\"\"\"
507
- """
508
-
509
- headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
510
- payload = {
511
- "model": MODEL_NAME,
512
- "messages": [{"role": "user", "content": prompt}],
513
- "temperature": 0.7,
514
- "max_tokens": 1800
515
- }
516
-
517
- async with httpx.AsyncClient(timeout=180) as client:
518
- try:
519
- response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
520
- response.raise_for_status()
521
- data = response.json()
522
- except Exception as e:
523
- logging.error(f"API request failed: {e}")
524
- return {"error": "API request failed", "details": str(e)}
525
-
526
- story_json_str = data["choices"][0]["message"]["content"].strip()
527
-
528
- # --- Clean the model output ---
529
- story_json_str = re.sub(r"```(?:json)?", "", story_json_str)
530
- story_json_str = story_json_str.replace("“", "\"").replace("”", "\"").replace("’", "'")
531
- story_json_str = re.sub(r",\s*([}\]])", r"\1", story_json_str).strip()
532
-
533
- parsed_story = None
534
- try:
535
- parsed_story = json.loads(story_json_str)
536
- except json.JSONDecodeError as e:
537
- logging.warning(f"Initial JSON parse failed: {e}")
538
- match = re.search(r"\{.*\}", story_json_str, re.DOTALL)
539
- if match:
540
- try:
541
- parsed_story = json.loads(match.group(0))
542
- except Exception as e2:
543
- logging.error(f"Fallback parse failed: {e2}")
544
-
545
- # --- Final fallback: minimal structure ---
546
- if not parsed_story:
547
- logging.warning("Model output invalid, generating fallback JSON.")
548
- parsed_story = {
549
- "characters": [],
550
- "scene1": {
551
- "character": "Unknown",
552
- "scene": "Failed to parse script properly",
553
- "keyframes": [
554
- {
555
- "seed": 0,
556
- "keyframe1": "Generic placeholder image",
557
- "keyframe2": "Generic placeholder image"
558
- }
559
- ],
560
- "camera": "Static fallback frame",
561
- "music": "None"
562
- }
563
- }
564
-
565
- # --- Ensure consistent seeds & single-action scenes ---
566
- characters = {c.get("name"): c for c in parsed_story.get("characters", [])}
567
- for key, scene in parsed_story.items():
568
- if not key.startswith("scene"):
569
- continue
570
-
571
- char_name = scene.get("character")
572
- seed = characters.get(char_name, {}).get("seed", 0)
573
- desc = characters.get(char_name, {}).get("description", "")
574
-
575
- # Limit to one clear action
576
- scene_text = scene.get("scene", "")
577
- scene["scene"] = re.split(r"[,.] and |, then |;| but | while ", scene_text)[0].strip()
578
-
579
- # Keyframe corrections
580
- for kf in scene.get("keyframes", []):
581
- kf["seed"] = seed
582
- for i, k in enumerate(["keyframe1", "keyframe2"]):
583
- if not kf.get(k):
584
- angle = "wide shot" if i == 0 else "close-up"
585
- kf[k] = (
586
- f"{char_name} ({desc}) performing '{scene['scene']}', "
587
- f"{angle}, cinematic tone, photorealistic lighting"
588
- )
589
-
590
- logging.info("✅ Storyboard generated successfully with consistent single-action scenes.")
591
- return parsed_story
592
-
593
-
 
 
1
+ # # # story_script.py
2
+ # # import asyncio
3
+ # # import json
4
+ # # import logging
5
+ # # import random
6
+
7
+ # # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
8
+
9
+ # # async def generate_story(script: str) -> dict:
10
+ # # """
11
+ # # Convert the ad script into a structured storyboard JSON format:
12
+ # # - Characters
13
+ # # - Scenes with keyframes, camera instructions, and music
14
+ # # """
15
+ # # # Sample character extraction (for simplicity, can be improved)
16
+ # # characters = [{"name": name, "seed": idx+1} for idx, name in enumerate(["MainGuy", "Friend1", "Friend2"])]
17
+
18
+ # # # Split script into lines and create scenes (basic heuristic, can be improved)
19
+ # # lines = [line.strip() for line in script.split("\n") if line.strip()]
20
+ # # scenes = {}
21
+ # # for idx, line in enumerate(lines, start=1):
22
+ # # char = characters[idx % len(characters)]["name"]
23
+ # # seed = characters[idx % len(characters)]["seed"]
24
+ # # scenes[f"scene{idx}"] = {
25
+ # # "character": char,
26
+ # # "scene": line,
27
+ # # "keyframes": [
28
+ # # {
29
+ # # "seed": seed,
30
+ # # "keyframe1": f"{char} in action based on script line: '{line[:40]}...'",
31
+ # # "keyframe2": f"{char} expressive close-up reacting to: '{line[:40]}...'"
32
+ # # }
33
+ # # ],
34
+ # # "camera": "Medium shot with dynamic zoom-ins",
35
+ # # "music": "Appropriate upbeat or dramatic tune based on action"
36
+ # # }
37
+
38
+ # # storyboard = {
39
+ # # "characters": characters,
40
+ # # **scenes
41
+ # # }
42
+
43
+ # # logging.info("Story script generated successfully")
44
+ # # return storyboard
45
+
46
+
47
+ # # # best
48
+ # # # story_script.py
49
+ # # import os
50
+ # # import asyncio
51
+ # # import httpx
52
+ # # import logging
53
+ # # from dotenv import load_dotenv
54
+ # # from dotenv import load_dotenv
55
+
56
+ # # dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
57
+ # # load_dotenv(dotenv_path)
58
+
59
+ # # load_dotenv()
60
+ # # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
61
+
62
+ # # OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
63
+ # # MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
64
+ # # OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
65
+
66
+ # # async def generate_story(script: str) -> dict:
67
+ # # """
68
+ # # Convert the ad script into a structured storyboard JSON using AI.
69
+ # # The JSON format includes:
70
+ # # - characters (with seeds)
71
+ # # - scenes (character, scene description, keyframes, camera, music)
72
+ # # """
73
+ # # prompt = f"""
74
+ # # You are a professional ad storyboard generator.
75
+ # # Take this ad script and convert it into a **storyboard JSON**.
76
+ # # Follow this format exactly:
77
+
78
+ # # ADD {{
79
+ # # "characters": [
80
+ # # {{"name": "MainGuy", "seed": 1}},
81
+ # # {{"name": "Friend1", "seed": 2}},
82
+ # # {{"name": "Friend2", "seed": 3}}
83
+ # # ],
84
+ # # "scene1": {{
85
+ # # "character": "MainGuy",
86
+ # # "scene": "Description of the scene",
87
+ # # "keyframes": [
88
+ # # {{
89
+ # # "seed": 1,
90
+ # # "keyframe1": "First keyframe description",
91
+ # # "keyframe2": "Second keyframe description"
92
+ # # }}
93
+ # # ],
94
+ # # "camera": "Camera instructions",
95
+ # # "music": "Music instructions"
96
+ # # }}
97
+ # # ...
98
+ # # }}
99
+
100
+ # # Ensure:
101
+ # # - Use the **script lines as scenes**.
102
+ # # - Assign characters logically to actions.
103
+ # # - Provide **keyframes, camera, and music**.
104
+ # # - Return **valid JSON only**, no extra text.
105
+ # # Script:
106
+ # # \"\"\"{script}\"\"\"
107
+ # # """
108
+
109
+ # # headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
110
+ # # payload = {
111
+ # # "model": MODEL_NAME,
112
+ # # "messages": [{"role": "user", "content": prompt}],
113
+ # # "temperature": 0.8,
114
+ # # "max_tokens": 1200
115
+ # # }
116
+
117
+ # # async with httpx.AsyncClient(timeout=120) as client:
118
+ # # response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
119
+ # # response.raise_for_status()
120
+ # # data = response.json()
121
+
122
+ # # # AI returns the JSON as a string
123
+ # # story_json_str = data["choices"][0]["message"]["content"]
124
+
125
+ # # # Remove possible extra text before/after JSON (some AI outputs might wrap with "ADD {...}")
126
+ # # if story_json_str.startswith("ADD"):
127
+ # # story_json_str = story_json_str[story_json_str.find("{"):]
128
+
129
+ # # # Convert string to dict
130
+ # # try:
131
+ # # story_dict = eval(story_json_str) # safe because AI returns JSON-like dict
132
+ # # except Exception as e:
133
+ # # logging.error(f"Failed to parse story JSON: {e}")
134
+ # # story_dict = {}
135
+
136
+ # # logging.info("Story script generated successfully using AI")
137
+ # # return story_dict
138
+
139
+
140
+
141
+
142
+ # # import os
143
+ # # import asyncio
144
+ # # import httpx
145
+ # # import logging
146
+ # # import json
147
+ # # from dotenv import load_dotenv
148
+
149
+ # # dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
150
+ # # load_dotenv(dotenv_path)
151
+
152
+ # # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
153
+
154
+ # # OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
155
+ # # MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
156
+ # # OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
157
+
158
+ # # async def generate_story(script: str) -> dict:
159
+ # # """
160
+ # # Convert the ad script into a structured storyboard JSON using AI.
161
+ # # The JSON format includes:
162
+ # # - characters (with seeds)
163
+ # # - scenes (character, scene description, keyframes, camera, music)
164
+ # # """
165
+ # # prompt = f"""
166
+ # # You are a professional ad storyboard generator.
167
+ # # Take this ad script and convert it into a **storyboard JSON**.
168
+ # # Follow this format exactly:
169
+
170
+ # # ADD {{
171
+ # # "characters": [
172
+ # # {{"name": "MainGuy", "seed": 1}},
173
+ # # {{"name": "Friend1", "seed": 2}},
174
+ # # {{"name": "Friend2", "seed": 3}}
175
+ # # ],
176
+ # # "scene1": {{
177
+ # # "character": "MainGuy",
178
+ # # "scene": "Description of the scene",
179
+ # # "keyframes": [
180
+ # # {{
181
+ # # "seed": 1,
182
+ # # "keyframe1": "First keyframe description",
183
+ # # "keyframe2": "Second keyframe description"
184
+ # # }}
185
+ # # ],
186
+ # # "camera": "Camera instructions",
187
+ # # "music": "Music instructions"
188
+ # # }}
189
+ # # ...
190
+ # # }}
191
+
192
+ # # Ensure:
193
+ # # - Use the **script lines as scenes**.
194
+ # # - Assign characters logically to actions.
195
+ # # - Provide **keyframes, camera, and music**.
196
+ # # - Return **valid JSON only**, no extra text, no markdown, no ``` fences.
197
+ # # Script:
198
+ # # \"\"\"{script}\"\"\"
199
+ # # """
200
+
201
+ # # headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
202
+ # # payload = {
203
+ # # "model": MODEL_NAME,
204
+ # # "messages": [{"role": "user", "content": prompt}],
205
+ # # "temperature": 0.8,
206
+ # # "max_tokens": 1500
207
+ # # }
208
+
209
+ # # async with httpx.AsyncClient(timeout=120) as client:
210
+ # # response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
211
+ # # response.raise_for_status()
212
+ # # data = response.json()
213
+
214
+ # # story_json_str = data["choices"][0]["message"]["content"]
215
+
216
+ # # # Clean unwanted wrappers
217
+ # # story_json_str = story_json_str.strip()
218
+ # # if story_json_str.startswith("ADD"):
219
+ # # story_json_str = story_json_str[story_json_str.find("{"):]
220
+
221
+ # # # Remove markdown fences
222
+ # # story_json_str = story_json_str.replace("```json", "").replace("```", "").strip()
223
+
224
+ # # # Parse safely
225
+ # # try:
226
+ # # story_dict = json.loads(story_json_str)
227
+ # # except json.JSONDecodeError as e:
228
+ # # logging.error(f"❌ Failed to parse story JSON properly: {e}")
229
+ # # story_dict = {"raw_output": story_json_str}
230
+
231
+ # # logging.info("Story script generated successfully using AI")
232
+ # # return story_dict
233
+
234
+
235
+ # import os
236
+ # import asyncio
237
+ # import httpx
238
+ # import logging
239
+ # import json
240
+ # import re
241
+ # from dotenv import load_dotenv
242
+
243
+ # dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
244
+ # load_dotenv(dotenv_path)
245
+
246
+ # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
247
+
248
+ # OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
249
+ # MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
250
+ # OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
251
+
252
+
253
+ # async def generate_story(script: str) -> dict:
254
+ # """
255
+ # Convert the ad script into a structured storyboard JSON using AI.
256
+ # Returns:
257
+ # {
258
+ # "raw_output": "<original AI string>",
259
+ # "parsed_output": <dict | None>
260
+ # }
261
+ # """
262
+ # prompt = f"""
263
+ # You are a professional ad storyboard generator.
264
+ # Take this ad script and convert it into a **storyboard JSON**.
265
+ # Follow this format exactly:
266
+
267
+ # ADD {{
268
+ # "characters": [
269
+ # {{"name": "MainGuy-", "seed": 1}},
270
+ # {{"name": "Friend1", "seed": 2}},
271
+ # {{"name": "Friend2", "seed": 3}}
272
+ # ],
273
+ # "scene1": {{
274
+ # "character": "MainGuy",
275
+ # "scene": "Description of the scene",
276
+ # "keyframes": [
277
+ # {{
278
+ # "seed": 1,
279
+ # "keyframe1": "First keyframe description",
280
+ # "keyframe2": "Second keyframe description"
281
+ # }}
282
+ # ],
283
+ # "camera": "Camera instructions",
284
+ # "music": "Music instructions"
285
+ # }}
286
+ # }}
287
+
288
+ # Ensure:
289
+ # - Each scene corresponds to a line in the script.
290
+ # - Assign logical characters.
291
+ # - Return only valid JSON (no markdown or explanations).
292
+ # Script:
293
+ # \"\"\"{script}\"\"\"
294
+ # """
295
+
296
+ # headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
297
+ # payload = {
298
+ # "model": MODEL_NAME,
299
+ # "messages": [{"role": "user", "content": prompt}],
300
+ # "temperature": 0.8,
301
+ # "max_tokens": 1500
302
+ # }
303
+
304
+ # async with httpx.AsyncClient(timeout=120) as client:
305
+ # response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
306
+ # response.raise_for_status()
307
+ # data = response.json()
308
+
309
+ # story_json_str = data["choices"][0]["message"]["content"].strip()
310
+ # raw_output = story_json_str
311
+
312
+ # # --- Cleaning Stage ---
313
+ # story_json_str = story_json_str.strip()
314
+ # if story_json_str.startswith("ADD"):
315
+ # story_json_str = story_json_str[story_json_str.find("{"):]
316
+
317
+ # # Remove markdown code fences and artifacts
318
+ # story_json_str = story_json_str.replace("```json", "").replace("```", "").strip()
319
+
320
+ # # Remove unwanted triple quotes, trailing commas, and unescaped slashes
321
+ # story_json_str = re.sub(r',\s*}', '}', story_json_str)
322
+ # story_json_str = re.sub(r',\s*\]', ']', story_json_str)
323
+ # story_json_str = story_json_str.replace('\\"', '"').replace("\\'", "'")
324
+
325
+ # parsed_story = None
326
+
327
+ # # --- Parsing Stage ---
328
+ # try:
329
+ # parsed_story = json.loads(story_json_str)
330
+ # except json.JSONDecodeError:
331
+ # try:
332
+ # # Handle double-encoded or escaped JSON
333
+ # cleaned_str = bytes(story_json_str, "utf-8").decode("unicode_escape")
334
+ # parsed_story = json.loads(cleaned_str)
335
+ # except Exception as e:
336
+ # logging.error(f"❌ JSON parse failed after cleaning: {e}")
337
+ # parsed_story = None
338
+
339
+ # logging.info("✅ Storyboard generation completed")
340
+ # return parsed_story
341
+
342
+
343
+ ##best best
344
+ # import os
345
+ # import asyncio
346
+ # import httpx
347
+ # import logging
348
+ # import json
349
+ # import re
350
+ # from dotenv import load_dotenv
351
+
352
+ # dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
353
+ # load_dotenv(dotenv_path)
354
+
355
+ # logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
356
+
357
+ # OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
358
+ # MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
359
+ # OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
360
+
361
+
362
+ # async def generate_story(script: str) -> dict:
363
+ # """
364
+ # Convert the ad script into a structured storyboard JSON using AI.
365
+ # Always returns:
366
+ # {
367
+ # "raw_output": "<original text from AI>",
368
+ # "parsed_output": <dict or None>
369
+ # }
370
+ # """
371
+ # prompt = f"""
372
+ # You are a professional ad storyboard generator.
373
+ # Convert this ad script into a **storyboard JSON** only — no extra text.
374
+
375
+ # Format example:
376
+ # {{
377
+ # "characters": [
378
+ # {{"name": "MainGuy","Description":"complete decsripiton of the character", "seed": 1}},
379
+ # {{"name": "Dog","Description":"complete decsripiton of the character", "seed": 2}}
380
+ # ],
381
+ # "scene1": {{
382
+ # "character": "MainGuy",
383
+ # "scene": "Man wakes up late and rushes outside",
384
+ # "keyframes": [
385
+ # {{"seed": 1, "keyframe1": "Man stepping in puddle", "keyframe2": "Reaction close-up"}}
386
+ # ],
387
+ # "camera": "Medium shot with soft lighting",
388
+ # "music": "Playful upbeat tune"
389
+ # }}
390
+ # }}
391
+ # Script:
392
+ # \"\"\"{script}\"\"\"
393
+ # Return **only valid JSON**, no markdown or commentary.
394
+ # """
395
+
396
+ # headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
397
+ # payload = {
398
+ # "model": MODEL_NAME,
399
+ # "messages": [{"role": "user", "content": prompt}],
400
+ # "temperature": 0.7,
401
+ # "max_tokens": 1500
402
+ # }
403
+
404
+ # async with httpx.AsyncClient(timeout=120) as client:
405
+ # response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
406
+ # response.raise_for_status()
407
+ # data = response.json()
408
+
409
+ # story_json_str = data["choices"][0]["message"]["content"].strip()
410
+ # raw_output = story_json_str
411
+
412
+ # # --- Clean the output ---
413
+ # story_json_str = re.sub(r"^ADD\s*", "", story_json_str)
414
+ # story_json_str = story_json_str.replace("```json", "").replace("```", "")
415
+ # story_json_str = story_json_str.replace("“", "\"").replace("”", "\"").replace("’", "'")
416
+ # story_json_str = re.sub(r",\s*([}\]])", r"\1", story_json_str) # remove trailing commas
417
+ # story_json_str = story_json_str.strip()
418
+
419
+ # # --- Parse the JSON safely ---
420
+ # parsed_story = None
421
+ # try:
422
+ # parsed_story = json.loads(story_json_str)
423
+ # except json.JSONDecodeError as e:
424
+ # logging.warning(f"JSON parse failed: {e}")
425
+ # try:
426
+ # # try to find JSON substring in case AI wrapped it with text
427
+ # match = re.search(r"\{.*\}", story_json_str, re.DOTALL)
428
+ # if match:
429
+ # parsed_story = json.loads(match.group(0))
430
+ # except Exception as e2:
431
+ # logging.error(f"Final parsing failed: {e2}")
432
+ # parsed_story = None
433
+
434
+ # logging.info("✅ Storyboard generation completed")
435
+ # return parsed_story
436
+
437
+
438
+
439
+
440
+
441
+ import os
442
+ import asyncio
443
+ import httpx
444
+ import logging
445
+ import json
446
+ import re
447
+ from dotenv import load_dotenv
448
+
449
+ # --- Load environment variables ---
450
+ dotenv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
451
+ load_dotenv(dotenv_path)
452
+
453
+ # --- Configure logging ---
454
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
455
+
456
+ # --- API Constants ---
457
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
458
+ MODEL_NAME = "deepseek/deepseek-r1-distill-llama-70b:free"
459
+ OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
460
+
461
+
462
+ async def generate_story(script: str) -> dict:
463
+ """
464
+ Converts ad script into a structured storyboard JSON.
465
+ Each scene = only one action.
466
+ Reuses character seeds for consistency.
467
+ Always returns a non-null dictionary.
468
+ """
469
+
470
+ prompt = f"""
471
+ You are a professional storyboard generator for AI video production.
472
+
473
+ Convert the given ad script into a JSON storyboard format.
474
+ Each scene must represent only ONE clear action or emotional beat.
475
+ Do not include any explanations, markdown, or text outside JSON.
476
+
477
+ ### STRICT JSON FORMAT:
478
+ {{
479
+ "characters": [
480
+ {{"name": "Man","description":"average build, brown hair, casual outfit","seed":1}},
481
+ {{"name": "Librarian","description":"stern woman, cat-eye glasses, neat bun","seed":2}}
482
+ ],
483
+ "scene1": {{
484
+ "character": "Man",
485
+ "scene": "Man enters the library holding a bag of potato chips",
486
+ "keyframes": [
487
+ {{
488
+ "seed": 1,
489
+ "keyframe1": "Man walking into a quiet library, holding a bag of potato chips, warm sunlight from windows, calm mood",
490
+ "keyframe2": "Side shot of man sitting down at a wooden table, casual expression, sunlight glows behind him"
491
+ }}
492
+ ],
493
+ "camera": "Medium wide shot with soft natural lighting",
494
+ "music": "Gentle ambient tune"
495
+ }}
496
+ }}
497
+
498
+ Rules:
499
+ - Each scene = ONE clear action only.
500
+ - Use each character’s 'seed' consistently across scenes.
501
+ - Each keyframe describes two cinematic angles of that action.
502
+ - Keep descriptions detailed but realistic.
503
+ - Return valid JSON only — no extra text, comments, or markdown.
504
+
505
+ Script:
506
+ \"\"\"{script}\"\"\"
507
+ """
508
+
509
+ headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
510
+ payload = {
511
+ "model": MODEL_NAME,
512
+ "messages": [{"role": "user", "content": prompt}],
513
+ "temperature": 0.7,
514
+ "max_tokens": 1800
515
+ }
516
+
517
+ async with httpx.AsyncClient(timeout=180) as client:
518
+ try:
519
+ response = await client.post(OPENROUTER_URL, json=payload, headers=headers)
520
+ response.raise_for_status()
521
+ data = response.json()
522
+ except Exception as e:
523
+ logging.error(f"API request failed: {e}")
524
+ return {"error": "API request failed", "details": str(e)}
525
+
526
+ story_json_str = data["choices"][0]["message"]["content"].strip()
527
+
528
+ # --- Clean the model output ---
529
+ story_json_str = re.sub(r"```(?:json)?", "", story_json_str)
530
+ story_json_str = story_json_str.replace("“", "\"").replace("”", "\"").replace("’", "'")
531
+ story_json_str = re.sub(r",\s*([}\]])", r"\1", story_json_str).strip()
532
+
533
+ parsed_story = None
534
+ try:
535
+ parsed_story = json.loads(story_json_str)
536
+ except json.JSONDecodeError as e:
537
+ logging.warning(f"Initial JSON parse failed: {e}")
538
+ match = re.search(r"\{.*\}", story_json_str, re.DOTALL)
539
+ if match:
540
+ try:
541
+ parsed_story = json.loads(match.group(0))
542
+ except Exception as e2:
543
+ logging.error(f"Fallback parse failed: {e2}")
544
+
545
+ # --- Final fallback: minimal structure ---
546
+ if not parsed_story:
547
+ logging.warning("Model output invalid, generating fallback JSON.")
548
+ parsed_story = {
549
+ "characters": [],
550
+ "scene1": {
551
+ "character": "Unknown",
552
+ "scene": "Failed to parse script properly",
553
+ "keyframes": [
554
+ {
555
+ "seed": 0,
556
+ "keyframe1": "Generic placeholder image",
557
+ "keyframe2": "Generic placeholder image"
558
+ }
559
+ ],
560
+ "camera": "Static fallback frame",
561
+ "music": "None"
562
+ }
563
+ }
564
+
565
+ # --- Ensure consistent seeds & single-action scenes ---
566
+ characters = {c.get("name"): c for c in parsed_story.get("characters", [])}
567
+ for key, scene in parsed_story.items():
568
+ if not key.startswith("scene"):
569
+ continue
570
+
571
+ char_name = scene.get("character")
572
+ seed = characters.get(char_name, {}).get("seed", 0)
573
+ desc = characters.get(char_name, {}).get("description", "")
574
+
575
+ # Limit to one clear action
576
+ scene_text = scene.get("scene", "")
577
+ scene["scene"] = re.split(r"[,.] and |, then |;| but | while ", scene_text)[0].strip()
578
+
579
+ # Keyframe corrections
580
+ for kf in scene.get("keyframes", []):
581
+ kf["seed"] = seed
582
+ for i, k in enumerate(["keyframe1", "keyframe2"]):
583
+ if not kf.get(k):
584
+ angle = "wide shot" if i == 0 else "close-up"
585
+ kf[k] = (
586
+ f"{char_name} ({desc}) performing '{scene['scene']}', "
587
+ f"{angle}, cinematic tone, photorealistic lighting"
588
+ )
589
+
590
+ logging.info("✅ Storyboard generated successfully with consistent single-action scenes.")
591
+ print(parsed_story)
592
+ return parsed_story
593
+
594
+