Spaces:
Running
on
Zero
Running
on
Zero
Update utils.py
Browse files
utils.py
CHANGED
|
@@ -130,45 +130,44 @@ def detect_scene_type_from_analysis(analysis_metadata: Dict[str, Any]) -> str:
|
|
| 130 |
|
| 131 |
def apply_flux_rules(prompt: str, analysis_metadata: Optional[Dict[str, Any]] = None) -> str:
|
| 132 |
"""
|
| 133 |
-
Apply enhanced prompt optimization
|
|
|
|
| 134 |
|
| 135 |
Args:
|
| 136 |
-
prompt: Raw prompt text from BAGEL analysis
|
| 137 |
analysis_metadata: Enhanced metadata with cinematography suggestions
|
| 138 |
|
| 139 |
Returns:
|
| 140 |
-
|
| 141 |
"""
|
| 142 |
if not prompt or not isinstance(prompt, str):
|
| 143 |
return ""
|
| 144 |
|
| 145 |
try:
|
| 146 |
-
# Step 1: Extract
|
| 147 |
-
|
| 148 |
-
if not core_description:
|
| 149 |
-
return "Professional photograph with technical excellence"
|
| 150 |
|
| 151 |
-
# Step 2:
|
| 152 |
-
camera_setup =
|
| 153 |
|
| 154 |
-
# Step 3:
|
| 155 |
-
|
| 156 |
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
logger.info(f"Prompt optimized: {len(prompt)} → {len(final_prompt)} chars")
|
| 161 |
-
return final_prompt
|
| 162 |
|
| 163 |
except Exception as e:
|
| 164 |
-
logger.error(f"
|
| 165 |
-
return
|
| 166 |
|
| 167 |
|
| 168 |
-
def
|
| 169 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 170 |
try:
|
| 171 |
-
#
|
| 172 |
if "CAMERA_SETUP:" in prompt:
|
| 173 |
description = prompt.split("CAMERA_SETUP:")[0].strip()
|
| 174 |
elif "2. CAMERA_SETUP" in prompt:
|
|
@@ -176,342 +175,129 @@ def _extract_clean_description(prompt: str) -> str:
|
|
| 176 |
else:
|
| 177 |
description = prompt
|
| 178 |
|
| 179 |
-
# Remove section headers
|
| 180 |
description = re.sub(r'^(DESCRIPTION:|1\.\s*DESCRIPTION:)\s*', '', description, flags=re.IGNORECASE)
|
| 181 |
|
| 182 |
-
#
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
sentences = re.split(r'[.!?]', description)
|
| 203 |
-
description = sentences[0] if sentences else description[:200]
|
| 204 |
-
|
| 205 |
-
return description.strip()
|
| 206 |
|
| 207 |
except Exception as e:
|
| 208 |
-
logger.warning(f"
|
| 209 |
-
return prompt
|
| 210 |
|
| 211 |
|
| 212 |
-
def
|
| 213 |
-
"""
|
|
|
|
|
|
|
| 214 |
try:
|
| 215 |
-
#
|
| 216 |
-
|
| 217 |
-
# Subject identification
|
| 218 |
-
(r'a (?:person|individual|figure|man|woman) (?:who is|that is)', r'person'),
|
| 219 |
-
(r' (?:who is|that is) (?:wearing|dressed in)', r' wearing'),
|
| 220 |
-
(r' (?:who appears to be|that appears to be)', r''),
|
| 221 |
-
|
| 222 |
-
# Location simplification
|
| 223 |
-
(r'(?:what appears to be|what seems to be) (?:a|an)', r''),
|
| 224 |
-
(r'in (?:what looks like|what appears to be) (?:a|an)', r'in'),
|
| 225 |
-
(r'(?:standing|sitting|positioned) in (?:the middle of|the center of)', r'in'),
|
| 226 |
-
|
| 227 |
-
# Action simplification
|
| 228 |
-
(r'(?:is|are) (?:currently|presently) (?:engaged in|performing)', r''),
|
| 229 |
-
(r'(?:can be seen|is visible|are visible)', r''),
|
| 230 |
-
|
| 231 |
-
# Background simplification
|
| 232 |
-
(r'(?:In the background|Behind (?:him|her|them)),? (?:there (?:is|are)|we can see)', r'Background:'),
|
| 233 |
-
(r'The background (?:features|shows|contains)', r'Background:'),
|
| 234 |
-
|
| 235 |
-
# Remove filler words
|
| 236 |
-
(r'\b(?:quite|rather|somewhat|fairly|very|extremely)\b', r''),
|
| 237 |
-
(r'\b(?:overall|generally|typically|usually)\b', r''),
|
| 238 |
-
]
|
| 239 |
-
|
| 240 |
-
result = text
|
| 241 |
-
for pattern, replacement in conversions:
|
| 242 |
-
result = re.sub(pattern, replacement, result, flags=re.IGNORECASE)
|
| 243 |
-
|
| 244 |
-
# Clean up extra spaces and punctuation
|
| 245 |
-
result = re.sub(r'\s+', ' ', result)
|
| 246 |
-
result = re.sub(r'\s*,\s*,+', ',', result)
|
| 247 |
-
result = re.sub(r'^\s*,\s*', '', result)
|
| 248 |
-
|
| 249 |
-
return result.strip()
|
| 250 |
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
camera_setup = analysis_metadata.get("camera_setup", "")
|
| 262 |
-
if camera_setup and len(camera_setup) > 10:
|
| 263 |
-
return _format_camera_setup(camera_setup)
|
| 264 |
|
| 265 |
-
#
|
| 266 |
-
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
| 268 |
|
| 269 |
except Exception as e:
|
| 270 |
-
logger.warning(f"Camera setup
|
| 271 |
-
return "
|
| 272 |
|
| 273 |
|
| 274 |
def _format_camera_setup(raw_setup: str) -> str:
|
| 275 |
-
"""
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
camera_patterns = [
|
| 279 |
-
r'(Canon EOS R\d+)',
|
| 280 |
-
r'(Sony A\d+[^\s,]*)',
|
| 281 |
-
r'(Leica [^\s,]+)',
|
| 282 |
-
r'(Phase One [^\s,]+)',
|
| 283 |
-
r'(Hasselblad [^\s,]+)',
|
| 284 |
-
r'(ARRI [^\s,]+)',
|
| 285 |
-
r'(RED [^\s,]+)'
|
| 286 |
-
]
|
| 287 |
-
|
| 288 |
-
camera = None
|
| 289 |
-
for pattern in camera_patterns:
|
| 290 |
-
match = re.search(pattern, raw_setup, re.IGNORECASE)
|
| 291 |
-
if match:
|
| 292 |
-
camera = match.group(1)
|
| 293 |
-
break
|
| 294 |
-
|
| 295 |
-
# Extract lens info
|
| 296 |
-
lens_pattern = r'(\d+mm[^,]*f/[\d.]+[^,]*)'
|
| 297 |
-
lens_match = re.search(lens_pattern, raw_setup, re.IGNORECASE)
|
| 298 |
-
lens = lens_match.group(1) if lens_match else None
|
| 299 |
-
|
| 300 |
-
# Extract ISO
|
| 301 |
-
iso_pattern = r'(ISO \d+)'
|
| 302 |
-
iso_match = re.search(iso_pattern, raw_setup, re.IGNORECASE)
|
| 303 |
-
iso = iso_match.group(1) if iso_match else None
|
| 304 |
-
|
| 305 |
-
# Build clean setup
|
| 306 |
-
parts = []
|
| 307 |
-
if camera:
|
| 308 |
-
parts.append(camera)
|
| 309 |
-
if lens:
|
| 310 |
-
parts.append(lens)
|
| 311 |
-
if iso:
|
| 312 |
-
parts.append(iso)
|
| 313 |
-
|
| 314 |
-
if parts:
|
| 315 |
-
return f"shot on {', '.join(parts)}"
|
| 316 |
-
else:
|
| 317 |
-
return "professional photography"
|
| 318 |
-
|
| 319 |
-
except Exception as e:
|
| 320 |
-
logger.warning(f"Camera setup formatting failed: {e}")
|
| 321 |
-
return "professional photography"
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
def _detect_scene_from_content(description: str) -> str:
|
| 325 |
-
"""Detect scene type from description content"""
|
| 326 |
-
description_lower = description.lower()
|
| 327 |
-
|
| 328 |
-
# Scene detection patterns
|
| 329 |
-
if any(term in description_lower for term in ["portrait", "person", "man", "woman", "face"]):
|
| 330 |
-
return "portrait"
|
| 331 |
-
elif any(term in description_lower for term in ["landscape", "mountain", "horizon", "nature", "outdoor"]):
|
| 332 |
-
return "landscape"
|
| 333 |
-
elif any(term in description_lower for term in ["street", "urban", "city", "building", "crowd"]):
|
| 334 |
-
return "street"
|
| 335 |
-
elif any(term in description_lower for term in ["architecture", "building", "structure", "interior"]):
|
| 336 |
-
return "architecture"
|
| 337 |
-
else:
|
| 338 |
-
return "general"
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
def _get_scene_camera_setup(scene_type: str) -> str:
|
| 342 |
-
"""Get camera setup based on scene type"""
|
| 343 |
-
setups = {
|
| 344 |
-
"portrait": "shot on Canon EOS R5, 85mm f/1.4 lens, ISO 200",
|
| 345 |
-
"landscape": "shot on Phase One XT, 24-70mm f/4 lens, ISO 100",
|
| 346 |
-
"street": "shot on Leica M11, 35mm f/1.4 lens, ISO 800",
|
| 347 |
-
"architecture": "shot on Canon EOS R5, 24-70mm f/2.8 lens, ISO 100",
|
| 348 |
-
"general": "shot on Canon EOS R6, 50mm f/1.8 lens, ISO 400"
|
| 349 |
-
}
|
| 350 |
-
|
| 351 |
-
return setups.get(scene_type, setups["general"])
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
def _get_essential_keywords(description: str, camera_setup: str, analysis_metadata: Optional[Dict[str, Any]]) -> List[str]:
|
| 355 |
-
"""Get essential lighting and composition keywords without redundancy"""
|
| 356 |
try:
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
# Detect and add lighting information
|
| 361 |
-
lighting = _detect_lighting_from_description(description_lower)
|
| 362 |
-
if lighting:
|
| 363 |
-
keywords.append(lighting)
|
| 364 |
-
|
| 365 |
-
# Detect and add composition technique
|
| 366 |
-
composition = _detect_composition_from_description(description_lower, camera_setup)
|
| 367 |
-
if composition:
|
| 368 |
-
keywords.append(composition)
|
| 369 |
|
| 370 |
-
#
|
| 371 |
-
if
|
| 372 |
-
|
| 373 |
-
keywords.append("shallow depth of field")
|
| 374 |
|
| 375 |
-
|
| 376 |
-
if "shot on" not in camera_setup and len(keywords) == 0:
|
| 377 |
-
keywords.append("professional photography")
|
| 378 |
|
| 379 |
-
return keywords[:3] # Limit to 3 essential keywords
|
| 380 |
-
|
| 381 |
-
except Exception as e:
|
| 382 |
-
logger.warning(f"Keyword extraction failed: {e}")
|
| 383 |
-
return ["natural lighting"]
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
def _detect_lighting_from_description(description_lower: str) -> Optional[str]:
|
| 387 |
-
"""Detect lighting type from description"""
|
| 388 |
-
try:
|
| 389 |
-
# Check for existing lighting mentions
|
| 390 |
-
if any(term in description_lower for term in ["lighting", "light", "lit", "illuminated"]):
|
| 391 |
-
return None # Already has lighting info
|
| 392 |
-
|
| 393 |
-
# Detect lighting conditions from scene context
|
| 394 |
-
if any(term in description_lower for term in ["sunset", "sunrise", "golden", "warm"]):
|
| 395 |
-
return "golden hour lighting"
|
| 396 |
-
elif any(term in description_lower for term in ["twilight", "dusk", "evening", "blue hour"]):
|
| 397 |
-
return "blue hour lighting"
|
| 398 |
-
elif any(term in description_lower for term in ["overcast", "cloudy", "soft", "diffused"]):
|
| 399 |
-
return "soft natural lighting"
|
| 400 |
-
elif any(term in description_lower for term in ["bright", "sunny", "daylight", "outdoor"]):
|
| 401 |
-
return "natural daylight"
|
| 402 |
-
elif any(term in description_lower for term in ["indoor", "interior", "inside"]):
|
| 403 |
-
return "ambient lighting"
|
| 404 |
-
elif any(term in description_lower for term in ["studio", "controlled", "professional"]):
|
| 405 |
-
return "studio lighting"
|
| 406 |
-
elif any(term in description_lower for term in ["dramatic", "moody", "shadow"]):
|
| 407 |
-
return "dramatic lighting"
|
| 408 |
-
else:
|
| 409 |
-
# Default based on scene type
|
| 410 |
-
if any(term in description_lower for term in ["portrait", "person", "face"]):
|
| 411 |
-
return "natural lighting"
|
| 412 |
-
elif any(term in description_lower for term in ["landscape", "outdoor", "nature"]):
|
| 413 |
-
return "natural daylight"
|
| 414 |
-
else:
|
| 415 |
-
return "natural lighting"
|
| 416 |
-
|
| 417 |
-
except Exception as e:
|
| 418 |
-
logger.warning(f"Lighting detection failed: {e}")
|
| 419 |
-
return "natural lighting"
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
def _detect_composition_from_description(description_lower: str, camera_setup: str) -> Optional[str]:
|
| 423 |
-
"""Detect composition technique from description and camera setup"""
|
| 424 |
-
try:
|
| 425 |
-
# Check for existing composition mentions
|
| 426 |
-
if any(term in description_lower for term in ["composition", "framing", "rule of thirds"]):
|
| 427 |
-
return None # Already has composition info
|
| 428 |
-
|
| 429 |
-
# Detect composition from perspective/angle
|
| 430 |
-
if any(term in description_lower for term in ["elevated", "above", "overhead", "aerial"]):
|
| 431 |
-
return "elevated perspective"
|
| 432 |
-
elif any(term in description_lower for term in ["low angle", "looking up", "from below"]):
|
| 433 |
-
return "low angle composition"
|
| 434 |
-
elif any(term in description_lower for term in ["close-up", "tight", "detailed"]):
|
| 435 |
-
return "close-up framing"
|
| 436 |
-
elif any(term in description_lower for term in ["wide", "expansive", "panoramic"]):
|
| 437 |
-
return "wide composition"
|
| 438 |
-
|
| 439 |
-
# Detect composition from subject arrangement
|
| 440 |
-
elif any(term in description_lower for term in ["centered", "center", "middle"]):
|
| 441 |
-
return "centered composition"
|
| 442 |
-
elif any(term in description_lower for term in ["symmetr", "balanced", "mirror"]):
|
| 443 |
-
return "symmetrical composition"
|
| 444 |
-
elif any(term in description_lower for term in ["leading", "lines", "path", "diagonal"]):
|
| 445 |
-
return "leading lines"
|
| 446 |
-
|
| 447 |
-
# Default composition based on camera setup
|
| 448 |
-
elif any(term in camera_setup for term in ["85mm", "135mm", "f/1.4", "f/2.8"]):
|
| 449 |
-
return "rule of thirds" # Portrait/shallow DOF typically uses rule of thirds
|
| 450 |
-
elif any(term in camera_setup for term in ["24mm", "35mm", "wide"]):
|
| 451 |
-
return "dynamic composition" # Wide angle allows for dynamic compositions
|
| 452 |
-
else:
|
| 453 |
-
return "rule of thirds" # Universal fallback
|
| 454 |
-
|
| 455 |
except Exception as e:
|
| 456 |
-
logger.warning(f"
|
| 457 |
-
return
|
| 458 |
|
| 459 |
|
| 460 |
-
def
|
| 461 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 462 |
try:
|
| 463 |
-
# Structure: Description + Technical + Style
|
| 464 |
parts = []
|
| 465 |
|
| 466 |
-
#
|
| 467 |
if description:
|
| 468 |
parts.append(description)
|
| 469 |
|
| 470 |
-
#
|
| 471 |
if camera_setup:
|
| 472 |
parts.append(camera_setup)
|
| 473 |
|
| 474 |
-
#
|
| 475 |
-
if keywords:
|
| 476 |
-
parts.extend(keywords)
|
| 477 |
-
|
| 478 |
-
# Join with consistent separator
|
| 479 |
result = ", ".join(parts)
|
| 480 |
|
| 481 |
-
#
|
| 482 |
result = re.sub(r'\s*,\s*,+', ',', result) # Remove double commas
|
| 483 |
-
result = re.sub(r'\s+', ' ', result) # Clean spaces
|
| 484 |
-
result = result.strip().rstrip(',') #
|
| 485 |
|
| 486 |
-
# Ensure
|
| 487 |
if result:
|
| 488 |
result = result[0].upper() + result[1:] if len(result) > 1 else result.upper()
|
| 489 |
|
| 490 |
return result
|
| 491 |
|
| 492 |
except Exception as e:
|
| 493 |
-
logger.error(f"
|
| 494 |
-
return "Professional photograph"
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
def _create_fallback_prompt(original_prompt: str) -> str:
|
| 498 |
-
"""Create fallback prompt when optimization fails"""
|
| 499 |
-
try:
|
| 500 |
-
# Extract first meaningful sentence
|
| 501 |
-
sentences = re.split(r'[.!?]', original_prompt)
|
| 502 |
-
if sentences:
|
| 503 |
-
clean_sentence = sentences[0].strip()
|
| 504 |
-
# Remove verbose starters
|
| 505 |
-
clean_sentence = re.sub(r'^(This image shows|The image depicts|This photograph)', '', clean_sentence, flags=re.IGNORECASE)
|
| 506 |
-
clean_sentence = clean_sentence.strip()
|
| 507 |
-
|
| 508 |
-
if len(clean_sentence) > 20:
|
| 509 |
-
return f"{clean_sentence}, professional photography"
|
| 510 |
-
|
| 511 |
-
return "Professional photograph with technical excellence"
|
| 512 |
-
|
| 513 |
-
except Exception:
|
| 514 |
-
return "Professional photograph"
|
| 515 |
|
| 516 |
|
| 517 |
def calculate_prompt_score(prompt: str, analysis_data: Optional[Dict[str, Any]] = None) -> Tuple[int, Dict[str, int]]:
|
|
@@ -531,8 +317,8 @@ def calculate_prompt_score(prompt: str, analysis_data: Optional[Dict[str, Any]]
|
|
| 531 |
breakdown = {}
|
| 532 |
|
| 533 |
# Enhanced Prompt Quality (0-25 points)
|
| 534 |
-
length_score = min(15, len(prompt) //
|
| 535 |
-
detail_score = min(10, len(prompt.split(',')) *
|
| 536 |
breakdown["prompt_quality"] = int(length_score + detail_score)
|
| 537 |
|
| 538 |
# Technical Details with Cinematography Focus (0-25 points)
|
|
@@ -553,22 +339,30 @@ def calculate_prompt_score(prompt: str, analysis_data: Optional[Dict[str, Any]]
|
|
| 553 |
if re.search(r'ISO \d+', prompt):
|
| 554 |
tech_score += 4
|
| 555 |
|
| 556 |
-
# Professional terminology
|
| 557 |
-
tech_keywords = ['shot on', 'lens', 'depth of field', 'bokeh']
|
| 558 |
-
tech_score += sum(
|
| 559 |
|
| 560 |
breakdown["technical_details"] = min(25, tech_score)
|
| 561 |
|
| 562 |
-
# Professional Cinematography (0-25 points)
|
| 563 |
cinema_score = 0
|
| 564 |
|
| 565 |
-
#
|
| 566 |
-
|
| 567 |
-
cinema_score += sum(4 for
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 568 |
|
| 569 |
-
# Composition
|
| 570 |
-
|
| 571 |
-
cinema_score += sum(3 for
|
| 572 |
|
| 573 |
# Professional context bonus
|
| 574 |
if analysis_data and analysis_data.get("has_camera_suggestion"):
|
|
@@ -579,23 +373,23 @@ def calculate_prompt_score(prompt: str, analysis_data: Optional[Dict[str, Any]]
|
|
| 579 |
# Multi-Engine Optimization (0-25 points)
|
| 580 |
optimization_score = 0
|
| 581 |
|
| 582 |
-
# Check for technical specifications
|
| 583 |
-
if re.search(r'(?:Canon|Sony|Leica|Phase One)', prompt):
|
| 584 |
optimization_score += 10
|
| 585 |
|
| 586 |
# Complete technical specs
|
| 587 |
-
if re.search(r'
|
| 588 |
optimization_score += 8
|
| 589 |
|
| 590 |
-
# Professional terminology
|
| 591 |
-
pro_terms = ['professional', 'shot on', '
|
| 592 |
-
optimization_score += sum(
|
| 593 |
|
| 594 |
-
# Length efficiency
|
| 595 |
word_count = len(prompt.split())
|
| 596 |
-
if
|
| 597 |
optimization_score += 5
|
| 598 |
-
elif word_count <=
|
| 599 |
optimization_score += 3
|
| 600 |
|
| 601 |
breakdown["multi_engine_optimization"] = min(25, optimization_score)
|
|
@@ -664,12 +458,12 @@ def format_analysis_report(analysis_data: Dict[str, Any], processing_time: float
|
|
| 664 |
**Professional Context:** {'✅ Applied' if has_cinema_context else '❌ Not Applied'}
|
| 665 |
|
| 666 |
**🎯 OPTIMIZATIONS APPLIED:**
|
| 667 |
-
✅
|
| 668 |
-
✅
|
| 669 |
-
✅
|
| 670 |
-
✅
|
| 671 |
-
✅
|
| 672 |
-
✅
|
| 673 |
|
| 674 |
**⚡ Powered by Pariente AI for MIA TV Series**"""
|
| 675 |
|
|
|
|
| 130 |
|
| 131 |
def apply_flux_rules(prompt: str, analysis_metadata: Optional[Dict[str, Any]] = None) -> str:
|
| 132 |
"""
|
| 133 |
+
Apply enhanced prompt optimization - FORMAT ONLY, do not filter content
|
| 134 |
+
Let professional_photography.py do ALL the cinematographic work
|
| 135 |
|
| 136 |
Args:
|
| 137 |
+
prompt: Raw prompt text from BAGEL analysis (already enriched by professional_photography.py)
|
| 138 |
analysis_metadata: Enhanced metadata with cinematography suggestions
|
| 139 |
|
| 140 |
Returns:
|
| 141 |
+
Clean formatted prompt preserving ALL professional cinematography content
|
| 142 |
"""
|
| 143 |
if not prompt or not isinstance(prompt, str):
|
| 144 |
return ""
|
| 145 |
|
| 146 |
try:
|
| 147 |
+
# Step 1: Extract the rich professional description (preserve ALL content)
|
| 148 |
+
description = _extract_professional_description(prompt)
|
|
|
|
|
|
|
| 149 |
|
| 150 |
+
# Step 2: Extract camera setup if provided by BAGEL
|
| 151 |
+
camera_setup = _extract_camera_setup(prompt, analysis_metadata)
|
| 152 |
|
| 153 |
+
# Step 3: Format into clean structure (NO filtering)
|
| 154 |
+
formatted_prompt = _format_professional_prompt(description, camera_setup)
|
| 155 |
|
| 156 |
+
logger.info(f"Professional prompt formatted: {len(prompt)} → {len(formatted_prompt)} chars")
|
| 157 |
+
return formatted_prompt
|
|
|
|
|
|
|
|
|
|
| 158 |
|
| 159 |
except Exception as e:
|
| 160 |
+
logger.error(f"Professional prompt formatting failed: {e}")
|
| 161 |
+
return prompt # Return original if formatting fails
|
| 162 |
|
| 163 |
|
| 164 |
+
def _extract_professional_description(prompt: str) -> str:
|
| 165 |
+
"""
|
| 166 |
+
Extract the professional description - preserve ALL cinematographic content
|
| 167 |
+
Only clean formatting, DO NOT filter content
|
| 168 |
+
"""
|
| 169 |
try:
|
| 170 |
+
# Split sections if present
|
| 171 |
if "CAMERA_SETUP:" in prompt:
|
| 172 |
description = prompt.split("CAMERA_SETUP:")[0].strip()
|
| 173 |
elif "2. CAMERA_SETUP" in prompt:
|
|
|
|
| 175 |
else:
|
| 176 |
description = prompt
|
| 177 |
|
| 178 |
+
# Remove only section headers, preserve ALL content
|
| 179 |
description = re.sub(r'^(DESCRIPTION:|1\.\s*DESCRIPTION:)\s*', '', description, flags=re.IGNORECASE)
|
| 180 |
|
| 181 |
+
# Clean up only formatting issues, preserve ALL professional terminology
|
| 182 |
+
# Remove only redundant whitespace
|
| 183 |
+
description = re.sub(r'\s+', ' ', description)
|
| 184 |
+
description = description.strip()
|
| 185 |
+
|
| 186 |
+
# If description is too long, preserve the most important parts
|
| 187 |
+
# But DO NOT remove cinematographic terms or professional language
|
| 188 |
+
if len(description) > 300:
|
| 189 |
+
# Only truncate at sentence boundaries to preserve meaning
|
| 190 |
+
sentences = re.split(r'[.!?]+', description)
|
| 191 |
+
truncated = ""
|
| 192 |
+
for sentence in sentences:
|
| 193 |
+
if len(truncated + sentence) < 280:
|
| 194 |
+
truncated += sentence + ". "
|
| 195 |
+
else:
|
| 196 |
+
break
|
| 197 |
+
if truncated:
|
| 198 |
+
description = truncated.strip()
|
| 199 |
+
|
| 200 |
+
return description
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
except Exception as e:
|
| 203 |
+
logger.warning(f"Professional description extraction failed: {e}")
|
| 204 |
+
return prompt
|
| 205 |
|
| 206 |
|
| 207 |
+
def _extract_camera_setup(prompt: str, analysis_metadata: Optional[Dict[str, Any]]) -> str:
|
| 208 |
+
"""
|
| 209 |
+
Extract camera setup from BAGEL output or metadata
|
| 210 |
+
"""
|
| 211 |
try:
|
| 212 |
+
# First check if BAGEL provided camera setup in the prompt
|
| 213 |
+
camera_setup = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
+
if "CAMERA_SETUP:" in prompt:
|
| 216 |
+
camera_section = prompt.split("CAMERA_SETUP:")[1].strip()
|
| 217 |
+
# Take first substantial line
|
| 218 |
+
lines = camera_section.split('\n')
|
| 219 |
+
for line in lines:
|
| 220 |
+
if len(line.strip()) > 20:
|
| 221 |
+
camera_setup = line.strip()
|
| 222 |
+
break
|
| 223 |
+
|
| 224 |
+
elif "2. CAMERA_SETUP" in prompt:
|
| 225 |
+
camera_section = prompt.split("2. CAMERA_SETUP")[1].strip()
|
| 226 |
+
lines = camera_section.split('\n')
|
| 227 |
+
for line in lines:
|
| 228 |
+
if len(line.strip()) > 20:
|
| 229 |
+
camera_setup = line.strip()
|
| 230 |
+
break
|
| 231 |
+
|
| 232 |
+
# If no setup in prompt, check metadata
|
| 233 |
+
if not camera_setup and analysis_metadata:
|
| 234 |
camera_setup = analysis_metadata.get("camera_setup", "")
|
|
|
|
|
|
|
| 235 |
|
| 236 |
+
# Format camera setup if found
|
| 237 |
+
if camera_setup:
|
| 238 |
+
return _format_camera_setup(camera_setup)
|
| 239 |
+
|
| 240 |
+
# Return empty if no camera setup (let the description speak for itself)
|
| 241 |
+
return ""
|
| 242 |
|
| 243 |
except Exception as e:
|
| 244 |
+
logger.warning(f"Camera setup extraction failed: {e}")
|
| 245 |
+
return ""
|
| 246 |
|
| 247 |
|
| 248 |
def _format_camera_setup(raw_setup: str) -> str:
|
| 249 |
+
"""
|
| 250 |
+
Format camera setup preserving ALL technical information
|
| 251 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
try:
|
| 253 |
+
# Clean up common prefixes but preserve all technical specs
|
| 254 |
+
setup = re.sub(r'^(Based on.*?recommend|I would recommend|For this.*?setup)\s*', '', raw_setup, flags=re.IGNORECASE)
|
| 255 |
+
setup = re.sub(r'^(CAMERA_SETUP:|2\.\s*CAMERA_SETUP:?)\s*', '', setup, flags=re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
+
# Ensure proper formatting
|
| 258 |
+
if setup and not setup.lower().startswith('shot on'):
|
| 259 |
+
setup = f"shot on {setup}"
|
|
|
|
| 260 |
|
| 261 |
+
return setup.strip()
|
|
|
|
|
|
|
| 262 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
except Exception as e:
|
| 264 |
+
logger.warning(f"Camera setup formatting failed: {e}")
|
| 265 |
+
return raw_setup
|
| 266 |
|
| 267 |
|
| 268 |
+
def _format_professional_prompt(description: str, camera_setup: str) -> str:
|
| 269 |
+
"""
|
| 270 |
+
Format the final prompt preserving ALL professional cinematography content
|
| 271 |
+
Structure: [Professional Description] + [Camera Setup]
|
| 272 |
+
"""
|
| 273 |
try:
|
|
|
|
| 274 |
parts = []
|
| 275 |
|
| 276 |
+
# Add the rich professional description (preserve ALL content)
|
| 277 |
if description:
|
| 278 |
parts.append(description)
|
| 279 |
|
| 280 |
+
# Add camera setup if available
|
| 281 |
if camera_setup:
|
| 282 |
parts.append(camera_setup)
|
| 283 |
|
| 284 |
+
# Join with clean formatting
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
result = ", ".join(parts)
|
| 286 |
|
| 287 |
+
# Clean up only formatting issues
|
| 288 |
result = re.sub(r'\s*,\s*,+', ',', result) # Remove double commas
|
| 289 |
+
result = re.sub(r'\s+', ' ', result) # Clean multiple spaces
|
| 290 |
+
result = result.strip().rstrip(',') # Clean edges
|
| 291 |
|
| 292 |
+
# Ensure proper capitalization
|
| 293 |
if result:
|
| 294 |
result = result[0].upper() + result[1:] if len(result) > 1 else result.upper()
|
| 295 |
|
| 296 |
return result
|
| 297 |
|
| 298 |
except Exception as e:
|
| 299 |
+
logger.error(f"Professional prompt formatting failed: {e}")
|
| 300 |
+
return description if description else "Professional cinematographic photograph"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
|
| 303 |
def calculate_prompt_score(prompt: str, analysis_data: Optional[Dict[str, Any]] = None) -> Tuple[int, Dict[str, int]]:
|
|
|
|
| 317 |
breakdown = {}
|
| 318 |
|
| 319 |
# Enhanced Prompt Quality (0-25 points)
|
| 320 |
+
length_score = min(15, len(prompt) // 15) # Reward appropriate length
|
| 321 |
+
detail_score = min(10, len(prompt.split(',')) * 1.5) # Reward structured detail
|
| 322 |
breakdown["prompt_quality"] = int(length_score + detail_score)
|
| 323 |
|
| 324 |
# Technical Details with Cinematography Focus (0-25 points)
|
|
|
|
| 339 |
if re.search(r'ISO \d+', prompt):
|
| 340 |
tech_score += 4
|
| 341 |
|
| 342 |
+
# Professional terminology from professional_photography.py
|
| 343 |
+
tech_keywords = ['shot on', 'lens', 'depth of field', 'bokeh', 'composition', 'lighting']
|
| 344 |
+
tech_score += sum(2 for keyword in tech_keywords if keyword in prompt.lower())
|
| 345 |
|
| 346 |
breakdown["technical_details"] = min(25, tech_score)
|
| 347 |
|
| 348 |
+
# Professional Cinematography (0-25 points) - Check for professional_photography.py terms
|
| 349 |
cinema_score = 0
|
| 350 |
|
| 351 |
+
# Photographic planes
|
| 352 |
+
planes = ['wide shot', 'close-up', 'medium shot', 'extreme wide', 'extreme close-up', 'detail shot']
|
| 353 |
+
cinema_score += sum(4 for plane in planes if plane in prompt.lower())
|
| 354 |
+
|
| 355 |
+
# Camera angles
|
| 356 |
+
angles = ['low angle', 'high angle', 'eye level', 'dutch angle', 'elevated perspective']
|
| 357 |
+
cinema_score += sum(4 for angle in angles if angle in prompt.lower())
|
| 358 |
+
|
| 359 |
+
# Lighting principles
|
| 360 |
+
lighting = ['golden hour', 'blue hour', 'natural lighting', 'studio lighting', 'dramatic lighting', 'soft lighting']
|
| 361 |
+
cinema_score += sum(3 for light in lighting if light in prompt.lower())
|
| 362 |
|
| 363 |
+
# Composition rules
|
| 364 |
+
composition = ['rule of thirds', 'leading lines', 'symmetrical', 'centered', 'dynamic composition']
|
| 365 |
+
cinema_score += sum(3 for comp in composition if comp in prompt.lower())
|
| 366 |
|
| 367 |
# Professional context bonus
|
| 368 |
if analysis_data and analysis_data.get("has_camera_suggestion"):
|
|
|
|
| 373 |
# Multi-Engine Optimization (0-25 points)
|
| 374 |
optimization_score = 0
|
| 375 |
|
| 376 |
+
# Check for complete technical specifications
|
| 377 |
+
if re.search(r'(?:Canon|Sony|Leica|Phase One|ARRI|RED)', prompt):
|
| 378 |
optimization_score += 10
|
| 379 |
|
| 380 |
# Complete technical specs
|
| 381 |
+
if re.search(r'shot on.*\d+mm.*f/[\d.]+', prompt):
|
| 382 |
optimization_score += 8
|
| 383 |
|
| 384 |
+
# Professional terminology density
|
| 385 |
+
pro_terms = ['professional', 'cinematographic', 'shot on', 'composition', 'lighting']
|
| 386 |
+
optimization_score += sum(1 for term in pro_terms if term in prompt.lower())
|
| 387 |
|
| 388 |
+
# Length efficiency (reward comprehensive but concise)
|
| 389 |
word_count = len(prompt.split())
|
| 390 |
+
if 40 <= word_count <= 80: # Optimal range for rich but efficient prompts
|
| 391 |
optimization_score += 5
|
| 392 |
+
elif 20 <= word_count <= 40:
|
| 393 |
optimization_score += 3
|
| 394 |
|
| 395 |
breakdown["multi_engine_optimization"] = min(25, optimization_score)
|
|
|
|
| 458 |
**Professional Context:** {'✅ Applied' if has_cinema_context else '❌ Not Applied'}
|
| 459 |
|
| 460 |
**🎯 OPTIMIZATIONS APPLIED:**
|
| 461 |
+
✅ Complete professional cinematography analysis
|
| 462 |
+
✅ Preserved all technical and artistic content
|
| 463 |
+
✅ Structured professional prompt format
|
| 464 |
+
✅ Multi-engine compatibility maintained
|
| 465 |
+
✅ Professional photography knowledge integrated
|
| 466 |
+
✅ Cinematographic terminology preserved
|
| 467 |
|
| 468 |
**⚡ Powered by Pariente AI for MIA TV Series**"""
|
| 469 |
|