Spaces:
Running
on
Zero
Running
on
Zero
Update utils.py
Browse files
utils.py
CHANGED
@@ -142,525 +142,300 @@ def apply_flux_rules(prompt: str, analysis_metadata: Optional[Dict[str, Any]] =
|
|
142 |
if not prompt or not isinstance(prompt, str):
|
143 |
return ""
|
144 |
|
145 |
-
# Clean the prompt from unwanted elements
|
146 |
-
cleaned_prompt = prompt
|
147 |
-
for pattern in FLUX_RULES["remove_patterns"]:
|
148 |
-
cleaned_prompt = re.sub(pattern, '', cleaned_prompt, flags=re.IGNORECASE)
|
149 |
-
|
150 |
-
# Extract description part only (remove CAMERA_SETUP section if present)
|
151 |
-
description_part = _extract_description_only(cleaned_prompt)
|
152 |
-
|
153 |
-
# NEW: Convert to generative language with cinematography angle detection
|
154 |
-
if PROFESSIONAL_PHOTOGRAPHY_CONFIG.get("prompt_condensation", True):
|
155 |
-
description_part = _convert_to_cinematographic_language(description_part)
|
156 |
-
logger.info("Applied cinematographic language conversion")
|
157 |
-
|
158 |
-
# Check if BAGEL provided intelligent camera setup with cinematography context
|
159 |
-
camera_config = ""
|
160 |
-
scene_type = "default"
|
161 |
-
|
162 |
-
if analysis_metadata and analysis_metadata.get("has_camera_suggestion") and analysis_metadata.get("camera_setup"):
|
163 |
-
# Use BAGEL's intelligent camera suggestion - enhanced with cinematography knowledge
|
164 |
-
bagel_camera = analysis_metadata["camera_setup"]
|
165 |
-
scene_type = detect_scene_type_from_analysis(analysis_metadata)
|
166 |
-
camera_config = _format_professional_camera_suggestion(bagel_camera, scene_type)
|
167 |
-
logger.info(f"Using BAGEL cinematography suggestion: {camera_config}")
|
168 |
-
else:
|
169 |
-
# Enhanced fallback with professional cinematography knowledge
|
170 |
-
scene_type = _detect_scene_from_description(description_part.lower())
|
171 |
-
camera_config = _get_enhanced_camera_config(scene_type, description_part.lower())
|
172 |
-
logger.info(f"Using enhanced cinematography configuration for {scene_type}")
|
173 |
-
|
174 |
-
# Add enhanced lighting with cinematography principles
|
175 |
-
lighting_enhancement = _get_cinematography_lighting_enhancement(description_part.lower(), camera_config, scene_type)
|
176 |
-
|
177 |
-
# Add style enhancement for multi-engine compatibility
|
178 |
-
style_enhancement = _get_style_enhancement(scene_type, description_part.lower())
|
179 |
-
|
180 |
-
# NEW: Smart keyword insertion with token economy
|
181 |
-
smart_keywords = _apply_smart_keyword_insertion(description_part, camera_config, scene_type)
|
182 |
-
|
183 |
-
# Build final prompt: Description + Camera + Lighting + Style + Smart Keywords
|
184 |
-
final_prompt = description_part + camera_config + lighting_enhancement + style_enhancement + smart_keywords
|
185 |
-
|
186 |
-
# NEW: Final length optimization with token economy
|
187 |
-
if PROFESSIONAL_PHOTOGRAPHY_CONFIG.get("prompt_optimization", {}).get("max_length"):
|
188 |
-
final_prompt = _optimize_prompt_with_token_economy(final_prompt)
|
189 |
-
|
190 |
-
# Clean up formatting
|
191 |
-
final_prompt = _clean_prompt_formatting(final_prompt)
|
192 |
-
|
193 |
-
return final_prompt
|
194 |
-
|
195 |
-
|
196 |
-
def _extract_description_only(prompt: str) -> str:
|
197 |
-
"""Extract only the description part, removing camera setup sections"""
|
198 |
-
# Remove CAMERA_SETUP section if present
|
199 |
-
if "CAMERA_SETUP:" in prompt:
|
200 |
-
parts = prompt.split("CAMERA_SETUP:")
|
201 |
-
description = parts[0].strip()
|
202 |
-
elif "2. CAMERA_SETUP" in prompt:
|
203 |
-
parts = prompt.split("2. CAMERA_SETUP")
|
204 |
-
description = parts[0].strip()
|
205 |
-
else:
|
206 |
-
description = prompt
|
207 |
-
|
208 |
-
# Remove "DESCRIPTION:" label if present
|
209 |
-
if description.startswith("DESCRIPTION:"):
|
210 |
-
description = description.replace("DESCRIPTION:", "").strip()
|
211 |
-
elif description.startswith("1. DESCRIPTION:"):
|
212 |
-
description = description.replace("1. DESCRIPTION:", "").strip()
|
213 |
-
|
214 |
-
# Clean up any remaining camera recommendations from the description
|
215 |
-
description = re.sub(r'For this type of scene.*?shooting style would be.*?\.', '', description, flags=re.DOTALL)
|
216 |
-
description = re.sub(r'I would recommend.*?aperture.*?\.', '', description, flags=re.DOTALL)
|
217 |
-
description = re.sub(r'Professional Context:.*?\.', '', description, flags=re.DOTALL)
|
218 |
-
description = re.sub(r'Cinematography context:.*?\.', '', description, flags=re.DOTALL)
|
219 |
-
|
220 |
-
# Remove numbered section residues
|
221 |
-
description = re.sub(r'\s*\d+\.\s*,?\s*$', '', description)
|
222 |
-
description = re.sub(r'\s*\d+\.\s*,?\s*', ' ', description)
|
223 |
-
|
224 |
-
return description.strip()
|
225 |
-
|
226 |
-
|
227 |
-
def _detect_camera_angles(description: str) -> List[str]:
|
228 |
-
"""Detect camera angles and perspectives using professional cinematography knowledge"""
|
229 |
try:
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
low_angle_indicators = [
|
235 |
-
"looking up at", "from below", "upward angle", "towering", "looming",
|
236 |
-
"shot from ground level", "worm's eye", "low angle"
|
237 |
-
]
|
238 |
-
if any(indicator in description_lower for indicator in low_angle_indicators):
|
239 |
-
angles_detected.append("low-angle shot")
|
240 |
-
|
241 |
-
# High angle (picado) detection
|
242 |
-
high_angle_indicators = [
|
243 |
-
"looking down", "from above", "overhead", "bird's eye", "aerial view",
|
244 |
-
"downward angle", "top-down", "high angle"
|
245 |
-
]
|
246 |
-
if any(indicator in description_lower for indicator in high_angle_indicators):
|
247 |
-
angles_detected.append("high-angle shot")
|
248 |
|
249 |
-
#
|
250 |
-
|
251 |
-
"eye level", "straight on", "direct view", "level with"
|
252 |
-
]
|
253 |
-
if any(indicator in description_lower for indicator in eye_level_indicators):
|
254 |
-
angles_detected.append("eye-level shot")
|
255 |
|
256 |
-
#
|
257 |
-
|
258 |
-
"tilted", "angled", "diagonal", "off-kilter", "dutch angle"
|
259 |
-
]
|
260 |
-
if any(indicator in description_lower for indicator in dutch_indicators):
|
261 |
-
angles_detected.append("dutch angle")
|
262 |
|
263 |
-
#
|
264 |
-
|
265 |
-
if ("close" in description_lower or "prominent" in description_lower) and "blurred" in description_lower:
|
266 |
-
# Suggests foreground element shot from specific angle with background perspective
|
267 |
-
if not angles_detected: # Only add if no specific angle detected
|
268 |
-
angles_detected.append("shallow depth perspective")
|
269 |
|
270 |
-
logger.info(f"
|
271 |
-
return
|
272 |
|
273 |
except Exception as e:
|
274 |
-
logger.
|
275 |
-
return
|
276 |
|
277 |
|
278 |
-
def
|
279 |
-
"""
|
280 |
try:
|
281 |
-
#
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
r'
|
|
|
|
|
|
|
|
|
295 |
]
|
296 |
|
297 |
-
for pattern in
|
298 |
-
|
299 |
-
|
300 |
-
# Remove uncertainty and verbose connector phrases
|
301 |
-
verbose_phrases = [
|
302 |
-
r'possibly (?:a|an) ',
|
303 |
-
r'appears to be (?:a|an) ',
|
304 |
-
r'seems to be (?:a|an) ',
|
305 |
-
r'might be (?:a|an) ',
|
306 |
-
r'could be (?:a|an) ',
|
307 |
-
r'suggests (?:a|an) ',
|
308 |
-
r'indicating (?:a|an) ',
|
309 |
-
r'(?:possibly|apparently|seemingly|likely)',
|
310 |
-
r'which (?:is|are|creates|adds)',
|
311 |
-
r'(?:In the background|In the foreground), (?:there are|there is)',
|
312 |
-
r'(?:The background|The foreground) (?:features|shows|contains)',
|
313 |
-
r'(?:There are|There is) [^,]+ (?:in the background|in the foreground)',
|
314 |
-
r'The overall (?:setting|atmosphere|mood) (?:suggests|indicates)',
|
315 |
-
]
|
316 |
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
# Convert spatial relationships to cinematographic terms
|
321 |
-
spatial_conversions = [
|
322 |
-
# Background/foreground to cinematographic terms
|
323 |
-
(r'prominently displayed in (?:the )?foreground', 'foreground focus'),
|
324 |
-
(r'in (?:the )?foreground', 'foreground'),
|
325 |
-
(r'in (?:the )?background', 'background'),
|
326 |
-
(r'blurred (?:figures|people|objects)', 'bokeh blur'),
|
327 |
-
(r'out of focus', 'soft focus'),
|
328 |
-
# Convert descriptive structure to noun phrases
|
329 |
-
(r'(?:close-up|medium shot|wide shot) of (?:a|an|the) ', r'close-up '),
|
330 |
-
(r'(?:a|an|the) (\w+)', r'\1'),
|
331 |
-
# Remove excessive connecting words
|
332 |
-
(r'(?:, and|, with|, featuring)', ','),
|
333 |
-
# Simplify location descriptions
|
334 |
-
(r'on (?:a|an|the) ', r'on '),
|
335 |
-
(r'in (?:a|an|the) ', r'in '),
|
336 |
-
]
|
337 |
|
338 |
-
|
339 |
-
|
340 |
|
341 |
-
#
|
342 |
-
|
343 |
-
(r'
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
]
|
348 |
|
349 |
-
|
350 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
351 |
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
generative = f"{angle_prefix}, {generative}"
|
356 |
|
357 |
# Clean up extra spaces and punctuation
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
generative = re.sub(r'\.+', '.', generative) # Remove multiple periods
|
362 |
-
|
363 |
-
# Ensure it starts with a capital letter
|
364 |
-
generative = generative.strip()
|
365 |
-
if generative:
|
366 |
-
generative = generative[0].upper() + generative[1:] if len(generative) > 1 else generative.upper()
|
367 |
|
368 |
-
|
369 |
-
return generative
|
370 |
|
371 |
except Exception as e:
|
372 |
-
logger.warning(f"
|
373 |
-
return
|
374 |
|
375 |
|
376 |
-
def
|
377 |
-
"""
|
378 |
try:
|
379 |
-
|
|
|
|
|
|
|
|
|
380 |
|
381 |
-
#
|
382 |
-
|
383 |
-
|
384 |
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
392 |
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
result = ", " + ", ".join(unique_keywords)
|
422 |
-
logger.info(f"Smart keywords applied: {unique_keywords}")
|
423 |
-
return result
|
424 |
else:
|
425 |
-
|
426 |
-
return ""
|
427 |
|
428 |
except Exception as e:
|
429 |
-
logger.warning(f"
|
430 |
-
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
431 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
432 |
|
433 |
-
|
434 |
-
|
|
|
435 |
try:
|
436 |
-
|
437 |
-
|
438 |
-
words = prompt.split()
|
439 |
-
if len(words) <= max_words:
|
440 |
-
return prompt
|
441 |
-
|
442 |
-
# Priority preservation order for token economy
|
443 |
-
essential_patterns = [
|
444 |
-
# 1. Camera angles (highest priority)
|
445 |
-
r'(?:low-angle|high-angle|eye-level|dutch angle|bird\'s eye|worm\'s eye) shot',
|
446 |
-
# 2. Camera and lens specs
|
447 |
-
r'(?:Canon|Sony|Leica|ARRI|RED|Hasselblad|Phase One) [^,]+',
|
448 |
-
r'\d+mm[^,]*f/[\d.]+[^,]*',
|
449 |
-
r'ISO \d+',
|
450 |
-
# 3. Core subject and composition
|
451 |
-
r'(?:close-up|medium shot|wide shot|shallow depth)',
|
452 |
-
r'(?:foreground|background|bokeh)',
|
453 |
-
# 4. Scene-specific technical terms
|
454 |
-
r'(?:cinematic|anamorphic|telephoto|wide-angle)',
|
455 |
-
]
|
456 |
|
457 |
-
#
|
458 |
-
|
459 |
-
|
|
|
460 |
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
if match not in essential_parts:
|
465 |
-
essential_parts.append(match)
|
466 |
-
# Remove from remaining text to avoid duplication
|
467 |
-
remaining_text = re.sub(re.escape(match), '', remaining_text, count=1, flags=re.IGNORECASE)
|
468 |
|
469 |
-
#
|
470 |
-
|
471 |
-
|
472 |
-
optimized_words.extend(part.split())
|
473 |
|
474 |
-
#
|
475 |
-
|
476 |
-
|
477 |
|
478 |
-
|
479 |
-
optimized_words.extend(remaining_words[:remaining_space])
|
480 |
|
481 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
482 |
|
483 |
-
|
|
|
|
|
484 |
|
485 |
-
return
|
486 |
|
487 |
except Exception as e:
|
488 |
-
logger.
|
489 |
-
return
|
490 |
|
491 |
|
492 |
-
def
|
493 |
-
"""
|
494 |
-
scene_keywords = PROFESSIONAL_PHOTOGRAPHY_CONFIG.get("scene_detection_keywords", {})
|
495 |
-
|
496 |
-
# Score each scene type
|
497 |
-
scene_scores = {}
|
498 |
-
for scene_type, keywords in scene_keywords.items():
|
499 |
-
score = sum(1 for keyword in keywords if keyword in description_lower)
|
500 |
-
if score > 0:
|
501 |
-
scene_scores[scene_type] = score
|
502 |
-
|
503 |
-
# Additional cinematography-specific detection
|
504 |
-
if any(term in description_lower for term in ["film", "movie", "cinematic", "dramatic lighting", "anamorphic"]):
|
505 |
-
scene_scores["cinematic"] = scene_scores.get("cinematic", 0) + 2
|
506 |
-
|
507 |
-
if any(term in description_lower for term in ["studio", "controlled lighting", "professional portrait"]):
|
508 |
-
scene_scores["portrait"] = scene_scores.get("portrait", 0) + 2
|
509 |
-
|
510 |
-
# Return highest scoring scene type
|
511 |
-
if scene_scores:
|
512 |
-
return max(scene_scores.items(), key=lambda x: x[1])[0]
|
513 |
-
else:
|
514 |
-
return "default"
|
515 |
-
|
516 |
-
|
517 |
-
def _format_professional_camera_suggestion(bagel_camera: str, scene_type: str) -> str:
|
518 |
-
"""Format BAGEL's camera suggestion with enhanced cinematography knowledge and fix formatting errors"""
|
519 |
try:
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
'aperture': r'(f/[\d.]+)'
|
528 |
-
}
|
529 |
-
|
530 |
-
extracted_parts = []
|
531 |
-
camera_model = None
|
532 |
-
lens_spec = None
|
533 |
-
aperture_spec = None
|
534 |
-
|
535 |
-
# Extract camera
|
536 |
-
camera_match = re.search(cinema_patterns['camera'], camera_text, re.IGNORECASE)
|
537 |
-
if camera_match:
|
538 |
-
camera_model = camera_match.group(1).strip()
|
539 |
-
|
540 |
-
# Extract lens
|
541 |
-
lens_match = re.search(cinema_patterns['lens'], camera_text, re.IGNORECASE)
|
542 |
-
if lens_match:
|
543 |
-
lens_spec = lens_match.group(1).strip()
|
544 |
-
|
545 |
-
# Extract aperture
|
546 |
-
aperture_match = re.search(cinema_patterns['aperture'], camera_text, re.IGNORECASE)
|
547 |
-
if aperture_match:
|
548 |
-
aperture_spec = aperture_match.group(1).strip()
|
549 |
-
|
550 |
-
# Build proper camera setup with all technical specs
|
551 |
-
if camera_model and lens_spec:
|
552 |
-
# Fix the "with, 35mm" error by proper formatting
|
553 |
-
camera_setup = f"{camera_model}, {lens_spec}"
|
554 |
-
|
555 |
-
# Add aperture if found
|
556 |
-
if aperture_spec:
|
557 |
-
if 'f/' not in lens_spec: # Don't duplicate aperture
|
558 |
-
camera_setup += f" at {aperture_spec}"
|
559 |
-
|
560 |
-
# Add ISO and composition based on scene type
|
561 |
-
enhanced_config = _get_enhanced_camera_config(scene_type, "")
|
562 |
-
|
563 |
-
# Extract ISO and composition from enhanced config
|
564 |
-
iso_match = re.search(r'ISO \d+', enhanced_config)
|
565 |
-
composition_match = re.search(r'(rule of thirds|leading lines|symmetrical|centered|hyperfocal distance)[^,]*', enhanced_config)
|
566 |
|
567 |
-
if
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
result = f", Shot on {camera_setup}" # Skip redundant "cinematic photography"
|
575 |
-
elif scene_type == "portrait":
|
576 |
-
result = f", Shot on {camera_setup}" # Skip redundant "professional portrait photography"
|
577 |
-
else:
|
578 |
-
result = f", Shot on {camera_setup}"
|
579 |
-
|
580 |
-
logger.info(f"Formatted camera setup with token economy: {result}")
|
581 |
-
return result
|
582 |
-
else:
|
583 |
-
# Fallback to enhanced config if parsing fails
|
584 |
-
return _get_enhanced_camera_config(scene_type, camera_text.lower())
|
585 |
-
|
586 |
-
except Exception as e:
|
587 |
-
logger.warning(f"Failed to format professional camera suggestion: {e}")
|
588 |
-
return _get_enhanced_camera_config(scene_type, "")
|
589 |
-
|
590 |
-
|
591 |
-
def _get_enhanced_camera_config(scene_type: str, description_lower: str) -> str:
|
592 |
-
"""Get enhanced camera configuration with cinematography knowledge"""
|
593 |
-
# Enhanced camera configurations with cinema equipment
|
594 |
-
enhanced_configs = {
|
595 |
-
"cinematic": ", Shot on ARRI Alexa LF, 35mm anamorphic lens at f/2.8, ISO 400",
|
596 |
-
"portrait": ", Shot on Canon EOS R5, 85mm f/1.4 lens at f/2.8, ISO 200, rule of thirds",
|
597 |
-
"landscape": ", Shot on Phase One XT, 24-70mm f/4 lens at f/8, ISO 100, hyperfocal distance",
|
598 |
-
"street": ", Shot on Leica M11, 35mm f/1.4 lens at f/2.8, ISO 800",
|
599 |
-
"architectural": ", Shot on Canon EOS R5, 24-70mm f/2.8 lens at f/8, ISO 100, symmetrical composition",
|
600 |
-
"commercial": ", Shot on Hasselblad X2D 100C, 90mm f/2.5 lens at f/4, ISO 100"
|
601 |
-
}
|
602 |
-
|
603 |
-
# Use enhanced config if available, otherwise fall back to FLUX_RULES
|
604 |
-
if scene_type in enhanced_configs:
|
605 |
-
return enhanced_configs[scene_type]
|
606 |
-
elif scene_type in FLUX_RULES["camera_configs"]:
|
607 |
-
return FLUX_RULES["camera_configs"][scene_type]
|
608 |
-
else:
|
609 |
-
return FLUX_RULES["camera_configs"]["default"]
|
610 |
-
|
611 |
-
|
612 |
-
def _get_cinematography_lighting_enhancement(description_lower: str, camera_config: str, scene_type: str) -> str:
|
613 |
-
"""Enhanced lighting with cinematography principles"""
|
614 |
-
# Don't add lighting if already mentioned
|
615 |
-
if any(term in description_lower for term in ["lighting", "lit", "illuminated"]) or 'lighting' in camera_config.lower():
|
616 |
-
return ""
|
617 |
-
|
618 |
-
# Enhanced lighting based on scene type and cinematography knowledge
|
619 |
-
if scene_type == "cinematic":
|
620 |
-
if any(term in description_lower for term in ["dramatic", "moody", "dark"]):
|
621 |
-
return ", dramatic lighting"
|
622 |
-
else:
|
623 |
-
return ", cinematic lighting"
|
624 |
-
elif scene_type == "portrait":
|
625 |
-
return ", studio lighting"
|
626 |
-
elif "dramatic" in description_lower or "chaos" in description_lower:
|
627 |
-
return ", dramatic lighting"
|
628 |
-
else:
|
629 |
-
return "" # Skip redundant lighting terms
|
630 |
-
|
631 |
-
|
632 |
-
def _get_style_enhancement(scene_type: str, description_lower: str) -> str:
|
633 |
-
"""Get style enhancement for multi-engine compatibility with token economy"""
|
634 |
-
# Token economy: only add style if it adds unique value
|
635 |
-
if scene_type == "cinematic":
|
636 |
-
if "film grain" not in description_lower:
|
637 |
-
return ", film grain"
|
638 |
-
elif scene_type == "architectural":
|
639 |
-
return ", clean lines"
|
640 |
-
|
641 |
-
return "" # Skip redundant style terms
|
642 |
-
|
643 |
-
|
644 |
-
def _clean_prompt_formatting(prompt: str) -> str:
|
645 |
-
"""Clean up prompt formatting"""
|
646 |
-
if not prompt:
|
647 |
-
return ""
|
648 |
-
|
649 |
-
# Ensure it starts with capital letter
|
650 |
-
prompt = prompt.strip()
|
651 |
-
if prompt:
|
652 |
-
prompt = prompt[0].upper() + prompt[1:] if len(prompt) > 1 else prompt.upper()
|
653 |
-
|
654 |
-
# Clean up spaces and commas
|
655 |
-
prompt = re.sub(r'\s+', ' ', prompt)
|
656 |
-
prompt = re.sub(r',\s*,+', ',', prompt)
|
657 |
-
prompt = re.sub(r'^\s*,\s*', '', prompt) # Remove leading commas
|
658 |
-
prompt = re.sub(r'\s*,\s*$', '', prompt) # Remove trailing commas
|
659 |
-
|
660 |
-
# Remove redundant periods
|
661 |
-
prompt = re.sub(r'\.+', '.', prompt)
|
662 |
-
|
663 |
-
return prompt.strip()
|
664 |
|
665 |
|
666 |
def calculate_prompt_score(prompt: str, analysis_data: Optional[Dict[str, Any]] = None) -> Tuple[int, Dict[str, int]]:
|
@@ -681,103 +456,75 @@ def calculate_prompt_score(prompt: str, analysis_data: Optional[Dict[str, Any]]
|
|
681 |
|
682 |
# Enhanced Prompt Quality (0-25 points)
|
683 |
length_score = min(15, len(prompt) // 10) # Reward appropriate length
|
684 |
-
detail_score = min(10, len(prompt.split(',')) *
|
685 |
breakdown["prompt_quality"] = int(length_score + detail_score)
|
686 |
|
687 |
# Technical Details with Cinematography Focus (0-25 points)
|
688 |
tech_score = 0
|
689 |
|
690 |
# Cinema equipment (higher scores for professional gear)
|
691 |
-
cinema_equipment = ['
|
692 |
for equipment in cinema_equipment:
|
693 |
if equipment.lower() in prompt.lower():
|
694 |
-
tech_score +=
|
695 |
break
|
696 |
|
697 |
# Lens specifications
|
698 |
if re.search(r'\d+mm.*f/[\d.]+', prompt):
|
699 |
-
tech_score +=
|
700 |
|
701 |
-
#
|
702 |
-
|
703 |
-
tech_score += sum(4 for term in angle_terms if term in prompt.lower())
|
704 |
-
|
705 |
-
# Anamorphic and specialized lenses
|
706 |
-
if 'anamorphic' in prompt.lower():
|
707 |
tech_score += 4
|
708 |
|
709 |
# Professional terminology
|
710 |
-
tech_keywords = ['shot on', 'lens', '
|
711 |
-
for keyword in tech_keywords
|
712 |
-
if keyword in prompt.lower():
|
713 |
-
tech_score += 2
|
714 |
|
715 |
-
# Bonus for BAGEL cinematography suggestions
|
716 |
-
if analysis_data and analysis_data.get("has_camera_suggestion"):
|
717 |
-
tech_score += 8
|
718 |
-
|
719 |
breakdown["technical_details"] = min(25, tech_score)
|
720 |
|
721 |
-
# Professional Cinematography (0-25 points)
|
722 |
cinema_score = 0
|
723 |
|
724 |
-
# Camera angles (high value for professional cinematography)
|
725 |
-
angle_terms = ['low-angle', 'high-angle', 'eye-level', 'dutch angle', 'bird\'s eye', 'worm\'s eye']
|
726 |
-
cinema_score += sum(5 for term in angle_terms if term in prompt.lower())
|
727 |
-
|
728 |
# Professional lighting techniques
|
729 |
-
lighting_terms = ['
|
730 |
-
cinema_score += sum(
|
731 |
|
732 |
# Composition techniques
|
733 |
-
composition_terms = ['composition', '
|
734 |
-
cinema_score += sum(
|
735 |
-
|
736 |
-
# Cinematography style elements
|
737 |
-
style_terms = ['film grain', 'anamorphic', 'telephoto compression', 'wide-angle', 'shallow depth']
|
738 |
-
cinema_score += sum(3 for term in style_terms if term in prompt.lower())
|
739 |
|
740 |
# Professional context bonus
|
741 |
-
if analysis_data and analysis_data.get("
|
742 |
-
cinema_score +=
|
743 |
|
744 |
breakdown["professional_cinematography"] = min(25, cinema_score)
|
745 |
|
746 |
-
# Multi-Engine Optimization (0-25 points)
|
747 |
optimization_score = 0
|
748 |
|
749 |
-
# Check for technical specifications
|
750 |
-
if re.search(r'(?:Canon|Sony|Leica|
|
751 |
-
optimization_score +=
|
752 |
|
|
|
753 |
if re.search(r'\d+mm.*f/[\d.]+.*ISO \d+', prompt):
|
754 |
-
optimization_score +=
|
755 |
-
|
756 |
-
# Token economy bonus: penalize redundant keywords
|
757 |
-
redundant_keywords = ['photorealistic', 'ultra-detailed', 'professional photography']
|
758 |
-
has_camera_specs = bool(re.search(r'(?:Canon|Sony|Leica|ARRI|RED)', prompt))
|
759 |
|
760 |
-
|
761 |
-
|
762 |
-
|
763 |
-
optimization_score += max(0, 5 - redundant_count * 2) # Penalty for redundancy
|
764 |
-
else:
|
765 |
-
# If no camera specs, quality keywords are valuable
|
766 |
-
quality_keywords = sum(1 for keyword in redundant_keywords if keyword in prompt.lower())
|
767 |
-
optimization_score += min(5, quality_keywords * 2)
|
768 |
|
769 |
-
#
|
770 |
-
if any(style in prompt for style in FLUX_RULES.get("style_enhancements", {}).values()):
|
771 |
-
optimization_score += 3
|
772 |
-
|
773 |
-
# Length efficiency bonus
|
774 |
word_count = len(prompt.split())
|
775 |
-
if word_count <=
|
776 |
-
optimization_score +=
|
|
|
|
|
777 |
|
778 |
breakdown["multi_engine_optimization"] = min(25, optimization_score)
|
779 |
|
780 |
-
# Calculate total
|
781 |
total_score = sum(breakdown.values())
|
782 |
|
783 |
return total_score, breakdown
|
@@ -794,7 +541,6 @@ def calculate_professional_enhanced_score(prompt: str, analysis_data: Optional[D
|
|
794 |
Returns:
|
795 |
Tuple of (total_score, breakdown_dict)
|
796 |
"""
|
797 |
-
# Use the enhanced scoring system
|
798 |
return calculate_prompt_score(prompt, analysis_data)
|
799 |
|
800 |
|
@@ -842,9 +588,9 @@ def format_analysis_report(analysis_data: Dict[str, Any], processing_time: float
|
|
842 |
**Professional Context:** {'✅ Applied' if has_cinema_context else '❌ Not Applied'}
|
843 |
|
844 |
**🎯 OPTIMIZATIONS APPLIED:**
|
845 |
-
✅
|
846 |
✅ Professional camera configuration
|
847 |
-
✅
|
848 |
✅ Token economy optimization
|
849 |
✅ Multi-engine compatibility
|
850 |
✅ Redundancy elimination
|
|
|
142 |
if not prompt or not isinstance(prompt, str):
|
143 |
return ""
|
144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
try:
|
146 |
+
# Step 1: Extract and clean the core description
|
147 |
+
core_description = _extract_clean_description(prompt)
|
148 |
+
if not core_description:
|
149 |
+
return "Professional photograph with technical excellence"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
|
151 |
+
# Step 2: Get camera configuration
|
152 |
+
camera_setup = _get_camera_setup(analysis_metadata, core_description)
|
|
|
|
|
|
|
|
|
153 |
|
154 |
+
# Step 3: Get essential style keywords
|
155 |
+
style_keywords = _get_essential_keywords(core_description, camera_setup, analysis_metadata)
|
|
|
|
|
|
|
|
|
156 |
|
157 |
+
# Step 4: Build final optimized prompt
|
158 |
+
final_prompt = _build_optimized_prompt(core_description, camera_setup, style_keywords)
|
|
|
|
|
|
|
|
|
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"Prompt optimization failed: {e}")
|
165 |
+
return _create_fallback_prompt(prompt)
|
166 |
|
167 |
|
168 |
+
def _extract_clean_description(prompt: str) -> str:
|
169 |
+
"""Extract and clean the core description from BAGEL output"""
|
170 |
try:
|
171 |
+
# Remove CAMERA_SETUP section
|
172 |
+
if "CAMERA_SETUP:" in prompt:
|
173 |
+
description = prompt.split("CAMERA_SETUP:")[0].strip()
|
174 |
+
elif "2. CAMERA_SETUP" in prompt:
|
175 |
+
description = prompt.split("2. CAMERA_SETUP")[0].strip()
|
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 |
+
# Remove verbose introduction phrases
|
183 |
+
remove_patterns = [
|
184 |
+
r'^This image (?:features|shows|depicts|presents|captures)',
|
185 |
+
r'^The image (?:features|shows|depicts|presents|captures)',
|
186 |
+
r'^This (?:photograph|picture|scene) (?:features|shows|depicts)',
|
187 |
+
r'^(?:In this image,?|Looking at this image,?)',
|
188 |
+
r'(?:possibly|apparently|seemingly|appears to be|seems to be)',
|
189 |
]
|
190 |
|
191 |
+
for pattern in remove_patterns:
|
192 |
+
description = re.sub(pattern, '', description, flags=re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
|
194 |
+
# Convert to concise, direct language
|
195 |
+
description = _convert_to_direct_language(description)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
|
197 |
+
# Clean up formatting
|
198 |
+
description = re.sub(r'\s+', ' ', description).strip()
|
199 |
|
200 |
+
# Limit length for efficiency
|
201 |
+
if len(description) > 200:
|
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"Description extraction failed: {e}")
|
209 |
+
return prompt[:100] if prompt else ""
|
210 |
+
|
211 |
+
|
212 |
+
def _convert_to_direct_language(text: str) -> str:
|
213 |
+
"""Convert verbose descriptive text to direct, concise language"""
|
214 |
+
try:
|
215 |
+
# Direct conversions for common verbose phrases
|
216 |
+
conversions = [
|
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 |
except Exception as e:
|
252 |
+
logger.warning(f"Language conversion failed: {e}")
|
253 |
+
return text
|
254 |
|
255 |
|
256 |
+
def _get_camera_setup(analysis_metadata: Optional[Dict[str, Any]], description: str) -> str:
|
257 |
+
"""Get camera setup configuration"""
|
258 |
try:
|
259 |
+
# Check if BAGEL provided camera setup
|
260 |
+
if analysis_metadata and analysis_metadata.get("has_camera_suggestion"):
|
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 |
+
# Detect scene type and provide appropriate camera setup
|
266 |
+
scene_type = _detect_scene_from_content(description)
|
267 |
+
return _get_scene_camera_setup(scene_type)
|
268 |
|
269 |
+
except Exception as e:
|
270 |
+
logger.warning(f"Camera setup detection failed: {e}")
|
271 |
+
return "shot on professional camera"
|
272 |
+
|
273 |
+
|
274 |
+
def _format_camera_setup(raw_setup: str) -> str:
|
275 |
+
"""Format camera setup into clean, concise format"""
|
276 |
+
try:
|
277 |
+
# Extract camera model
|
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 style keywords without redundancy"""
|
356 |
try:
|
357 |
+
keywords = []
|
358 |
+
description_lower = description.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
359 |
|
360 |
+
# Only add depth of field if not already mentioned
|
361 |
+
if "depth" not in description_lower and "bokeh" not in description_lower:
|
362 |
+
if any(term in camera_setup for term in ["f/1.4", "f/2.8", "85mm"]):
|
363 |
+
keywords.append("shallow depth of field")
|
364 |
|
365 |
+
# Add professional photography only if no specific camera mentioned
|
366 |
+
if "shot on" not in camera_setup:
|
367 |
+
keywords.append("professional photography")
|
|
|
|
|
|
|
|
|
368 |
|
369 |
+
# Scene-specific keywords
|
370 |
+
if "portrait" in description_lower and "studio lighting" not in description_lower:
|
371 |
+
keywords.append("professional portrait")
|
|
|
372 |
|
373 |
+
# Technical quality (only if needed)
|
374 |
+
if len(keywords) < 2:
|
375 |
+
keywords.append("high quality")
|
376 |
|
377 |
+
return keywords[:3] # Limit to 3 essential keywords
|
|
|
378 |
|
379 |
+
except Exception as e:
|
380 |
+
logger.warning(f"Keyword extraction failed: {e}")
|
381 |
+
return ["professional photography"]
|
382 |
+
|
383 |
+
|
384 |
+
def _build_optimized_prompt(description: str, camera_setup: str, keywords: List[str]) -> str:
|
385 |
+
"""Build final optimized prompt with proper structure"""
|
386 |
+
try:
|
387 |
+
# Structure: Description + Technical + Style
|
388 |
+
parts = []
|
389 |
+
|
390 |
+
# Core description (clean and concise)
|
391 |
+
if description:
|
392 |
+
parts.append(description)
|
393 |
+
|
394 |
+
# Technical setup
|
395 |
+
if camera_setup:
|
396 |
+
parts.append(camera_setup)
|
397 |
+
|
398 |
+
# Essential keywords
|
399 |
+
if keywords:
|
400 |
+
parts.extend(keywords)
|
401 |
+
|
402 |
+
# Join with consistent separator
|
403 |
+
result = ", ".join(parts)
|
404 |
+
|
405 |
+
# Final cleanup
|
406 |
+
result = re.sub(r'\s*,\s*,+', ',', result) # Remove double commas
|
407 |
+
result = re.sub(r'\s+', ' ', result) # Clean spaces
|
408 |
+
result = result.strip().rstrip(',') # Remove trailing comma
|
409 |
|
410 |
+
# Ensure it starts with capital letter
|
411 |
+
if result:
|
412 |
+
result = result[0].upper() + result[1:] if len(result) > 1 else result.upper()
|
413 |
|
414 |
+
return result
|
415 |
|
416 |
except Exception as e:
|
417 |
+
logger.error(f"Prompt building failed: {e}")
|
418 |
+
return "Professional photograph"
|
419 |
|
420 |
|
421 |
+
def _create_fallback_prompt(original_prompt: str) -> str:
|
422 |
+
"""Create fallback prompt when optimization fails"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
423 |
try:
|
424 |
+
# Extract first meaningful sentence
|
425 |
+
sentences = re.split(r'[.!?]', original_prompt)
|
426 |
+
if sentences:
|
427 |
+
clean_sentence = sentences[0].strip()
|
428 |
+
# Remove verbose starters
|
429 |
+
clean_sentence = re.sub(r'^(This image shows|The image depicts|This photograph)', '', clean_sentence, flags=re.IGNORECASE)
|
430 |
+
clean_sentence = clean_sentence.strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
431 |
|
432 |
+
if len(clean_sentence) > 20:
|
433 |
+
return f"{clean_sentence}, professional photography"
|
434 |
+
|
435 |
+
return "Professional photograph with technical excellence"
|
436 |
+
|
437 |
+
except Exception:
|
438 |
+
return "Professional photograph"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
439 |
|
440 |
|
441 |
def calculate_prompt_score(prompt: str, analysis_data: Optional[Dict[str, Any]] = None) -> Tuple[int, Dict[str, int]]:
|
|
|
456 |
|
457 |
# Enhanced Prompt Quality (0-25 points)
|
458 |
length_score = min(15, len(prompt) // 10) # Reward appropriate length
|
459 |
+
detail_score = min(10, len(prompt.split(',')) * 2) # Reward structured detail
|
460 |
breakdown["prompt_quality"] = int(length_score + detail_score)
|
461 |
|
462 |
# Technical Details with Cinematography Focus (0-25 points)
|
463 |
tech_score = 0
|
464 |
|
465 |
# Cinema equipment (higher scores for professional gear)
|
466 |
+
cinema_equipment = ['Canon EOS R', 'Sony A1', 'Leica', 'Hasselblad', 'Phase One', 'ARRI', 'RED']
|
467 |
for equipment in cinema_equipment:
|
468 |
if equipment.lower() in prompt.lower():
|
469 |
+
tech_score += 8
|
470 |
break
|
471 |
|
472 |
# Lens specifications
|
473 |
if re.search(r'\d+mm.*f/[\d.]+', prompt):
|
474 |
+
tech_score += 6
|
475 |
|
476 |
+
# ISO settings
|
477 |
+
if re.search(r'ISO \d+', prompt):
|
|
|
|
|
|
|
|
|
478 |
tech_score += 4
|
479 |
|
480 |
# Professional terminology
|
481 |
+
tech_keywords = ['shot on', 'lens', 'depth of field', 'bokeh']
|
482 |
+
tech_score += sum(3 for keyword in tech_keywords if keyword in prompt.lower())
|
|
|
|
|
483 |
|
|
|
|
|
|
|
|
|
484 |
breakdown["technical_details"] = min(25, tech_score)
|
485 |
|
486 |
+
# Professional Cinematography (0-25 points)
|
487 |
cinema_score = 0
|
488 |
|
|
|
|
|
|
|
|
|
489 |
# Professional lighting techniques
|
490 |
+
lighting_terms = ['professional lighting', 'studio lighting', 'natural lighting']
|
491 |
+
cinema_score += sum(4 for term in lighting_terms if term in prompt.lower())
|
492 |
|
493 |
# Composition techniques
|
494 |
+
composition_terms = ['composition', 'depth of field', 'bokeh', 'shallow depth']
|
495 |
+
cinema_score += sum(3 for term in composition_terms if term in prompt.lower())
|
|
|
|
|
|
|
|
|
496 |
|
497 |
# Professional context bonus
|
498 |
+
if analysis_data and analysis_data.get("has_camera_suggestion"):
|
499 |
+
cinema_score += 6
|
500 |
|
501 |
breakdown["professional_cinematography"] = min(25, cinema_score)
|
502 |
|
503 |
+
# Multi-Engine Optimization (0-25 points)
|
504 |
optimization_score = 0
|
505 |
|
506 |
+
# Check for technical specifications
|
507 |
+
if re.search(r'(?:Canon|Sony|Leica|Phase One)', prompt):
|
508 |
+
optimization_score += 10
|
509 |
|
510 |
+
# Complete technical specs
|
511 |
if re.search(r'\d+mm.*f/[\d.]+.*ISO \d+', prompt):
|
512 |
+
optimization_score += 8
|
|
|
|
|
|
|
|
|
513 |
|
514 |
+
# Professional terminology
|
515 |
+
pro_terms = ['professional', 'shot on', 'high quality']
|
516 |
+
optimization_score += sum(2 for term in pro_terms if term in prompt.lower())
|
|
|
|
|
|
|
|
|
|
|
517 |
|
518 |
+
# Length efficiency bonus (reward conciseness)
|
|
|
|
|
|
|
|
|
519 |
word_count = len(prompt.split())
|
520 |
+
if 30 <= word_count <= 60: # Optimal range
|
521 |
+
optimization_score += 5
|
522 |
+
elif word_count <= 30:
|
523 |
+
optimization_score += 3
|
524 |
|
525 |
breakdown["multi_engine_optimization"] = min(25, optimization_score)
|
526 |
|
527 |
+
# Calculate total
|
528 |
total_score = sum(breakdown.values())
|
529 |
|
530 |
return total_score, breakdown
|
|
|
541 |
Returns:
|
542 |
Tuple of (total_score, breakdown_dict)
|
543 |
"""
|
|
|
544 |
return calculate_prompt_score(prompt, analysis_data)
|
545 |
|
546 |
|
|
|
588 |
**Professional Context:** {'✅ Applied' if has_cinema_context else '❌ Not Applied'}
|
589 |
|
590 |
**🎯 OPTIMIZATIONS APPLIED:**
|
591 |
+
✅ Clean description extraction
|
592 |
✅ Professional camera configuration
|
593 |
+
✅ Essential keyword optimization
|
594 |
✅ Token economy optimization
|
595 |
✅ Multi-engine compatibility
|
596 |
✅ Redundancy elimination
|