auto-save 2026-05-17 20:47 (~4)

This commit is contained in:
2026-05-17 20:47:53 +08:00
parent 6f7bb910f7
commit db248221f7
4 changed files with 289 additions and 31 deletions

View File

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