SpotMaster_v1.0 / core /shooting_board.py
Marek4321's picture
Update core/shooting_board.py
ce54cc0 verified
import json
import httpx
import asyncio
from typing import Dict, Any, Optional
from utils.logger import SpotMakerLogger
class ShootingBoardGenerator:
"""
Generator szczeg贸艂owego scenariusza spotu reklamowego przy u偶yciu Hailuo API.
"""
def __init__(self, api_key: str, logger: SpotMakerLogger):
self.api_key = api_key
self.logger = logger
self.api_url = "https://api.minimaxi.chat/v1/text/chatcompletion_v2"
self.model = "MiniMax-Text-01"
self.max_retries = 3
self.retry_delay = 5 # sekundy
async def generate(self, concept: str) -> Dict[str, Any]:
"""
Generuje szczeg贸艂owy scenariusz na podstawie koncepcji.
Args:
concept: Opis koncepcji reklamowej
Returns:
Dict zawieraj膮cy scenariusz i tekst lektora
"""
retry_count = 0
last_error = None
while retry_count < self.max_retries:
try:
self.logger.log("shooting_board",
f"Pr贸ba {retry_count + 1}/{self.max_retries}",
"info")
self.logger.update_progress("shooting_board", 10, "Przygotowuj臋 prompt...")
prompt = self._prepare_prompt(concept)
self.logger.update_progress("shooting_board", 30, "Wysy艂am zapytanie do API...")
response = await self._call_api(prompt)
self.logger.update_progress("shooting_board", 60, "Przetwarzam odpowied藕...")
result = self._parse_response(response)
self.logger.update_progress("shooting_board", 100, "Zako艅czono generowanie scenariusza")
return result
except httpx.TimeoutException as e:
last_error = f"Timeout podczas po艂膮czenia z API: {str(e)}"
self.logger.log("shooting_board", last_error, "warning")
except httpx.HTTPError as e:
last_error = f"B艂膮d HTTP podczas po艂膮czenia z API: {str(e)}"
self.logger.log("shooting_board", last_error, "warning")
except Exception as e:
if "Znaleziono" in str(e) and "uj臋膰" in str(e):
# B艂膮d parsowania odpowiedzi - nie pr贸bujemy ponownie
raise
last_error = str(e)
self.logger.log("shooting_board", f"Nieoczekiwany b艂膮d: {last_error}", "warning")
retry_count += 1
if retry_count < self.max_retries:
delay = self.retry_delay * retry_count
self.logger.log("shooting_board",
f"Ponawiam za {delay}s...",
"info")
await asyncio.sleep(delay)
# Je艣li wszystkie pr贸by si臋 nie powiod艂y
error_msg = f"Nie uda艂o si臋 wygenerowa膰 scenariusza po {self.max_retries} pr贸bach. Ostatni b艂膮d: {last_error}"
self.logger.log("shooting_board", error_msg, "error")
raise Exception(error_msg)
def _prepare_prompt(self, concept: str) -> str:
"""Przygotowuje prompt dla modelu LLM."""
return f"""Jako do艣wiadczony re偶yser reklam, stw贸rz szczeg贸艂owy scenariusz spotu reklamowego
na podstawie poni偶szej koncepcji.
Koncepcja:
{concept}
Utw贸rz DOK艁ADNIE 5 uj臋膰 oraz tekst lektora. Format odpowiedzi MUSI by膰 nast臋puj膮cy:
Uj臋cie 1: [szczeg贸艂owy opis pierwszej sceny]
Uj臋cie 2: [szczeg贸艂owy opis drugiej sceny]
Uj臋cie 3: [szczeg贸艂owy opis trzeciej sceny]
Uj臋cie 4: [szczeg贸艂owy opis czwartej sceny]
Uj臋cie 5: [szczeg贸艂owy opis pi膮tej sceny]
TEKST LEKTORA:
[tekst lektora]
UWAGA: Odpowied藕 MUSI zawiera膰 DOK艁ADNIE 5 uj臋膰 oznaczonych numerami od 1 do 5.
Ka偶de uj臋cie musi zaczyna膰 si臋 od "Uj臋cie X:" gdzie X to numer uj臋cia.
Tekst lektora musi by膰 oddzielony nag艂贸wkiem "TEKST LEKTORA:".
Prosz臋 艣ci艣le trzyma膰 si臋 tego formatu."""
async def _call_api(self, prompt: str) -> Dict[str, Any]:
"""Wysy艂a zapytanie do Hailuo API."""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
data = {
"model": self.model,
"temperature": 0.2, # Ni偶sza temperatura dla bardziej sp贸jnych wynik贸w
"max_tokens": 1000, # Zwi臋kszamy limit token贸w dla d艂u偶szych opis贸w
"messages": [
{
"role": "system",
"name": "Director",
"content": "Jeste艣 do艣wiadczonym re偶yserem reklam specjalizuj膮cym si臋 w tworzeniu kreatywnych i anga偶uj膮cych spot贸w reklamowych."
},
{
"role": "user",
"name": "user",
"content": prompt
}
]
}
self.logger.log("shooting_board",
f"Wysy艂am request do: {self.api_url}",
"debug")
async with httpx.AsyncClient() as client:
try:
response = await client.post(
self.api_url,
headers=headers,
json=data,
timeout=30.0
)
if response.status_code != 200:
error_msg = f"API error: {response.status_code} - {response.text}"
self.logger.log("shooting_board", error_msg, "error")
raise httpx.HTTPError(error_msg)
return response.json()
except httpx.TimeoutException as e:
raise httpx.TimeoutException(f"Timeout podczas po艂膮czenia z API: {str(e)}")
except Exception as e:
raise Exception(f"B艂膮d podczas wywo艂ania API: {str(e)}")
def _parse_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
"""Przetwarza odpowied藕 z API."""
try:
if response.get('base_resp', {}).get('status_code') != 0:
error_msg = response.get('base_resp', {}).get('status_msg', 'Unknown API error')
raise ValueError(f"API error: {error_msg}")
content = response['choices'][0]['message']['content']
self.logger.log("shooting_board", f"Przetwarzam odpowied藕:\n{content}", "debug")
# Przygotowanie kontener贸w na wyniki
key_frames = []
voice_over = ""
# Podziel tekst na linie i znajd藕 uj臋cia oraz tekst lektora
lines = content.split('\n')
current_section = None
current_description = ""
for line in lines:
line = line.strip()
if not line:
continue
# Bardziej elastyczne wykrywanie uj臋膰
if any(marker in line.lower() for marker in ["uj臋cie", "scena", "frame", "shot"]):
# Je艣li mamy poprzedni opis, zapisz go
if current_description and current_section == 'frame':
key_frames.append({"description": current_description.strip()})
current_section = 'frame'
current_description = line.split(":", 1)[1].strip() if ":" in line else ""
elif any(marker in line.upper() for marker in ["TEKST LEKTORA", "VOICE OVER", "LEKTOR"]):
# Je艣li mamy ostatni opis uj臋cia, zapisz go
if current_description and current_section == 'frame':
key_frames.append({"description": current_description.strip()})
current_section = 'lektor'
current_description = ""
else:
# Dodaj lini臋 do aktualnego opisu
if current_section == 'frame':
current_description += " " + line
elif current_section == 'lektor':
voice_over += " " + line.strip('"')
# Dodaj ostatni opis je艣li istnieje
if current_description and current_section == 'frame':
key_frames.append({"description": current_description.strip()})
# Walidacja wynik贸w z bardziej szczeg贸艂owym komunikatem b艂臋du
if len(key_frames) != 5:
error_details = f"Znaleziono {len(key_frames)} uj臋膰 zamiast wymaganych 5."
self.logger.log("shooting_board", error_details, "error")
self.logger.log("shooting_board", f"Pe艂na odpowied藕: {content}", "debug")
raise ValueError(error_details)
if not voice_over.strip():
raise ValueError("Brak tekstu dla lektora")
return {
"key_frames": key_frames,
"voice_over": voice_over.strip()
}
except Exception as e:
error_msg = f"B艂膮d przetwarzania odpowiedzi: {str(e)}"
self.logger.log("shooting_board", error_msg, "error")
raise Exception(error_msg)
def get_status(self) -> Dict[str, Any]:
"""Zwraca aktualny status generatora."""
return {
'progress': self.logger.get_module_progress("shooting_board"),
'status': self.logger.get_module_status("shooting_board")
}