feat: 新增视频风格功能

close #32
This commit is contained in:
TheSmallHanCat
2025-12-24 10:12:59 +08:00
parent 2f6fc345a9
commit 2c2fd44b6a
3 changed files with 130 additions and 12 deletions

View File

@@ -166,6 +166,27 @@ class GenerationHandler:
return cleaned
def _extract_style(self, prompt: str) -> tuple[str, Optional[str]]:
"""Extract style from prompt
Args:
prompt: Original prompt
Returns:
Tuple of (cleaned_prompt, style_id)
"""
# Extract {style} pattern
match = re.search(r'\{([^}]+)\}', prompt)
if match:
style_id = match.group(1).strip()
# Remove {style} from prompt
cleaned_prompt = re.sub(r'\{[^}]+\}', '', prompt).strip()
# Clean up extra whitespace
cleaned_prompt = ' '.join(cleaned_prompt.split())
debug_logger.log_info(f"Extracted style: '{style_id}' from prompt: '{prompt}'")
return cleaned_prompt, style_id
return prompt, None
async def _download_file(self, url: str) -> bytes:
"""Download file from URL
@@ -339,30 +360,35 @@ class GenerationHandler:
# Get n_frames from model configuration
n_frames = model_config.get("n_frames", 300) # Default to 300 frames (10s)
# Extract style from prompt
clean_prompt, style_id = self._extract_style(prompt)
# Check if prompt is in storyboard format
if self.sora_client.is_storyboard_prompt(prompt):
if self.sora_client.is_storyboard_prompt(clean_prompt):
# Storyboard mode
if stream:
yield self._format_stream_chunk(
reasoning_content="Detected storyboard format. Converting to storyboard API format...\n"
)
formatted_prompt = self.sora_client.format_storyboard_prompt(prompt)
formatted_prompt = self.sora_client.format_storyboard_prompt(clean_prompt)
debug_logger.log_info(f"Storyboard mode detected. Formatted prompt: {formatted_prompt}")
task_id = await self.sora_client.generate_storyboard(
formatted_prompt, token_obj.token,
orientation=model_config["orientation"],
media_id=media_id,
n_frames=n_frames
n_frames=n_frames,
style_id=style_id
)
else:
# Normal video generation
task_id = await self.sora_client.generate_video(
prompt, token_obj.token,
clean_prompt, token_obj.token,
orientation=model_config["orientation"],
media_id=media_id,
n_frames=n_frames
n_frames=n_frames,
style_id=style_id
)
else:
task_id = await self.sora_client.generate_image(
@@ -1325,6 +1351,9 @@ class GenerationHandler:
# Clean remix link from prompt to avoid duplication
clean_prompt = self._clean_remix_link_from_prompt(prompt)
# Extract style from prompt
clean_prompt, style_id = self._extract_style(clean_prompt)
# Get n_frames from model configuration
n_frames = model_config.get("n_frames", 300) # Default to 300 frames (10s)
@@ -1337,7 +1366,8 @@ class GenerationHandler:
prompt=clean_prompt,
token=token_obj.token,
orientation=model_config["orientation"],
n_frames=n_frames
n_frames=n_frames,
style_id=style_id
)
debug_logger.log_info(f"Remix generation started, task_id: {task_id}")

View File

@@ -254,7 +254,7 @@ class SoraClient:
return result["id"]
async def generate_video(self, prompt: str, token: str, orientation: str = "landscape",
media_id: Optional[str] = None, n_frames: int = 450) -> str:
media_id: Optional[str] = None, n_frames: int = 450, style_id: Optional[str] = None) -> str:
"""Generate video (text-to-video or image-to-video)"""
inpaint_items = []
if media_id:
@@ -270,7 +270,8 @@ class SoraClient:
"size": "small",
"n_frames": n_frames,
"model": "sy_8",
"inpaint_items": inpaint_items
"inpaint_items": inpaint_items,
"style_id": style_id
}
# 生成请求需要添加 sentinel token
@@ -648,7 +649,7 @@ class SoraClient:
return True
async def remix_video(self, remix_target_id: str, prompt: str, token: str,
orientation: str = "portrait", n_frames: int = 450) -> str:
orientation: str = "portrait", n_frames: int = 450, style_id: Optional[str] = None) -> str:
"""Generate video using remix (based on existing video)
Args:
@@ -657,6 +658,7 @@ class SoraClient:
token: Access token
orientation: Video orientation (portrait/landscape)
n_frames: Number of frames
style_id: Optional style ID
Returns:
task_id
@@ -670,14 +672,15 @@ class SoraClient:
"cameo_replacements": {},
"model": "sy_8",
"orientation": orientation,
"n_frames": n_frames
"n_frames": n_frames,
"style_id": style_id
}
result = await self._make_request("POST", "/nf/create", token, json_data=json_data, add_sentinel_token=True)
return result.get("id")
async def generate_storyboard(self, prompt: str, token: str, orientation: str = "landscape",
media_id: Optional[str] = None, n_frames: int = 450) -> str:
media_id: Optional[str] = None, n_frames: int = 450, style_id: Optional[str] = None) -> str:
"""Generate video using storyboard mode
Args:
@@ -686,6 +689,7 @@ class SoraClient:
orientation: Video orientation (portrait/landscape)
media_id: Optional image media_id for image-to-video
n_frames: Number of frames
style_id: Optional style ID
Returns:
task_id
@@ -709,7 +713,7 @@ class SoraClient:
"remix_target_id": None,
"model": "sy_8",
"metadata": None,
"style_id": None,
"style_id": style_id,
"cameo_ids": None,
"cameo_replacements": None,
"audio_caption": None,