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 |
|