auto-save 2026-05-17 20:47 (~4)
This commit is contained in:
117
api/main.py
117
api/main.py
@@ -2567,6 +2567,21 @@ class TranslateReq(BaseModel):
|
||||
target: Literal["en", "zh"] = "en"
|
||||
|
||||
|
||||
class ScriptRewriteSegmentReq(BaseModel):
|
||||
index: int
|
||||
start: float = 0.0
|
||||
end: float = 0.0
|
||||
role: str = ""
|
||||
source: str = ""
|
||||
current_text: str = ""
|
||||
|
||||
|
||||
class RewriteStoryboardScriptReq(BaseModel):
|
||||
mode: Literal["segment", "all"] = "segment"
|
||||
author_intent: str = ""
|
||||
segments: list[ScriptRewriteSegmentReq] = Field(default_factory=list)
|
||||
|
||||
|
||||
@app.post("/translate")
|
||||
def translate_text(req: TranslateReq) -> dict:
|
||||
"""单条文本翻译(给生图自定义提取元素 zh→en 用)"""
|
||||
@@ -2602,6 +2617,108 @@ def translate_text(req: TranslateReq) -> dict:
|
||||
raise HTTPException(500, f"translate failed: {e}")
|
||||
|
||||
|
||||
def _fallback_script_rewrite_item(segment: ScriptRewriteSegmentReq, author_intent: str = "") -> dict:
|
||||
text = (segment.current_text or "").strip()
|
||||
source = (segment.source or "").strip()
|
||||
intent = (author_intent or "").strip()
|
||||
if text:
|
||||
rewritten = text
|
||||
elif source:
|
||||
rewritten = f"这一段按原片节奏切到 SKG 肩颈按摩仪,把{source[:28]}转成肩颈放松场景。"
|
||||
else:
|
||||
rewritten = "这一段延续原片节奏,把画面和口播落到 SKG 肩颈放松体验。"
|
||||
if intent and intent not in rewritten:
|
||||
rewritten = f"{rewritten} {intent[:48]}"
|
||||
return {"index": segment.index, "text": rewritten[:220]}
|
||||
|
||||
|
||||
def _parse_script_rewrite_items(raw: str, requested: list[ScriptRewriteSegmentReq], author_intent: str = "") -> list[dict]:
|
||||
text = (raw or "").strip()
|
||||
text = re.sub(r"^```(?:json)?\s*", "", text, flags=re.I).strip()
|
||||
text = re.sub(r"\s*```$", "", text).strip()
|
||||
match = re.search(r"\{[\s\S]*\}", text)
|
||||
json_text = match.group(0) if match else text
|
||||
try:
|
||||
data = json.loads(json_text)
|
||||
except Exception:
|
||||
return [_fallback_script_rewrite_item(segment, author_intent) for segment in requested]
|
||||
raw_items = data.get("items") if isinstance(data, dict) else data
|
||||
if not isinstance(raw_items, list):
|
||||
raw_items = []
|
||||
by_index: dict[int, str] = {}
|
||||
for item in raw_items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
try:
|
||||
idx = int(item.get("index"))
|
||||
except Exception:
|
||||
continue
|
||||
value = str(item.get("text") or item.get("rewritten_text") or "").strip()
|
||||
if value:
|
||||
by_index[idx] = re.sub(r"\s+", " ", value).strip()[:260]
|
||||
return [
|
||||
{"index": segment.index, "text": by_index.get(segment.index) or _fallback_script_rewrite_item(segment, author_intent)["text"]}
|
||||
for segment in requested
|
||||
]
|
||||
|
||||
|
||||
def _rewrite_storyboard_script_sync(req: RewriteStoryboardScriptReq) -> list[dict]:
|
||||
segments = [segment for segment in req.segments if (segment.source or segment.current_text).strip()]
|
||||
if not segments:
|
||||
return []
|
||||
author_intent = (req.author_intent or "").strip()
|
||||
if not LLM_API_KEY:
|
||||
return [_fallback_script_rewrite_item(segment, author_intent) for segment in segments]
|
||||
payload = [
|
||||
{
|
||||
"index": segment.index,
|
||||
"time": f"{segment.start:.1f}-{segment.end:.1f}s",
|
||||
"role": segment.role,
|
||||
"source_reference": segment.source,
|
||||
"current_voiceover": segment.current_text,
|
||||
}
|
||||
for segment in segments
|
||||
]
|
||||
prompt = (
|
||||
"你是信息流广告脚本文案改写师。任务:基于原参考文案的节奏和信息结构,把每段改写成 SKG 挂脖肩颈按摩仪的新口播文案。\n"
|
||||
"硬规则:\n"
|
||||
"1. 输出中文短视频口播,不要英文,不要舞台说明,不要引号。\n"
|
||||
"2. 不逐字翻译原文,不保留原品牌、价格、优惠码、平台话术;只参考节奏、钩子、痛点、转化结构。\n"
|
||||
"3. 产品固定为套在脖子上的 U 形肩颈按摩仪,表达肩颈紧绷、久坐低头、热敷感、揉按感、佩戴放松和日常使用场景。\n"
|
||||
"4. 避免医疗疗效、治疗、治愈、止痛等强功效承诺。\n"
|
||||
"5. 每段尽量短,适配该段时间;保持自然创作者口吻。\n"
|
||||
"6. mode=all 时,整片要前后连贯;mode=segment 时,只改给定段落但仍要贴合上下文风格。\n"
|
||||
f"作者想法:{author_intent or '没有额外想法,按原片节奏改成自然卖点口播。'}\n"
|
||||
f"改写模式:{req.mode}\n"
|
||||
f"SKG 产品背景:{AUDIO_PRODUCT_BRIEF}\n\n"
|
||||
"输入段落 JSON:\n"
|
||||
+ json.dumps(payload, ensure_ascii=False)
|
||||
+ '\n\n只输出严格 JSON:{"items":[{"index":0,"text":"改写后的中文口播"}]}'
|
||||
)
|
||||
try:
|
||||
resp = llm().chat.completions.create(
|
||||
model=AUDIO_REWRITE_MODEL,
|
||||
messages=[
|
||||
{"role": "system", "content": "只返回合法 JSON,不要 markdown,不要解释。"},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
response_format={"type": "json_object"},
|
||||
temperature=0.68 if req.mode == "all" else 0.62,
|
||||
max_tokens=max(900, min(5000, 180 * len(segments) + 500)),
|
||||
)
|
||||
raw = (resp.choices[0].message.content or "").strip()
|
||||
return _parse_script_rewrite_items(raw, segments, author_intent)
|
||||
except Exception:
|
||||
return [_fallback_script_rewrite_item(segment, author_intent) for segment in segments]
|
||||
|
||||
|
||||
@app.post("/jobs/{job_id}/script/rewrite")
|
||||
def rewrite_storyboard_script(job_id: str, req: RewriteStoryboardScriptReq) -> dict:
|
||||
if job_id not in JOBS:
|
||||
raise HTTPException(404, "job not found")
|
||||
return {"items": _rewrite_storyboard_script_sync(req)}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health() -> dict:
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user