Commit ·
902efbe
1
Parent(s): c8b39c5
Major Story Reels update: Direct script mode, sticky animation style, improved scene continuity prompts
Browse files
modules/story_reels/router.py
CHANGED
|
@@ -34,23 +34,22 @@ router = APIRouter()
|
|
| 34 |
response_model=GenerateVideoResponse,
|
| 35 |
status_code=201,
|
| 36 |
summary="Create a new story reel",
|
| 37 |
-
description="Create a
|
| 38 |
)
|
| 39 |
async def create_story_reel(request: GenerateVideoRequest):
|
| 40 |
"""
|
| 41 |
-
Create a new story reel from
|
| 42 |
|
| 43 |
-
- AI generates script from topic
|
| 44 |
- Converts script to speech (TTS)
|
| 45 |
- Generates captions (Whisper)
|
| 46 |
- Creates AI images (NVIDIA/Cloudflare)
|
| 47 |
- Composes final video (MoviePy)
|
| 48 |
"""
|
| 49 |
try:
|
| 50 |
-
logger.info(f"Creating story reel
|
| 51 |
|
| 52 |
video_id = story_creator.add_to_queue(
|
| 53 |
-
|
| 54 |
image_style=request.image_style.value,
|
| 55 |
voice=request.voice
|
| 56 |
)
|
|
|
|
| 34 |
response_model=GenerateVideoResponse,
|
| 35 |
status_code=201,
|
| 36 |
summary="Create a new story reel",
|
| 37 |
+
description="Create a story video from script. Returns video_id to track progress."
|
| 38 |
)
|
| 39 |
async def create_story_reel(request: GenerateVideoRequest):
|
| 40 |
"""
|
| 41 |
+
Create a new story reel from script.
|
| 42 |
|
|
|
|
| 43 |
- Converts script to speech (TTS)
|
| 44 |
- Generates captions (Whisper)
|
| 45 |
- Creates AI images (NVIDIA/Cloudflare)
|
| 46 |
- Composes final video (MoviePy)
|
| 47 |
"""
|
| 48 |
try:
|
| 49 |
+
logger.info(f"Creating story reel from direct script ({len(request.script)} chars)")
|
| 50 |
|
| 51 |
video_id = story_creator.add_to_queue(
|
| 52 |
+
script=request.script,
|
| 53 |
image_style=request.image_style.value,
|
| 54 |
voice=request.voice
|
| 55 |
)
|
modules/story_reels/schemas.py
CHANGED
|
@@ -14,6 +14,7 @@ class StyleEnum(str, Enum):
|
|
| 14 |
cartoon = "cartoon"
|
| 15 |
realistic = "realistic"
|
| 16 |
watercolor = "watercolor"
|
|
|
|
| 17 |
|
| 18 |
|
| 19 |
class CameraEnum(str, Enum):
|
|
@@ -80,8 +81,8 @@ class GeneratedScene(BaseModel):
|
|
| 80 |
# ===================
|
| 81 |
|
| 82 |
class GenerateVideoRequest(BaseModel):
|
| 83 |
-
"""Main video generation request -
|
| 84 |
-
|
| 85 |
image_style: StyleEnum = Field(StyleEnum.semi_realistic, description="Image generation style")
|
| 86 |
voice: str = Field("af_heart", description="TTS voice")
|
| 87 |
|
|
|
|
| 14 |
cartoon = "cartoon"
|
| 15 |
realistic = "realistic"
|
| 16 |
watercolor = "watercolor"
|
| 17 |
+
sticky_animation = "sticky animation"
|
| 18 |
|
| 19 |
|
| 20 |
class CameraEnum(str, Enum):
|
|
|
|
| 81 |
# ===================
|
| 82 |
|
| 83 |
class GenerateVideoRequest(BaseModel):
|
| 84 |
+
"""Main video generation request - direct script mode"""
|
| 85 |
+
script: str = Field(..., description="Voice script text (will be converted to TTS directly)")
|
| 86 |
image_style: StyleEnum = Field(StyleEnum.semi_realistic, description="Image generation style")
|
| 87 |
voice: str = Field("af_heart", description="TTS voice")
|
| 88 |
|
modules/story_reels/services/script_generator.py
CHANGED
|
@@ -151,16 +151,31 @@ Your task: Generate detailed image prompts for each 2-second scene of a story vi
|
|
| 151 |
CONTEXT:
|
| 152 |
- Full story script is provided so you understand the narrative
|
| 153 |
- Each 2-second chunk needs a visual prompt
|
| 154 |
-
-
|
| 155 |
-
-
|
| 156 |
|
| 157 |
-
RULES FOR
|
| 158 |
-
1.
|
| 159 |
-
2.
|
| 160 |
-
3.
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
OUTPUT FORMAT:
|
| 166 |
Return ONLY valid JSON array, no markdown, no explanation:
|
|
|
|
| 151 |
CONTEXT:
|
| 152 |
- Full story script is provided so you understand the narrative
|
| 153 |
- Each 2-second chunk needs a visual prompt
|
| 154 |
+
- Images will play in SEQUENCE to tell a story
|
| 155 |
+
- All images MUST look like they belong to the SAME VIDEO
|
| 156 |
|
| 157 |
+
CRITICAL RULES FOR CONSISTENCY:
|
| 158 |
+
1. SAME STYLE: Every prompt MUST start with the exact style name (e.g., "semi-realistic style", "anime style", "sticky animation style")
|
| 159 |
+
2. SAME CHARACTER: If a character is described, use IDENTICAL description in EVERY prompt (same clothes, hair, face features)
|
| 160 |
+
3. SCENE CONTINUITY: Each scene should logically follow the previous one
|
| 161 |
+
- Example: Scene 1 "boy picking up bag" → Scene 2 "boy walking with bag on shoulder" → Scene 3 "boy approaching school gate"
|
| 162 |
+
4. CONSISTENT LIGHTING: Use same lighting style across all scenes
|
| 163 |
+
5. CONSISTENT COLOR PALETTE: Maintain similar color tones
|
| 164 |
+
|
| 165 |
+
PROMPT STRUCTURE:
|
| 166 |
+
1. [STYLE] - Always start with style (e.g., "semi-realistic style artwork")
|
| 167 |
+
2. [CHARACTER] - Describe the character with exact same details every time
|
| 168 |
+
3. [ACTION] - What's happening in THIS specific 2-second moment
|
| 169 |
+
4. [ENVIRONMENT] - Where is this taking place
|
| 170 |
+
5. [CAMERA] - Camera angle (close-up, medium shot, wide shot)
|
| 171 |
+
6. [LIGHTING & MOOD] - Lighting and emotional atmosphere
|
| 172 |
+
7. [QUALITY TAGS] - high quality, detailed, cinematic, 8k
|
| 173 |
+
|
| 174 |
+
CONTINUITY TIPS:
|
| 175 |
+
- If character was sitting, show transition to standing (not jumping to running)
|
| 176 |
+
- Keep background elements consistent (same room, same street)
|
| 177 |
+
- Props should persist (if bag appeared, keep showing it)
|
| 178 |
+
- Time progression should be logical
|
| 179 |
|
| 180 |
OUTPUT FORMAT:
|
| 181 |
Return ONLY valid JSON array, no markdown, no explanation:
|
modules/story_reels/services/story_creator.py
CHANGED
|
@@ -58,13 +58,16 @@ class StoryCreator:
|
|
| 58 |
|
| 59 |
def add_to_queue(
|
| 60 |
self,
|
| 61 |
-
|
| 62 |
image_style: str = "semi-realistic",
|
| 63 |
voice: str = "af_heart"
|
| 64 |
) -> str:
|
| 65 |
"""
|
| 66 |
Add story to generation queue.
|
| 67 |
|
|
|
|
|
|
|
|
|
|
| 68 |
Returns:
|
| 69 |
job_id for tracking
|
| 70 |
"""
|
|
@@ -72,7 +75,7 @@ class StoryCreator:
|
|
| 72 |
|
| 73 |
job = {
|
| 74 |
"id": job_id,
|
| 75 |
-
"
|
| 76 |
"image_style": image_style,
|
| 77 |
"voice": voice,
|
| 78 |
"status": JobStatus.queued,
|
|
@@ -195,19 +198,14 @@ class StoryCreator:
|
|
| 195 |
|
| 196 |
try:
|
| 197 |
# ====================
|
| 198 |
-
# Step
|
| 199 |
# ====================
|
| 200 |
-
|
|
|
|
| 201 |
job["progress"] = 5
|
| 202 |
|
| 203 |
-
script = self.script_gen.generate_script(
|
| 204 |
-
topic=job["topic"],
|
| 205 |
-
max_chars=1000
|
| 206 |
-
)
|
| 207 |
-
logger.info(f"[{job_id}] Generated script: {len(script)} chars")
|
| 208 |
-
|
| 209 |
# ====================
|
| 210 |
-
# Step
|
| 211 |
# ====================
|
| 212 |
job["status"] = JobStatus.generating_audio
|
| 213 |
job["progress"] = 10
|
|
|
|
| 58 |
|
| 59 |
def add_to_queue(
|
| 60 |
self,
|
| 61 |
+
script: str,
|
| 62 |
image_style: str = "semi-realistic",
|
| 63 |
voice: str = "af_heart"
|
| 64 |
) -> str:
|
| 65 |
"""
|
| 66 |
Add story to generation queue.
|
| 67 |
|
| 68 |
+
Args:
|
| 69 |
+
script: Voice script text (will be converted to TTS directly)
|
| 70 |
+
|
| 71 |
Returns:
|
| 72 |
job_id for tracking
|
| 73 |
"""
|
|
|
|
| 75 |
|
| 76 |
job = {
|
| 77 |
"id": job_id,
|
| 78 |
+
"script": script,
|
| 79 |
"image_style": image_style,
|
| 80 |
"voice": voice,
|
| 81 |
"status": JobStatus.queued,
|
|
|
|
| 198 |
|
| 199 |
try:
|
| 200 |
# ====================
|
| 201 |
+
# Step 1: Use Script Directly (No AI Generation)
|
| 202 |
# ====================
|
| 203 |
+
script = job["script"]
|
| 204 |
+
logger.info(f"[{job_id}] Using direct script: {len(script)} chars")
|
| 205 |
job["progress"] = 5
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
# ====================
|
| 208 |
+
# Step 2: Generate TTS
|
| 209 |
# ====================
|
| 210 |
job["status"] = JobStatus.generating_audio
|
| 211 |
job["progress"] = 10
|
static/index.html
CHANGED
|
@@ -285,12 +285,12 @@
|
|
| 285 |
|
| 286 |
<form id="storyForm">
|
| 287 |
<div class="form-group">
|
| 288 |
-
<label>
|
| 289 |
-
<textarea id="
|
| 290 |
-
placeholder="e.g.,
|
| 291 |
required></textarea>
|
| 292 |
-
<small style="color: var(--text-secondary);">
|
| 293 |
-
|
| 294 |
</div>
|
| 295 |
|
| 296 |
<div class="form-row">
|
|
@@ -302,6 +302,7 @@
|
|
| 302 |
<option value="cartoon">Cartoon</option>
|
| 303 |
<option value="realistic">Realistic</option>
|
| 304 |
<option value="watercolor">Watercolor</option>
|
|
|
|
| 305 |
</select>
|
| 306 |
</div>
|
| 307 |
<div class="form-group">
|
|
@@ -479,7 +480,7 @@
|
|
| 479 |
status.classList.remove('hidden');
|
| 480 |
|
| 481 |
const data = {
|
| 482 |
-
|
| 483 |
image_style: document.getElementById('storyStyle').value,
|
| 484 |
voice: document.getElementById('storyVoice').value
|
| 485 |
};
|
|
|
|
| 285 |
|
| 286 |
<form id="storyForm">
|
| 287 |
<div class="form-group">
|
| 288 |
+
<label>Voice Script *</label>
|
| 289 |
+
<textarea id="storyScript" rows="4"
|
| 290 |
+
placeholder="e.g., Have you ever wondered why some people seem to attract success effortlessly? The secret lies in their mindset. When you believe in yourself, opportunities start flowing toward you..."
|
| 291 |
required></textarea>
|
| 292 |
+
<small style="color: var(--text-secondary);">Enter the exact script you want converted to voice.
|
| 293 |
+
This will be spoken directly.</small>
|
| 294 |
</div>
|
| 295 |
|
| 296 |
<div class="form-row">
|
|
|
|
| 302 |
<option value="cartoon">Cartoon</option>
|
| 303 |
<option value="realistic">Realistic</option>
|
| 304 |
<option value="watercolor">Watercolor</option>
|
| 305 |
+
<option value="sticky animation">Sticky Animation</option>
|
| 306 |
</select>
|
| 307 |
</div>
|
| 308 |
<div class="form-group">
|
|
|
|
| 480 |
status.classList.remove('hidden');
|
| 481 |
|
| 482 |
const data = {
|
| 483 |
+
script: document.getElementById('storyScript').value,
|
| 484 |
image_style: document.getElementById('storyStyle').value,
|
| 485 |
voice: document.getElementById('storyVoice').value
|
| 486 |
};
|