feat(monitor): 抖音 Web 接口客户端 —— 绕开爬虫子进程,直接发 HTTP
移植自 mac-agent-os 的 mediacrawler_adapter:不起子进程、不开页面,用浏览器里那份
登录态直接调抖音 Web 接口。产物键名照抄 store/douyin,所以 ingest 那条链路一个字不用改。
**目前能用的(真环境实测,非推断)**:
profile/other : 200, 7075 字节 —— 博主主页指标(粉丝/获赞/作品数/昵称)
aweme/detail : 200, 45425 字节 —— 单条作品详情(含点赞/评论/收藏/分享)
**目前不能用的:作品列表 `aweme/post`。** 两个互相独立的原因:
1. 这个接口被抖音单独升级成了真校验:不带 x-tt-argus 回 403「Uifid Not Found」,
带上 dummy 值回 200 + **空 body**。也就是说「头在不在」骗得过,「真校验」过不了。
同一套头打 profile/other 和 aweme/detail 都是通的 —— 抖音是挑着接口加保护的,
挑中的恰好是「批量拉作品列表」这个最敏感的动作。
2. 改走页面截获也不行:CDP 浏览器打开博主主页会落到「验证码中间页」(当天大量探测的
代价,过几小时要重测)。
所以现在的边界是:**已知作品的指标刷新能做,自动发现新作品做不了**。
**排查中控住变量后得到的两条事实**(都写进注释了):
· `Accept` / `Accept-Language` / `Referer` 才是主页接口能返回真数据的原因 —— 只有
UA+client hints+Cookie 时是 200 但仅 121 字节的空壳,补上这三个头变 7074 字节。
(我先前猜的 sec-ch-ua 不是关键。)
· 因此 UA 与 client hints 必须**成套地取自同一个浏览器**,所以 BrowserIdentity 一次
从 CDP 取齐 cookie + UA + hints,而不是各自写死。
「200 + 空 body 必须当场报错」也是刻意写死的:放过去它会在下游变成「这个博主没作品」,
把一次失败伪装成一条正常结果 —— 爬虫那条路正是这么栽的,还被翻译成「账号被封」。
测试 +11:cookie 解析、请求头成套性(含 uifid 缺失/回退)、产物键名与 store 对齐、
以及 _get 的三条失败路径(空 body / 403 带网关原话 / 正常返回)。
This commit is contained in:
@@ -0,0 +1,801 @@
|
||||
"""
|
||||
composer/realism.py — 写实感增强(去 AI 感)
|
||||
|
||||
来源:外部方法论学习(详见 PLANS/REALISM_GUIDE.md + 09_ave/docs/realism/)
|
||||
作用:为提示词编译提供三件东西——
|
||||
① EMOTION_ACTIONS 情绪外化表(抽象情绪 → 具体身体动作)
|
||||
② REALISM_ANCHORS 写实锚点包(按拍摄风格给"反 AI 感"的具体锚点)
|
||||
③ check_conflicts 矛盾约束自检(写了互相抵消的组合)
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# ① 情绪外化表
|
||||
# 原则:视频模型看不懂抽象形容词,必须外化为身体细节;
|
||||
# 每个镜头只写 1-2 个关键动作(多了稀释焦点)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
EMOTION_ACTIONS: dict = {
|
||||
"悲伤": "低头、肩膀微微颤抖、眼眶泛红,手指无意识地攥紧衣角",
|
||||
"难过": "低头、肩膀微微颤抖、眼眶泛红,手指无意识地攥紧衣角",
|
||||
"喜悦": "嘴角抑制不住地上扬、眉眼舒展,说话时眼睛弯起来",
|
||||
"开心": "嘴角抑制不住地上扬、眉眼舒展,说话时眼睛弯起来",
|
||||
"高兴": "嘴角抑制不住地上扬、眉眼舒展,说话时眼睛弯起来",
|
||||
"紧张": "手指无意识地攥紧衣角、眼神闪躲、呼吸略急",
|
||||
"焦虑": "频繁看表、手指轻敲、眼神游移、下意识抿嘴",
|
||||
"愤怒": "双拳紧握、下颌线绷紧、胸口起伏,从牙缝里挤话",
|
||||
"生气": "双拳紧握、下颌线绷紧、胸口起伏,从牙缝里挤话",
|
||||
"释然": "长舒一口气、紧绷的肩膀放松下来,浮起淡淡的微笑",
|
||||
"放松": "长舒一口气、紧绷的肩膀放松下来,浮起淡淡的微笑",
|
||||
"恐惧": "瞳孔放大、后退半步、手不自觉抓住身边物体、嘴唇发抖",
|
||||
"害怕": "瞳孔放大、后退半步、手不自觉抓住身边物体、嘴唇发抖",
|
||||
"疲惫": "揉太阳穴、眼皮下沉、长呼气、动作迟缓",
|
||||
"疲惫不堪": "揉太阳穴、眼皮下沉、长呼气、动作迟缓",
|
||||
"专注": "眉头微皱、眼神锁定一点、嘴唇抿紧、身体前倾",
|
||||
"认真": "眉头微皱、眼神锁定一点、嘴唇抿紧、身体前倾",
|
||||
"犹豫": "张嘴又闭上、视线游移、手悬在半空、欲言又止",
|
||||
"纠结": "张嘴又闭上、视线游移、手悬在半空、欲言又止",
|
||||
"惊喜": "眼睛瞬间睁大、嘴巴微张、身体向前倾,笑容缓慢绽放",
|
||||
"期待": "身体微微前倾、目光停在一点、呼吸放轻",
|
||||
"尴尬": "视线移向别处、嘴角抽动一下、手指摸了摸鼻尖",
|
||||
"无奈": "轻轻摇头、嘴角扯出一个笑、肩膀垮下来",
|
||||
"委屈": "嘴唇抿成一条线、眼眶发热、目光低垂看向地面",
|
||||
"好奇": "微微歪头、眼睛亮起来、视线跟着对象移动",
|
||||
"警觉": "下颌收紧、眼神快速扫向声源、身体下意识绷住",
|
||||
"疏离": "目光越过对方看向远处、表情平静没有起伏、动作缓慢",
|
||||
"冷漠": "目光越过对方看向远处、表情平静没有起伏、动作缓慢",
|
||||
}
|
||||
|
||||
|
||||
def externalize_emotion(sentiment: str) -> str:
|
||||
"""抽象情绪 → 具体身体动作;未命中则原样返回(调用方可自行处理)"""
|
||||
s = (sentiment or "").strip()
|
||||
if not s:
|
||||
return ""
|
||||
# 直接命中
|
||||
if s in EMOTION_ACTIONS:
|
||||
return EMOTION_ACTIONS[s]
|
||||
# 含"紧张/好奇"这类复合写法(用 / 、 分隔)→ 取第一个能命中的
|
||||
for sep in ("/", "、", ",", ",", " "):
|
||||
if sep in s:
|
||||
for piece in s.split(sep):
|
||||
piece = piece.strip()
|
||||
if piece in EMOTION_ACTIONS:
|
||||
return EMOTION_ACTIONS[piece]
|
||||
# 部分包含
|
||||
for k, v in EMOTION_ACTIONS.items():
|
||||
if k in s:
|
||||
return v
|
||||
return s
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# ② 写实锚点包(按拍摄风格)
|
||||
# 核心:用"设备型号 / 真实光源 / 微瑕疵 / 皮肤细节"替换抽象形容词
|
||||
# ════════════════════════════════════════════════════════════
|
||||
REALISM_ANCHORS: dict = {
|
||||
# 📱 自拍 Vlog(用户核心诉求)
|
||||
"selfie_pov": {
|
||||
"name": "手机自拍 · 第一人称",
|
||||
"device": "iPhone 前置摄像头拍摄,竖屏 9:16,一臂距离(鼻梁与脸颊有轻微广角透视变形)",
|
||||
"hand": "手持自拍,画面随呼吸与手臂疲劳轻微起伏;偶尔抬手微调构图",
|
||||
"light": "窗光从侧面打在脸上、室内顶灯偏暖;背景比人脸暗一档",
|
||||
"skin": "真实皮肤纹理、毛孔与细小绒毛可见、不过度磨皮、没有塑料质感",
|
||||
"flaws": ("前置镜头为**定焦**(不会有对焦搜索);近距离透视变形;"
|
||||
"动态范围有限——窗外或灯下的高光容易过曝、暗部有可见噪点;"
|
||||
"头发有几缕没梳顺"),
|
||||
"motion": "说话时自然眨眼、吞咽、鼻翼微微起伏,视线偶尔离开镜头再回来",
|
||||
"sound": "房间轻微混响、远处环境声、衣服摩擦声(手机麦克风拾音,人声比正常对话稍远)",
|
||||
"_no": ("不要三脚架式的绝对稳定(自然的手持呼吸感要保留)、不要影棚布光、"
|
||||
"不要滤镜磨皮、不要对焦搜索(手机前置是定焦)"),
|
||||
},
|
||||
# 伪纪录片 · 真实自然(现有默认)
|
||||
"documentary_realism": {
|
||||
"device": "手持摄影机质感,轻微呼吸起伏",
|
||||
"hand": "镜头随拍摄者呼吸轻微移动,构图不是完美的",
|
||||
"light": "现场实际光源(窗光、路灯、顶灯),明暗过渡自然",
|
||||
"skin": "真实皮肤纹理、毛孔可见、不过度磨皮",
|
||||
"flaws": "轻微噪点、暗部细节不完全干净、对焦有呼吸感",
|
||||
"motion": "被拍摄者的动作不追求完美,有停顿与迟疑",
|
||||
"_no": "不要运镜特效痕迹、不要 AI 塑料质感、不要字幕水印",
|
||||
},
|
||||
# 电影感
|
||||
"cinematic": {
|
||||
"device": "35mm 电影镜头,浅景深",
|
||||
"hand": "三脚架或稳定器,运动平滑",
|
||||
"light": "动机光(画面内有光源依据)+ 逆光轮廓",
|
||||
"skin": "电影级肤色还原、保留皮肤质感",
|
||||
"flaws": "轻微的镜头光晕、真实的光斑",
|
||||
"motion": "调度从容,动作有起势与收势",
|
||||
"_no": "不要手持晃动、不要纪实粗糙感",
|
||||
},
|
||||
# 棚拍干净
|
||||
"studio_clean": {
|
||||
"device": "专业摄影机,稳定机位",
|
||||
"hand": "固定机位 / 滑轨",
|
||||
"light": "柔光箱主光 + 轮廓光",
|
||||
"skin": "干净但保留皮肤纹理(不要过度磨皮)",
|
||||
"flaws": "极少的噪点,干净背景",
|
||||
"motion": "表演可预期、动作清晰",
|
||||
"_no": "不要手持晃动、不要环境杂光",
|
||||
},
|
||||
# 手持 Vlog · 第一视角(边走边拍,镜头=拍摄者的眼睛)
|
||||
"vlog_handheld": {
|
||||
"device": "手机或轻便相机手持拍摄,竖屏 9:16",
|
||||
"hand": "第一视角手持,画面随走路上下起伏,转向时略有回摆",
|
||||
"light": "自然光为主( daylight / 室内灯),明暗随走动变化",
|
||||
"skin": "真实皮肤纹理、毛孔可见、不过度磨皮",
|
||||
"flaws": "走动时的轻微抖动、暗部噪点、偶尔的手部入画",
|
||||
"motion": "被拍对象自然走动、走动时说话有些喘",
|
||||
"sound": "环境音随移动变化、脚步声、风噪",
|
||||
"_no": "不要稳定器般平滑、不要影棚布光",
|
||||
},
|
||||
# 访谈纪实 · 半身对镜
|
||||
"interview_doc": {
|
||||
"device": "中焦镜头,半身构图,略浅景深",
|
||||
"hand": "机位基本固定,允许极轻微的手持呼吸感",
|
||||
"light": "柔和侧光或窗户光,面部立体但不过曝",
|
||||
"skin": "真实皮肤纹理、皱纹与毛孔保留、不过度磨皮",
|
||||
"flaws": "背景略暗、轻微噪点、被访者偶尔的停顿与眼神移动",
|
||||
"motion": "被访者自然对镜说话,有停顿、思考、手势",
|
||||
"sound": "安静室内混响、衣料摩擦声",
|
||||
"_no": "不要花哨运镜、不要强风格化调色",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_anchors(shooting_style: str) -> dict:
|
||||
"""按拍摄风格取锚点包;未知名回退 documentary_realism"""
|
||||
s = (shooting_style or "").strip().lower()
|
||||
if s in REALISM_ANCHORS:
|
||||
return REALISM_ANCHORS[s]
|
||||
# 常见别名
|
||||
alias = {
|
||||
"selfie": "selfie_pov", "vlog": "selfie_pov", "自拍": "selfie_pov",
|
||||
"手机自拍": "selfie_pov", "第一人称": "selfie_pov",
|
||||
"documentary": "documentary_realism", "纪实": "documentary_realism",
|
||||
"film": "cinematic", "电影感": "cinematic", "movie": "cinematic",
|
||||
"studio": "studio_clean", "棚拍": "studio_clean",
|
||||
}
|
||||
for k, v in alias.items():
|
||||
if k in s:
|
||||
return REALISM_ANCHORS[v]
|
||||
return REALISM_ANCHORS["documentary_realism"]
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# ③ 矛盾约束自检(写了等于没写)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
CONFLICT_PAIRS: list = [
|
||||
(("画面稳定", "稳定机位", "三脚架"), ("手持", "晃动", "呼吸起伏"),
|
||||
"「画面稳定」与「手持晃动」冲突(稳定=三脚架语言)"),
|
||||
(("电影感", "cinematic"), ("纪实", "documentary", "手持"),
|
||||
"「电影感」与「纪实手持」冲突(风格相互拉扯)"),
|
||||
(("胶片", "film grain", "颗粒感"), ("4K", "超清", "ultra sharp", "高清锐利"),
|
||||
"「胶片感」与「超清」冲突(胶片感本身是模糊+颗粒)"),
|
||||
(("慢动作", "slow motion"), ("高速", "快切", "high speed"),
|
||||
"「慢动作」与「高速动作」冲突"),
|
||||
]
|
||||
|
||||
|
||||
def check_conflicts(text: str) -> list:
|
||||
"""扫描提示词里的矛盾组合,返回告警列表(空 = 无冲突)
|
||||
|
||||
⚠️ 先剔除负向语境:负向约束里写「不要画面稳定」是在**否定**该项,
|
||||
不能被当成"要求画面稳定"(否则与"手持晃动"误报冲突)。
|
||||
"""
|
||||
t = (text or "")
|
||||
# 剔除"不要X / 避免X / 禁止X / 无X"片段(X 最多 12 字),再做正向检测
|
||||
t_positive = re.sub(r"(不要|避免|禁止|无)[^、;。\n]{0,12}", " ", t)
|
||||
warns = []
|
||||
for group_a, group_b, msg in CONFLICT_PAIRS:
|
||||
hit_a = next((a for a in group_a if a.lower() in t_positive.lower()), "")
|
||||
hit_b = next((b for b in group_b if b.lower() in t_positive.lower()), "")
|
||||
if hit_a and hit_b:
|
||||
warns.append(f"{msg}(同时出现「{hit_a}」与「{hit_b}」)")
|
||||
return warns
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 必加约束(护栏)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
BASE_CONSTRAINTS = "不要水印、不要 Logo、不要字幕"
|
||||
FACE_CONSTRAINTS = "人物面部稳定不变形、动作连贯自然不僵硬、无穿模无卡顿无闪烁"
|
||||
ANTI_TWIN = "同一画面中不出现外形着装完全一致的人物(防止分身/双胞胎)"
|
||||
|
||||
|
||||
def compose_constraints(extra_no: str = "", with_anti_twin: bool = True) -> str:
|
||||
"""组装约束串(负向 + 正向要求,自动去重)
|
||||
|
||||
注意:两类语义不同——
|
||||
· 负向约束("不要 X")用「负向约束:」引导
|
||||
· 正向要求("人物面部稳定不变形"这类陈述句)用「画面要求:」引导
|
||||
⚠️ 不能给正向要求硬加"不要"前缀(会把要求变成禁止,语义反转)
|
||||
"""
|
||||
negatives: list = [] # 形如 "不要水印"
|
||||
positives: list = [] # 形如 "人物面部稳定不变形"
|
||||
|
||||
for x in BASE_CONSTRAINTS.split("、"):
|
||||
x = x.strip()
|
||||
if x:
|
||||
negatives.append(x if x.startswith("不要") else "不要" + x)
|
||||
for x in FACE_CONSTRAINTS.split("、"):
|
||||
x = x.strip()
|
||||
if x:
|
||||
positives.append(x)
|
||||
if with_anti_twin:
|
||||
positives.append(ANTI_TWIN)
|
||||
if extra_no:
|
||||
e = extra_no.strip()
|
||||
for x in e.split("、"):
|
||||
x = x.strip()
|
||||
# ⚠️ 每项单独去前缀(锚点包每项通常自带"不要",若只去整体前缀会出现"不要不要X")
|
||||
while x.startswith("不要"):
|
||||
x = x[len("不要"):].strip()
|
||||
if x:
|
||||
negatives.append("不要" + x)
|
||||
|
||||
def _dedup(seq):
|
||||
"""**完全去重**(相同项只留一条)
|
||||
|
||||
⚠️ 我曾经想在这里做"子串合并"("不要字幕水印" 吞掉 "不要字幕"),
|
||||
但实测证明那是**错的**:
|
||||
· `不要字幕` 与 `不要字幕水印` 是两条**不同**的约束(后者更具体),都该保留
|
||||
· 而且按子串匹配会把 `不要水印` 误判成 `字幕水印` 的子集 → 错误替换
|
||||
结论:**只做完全去重,不做子串合并。**
|
||||
"""
|
||||
seen, out = set(), []
|
||||
for it in seq:
|
||||
k = it.strip()
|
||||
if not k or k in seen:
|
||||
continue
|
||||
seen.add(k)
|
||||
out.append(it)
|
||||
return out
|
||||
|
||||
parts = []
|
||||
if negatives:
|
||||
parts.append("负向约束:" + "、".join(_dedup(negatives)))
|
||||
if positives:
|
||||
parts.append("画面要求:" + "、".join(_dedup(positives)))
|
||||
return ";".join(parts)
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# ④ 风格包(Style Pack)—— 把"导演提示词知识"变成可选参数
|
||||
# 用户 2026-10-03 要求:把学到的提示词技巧整理成参数选项,
|
||||
# 选一个风格 = 自动带上一整套「设备/光/机位/瑕疵/情绪/负面词」设定。
|
||||
# ════════════════════════════════════════════════════════════
|
||||
STYLE_PACKS: dict = {
|
||||
"selfie_pov": {
|
||||
"label": "📱 手机自拍 · 第一人称",
|
||||
"device": "iPhone 前置摄像头,手持自拍,手臂伸出一臂距离",
|
||||
"lighting": "室内自然光或窗光为主,光源不均匀,脸上有明显明暗过渡",
|
||||
"camera": "轻微手持晃动,构图略微歪斜不居中,对焦偶尔漂移",
|
||||
"flaws": "轻微手机噪点、真实肤质(可见毛孔与细纹)、有一缕碎发没整理好",
|
||||
"emotion": "外化", "realism_default": "mild",
|
||||
"negatives": ["影棚均匀布光", "三脚架固定机位", "过度磨皮", "商业修图感"],
|
||||
},
|
||||
"vlog_handheld": {
|
||||
"label": "🚶 手持 Vlog · 第一视角",
|
||||
"device": "手持相机或手机,走动跟拍,第一视角",
|
||||
"lighting": "户外环境光/店内混合光源,色温不统一",
|
||||
"camera": "明显手持晃动(模拟步行节奏),偶有快速转向,构图随意",
|
||||
"flaws": "轻微运动模糊、曝光偶尔跳动、背景杂乱真实",
|
||||
"emotion": "外化", "realism_default": "mild",
|
||||
"negatives": ["稳定器丝滑运镜", "影棚打光", "摆拍感"],
|
||||
},
|
||||
"documentary_realism": {
|
||||
"label": "🎥 伪纪录片 · 真实自然",
|
||||
"device": "手持摄像机或长焦,像随手记录",
|
||||
"lighting": "以现场自然光为主,不补光,允许暗部欠曝",
|
||||
"camera": "长镜头、轻微呼吸感晃动、不稳定构图",
|
||||
"flaws": "轻微胶片颗粒、真实环境杂乱、画面有生活痕迹",
|
||||
"emotion": "外化", "realism_default": "mild",
|
||||
"negatives": ["影棚布光", "摆拍摆姿", "商业感"],
|
||||
},
|
||||
"interview_doc": {
|
||||
"label": "📺 访谈纪实 · 半身对镜",
|
||||
"device": "固定机位,中焦镜头,半身构图",
|
||||
"lighting": "柔和主光 + 环境光,背景稍暗",
|
||||
"camera": "机位固定,极轻微晃动,人物可略偏离画面中心",
|
||||
"flaws": "背景真实但不抢眼、肤色自然不修饰",
|
||||
"emotion": "外化", "realism_default": "off",
|
||||
"negatives": ["夸张运镜", "过度磨皮"],
|
||||
},
|
||||
"cinematic": {
|
||||
"label": "🎞️ 电影感 · 考究构图",
|
||||
"device": "专业电影机,变焦或定焦,景深控制",
|
||||
"lighting": "有设计感的光比,侧逆光/轮廓光,暗部有层次",
|
||||
"camera": "稳定运镜或精心设计的推拉摇移,构图讲究",
|
||||
"flaws": "轻微胶片颗粒(不要数码锐化的塑料感)",
|
||||
"emotion": "外化", "realism_default": "off",
|
||||
"negatives": ["手机感", "随手拍", "画面杂乱"],
|
||||
},
|
||||
"studio_clean": {
|
||||
"label": "💡 商业棚拍 · 高清干净",
|
||||
"device": "专业摄影机,三脚架固定,中长焦",
|
||||
"lighting": "专业布光:主光 + 辅光 + 轮廓光,光比均匀",
|
||||
"camera": "稳定固定机位,构图工整居中",
|
||||
"flaws": "(此风格刻意干净 —— 但保留真实肤质,避免塑料感)",
|
||||
"emotion": "外化", "realism_default": "off",
|
||||
"negatives": ["画面杂乱", "手持晃动", "环境噪音"],
|
||||
},
|
||||
"anime_2d": {
|
||||
"label": "🎨 2D 动画 · 漫剧",
|
||||
"device": "动画分镜,2D 手绘或动漫渲染",
|
||||
"lighting": "动画式平光或风格化光影(非物理真实)",
|
||||
"camera": "动画运镜:推拉摇移、定格、分格",
|
||||
"flaws": "(动画无需真实瑕疵;改为保持线条稳定、色块不跳变)",
|
||||
"emotion": "夸张外化(动漫式表情与动作幅度)", "realism_default": "off",
|
||||
"negatives": ["写实人脸照片感", "真实摄影质感", "胶片颗粒", "真人实拍"],
|
||||
},
|
||||
"anime_3d": {
|
||||
"label": "🧸 3D 动画 · 卡通渲染",
|
||||
"device": "3D 渲染,卡通/风格化着色",
|
||||
"lighting": "风格化打光,柔和阴影或卡通描边",
|
||||
"camera": "3D 运镜(环绕/位移/景深)",
|
||||
"flaws": "(保持模型一致,避免穿模与闪烁)",
|
||||
"emotion": "夸张外化", "realism_default": "off",
|
||||
"negatives": ["写实照片", "真人实拍", "皮肤毛孔", "胶片噪点"],
|
||||
},
|
||||
"product_ad": {
|
||||
"label": "🧊 产品广告 · 精致特写",
|
||||
"device": "微距或长焦,产品特写,浅景深",
|
||||
"lighting": "精细打光:轮廓光 + 反光板,突出材质",
|
||||
"camera": "稳定运镜(滑轨/环绕),构图讲究",
|
||||
"flaws": "材质细节清晰(不是缩小版瑕疵)",
|
||||
"emotion": "(产品无情绪;如有模特出镜则外化)", "realism_default": "off",
|
||||
"negatives": ["画面杂乱", "廉价感", "低清晰度"],
|
||||
},
|
||||
}
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# ⑤ 反真实词库(★ 研究核心发现)
|
||||
# "AI 感"的头号来源:提示词里堆砌「4K/8K/超清/无瑕/大师作品」——
|
||||
# 模型会理解成"要无瑕",于是过度磨皮、抹平纹理、统一光影 → 塑料感。
|
||||
# 真实感增强开启时,自动剔除这些词。
|
||||
# ════════════════════════════════════════════════════════════
|
||||
ANTI_REALISM_WORDS: list = [
|
||||
"4k", "8k", "4K", "8K", "超清", "超高清", "高清", "极致画质", "顶级画质",
|
||||
"电影级", "电影感构图", "大片感", "杂志级",
|
||||
"无瑕", "无瑕疵", "完美", "大师作品", "大师级", "杰作",
|
||||
"masterpiece", "best quality", "ultra detailed", "ultra realistic",
|
||||
"hyperrealistic", "photorealistic", "8k uhd", "highly detailed",
|
||||
"过度锐化", "塑料感", "CG感", "3D渲染感",
|
||||
]
|
||||
|
||||
# 反真实词在"写实类"风格下才剔除(动画类风格里这些词无害甚至有用)
|
||||
_REALISM_STYLES = {"selfie_pov", "vlog_handheld", "documentary_realism",
|
||||
"interview_doc", "cinematic", "studio_clean"}
|
||||
|
||||
|
||||
def strip_anti_realism(text: str) -> tuple:
|
||||
"""剔除会导致「塑料感」的反真实词(写实风格下)
|
||||
|
||||
返回 (清洗后文本, 被剔除的词列表)
|
||||
"""
|
||||
if not text:
|
||||
return "", []
|
||||
out = text
|
||||
removed = []
|
||||
for w in ANTI_REALISM_WORDS:
|
||||
if w in out:
|
||||
out = out.replace(w, "")
|
||||
removed.append(w)
|
||||
# 清理残留下的多余标点/空格
|
||||
out = re.sub(r"[、,,]\s*(?=[、,,])", "", out)
|
||||
out = re.sub(r"\s{2,}", " ", out).strip(" 、,,")
|
||||
return out, removed
|
||||
|
||||
|
||||
def apply_style_pack(prompt: str, shooting_style: str,
|
||||
realism_boost: str = "mild") -> tuple:
|
||||
"""把「风格包」应用到提示词上(去 AI 感的核心动作)
|
||||
|
||||
realism_boost: off(不动)/ mild(加瑕疵锚点)/ strong(瑕疵 + 剔除反真实词)
|
||||
|
||||
返回 (增强后的提示词, 说明列表)
|
||||
"""
|
||||
notes: list = []
|
||||
if not prompt:
|
||||
return prompt, notes
|
||||
pack = STYLE_PACKS.get(shooting_style) or {}
|
||||
boost = (realism_boost or "off").lower()
|
||||
out = prompt
|
||||
|
||||
if boost == "off" or not pack:
|
||||
return out, notes
|
||||
|
||||
# ① strong:剔除反真实词(研究里最大的坑)
|
||||
if boost == "strong" and shooting_style in _REALISM_STYLES:
|
||||
out, removed = strip_anti_realism(out)
|
||||
if removed:
|
||||
notes.append(f"已剔除反真实词:{'、'.join(removed[:6])}")
|
||||
|
||||
# ② 追加瑕疵锚点(如果提示词里还没有类似描述)
|
||||
flaws = pack.get("flaws") or ""
|
||||
if flaws and not any(k in out for k in ("噪点", "颗粒", "毛孔", "瑕疵", "纹理")):
|
||||
out = out.rstrip("。. ") + f"。{flaws}。"
|
||||
notes.append("已追加真实质感锚点")
|
||||
|
||||
# ③ strong:额外补一句"反完美"总纲
|
||||
if boost == "strong":
|
||||
tail = "真实拍摄的不完美感:光线不均匀、构图不刻意、画面有生活痕迹"
|
||||
if "不完美" not in out and "不均匀" not in out:
|
||||
out = out.rstrip("。. ") + f"。{tail}。"
|
||||
notes.append("已追加反完美总纲")
|
||||
|
||||
return out, notes
|
||||
|
||||
|
||||
def style_pack_options() -> dict:
|
||||
"""供前端生成「拍摄风格」下拉(含中文标签)"""
|
||||
return {k: v.get("label", k) for k, v in STYLE_PACKS.items()}
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# ★ 去 AI 感扩展库(2026-10-04 研究落地)
|
||||
# 来源:zhouwei713/seedance-prompt 的 anti-ai-checklist.md
|
||||
# (原文存 09_ave/docs/realism/anti-ai-checklist.md)
|
||||
#
|
||||
# 核心洞察:**"AI 感"主要来自模型的默认审美**(电影化运镜/稳定画面/
|
||||
# 商业调色/完美构图/磨皮皮肤)—— 必须用负面约束显式关闭,
|
||||
# 并主动注入「非完美事件」才像真实拍摄。
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
# ① AI 默认行为 → 负面约束句(10 项,含"造成的问题")
|
||||
AI_DEFAULT_NEGATIVES = [
|
||||
("电影化平滑运镜(推轨/摇臂感)", "没有电影化的摄像机移动"),
|
||||
("画面稳定如三脚架", "不要三脚架式的绝对稳定(自然手持呼吸感是允许的)"),
|
||||
("现代商业调色(青橙色调、高对比)", "没有现代色彩分级"),
|
||||
("完美居中构图", "不完美的构图,构图偏离中心"),
|
||||
("场景自动加商店/咖啡馆/人群/广告牌", "没有商店、广告、咖啡馆、人群或商业活动"),
|
||||
("自动配背景音乐", "没有音乐。没有音效设计。没有旁白"),
|
||||
("表演感强、直视镜头摆姿势", "真实的即兴行为,无剧本的日常生活片段感"),
|
||||
("皮肤过度平滑/磨皮感", "逼真的皮肤纹理"),
|
||||
("角色跨镜头漂移", "在整个视频中保持一致的身份、服装、发型和外貌"),
|
||||
("灯光过于均匀完美", "自然光,在阳光和阴影间移动时的曝光波动"),
|
||||
]
|
||||
|
||||
# ② 非完美事件库(21 个 · 四类)—— 每镜头选一个,全片不重复
|
||||
IMPERFECT_EVENTS = {
|
||||
"photographer": [
|
||||
"自动对焦在主体和前景/背景之间反复搜索",
|
||||
"构图偏离中心(操作者试图跟上主体)",
|
||||
"摄像机稍晚才捕捉到关键瞬间(人已转身才对准)",
|
||||
"变焦生硬,推过了又拉回来",
|
||||
"走动跟拍时的强烈晃动和运动模糊",
|
||||
"手指或背带短暂遮挡镜头一角",
|
||||
"镜头努力对焦但短暂失败,画面呼吸",
|
||||
],
|
||||
"environment": [
|
||||
"一阵微风吹动散落的发丝或衣角",
|
||||
"云影掠过,曝光随之波动",
|
||||
"小动物(猫/狗/鸽子)闯入画面,主体自然回应",
|
||||
"场外有人打招呼,主体转头回应",
|
||||
"阳光透过树叶在脸上闪烁明灭",
|
||||
"路人从镜头前无意穿过",
|
||||
],
|
||||
"subject": [
|
||||
"注意到镜头,露出小而真诚的微笑后移开视线",
|
||||
"说话被环境音打断,笑场",
|
||||
"整理头发、拨发到耳后等无意识小动作",
|
||||
"动作做到一半改变主意(伸手拿杯子又先擦了下桌子)",
|
||||
],
|
||||
"ending": [
|
||||
"录制在中途突然切到黑屏,仿佛摄像机关闭",
|
||||
"主体走出画面,镜头停留在空景两秒",
|
||||
"镜头垂下拍到地面/脚,然后切断",
|
||||
"有人喊拍摄者的名字,镜头晃动着转向声源时切断",
|
||||
],
|
||||
}
|
||||
|
||||
# ③ 特殊字符规范(信息类型标注)—— 来自 07-特殊字符规范.md
|
||||
SYMBOL_SPEC = {
|
||||
"台词": "{你好,世界}(外语需标语种:用日语说道{こんにちは})",
|
||||
"音效": "<远处传来狗叫声>",
|
||||
"音乐": "(背景中播放着快节奏的摇滚乐)",
|
||||
"字幕": "【第一章:启程】",
|
||||
}
|
||||
|
||||
|
||||
def ai_negative_block(hard: bool = True, extra: list = ()) -> str:
|
||||
"""组装「关闭 AI 默认审美」负面约束段
|
||||
|
||||
hard=False 时只取前 6 条(精简版,用于短提示词)
|
||||
"""
|
||||
items = [neg for _, neg in (AI_DEFAULT_NEGATIVES if hard else AI_DEFAULT_NEGATIVES[:6])]
|
||||
items.extend([str(x) for x in (extra or []) if x])
|
||||
return "【负面约束(关闭 AI 默认审美)】" + ";".join(items) + "。"
|
||||
|
||||
|
||||
def pick_imperfect_events(n_shots: int, categories: tuple = None) -> list:
|
||||
"""为 N 个镜头分配「非完美事件」(不重复;最后一条自动用收束方式)"""
|
||||
import random
|
||||
cats = list(categories or ("photographer", "environment", "subject"))
|
||||
pool = []
|
||||
for c in cats:
|
||||
pool.extend(IMPERFECT_EVENTS.get(c, []))
|
||||
random.shuffle(pool)
|
||||
out = []
|
||||
for i in range(max(0, n_shots)):
|
||||
if i == n_shots - 1 and n_shots > 1:
|
||||
out.append(random.choice(IMPERFECT_EVENTS["ending"]))
|
||||
else:
|
||||
out.append(pool[i % len(pool)] if pool else "")
|
||||
return out
|
||||
|
||||
|
||||
def imperfect_events_prompt() -> str:
|
||||
"""给导演的「非完美事件」纪律(逐镜头分配、不重复、结尾收束、2 秒节奏)"""
|
||||
return (
|
||||
"【真实感纪律:非完美事件(每个镜头分配一个,全片不重复)】\n"
|
||||
"真实拍摄必然有瑕疵。请为每个镜头挑 1 个(**一镜一个,不要堆**):\n"
|
||||
"· 摄影师失误:" + ";".join(IMPERFECT_EVENTS["photographer"][:4]) + "\n"
|
||||
"· 环境介入:" + ";".join(IMPERFECT_EVENTS["environment"][:4]) + "\n"
|
||||
"· 主体自然行为:" + ";".join(IMPERFECT_EVENTS["subject"][:3]) + "\n"
|
||||
"· **结尾必须用一种收束方式**:" + ";".join(IMPERFECT_EVENTS["ending"]) + "\n"
|
||||
"另外:镜头约 2 秒一个(超过 3 秒会滑向「精心拍摄」的假感);"
|
||||
"除非明确设定为手机慢动作,否则不要写慢动作。"
|
||||
)
|
||||
|
||||
|
||||
def check_abstract_words(text: str) -> list:
|
||||
"""自检:扫出会引发 AI 感的抽象词(返回命中列表)"""
|
||||
BAD = ["美丽", "唯美", "高级感", "大片感", "史诗", "震撼", "精致", "梦幻", "氛围感"]
|
||||
return [w for w in BAD if w in str(text or "")]
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# ★ 设备美学缺陷参数包(9 个)—— 2026-10-04 研究落地
|
||||
# 来源:camera-aesthetics.md(原文存 docs/realism/)
|
||||
#
|
||||
# 核心原则:**想要某种年代质感,就完整描述那个设备的物理缺陷,缺陷即真实。**
|
||||
# 与 STYLE_PACKS 的关系:
|
||||
# · STYLE_PACKS = 抽象风格取向(自拍/纪录片/电影感…)
|
||||
# · DEVICE_PACKS = **具体设备的物理缺陷**(DV/VHS/胶片/手机/GoPro…)
|
||||
# 两者互补,可叠加。
|
||||
# ════════════════════════════════════════════════════════════
|
||||
DEVICE_PACKS: dict = {
|
||||
"dv_2000s": {
|
||||
"name": "2000年代初消费级DV摄像机",
|
||||
"fit": "家庭录像、朋友随拍、千禧年怀旧、韩日老社区题材",
|
||||
"visual": ["中等数字压缩伪影", "褪色的色彩,柔和的对比度", "轻微的传感器噪点",
|
||||
"轻微的滚动快门", "480p 到轻微高清之间的分辨率感", "高光容易过曝,暗部发灰"],
|
||||
"operation": ["强烈的手持抖动,不完美的构图", "频繁的自动对焦搜索",
|
||||
"镜头呼吸(对焦时视角轻微变化)", "在阳光和阴影间移动时的曝光波动",
|
||||
"偶尔的运动模糊", "变焦生硬,速度不均匀", "录制中途突然开始或结束"],
|
||||
"negative": "不要三脚架式的绝对稳定。不要电影化的摄像机移动。不要现代色彩分级。",
|
||||
},
|
||||
"vhs_8090s": {
|
||||
"name": "80至90年代VHS家用摄像机",
|
||||
"fit": "童年录像、生日聚会、更旧的怀旧感、复古恐怖",
|
||||
"visual": ["明显的磁带噪点和扫描线", "色彩溢出(红色尤其容易晕开)",
|
||||
"底部时间码或日期戳", "画面边缘轻微的几何畸变",
|
||||
"低分辨率,细节模糊", "磁带损伤造成的偶尔画面撕裂或跳帧"],
|
||||
"operation": ["沉重机身带来的缓慢笨重移动", "手动变焦的顿挫感",
|
||||
"白平衡偏移(室内偏橙,室外偏蓝)", "逆光时人物完全剪影化"],
|
||||
"negative": "不要数字锐利感。不要三脚架式的绝对稳定。不要高动态范围。",
|
||||
},
|
||||
"film_8mm": {
|
||||
"name": "8mm/Super 8 胶片",
|
||||
"fit": "更久远的家庭记忆、梦境感、婚礼老片、艺术短片",
|
||||
"visual": ["明显的胶片颗粒", "温暖偏黄或偏品红的色彩偏移", "画面边缘的暗角",
|
||||
"灰尘、毛发、划痕偶尔闪过", "18fps 带来的轻微卡顿感",
|
||||
"过曝的高光呈现柔和的光晕"],
|
||||
"operation": ["每段镜头很短(胶片昂贵,3到5秒就切)", "硬切,没有转场",
|
||||
"曝光靠估计,时对时错", "无声(胶片本身不录音)或后期配的环境音"],
|
||||
"negative": "没有同期声对白。没有平滑运镜。没有数字清晰度。",
|
||||
},
|
||||
"smartphone_vertical": {
|
||||
"name": "智能手机竖屏(2015年后)",
|
||||
"fit": "Vlog、社交媒体感、街头偶遇、当代日常",
|
||||
"visual": ["9:16 竖构图", "过度锐化和 HDR 处理感",
|
||||
"电子防抖造成的果冻效应", "暗光下的涂抹感降噪",
|
||||
"前置镜头的轻微广角畸变"],
|
||||
"operation": ["单手持机的轻微倾斜", "走路时的上下浮动(电子防抖压制后仍残留)",
|
||||
"对着镜头说话的自拍视角切换", "手指偶尔擦过镜头边缘",
|
||||
"突然抬起或放下手机的开始与结束"],
|
||||
"negative": "没有横屏。没有三脚架稳定。没有专业布光。",
|
||||
},
|
||||
"gopro": {
|
||||
"name": "GoPro / 运动相机",
|
||||
"fit": "第一人称视角、运动、旅行、冒险感",
|
||||
"visual": ["超广角鱼眼畸变,边缘拉伸", "高饱和的默认色彩",
|
||||
"强光下的镜头眩光", "水滴或灰尘沾在镜头上"],
|
||||
"operation": ["胸挂或头戴的第一人称视角", "剧烈但被电子增稳压制的震动",
|
||||
"呼吸和身体晃动传导到画面", "视角随头部转动突然甩动"],
|
||||
"negative": "没有第三人称跟拍。没有电影构图。没有慢动作(除非明确要求)。",
|
||||
},
|
||||
"cctv": {
|
||||
"name": "监控摄像头 / CCTV",
|
||||
"fit": "悬疑、都市传说、客观记录感、伪纪录片",
|
||||
"visual": ["固定高位俯视角", "低帧率(每秒约10到15帧的顿挫感)",
|
||||
"单色或低饱和色彩,夜间红外模式呈灰白", "角落的时间戳持续跳动",
|
||||
"低分辨率,人脸模糊难辨"],
|
||||
"operation": ["完全静止,或极缓慢的预设巡航转动", "无声",
|
||||
"人物不知道被拍摄,行为完全自然"],
|
||||
"negative": "没有摄影师存在。没有构图意识。没有声音。没有变焦跟随主体。",
|
||||
},
|
||||
"doc_handheld": {
|
||||
"name": "专业纪录片(手持电影机)",
|
||||
"fit": "高质感纪实、人物特稿、社会观察",
|
||||
"visual": ["浅景深,主体清晰背景柔和", "自然光为主,宽容度高",
|
||||
"克制的调色,接近肉眼所见"],
|
||||
"operation": ["有经验的手持,微晃但不失控", "跟焦偶尔慢半拍(保留这个不完美)",
|
||||
"长镜头耐心观察,不急于切换", "被摄者偶尔瞥向镜头又移开"],
|
||||
"negative": "没有摆拍感。没有采访灯光。没有戏剧化运镜。没有配乐驱动的剪辑节奏。",
|
||||
},
|
||||
"webcam": {
|
||||
"name": "网络摄像头 / 视频通话",
|
||||
"fit": "疫情记忆、远程对话、屏幕录制感、亲密而低保真的交流",
|
||||
"visual": ["720p 以下的低清晰度", "压缩造成的块状伪影,网络波动时画面糊成马赛克",
|
||||
"顶光或屏幕光打在脸上的青白色", "固定机位,仰视或平视的笔记本视角"],
|
||||
"operation": ["音画偶尔不同步", "人物偶尔卡顿定格再恢复", "背景是真实的房间一角"],
|
||||
"negative": "没有专业收音。没有补光灯质感。没有运镜。",
|
||||
},
|
||||
"flip_phone_2003": {
|
||||
"name": "2003年翻盖手机摄像(CMOS低保真)",
|
||||
"fit": "极度怀旧、千禧年初学生时代、赛博朋克低保真、暗角情绪短片",
|
||||
"visual": ["极低分辨率(176×144 或 320×240)", "严重 JPEG 压缩块状伪影,色块化严重",
|
||||
"色彩饱和度极端且偏色严重(通常偏蓝绿或偏品红)", "暗部几乎完全噪点糊死",
|
||||
"画面中央清晰度尚可,边缘急剧劣化", "固定数字变焦(裁切放大)",
|
||||
"超窄动态范围,高光纯白一片"],
|
||||
"operation": ["单手持机,手臂伸直自拍或随意抓拍", "翻盖打开即拍,几乎没有延迟补偿",
|
||||
"快门时滞极长(按下到实际拍摄有0.5到1秒延迟)",
|
||||
"录像按一下开始,再按一下结束,没有预览确认",
|
||||
"对焦基本靠固定焦段,前后移动自身来「对焦」",
|
||||
"录制过程中镜头容易被手指或翻盖边缘遮挡"],
|
||||
"negative": "没有清晰度。没有平滑运动。没有色彩准确度。没有变焦操作。",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# ★ 氛围词典(抽象氛围 → 具体可拍现象)
|
||||
# 来源:atmosphere-dictionary.md(原文存 docs/realism/)
|
||||
#
|
||||
# 原则:**禁止直接使用抽象氛围词**,必须翻译成具体视觉/听觉现象,
|
||||
# 每种氛围至少选 3~5 个现象组合使用。
|
||||
# ════════════════════════════════════════════════════════════
|
||||
ATMOSPHERE_DICT: dict = {
|
||||
"温暖": {
|
||||
"visual": ["午后阳光透过树叶在地面投下移动的斑驳光影", "晾衣绳上的织物在微风中缓慢摇曳",
|
||||
"陶瓷杯口冒出的细微热气", "猫或狗慵懒地靠近人",
|
||||
"盆栽植物摆在窗台和门口台阶上", "人物把头发拨到耳后的小动作",
|
||||
"不经意的真诚微笑,随即移开视线"],
|
||||
"audio": ["晨间鸟鸣", "远处小孩嬉闹声", "织物摩擦声", "猫叫", "勺子碰杯壁"],
|
||||
},
|
||||
"怀旧": {
|
||||
"visual": ["褪色的色彩与柔和的对比度", "老式电线杆和架空电线切割天空",
|
||||
"墙面斑驳的低层住宅", "停在巷口的旧自行车", "画面上的日期戳",
|
||||
"人物穿着宽松水洗牛仔裤等年代服饰"],
|
||||
"audio": ["远处摩托车驶过", "老风扇转动声", "模拟信号的底噪"],
|
||||
},
|
||||
"孤独": {
|
||||
"visual": ["大面积负空间构图,人物偏居画面一角", "空旷街道上唯一移动的身影",
|
||||
"荧光灯的青白色调", "便利店或地铁站的深夜灯光",
|
||||
"人物长时间静止,只有环境在动(车流、雨、霓虹闪烁)", "玻璃反光中模糊的人影"],
|
||||
"audio": ["空调外机嗡鸣", "脚步回声", "远处地铁进站声", "雨点打在遮阳棚上"],
|
||||
},
|
||||
"紧张": {
|
||||
"visual": ["画面边缘偶尔闯入不明物体或人影", "光源不稳定,灯管闪烁",
|
||||
"门缝、走廊尽头等纵深阴影", "手持镜头呼吸感加重,微微颤抖",
|
||||
"人物频繁回头或停顿倾听"],
|
||||
"audio": ["突然中断的环境音", "远处不明的金属碰撞声", "自己的呼吸声被收进麦克风"],
|
||||
},
|
||||
"平静": {
|
||||
"visual": ["极缓慢的光线变化(云影掠过整个画面)", "水面或窗帘的规律性微动",
|
||||
"长镜头,机位几乎不动", "人物做重复性动作(浇水、叠衣、剥豆)"],
|
||||
"audio": ["风穿过树叶的持续沙沙声", "水滴声", "远处若有若无的生活声"],
|
||||
},
|
||||
"热闹": {
|
||||
"visual": ["前景不断有人或车穿过镜头", "摊贩的动作细节(翻炒、找零、擦桌子)",
|
||||
"蒸汽、油烟、烤炉的热浪扭曲空气",
|
||||
"多层次的动态:近景手部动作、中景人流、远景车流"],
|
||||
"audio": ["叫卖声", "锅铲碰撞", "多人交谈混叠", "自行车铃"],
|
||||
},
|
||||
"梦境": {
|
||||
"visual": ["过曝的窗户白成一片", "胶片颗粒与光晕", "镜头短,硬切,时间线跳跃",
|
||||
"人物对镜头挥手或大笑但无声", "逆光轮廓,面部细节丢失"],
|
||||
"audio": ["无声,或只有一层模糊的环境底噪"],
|
||||
},
|
||||
"田园": {
|
||||
"visual": ["大片稻田或麦田在风中如波浪起伏", "人物赤脚踩在湿润泥土或田埂上",
|
||||
"荷叶、野花、藤蔓等植物特写", "阳光透过树叶在人物脸上流动的光斑",
|
||||
"远处农舍的炊烟和黑瓦白墙", "蜻蜓点水、蝴蝶停在花上",
|
||||
"人物背影渐渐走远,融入田野尽头"],
|
||||
"audio": ["连续的蛙鸣和虫鸣", "风吹稻田的沙沙声", "远处公鸡打鸣", "溪流声",
|
||||
"脚踏湿泥的柔软声音", "竹叶碰撞声"],
|
||||
},
|
||||
"夏日": {
|
||||
"visual": ["树影在地面剧烈晃动,光斑密集而明亮", "人物额头和鬓角有细微汗珠",
|
||||
"手中冰饮或西瓜表面凝结水珠", "风扇叶片或蒲扇的转动",
|
||||
"强烈阳光造成的高光过曝和镜头眩光", "热空气扭曲远处景物",
|
||||
"牵牛花、向日葵、荷叶等盛夏植物", "偶尔的云影掠过造成短暂阴凉"],
|
||||
"audio": ["比任何季节都响亮的蝉鸣", "空调外机或风扇的低沉嗡鸣",
|
||||
"冰块碰杯的清脆声", "远处孩童的嬉笑和泼水声"],
|
||||
},
|
||||
"通勤": {
|
||||
"visual": ["密集的人流与单个主体形成对比", "从室内到室外/地上到地下的曝光剧烈变化",
|
||||
"呼出的白气(秋冬)或额头的汗(盛夏)", "低头看手机、翻包找卡等碎片化动作",
|
||||
"车窗玻璃上反射的模糊面容", "通勤者独特的身体语言(缩脖子、拉围巾)",
|
||||
"地铁隧道中快速掠过的灯光管"],
|
||||
"audio": ["广播模糊的播报声", "列车进站的轰鸣和刹车尖叫", "闸机蜂鸣声",
|
||||
"脚步匆忙的节奏", "车门气动声", "隧道风噪声"],
|
||||
},
|
||||
"青春": {
|
||||
"visual": ["漫无目的的闲逛或长时间坐着发呆", "手部小动作(拨弄衣角、卷头发、转笔)",
|
||||
"靠在墙边、栏杆上,重心随意切换", "低头笑、抬头看天、把脸埋进手臂",
|
||||
"旧电扇转头吹动刘海和衣领", "地面上的影子被拉长,时间缓慢推移",
|
||||
"零星对话,说话间隙被环境填满"],
|
||||
"audio": ["远处收音机或电视的模糊声音", "老旧风扇的转动声", "冰棍棒折断声",
|
||||
"偶尔的笑声然后继续安静"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def device_pack_block(key: str, include_negative: bool = True) -> str:
|
||||
"""设备缺陷参数包 → 提示词片段(可直接粘进「摄像风格」段)"""
|
||||
p = DEVICE_PACKS.get((key or "").strip())
|
||||
if not p:
|
||||
return ""
|
||||
out = [f"【设备美学:{p['name']}】"]
|
||||
out.append("画面特征:" + ";".join(p["visual"]))
|
||||
out.append("操作特征:" + ";".join(p["operation"]))
|
||||
if include_negative and p.get("negative"):
|
||||
out.append("负面约束:" + p["negative"])
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def atmosphere_block(keys, n_each: int = 4) -> str:
|
||||
"""氛围 → 具体现象片段(每种氛围取 n 个视觉 + 全部听觉的前若干个)
|
||||
|
||||
keys 可以是 str 或 list;未命中则原样返回空串(调用方可自行翻译)。
|
||||
用法原则:**不要直接写抽象氛围词,用这些具体现象替代**。
|
||||
"""
|
||||
if isinstance(keys, str):
|
||||
keys = [keys]
|
||||
out = []
|
||||
for k in (keys or []):
|
||||
a = ATMOSPHERE_DICT.get(str(k).strip())
|
||||
if not a:
|
||||
continue
|
||||
vis = a["visual"][:max(1, n_each)]
|
||||
aud = a["audio"][:max(1, n_each // 2)]
|
||||
out.append(f"【{k}(用具体现象替代)】视觉:" + ";".join(vis)
|
||||
+ ("。听觉:" + ";".join(aud) if aud else ""))
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def assign_device_defects(n_shots: int) -> list:
|
||||
"""按「缺陷不重复原则」给每个镜头分配**不同类型**的摄像机缺陷
|
||||
|
||||
来源:camera-aesthetics.md 进阶用法 —— 同一提示词里每镜的缺陷类型不重复:
|
||||
对焦 → 构图 → 抖动 → 曝光 → 反应延迟 → 呼吸感 → 录制中断
|
||||
"""
|
||||
seq = [
|
||||
"自动对焦在前景和背景之间搜索", # 对焦缺陷
|
||||
"构图偏离中心,操作者跟不上主体", # 构图缺陷
|
||||
"跟拍晃动 + 运动模糊", # 抖动缺陷
|
||||
"进入阴影时曝光波动", # 曝光缺陷
|
||||
"镜头慢了半拍才跟上动作", # 反应延迟
|
||||
"自然的镜头呼吸和焦距漂移", # 呼吸感
|
||||
]
|
||||
out = []
|
||||
for i in range(max(0, n_shots)):
|
||||
if i == n_shots - 1 and n_shots > 1:
|
||||
out.append("画面突然切到黑屏(录制中断)") # 结尾固定收束
|
||||
else:
|
||||
out.append(seq[i % len(seq)])
|
||||
return out
|
||||
|
||||
|
||||
def focus_path_hint() -> str:
|
||||
"""「自动对焦路径指定法」提示(效果翻倍的关键技巧)
|
||||
|
||||
不要笼统写"自动对焦搜索",要给出**对焦起点和终点**。
|
||||
"""
|
||||
return ("【对焦路径指定(关键技巧)】不要笼统写「自动对焦搜索」——"
|
||||
"要明确写出对焦的起点和终点,模型才知道对焦该在哪两个物体之间走:\n"
|
||||
" ❌ 自动对焦反复搜索\n"
|
||||
" ✅ 自动对焦在她脸部和她手中的西瓜之间来回切换\n"
|
||||
" ✅ 自动对焦从远处山影漂移到她后背再拉回远处")
|
||||
Reference in New Issue
Block a user