CineGen-CPU / cinegen /character_engine.py
VirtualOasis's picture
init
55b3b1b
from __future__ import annotations
import os
from typing import List, Optional, Tuple
from .models import Storyboard
from .placeholders import synthesize_character_card
DEFAULT_IMAGE_MODEL = os.environ.get("CINEGEN_CHARACTER_MODEL", "gemini-2.5-flash-image")
def _load_google_client(api_key: Optional[str]):
if not api_key:
return None
try:
from google import genai
return genai.Client(api_key=api_key)
except Exception: # pragma: no cover - optional dependency
return None
class CharacterDesigner:
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key or os.environ.get("GOOGLE_API_KEY")
self.client = _load_google_client(self.api_key)
def design(self, storyboard: Storyboard) -> Tuple[List[Tuple[str, str]], Storyboard]:
gallery: List[Tuple[str, str]] = []
for character in storyboard.characters:
gallery.append(self._refresh_reference(character, storyboard.style))
return gallery, storyboard
def redesign_character(self, storyboard: Storyboard, character_id: str) -> Tuple[Tuple[str, str], Storyboard]:
target = next((char for char in storyboard.characters if char.identifier == character_id), None)
if not target:
raise ValueError(f"Character {character_id} not found.")
card = self._refresh_reference(target, storyboard.style)
return card, storyboard
def _refresh_reference(self, character, style: str) -> Tuple[str, str]:
image_path = None
if self.client:
image_path = self._try_generate(character, style)
if not image_path:
image_path = synthesize_character_card(character, style)
character.reference_image = image_path
caption = f"{character.name}{character.role}"
return image_path, caption
def _try_generate(self, character, style: str) -> Optional[str]: # pragma: no cover
prompt = (
f"Create a portrait for {character.name}, a {character.role} in a {style} short film. "
f"Traits: {', '.join(character.traits)}. Description: {character.description}."
)
try:
response = self.client.models.generate_content(
model=DEFAULT_IMAGE_MODEL,
contents=[prompt],
)
for part in response.parts:
if getattr(part, "inline_data", None):
image = part.as_image()
tmp_dir = os.path.join("/tmp", "cinegen-characters")
os.makedirs(tmp_dir, exist_ok=True)
path = os.path.join(tmp_dir, f"{character.identifier.lower()}.png")
image.save(path)
return path
except Exception:
return None
return None