Spaces:
Sleeping
Sleeping
File size: 13,208 Bytes
91d209c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
"""
GPT-4o Prompt Generation API
Structured, validated segment generation for video prompts
"""
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from typing import Optional
import base64
from utils.prompt_generator import (
VeoInputs,
generate_segments_payload,
split_script_into_segments
)
from openai import OpenAI
import os
import json
router = APIRouter()
class PromptGenerationRequest(BaseModel):
"""Request for prompt generation"""
script: str
style: str = "clean, lifestyle UGC"
jsonFormat: str = "standard"
continuationMode: bool = True
voiceType: Optional[str] = None
energyLevel: Optional[str] = None
settingMode: str = "single"
cameraStyle: Optional[str] = "handheld steadicam"
energyArc: Optional[str] = None
narrativeStyle: Optional[str] = "direct address"
accentRegion: Optional[str] = None
model: str = "gpt-4o"
@router.post("/generate-prompts")
async def generate_prompts_api(
script: str = Form(...),
style: str = Form("clean, lifestyle UGC"),
jsonFormat: str = Form("standard"),
continuationMode: str = Form("true"),
voiceType: Optional[str] = Form(None),
energyLevel: Optional[str] = Form(None),
settingMode: str = Form("single"),
cameraStyle: Optional[str] = Form("handheld steadicam"),
energyArc: Optional[str] = Form(None),
narrativeStyle: Optional[str] = Form("direct address"),
accentRegion: Optional[str] = Form(None),
model: str = Form("gpt-4o"),
image: UploadFile = File(...)
):
"""
Generate structured video prompts using GPT-4o
This endpoint:
1. Splits the script into 8-second segments
2. Generates detailed production prompts using GPT-4o
3. Validates the output against strict rules
4. Returns structured JSON for video generation
Accepts multipart/form-data with:
- script: The video script text
- style: Visual style description
- image: Character reference image (required)
- Other optional parameters for fine-tuning
Returns:
Validated segments payload ready for video generation
"""
try:
# Read image
image_bytes = await image.read()
print(f"π· Received reference image: {len(image_bytes)} bytes")
# Convert continuationMode string to boolean
continuation_mode = continuationMode.lower() == "true"
# Create inputs from form data
inputs = VeoInputs(
script=script,
style=style,
jsonFormat=jsonFormat,
continuationMode=continuation_mode,
voiceType=voiceType if voiceType else None,
energyLevel=energyLevel if energyLevel else None,
settingMode=settingMode,
cameraStyle=cameraStyle if cameraStyle else None,
energyArc=energyArc if energyArc else None,
narrativeStyle=narrativeStyle if narrativeStyle else None,
accentRegion=accentRegion if accentRegion else None
)
# Check environment mode
environment = os.getenv('ENVIRONMENT', 'dev').lower()
is_dev_mode = environment == 'dev' or environment == 'development'
# Generate payload
payload = generate_segments_payload(
inputs=inputs,
image_bytes=image_bytes,
model=model
)
# Add environment mode to response
payload['environment'] = environment
payload['is_dev_mode'] = is_dev_mode
payload['max_segments'] = 2 if is_dev_mode else None
# Validation warnings (if any) are logged to console but don't block
return JSONResponse(content=payload)
except Exception as e:
# API/network errors only (validation is non-blocking now)
raise HTTPException(
status_code=500,
detail=f"Prompt generation failed: {str(e)}"
)
@router.post("/split-script")
async def split_script_api(
script: str = Form(...),
seconds_per_segment: int = Form(8),
words_per_second: float = Form(2.2)
):
"""
Split script into segments for preview
Useful for checking how the script will be divided before generation
"""
try:
segments = split_script_into_segments(
script,
seconds_per_segment=seconds_per_segment,
words_per_second=words_per_second
)
return {
"segments": segments,
"count": len(segments),
"total_words": sum(len(s.split()) for s in segments)
}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Script splitting failed: {str(e)}"
)
@router.post("/validate-payload")
async def validate_payload_api(payload: dict):
"""
Validate a segments payload against strict rules
Use this to check if a manually created or modified payload is valid
"""
try:
from utils.prompt_generator import validate_segments_payload
expected_segments = len(payload.get("segments", []))
errors = validate_segments_payload(payload, expected_segments)
if errors:
return {
"valid": False,
"errors": errors
}
return {
"valid": True,
"message": "Payload is valid"
}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Validation failed: {str(e)}"
)
@router.get("/prompt-status")
async def prompt_status():
"""
Check if GPT-4o prompt generation is available
"""
import os
openai_key = os.getenv('OPENAI_API_KEY')
return {
"available": bool(openai_key),
"message": "GPT-4o is configured" if openai_key
else "Add OPENAI_API_KEY to .env.local"
}
@router.post("/refine-prompt-continuity")
async def refine_prompt_for_continuity(
segmentPrompt: str = Form(...), # JSON string of the next segment
lastFrame: UploadFile = File(...), # Last frame image from previous video
transcribedDialogue: str = Form(default=""), # Whisper transcription from previous segment
expectedDialogue: str = Form(default="") # Expected dialogue from previous segment
):
"""
Refine a segment prompt to match the actual visual AND audio from the previous segment.
This ensures perfect continuity by having GPT-4o analyze:
1. The last frame (visual consistency)
2. The transcribed dialogue (audio consistency - what was actually said)
"""
try:
# Read the image
image_bytes = await lastFrame.read()
encoded_image = base64.b64encode(image_bytes).decode('utf-8')
# Parse the segment prompt
try:
segment_data = json.loads(segmentPrompt)
except json.JSONDecodeError:
raise HTTPException(
status_code=400,
detail="Invalid JSON in segmentPrompt"
)
# Initialize OpenAI client
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
# Build audio context if available
audio_context = ""
if transcribedDialogue.strip():
audio_context = f"""
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
AUDIO CONTINUITY CONTEXT (WHAT WAS ACTUALLY SPOKEN)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Previous segment's dialogue (from Whisper transcription):
\"{transcribedDialogue.strip()}\"
Expected dialogue was:
\"{expectedDialogue.strip() if expectedDialogue.strip() else 'Not provided'}\"
IMPORTANT: The next segment should continue naturally from what was ACTUALLY said.
If there are differences between expected and transcribed dialogue, use the TRANSCRIBED version
as the ground truth for continuity (it's what the viewer actually heard).
"""
# Build the refinement prompt
refinement_instructions = f"""
You are a video continuity expert. Your task is to UPDATE the provided segment prompt to ensure PERFECT VISUAL AND AUDIO CONTINUITY with the previous video segment.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
VISUAL CONTINUITY (from attached image)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Analyze the image carefully - this is the ACTUAL last frame from the previous video.
1. Update the character_description to match the ACTUAL person in the image:
- Physical appearance (EXACT age, hair color/style, facial features, skin tone)
- Clothing (EXACTLY what they're wearing - color, style, pattern)
- Current state (their actual expression and posture at this moment)
- Voice matching (adjust to match their appearance)
2. Update the scene_continuity to match the ACTUAL environment:
- Environment (describe what you see - bedroom, office, outdoor, etc.)
- Camera position (maintain the SAME angle/framing)
- Lighting state (match the EXACT lighting conditions in the image)
- Props and background elements (describe what's actually visible)
- Spatial relationships (match the actual layout)
{audio_context}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ORIGINAL PROMPT TO UPDATE
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
{json.dumps(segment_data, indent=2)}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CRITICAL RULES
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Be EXTREMELY specific about what you see in the image
- If the image shows a young woman with red hair, describe EXACTLY that
- If it's a sunset beach scene, describe EXACTLY that setting
- If they're wearing a beige blazer, describe EXACTLY that clothing
- Match colors, styles, and details PRECISELY to what's visible
- Maintain the SAME camera angle and distance
- Keep the action_timeline.dialogue EXACTLY as provided (this is the NEXT segment's dialogue)
- Update segment_info.continuity_markers to reflect the visual state
- Adjust synchronized_actions to fit the actual character appearance
π¨ CRITICAL: NO BLUR TRANSITIONS AT SEGMENT START π¨
- The video MUST start immediately at 0:00 with a SHARP, CLEAR, IN-FOCUS frame
- NO fade-in, NO blur transition, NO gradual focus effect at the start
- The first frame (0:00) must be as clear and sharp as any other frame
- camera_movement MUST describe movement that starts from a clear, sharp state
The goal is SEAMLESS video extension with ZERO visual or audio discontinuity.
Return ONLY the updated JSON segment object with the same structure. No explanation, just the corrected JSON.
"""
print(f"π Refining prompt for visual continuity...")
# Call GPT-4o with vision
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": refinement_instructions
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{encoded_image}"
}
}
]
}
],
response_format={"type": "json_object"},
temperature=0.3, # Lower temperature for precise matching
)
# Parse the response
refined_prompt = json.loads(response.choices[0].message.content)
print(f"β
Prompt refined for visual continuity")
return JSONResponse(content={
"refined_prompt": refined_prompt,
"original_prompt": segment_data
})
except Exception as e:
print(f"β Prompt refinement error: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"Prompt refinement failed: {str(e)}"
)
|