""" 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" " ✅ 自动对焦从远处山影漂移到她后背再拉回远处")