视频复刻添加描述框
This commit is contained in:
@@ -95,7 +95,9 @@ CHARACTER_PROMPT = (
|
||||
"【声音重建规则——最高优先级】被替换人物对应的所有声音必须同步替换,包括画面内对白、口播、旁白、画外音、"
|
||||
"语气词和呼吸声。只保留原口播的文字内容、信息含义、句子顺序、出现时间、停顿位置、情绪类型和表达节奏;"
|
||||
"不得保留原人物的声线、音色、音高、年龄感、性别特征、口音、声纹或其他说话人身份特征。"
|
||||
"根据@目标角色的身份及系统提供的目标声音设定,重新生成全部相关人声。"
|
||||
"根据@目标角色的形象、下方【目标角色设定】,以及参考图中可见的性别与年龄特征,重新生成全部相关人声。"
|
||||
"当@目标角色与参考视频中原人物的性别或年龄段不同时,重建人声的性别音色必须随目标角色改变:"
|
||||
"男性角色必须用男声,女性角色必须用女声,绝不允许沿用原人物的性别音色。"
|
||||
"从视频开始到结束,被替换人物的声音必须始终使用同一个目标声音;不得出现原人物声线残留、声音身份中途变化、"
|
||||
"新旧声音混用,或画面角色已经替换但声音仍属于原人物的情况。"
|
||||
"画面中目标角色的嘴部动作必须与重新生成的人声一致,口型、台词开始时间、停顿、语速和情绪表达保持同步。"
|
||||
@@ -103,6 +105,51 @@ CHARACTER_PROMPT = (
|
||||
"如保留原视频声音与目标角色声音冲突,必须舍弃原人物声音,以重新生成的目标声音为最高优先级。"
|
||||
)
|
||||
|
||||
# 用户在第 3 步填的补充信息(商品名/品类,或角色性别年龄音色)。选填,不填维持原行为。
|
||||
MAX_SUBJECT_BRIEF = 500
|
||||
|
||||
|
||||
def _clean_subject_brief(value) -> str:
|
||||
return str(value or "").strip()[:MAX_SUBJECT_BRIEF]
|
||||
|
||||
|
||||
def _voice_gender_directive(brief: str) -> str:
|
||||
"""从用户填的角色信息里读出性别 → 写死声线要求。
|
||||
|
||||
Seedance 拿不到明确性别时倾向于沿用原音轨声线,女换男会出现
|
||||
「画面换成男的、声音还是女的」。用户明确写了性别就不再让模型猜。
|
||||
男女都提到(例如「把女的换成男的」)时不下断言,交给上面的通用规则。
|
||||
"""
|
||||
has_male = "男" in brief
|
||||
has_female = "女" in brief
|
||||
if has_male and not has_female:
|
||||
return (
|
||||
"【目标声音设定】目标角色为男性:重建的全部人声必须是成年男性声线,中低音域,胸腔共鸣,"
|
||||
"绝不允许出现女性音色、女性音高或偏女的中性声线。"
|
||||
)
|
||||
if has_female and not has_male:
|
||||
return (
|
||||
"【目标声音设定】目标角色为女性:重建的全部人声必须是成年女性声线,中高音域,"
|
||||
"绝不允许出现男性音色或男性音高。"
|
||||
)
|
||||
return ""
|
||||
|
||||
|
||||
def build_character_submit_prompt(subject_brief: str = "") -> str:
|
||||
"""角色复刻的提交提示词 = 固定规则 + 用户填的角色设定 + 推导出的声线要求。
|
||||
|
||||
⚠️ CHARACTER_PROMPT 必须留在开头:老任务靠 prompt 前缀认 replace_mode。
|
||||
"""
|
||||
brief = _clean_subject_brief(subject_brief)
|
||||
parts = [CHARACTER_PROMPT]
|
||||
if brief:
|
||||
parts.append(f"【目标角色设定】(用户填写,形象与声音的依据){brief}")
|
||||
voice = _voice_gender_directive(brief)
|
||||
if voice:
|
||||
parts.append(voice)
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
# 拆解期间的占位提示词。真正的提示词在 worker 拆完后回写。
|
||||
DIGEST_PENDING_PROMPT = "正在提炼参考视频的分镜稿…"
|
||||
|
||||
@@ -668,6 +715,7 @@ def serialize_video_replace_task(task, *, include_deleted_assets: bool = False)
|
||||
"replace_mode": replace_mode,
|
||||
"subject_name": payload.get("subject_name") or "",
|
||||
"subject_source": payload.get("subject_source") or "",
|
||||
"subject_brief": payload.get("subject_brief") or "",
|
||||
"product_id": payload.get("product_id") or "",
|
||||
"model_id": payload.get("model_id") or "",
|
||||
"review_stage": "reviewing" if reviewing else "",
|
||||
@@ -726,6 +774,9 @@ def submit_video_replace(*, team, user, params: dict):
|
||||
if video_seconds > REPLACE_REF_DURATION_MAX:
|
||||
raise ValueError("参考视频不能超过 30 秒,请剪短后重试")
|
||||
|
||||
# 第 3 步用户填的补充信息。商品:名称/品类/卖点;角色:性别/年龄/音色。选填。
|
||||
subject_brief = _clean_subject_brief(params.get("subject_brief"))
|
||||
|
||||
if has_product:
|
||||
subject_name, image_refs, subject_source = _product_library_refs(team, product_id)
|
||||
product_facts = _product_semantic_facts_for_id(team, product_id)
|
||||
@@ -744,6 +795,17 @@ def submit_video_replace(*, team, user, params: dict):
|
||||
"禁止推断": "不得编造功效、成分、认证和任何具体卖点;口播只描述看得见的外观、质地和使用感受。",
|
||||
}
|
||||
|
||||
# 用户手填的商品信息优先级最高:临时上传时这往往是唯一的品类来源,
|
||||
# 商品库选品时它补充库里没填的字段。填了就不再是「仅外观」。
|
||||
if replace_mode == "product" and subject_brief:
|
||||
product_facts = {
|
||||
**product_facts,
|
||||
"用户补充信息": subject_brief,
|
||||
"用户补充信息的权威性": "这是用户手动填写的商品事实,可直接作为品类、用途和卖点的依据,优先级高于据图推断。",
|
||||
}
|
||||
if product_facts.get("资料状态") == "仅外观":
|
||||
product_facts["资料状态"] = "部分"
|
||||
|
||||
# 角色替换是 Seedance 视频编辑:火山要求比例/时长跟随输入视频,不能由前端指定。
|
||||
duration = -1 if replace_mode == "character" else _output_duration(params.get("duration"), video_seconds)
|
||||
billing_ratio = str(params.get("aspect_ratio") or "9:16")
|
||||
@@ -756,6 +818,7 @@ def submit_video_replace(*, team, user, params: dict):
|
||||
"product_id": str(product_id) if product_id else "",
|
||||
"model_id": str(model_id) if model_id else "",
|
||||
"product_facts": product_facts,
|
||||
"subject_brief": subject_brief,
|
||||
# adaptive / -1 只用于火山请求;预估积分仍按参考视频实际时长和上传时检测出的比例计算。
|
||||
"billing_duration": max(4, int(round(video_seconds or 4))),
|
||||
"billing_aspect_ratio": billing_ratio,
|
||||
@@ -802,7 +865,10 @@ def submit_video_replace(*, team, user, params: dict):
|
||||
user=user,
|
||||
params={
|
||||
**base_params,
|
||||
"prompt": DIGEST_PENDING_PROMPT if replace_mode == "product" else CHARACTER_PROMPT,
|
||||
"prompt": (
|
||||
DIGEST_PENDING_PROMPT if replace_mode == "product"
|
||||
else build_character_submit_prompt(subject_brief)
|
||||
),
|
||||
"references": image_refs,
|
||||
"extra_payload": extra,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user