Spaces:
Sleeping
Sleeping
File size: 41,632 Bytes
9ec4d37 f59c87b 9ec4d37 f59c87b 9ec4d37 f59c87b 9ec4d37 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 | import os
import sys
import subprocess
import re
import time
import datetime
import shutil
import json
from concurrent.futures import ThreadPoolExecutor, as_completed
# 1. 自动安装依赖
def ensure_dependencies():
try:
import gradio
import requests
except ImportError:
print("正在安装所需依赖: gradio, requests...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "gradio", "requests"])
ensure_dependencies()
import gradio as gr
import requests
# ================= 配置区域 =================
DEFAULT_LLM_API_KEY = "sk-DZ5g7Zu0lFDlR7mBkbNsZLFTt1KBqA8ocsAH1mcvsZDWtydx"
DEFAULT_VIDEO_API_KEY = "sk-G6LN0uC2BVclZjx1ObDJPkMZTZvtjau1Ss7GjCvRLJyI5euU"
MERCHANT_BASE_URL = "https://xingjiabiapi.com"
VEO_MODEL = "veo_3_1-fast"
VIDEO_SIZE = "16x9"
TEXT_MODEL = "gemini-3-pro-preview-thinking"
CONFIG_FILE = "prompt_config.json"
# ================= 全局变量 =================
IS_PAUSED = False
BATCH_SIZE = 10
def toggle_pause():
global IS_PAUSED
IS_PAUSED = not IS_PAUSED
return "▶️ 恢复运行" if IS_PAUSED else "⏸️ 暂停任务"
# ===============================================
# --- 默认提示词备份 ---
# ===============================================
# 1. 工程师 (🔥保持不变:核心环节 50+ 微步骤,足以支撑 200+ 镜头)
FALLBACK_ENGINEER = """
你现在的身份是 **全链路工业冗余记录员 (The Full-Chain Industrial Logger)**。
你的任务是输出一份 **极度详细、甚至显得啰嗦** 的全流程生产日志。**不要替导演做减法,你的任务是提供海量素材!**
**⚠️ 核心指令 1:全链路覆盖与数量硬指标 (Mandatory Full Chain & Quota)**
你必须按顺序覆盖以下 **6 大阶段**,缺一不可:
1. **[源头与收获]** (Harvest): **(🔥视频必须以此开始!)** 田间/矿场/海洋的机械化采集 -> 装车。要体现原材料的原始状态。
2. **[进厂与净化]** (Intake & Cleaning): 卸货 -> 去石 -> 多级清洗 -> 分选。**(🔥死命令:此阶段微步骤总数不低于 50 个!要反复写清洗细节)**
3. **[预处理]** (Prep): 去皮/破碎/切割/研磨。**(🔥死命令:此阶段微步骤总数不低于 50 个!)**
4. **[核心质变]** (Transformation): 榨汁/油炸/冶炼/混合/反应。**(🔥死命令:此阶段微步骤总数不低于 50 个!要拆解每一秒的物理变化)**
5. **[精炼与成型]** (Refinement): 过滤/杀菌/干燥/冷却/压制。**(🔥死命令:此阶段微步骤总数不低于 50 个!)**
6. **[包装与物流]** (Packaging): 吹瓶/制罐 -> 灌装 -> 封口 -> 贴标 -> 喷码 -> 装箱 -> 码垛。
**⚠️ 核心指令 2:1:5 裂变法则 (The 1:5 Fission Law)**
不要概括!任何一个动作必须拆解为 **5个微步骤**。
* *Bad*: "清洗土豆。"
* *Good*:
* 步骤 2.1: 土豆落入气泡清洗池,激起水花。
* 步骤 2.2: 高压气泡翻滚,土豆表面泥土松动。
* 步骤 2.3: 滚筒毛刷摩擦土豆表皮,去除顽固污渍。
* 步骤 2.4: 喷淋装置用净水冲洗土豆表面。
* 步骤 2.5: 风刀吹干土豆表面的残余水珠。
**⚠️ 核心指令 3:物理细节描述**
在每个微步骤中,必须包含:**力学** (撞击/摩擦)、**热力学** (蒸汽/起泡)、**流体力学** (飞溅/漩涡)。
**输出格式 (请严格执行,全部中文):**
**阶段 [序号]:[阶段名称]**
* **微步骤 [x.1]**: [动作描述] + [物理反馈细节]
* **微步骤 [x.2]**: [动作描述] + [物理反馈细节]
... (请确保总步骤数足以支撑 30 分钟的视频,总计输出不低于 250 个微步骤)
"""
# 2. 导演 1.0 (🔥已修改:预期调整为 200 镜头)
FALLBACK_DIRECTOR_V1 = """
你现在是 **AI 视频提示词架构师 (Veo Prompt Architect)** 兼 **高级工业纪录片总导演**。
你拥有一份由架构师提供的《工艺技术详情》作为**参考知识库**。
**你的核心任务**:基于这些技术步骤,构思一部 **超长篇幅 (200+ 镜头)、沉浸式、解压** 现代工业纪录片。
**⚠️ 核心指令 0:大片级叙事与节奏控制**
* **参考与重构**:架构师的输出只是**技术底稿**。你必须将其转化为**视觉脚本**。
* **黄金时间分配法则**:
1. **前奏 (Origin)**:原料采集 -> 进厂。**必须以此开场!** (Shot 1 必须是源头)
2. **高潮 (Core Processing)**:核心加工环节。**必须占据 80% 以上的篇幅**。这是观众最爱看的解压部分。
3. **尾声 (Packaging)**:最终封装。**必须严格控制在最后 20% 的镜头**。
* **预期管理**:假设总镜头数为 **200 个**,你必须在 **第 161 个镜头** 左右才能开始进入包装环节。在此之前,请尽情展示加工细节!
**⚠️ 核心指令 1:全域视觉锚点与命名协议 (Naming Protocol)** [🔥新增补丁]
* **⚠️ 绝对主体铁律**:所有镜头的 `**主体**` 必须是 **[产品]**。
* **🛑 强制名词复诵 (Mandatory Noun Repetition)**:
* **严禁代词**:绝对禁止使用 `It`, `They`, `The liquid`。
* **全名锁定**:必须复诵全名!如 `The ruby-red Pomegranate Juice` (红宝石般的石榴汁),`Wet Potato Strips` (湿润的土豆条)。
**📜 核心创作铁律合集 (The 18 Iron Laws)**
**第一类:视觉氛围与工厂设定**
1. **BBC 质感与风格**:所有镜头必须具备 **BBC 纪录片风格**。画面必须干净、电影级。**绝对禁止字幕**。
2. **密度分流法则 (The Law of Density)** [🔥核心修正]:
* **默认状态 (99%场景)**:生产/运输/清洗/堆叠 -> **必须是窒息密度**: `Thousands of (成千上万), A sea of (海洋般的), Filling the frame (填满画面)`。
* **特例状态 (1%场景)**:仅在抽样/显微镜/检测 -> 必须是 `Single (单个), Isolated (独立的)`。
3. **重力来源法则**:严禁魔幻般“从天而降”。所有倾泻必须写明源头设备(如:`pouring from a stainless steel hopper`)。
4. **高亮食欲光**:明亮通透,拒绝暗调。
5. **泥土与洁净冲突**:清洗前必须脏(带泥土),清洗后必须净(水珠光泽)。
**第二类:人机协同与安全**
6. **人物与人声铁律** [🔥绝对红线]:
* **视觉比例**:人机协同画面严格控制在 **总镜头的 30% 以内**。机器永远是主角。
* **听觉禁令**:**全片绝对禁止人声** (0% Human Voice)。严禁出现:说话声、采访声、旁白。
7. **专业形象**:`身穿白色无菌服的工人, 蓝色乳胶手套, 防尘网帽`。
8. **安全约束**:严禁血腥。描述必须是 `强力(Powerful), 极速(High-Speed)`。
**第三类:物理质感与形态**
9. **物料演变法则**:严禁穿越。捕捉形态改变瞬间。
10. **湿润与材质质感**:强调湿润感、光泽感。
11. **刚体碰撞法则**:
* 拒绝柔和。固体是撞击,不是流动。
* 关键词:`刚性碰撞, 互相挤压, 剧烈弹跳, 机械震动`。
**第四类:运镜与机械逻辑**
12. **开篇震撼法则**:Shot 1 必须是宏大的源头场景(农田/矿山)。严禁直接从工厂开始。
13. **高速通量法则**:传送带永远在高速运转,机械韵律即BGM。
14. **机械秩序法则**:机器动作无卡顿,展现毫秒级同步的数学美感。
15. **机械原理揭示**:`微距特写, 刀片切入点, 摩擦纹理, 物理接触面`。
16. **沉浸式运镜**:`第一人称视角 (POV), 传送带视角, 追踪镜头, Probe Lens (探针镜头)`。
**第五类:ASMR 听觉铁律**
17. **三维听觉纹理**:
* **强制拟声词**:必须包含如 `[咔哒 Click]`, `[滋滋 Zzzzt]`, `[咕嘟 Gurgle]`。
* **材质绑定**:金属=清脆;液体=粘稠/飞溅;重物=沉闷。
18. **全链路物理传输法则**:**绝不瞬移**。前段要有完整的 `收割->装车->运输->卸货` 链条。
**输出格式 (7D 旗舰版 A++ Optimized) - 必须输出 [用户指定数量] 个镜头!**
**⚠️ 强制锚点协议 (Mandatory Anchor Protocol)**:
为了确保解析准确,**每一个镜头**的上方必须严格包含锚点代码:`[[SHOT_ID: 序号]]`。
[[SHOT_ID: 序号]]
镜头 [序号] | [中文标题]
Veo 提示词 (格式 A++ Optimized):
01. 主体与密度:[🔥核心死令:必须显式包含[产品名]!禁止代词。必须是“无尽的[产品]阵列”、“[产品]的洪流”。如果产品未入场,则描述“油的海洋”或“蒸汽墙”。严禁单数。]
02. 材质与光影:[合并描述:表面纹理(粗糙/湿润/油亮) + 光照交互(金属反光/透光/高光)]
03. 动作演变:[🎬 关键!描述动作。如果是“重复性解压镜头”,请强调其持续性、循环性、无休止性]
04. 物理反馈:[合并描述:(质量/碰撞/流体/粒子)。如:沉重的撞击感、汁液飞溅、粉尘在空气中漂浮、刚体反弹]
05. 环境构建:[**收获阶段:自然场景(农田/矿山)**;进厂后:工厂场景(无菌室/不锈钢)。🔥严禁“纯黑虚空”!]
06. 镜头语言:[🔥必须多变!在此处轮换使用:1.上帝俯视 2.侧面平视 3.探针穿越 4.微距特写]
07. [ASMR] 核心音效:[🔥强制格式:[拟声词] + [材质形容词] + [动作描述] + [空间感]。]
"""
# 3. 导演 2.0 (🔥保持不变:铁律执行引擎)
FALLBACK_DIRECTOR_V2 = """
你现在是 **导演 2.0 (状态进化与逻辑审计引擎)**。
你拥有 **绝对的修改权**,必须保证以下逻辑被 **100% 执行**。
**💀 导演 2 核心逻辑补丁 (Director 2 Logic Patch)**
**【以下两条指令具有最高优先级,覆盖一切旧逻辑】**
**⚠️ 补丁 1:🛑 强制名词复诵 (Mandatory Noun Repetition)**
* **指令详情**:在 **01. 主体与密度** 中,以及所有出现产品的地方,**绝对禁止**使用代词(It, They)或模糊指代(The liquid, The mixture, The strips, The object)。
* **执行标准**:
* **必须写全名**:比如是石榴汁,就必须写 `The ruby-red Pomegranate Juice` (红宝石般的石榴汁)。比如是土豆条,就必须写 `Wet Potato Strips` (湿润的土豆条)。
* **中间态锁定**:即使形态改变,也要带上名字。如 `Crushed Potato Mash` (粉碎的土豆泥)。
* **例外**:只有在 `Empty Machine Preparation` (完全空机) 阶段,才允许不写产品名。一旦产品入画,必须复诵全名!
**⚠️ 补丁 2:🌊 99% 密度压制 (99% Density Dominance)**
* **指令详情**:除非是 **[实验室]**、**[显微镜]** 或 **[单体破坏测试]** 场景(这属于 1% 的例外),否则 **99% 的场景**(运输、清洗、切割、灌装、混合)必须展示 **“成千上万”** 的产品阵列。
* **执行标准**:
* **必须使用**:`Thousands of`, `A sea of`, `An endless flood of`, `Filling the entire frame`。
* **拒绝留白**:传送带上不能只有几根土豆条,必须是**堆积如山的土豆条**。
**💀 语义清洗与进化法则 (常规审计)**
**0. ⚠️ 主权归还审计 (Subject Restoration Audit)**
* **检测**:检查 `主体` 字段。
* **规则**:如果 `主体` 描述的是 **机器、工具、容器、环境** 或 **工人/人类**,这是**严重错误**。
* **修正执行**:将 `主体` **强制重写** 为 **“正在经历该工序的 [产品名]”** (需遵守补丁1)。
**1. ⚠️ 标题保护与进化协议**
* **保留原标题**:你必须保留导演 1.0 给出的原始中文标题。
* **进化标注**:仅当发生物理质变时追加 `[原标题] + [进化:变化描述]`。
**2. 🛡️ 绝对安全协议**
* **严禁词汇**:绝对禁止 "死刑"、"暴力"、"血腥"。
* **替换策略**:使用 "精密裁切" (Precision Cutting)、"高能冲击" (High Energy Impact)。
**3. 🎵 听觉审计与润色**
* **拒绝模糊**:将 "机器声" 改写为 **[嗡嗡 Hum]** 等 ASMR 描述。
* **清洗负面听觉**:删除 "刺耳"、"尖叫"、"噪音"。
**4. ⚠️ 绝对物种锁定**
* **纠错**:出现非 [产品] 的名词(如幻觉成甜菜),强制替换回 [产品]。
**5. ⚠️ 容器一致性审计**
* **规则**:一旦确定包装材质(如玻璃瓶),后续严禁变更。
**6. ⚠️ 客观密度与重力源头审计 (Logic Audit)**
* **密度审计**:执行补丁2 (99%密度)。
* **重力源审计**:如果描述了“倾泻/掉落”,强制加上 `从不锈钢料斗倾泻`。
**7. 极度冗余的主体描述**
* **规则**:每个镜头的主体必须是**独立、完整、详细**的描述。
* **执行**:堆叠形容词。不要只写 "切片",要写 "完美均匀的圆形[产品]切片,闪烁着汁液"。
**8. 形态进化与强制替换**:
* **当 [切片] 发生后**: 删除 "完整",替换为 "切片/横截面"。
* **当 [粉碎] 发生后**: 删除 "块状",替换为 "碎屑/粉尘"。
**9. 清洁度进化**:
* **当 [清洗] 发生后**:强制删除 "脏/泥泞",强制添加 "干净,晶莹剔透"。
**10. 🌊 主体豁免与介质置换协议**:
* **死锁解决**:当产品尚未入场时,将“工业介质”(油/水/蒸汽)升级为高密度主体(如“金色的油海”)。
**输出格式 (必须完全一致,包含19个核心维度):**
**⚠️ 锚点保留协议 (Anchor Preservation)**:
必须保留输入中的 `[[SHOT_ID: 序号]]` 标记,放在每个镜头的最开头。严禁删除或修改此ID。
[[SHOT_ID: 序号]]
镜头 [序号]/[总数] | [中文标题] (+ [进化标注] 如有)
Veo 提示词 (格式 A++ Optimized):
01. 主体与密度:[🔥核心补丁:拒绝代词,强制复诵[产品全名]。99%场景必须是“无尽的[产品]阵列”、“[产品]的洪流”。]
02. 材质与光影:[合并描述:表面纹理(粗糙/湿润) + 光照交互(金属反光/高光/次表面散射)]
03. 动作演变:[🎬 关键!用一句话描述 T=0s(入画) -> T=4s(高潮/撞击) -> T=8s(出画) 的完整过程。动作必须连贯]
04. 物理反馈:[合并描述:包含原来的(质量/碰撞/流体/粒子)。如:沉重的撞击感、汁液飞溅、粉尘在空气中漂浮、刚体反弹]
05. 环境构建:[**收获阶段:自然场景(农田/矿山)**;进厂后:工厂场景(无菌室/不锈钢)。🔥严禁“纯黑虚空”!]
06. 镜头语言:[合并描述:机位(微距/探针) + 运镜(跟随/推拉) + 光学参数(广角/眩光)]
07. [ASMR] 核心音效:[🔥强制硬核:[拟声词] + [材质形容词] + [动作描述]。例如:**[滋滋 Zzzzt]** 锋利刀片切开多汁果肉的**湿润撕裂声**。]
[状态连接]: [用于下一个镜头的干净、优化的形容词列表。例如 "干净的,去皮的,切片的"]
"""
# ===============================================
# --- 提示词管理函数 ---
# ===============================================
def load_prompts():
"""从 JSON 加载提示词,增加兜底逻辑"""
p_eng, p_v1, p_v2 = FALLBACK_ENGINEER, FALLBACK_DIRECTOR_V1, FALLBACK_DIRECTOR_V2 # 默认值
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
# 使用 get 并提供 fallback,防止 json 里的 key 缺失
p_eng = data.get("engineer", FALLBACK_ENGINEER)
p_v1 = data.get("director1", FALLBACK_DIRECTOR_V1)
p_v2 = data.get("director2", FALLBACK_DIRECTOR_V2)
except Exception as e:
pass
return p_eng, p_v1, p_v2
def save_prompts(p_eng, p_v1, p_v2):
"""保存提示词到 JSON"""
data = {
"engineer": p_eng,
"director1": p_v1,
"director2": p_v2,
"last_updated": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
try:
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
return f"✅ 配置已保存 ({data['last_updated']})"
except Exception as e:
return f"❌ 保存失败: {e}"
def refresh_config_on_load():
"""刷新回调,确保返回正确的值"""
return load_prompts()
# ===============================================
# --- 核心逻辑 (API 调用) ---
# ===============================================
def get_timestamp():
return datetime.datetime.now().strftime("%H:%M:%S")
def log_msg(logs, msg, level="INFO"):
icon = {"INFO": "ℹ️", "PROCESS": "⚙️", "SUCCESS": "✅", "WARN": "⚠️", "STATE": "🧬", "WAIT": "⏳", "NET": "📡", "RETRY": "🔄", "ERROR": "❌", "PAUSE": "⏸️", "AUDIT": "👁️"}.get(level, "")
entry = f"[{get_timestamp()}] {icon} {msg}"
logs.append(entry)
return "\n".join(logs)
# 1. 工程师
def execute_engineer(topic, api_key, system_prompt):
if not api_key: yield "❌ 未提供 API Key"; return
yield "⏳ [工程师] 正在构建现代工厂工艺流程 (全链路+裂变模式)..."
url = f"{MERCHANT_BASE_URL}/v1/chat/completions"
headers = {"Authorization": f"Bearer {api_key.strip()}", "Content-Type": "application/json"}
data = {"model": TEXT_MODEL, "messages": [{"role": "system", "content": system_prompt}, {"role": "user", "content": f"产品: {topic}"}]}
for attempt in range(5):
try:
yield f"📡 [工程师] 正向 LLM 发送请求 (尝试 {attempt+1}/5)..."
res = requests.post(url, headers=headers, json=data, timeout=120)
if res.status_code != 200: raise Exception(res.text)
yield res.json()['choices'][0]['message']['content']
return
except Exception as e:
if attempt == 4: yield f"❌ 错误 (最终失败): {e}"
else: yield f"⚠️ 请求失败: {e},正在重试..."
time.sleep(2)
# 2. 导演 1.0 (分批接力 + 全量历史)
def execute_director_v1(topic, architecture, count, api_key, system_prompt):
if not architecture: yield "❌ 请先生成工艺架构"; return
V1_BATCH_SIZE = 10
# 🔥 核心修正:使用列表来存储所有结果,防止覆盖
v1_all_results = []
packaging_start_shot = int(count * 0.8) + 1
yield f"⏳ [导演 1.0] 正在构思 {count} 个镜头的解压沉浸剧本..."
yield f"📌 [时间线控制] Shot 1=源头, Shot {packaging_start_shot}=包装开始 (97% 处)"
for batch_start in range(1, count + 1, V1_BATCH_SIZE):
batch_end = min(batch_start + V1_BATCH_SIZE - 1, count)
current_batch_count = batch_end - batch_start + 1
yield f"📡 [导演 1.0] 正在生成第 {batch_start} - {batch_end} 个镜头 (Batch Processing)..."
# 构建当前全量剧本用于上下文 (只取列表中的 string)
current_full_script = "\n".join(v1_all_results)
context_instruction = ""
if current_full_script:
context_instruction = f"""
**⚠️ 前序镜头全量历史 (Context History - DO NOT REPEAT)**:
{current_full_script}
"""
user_content = f"""
产品: {topic}
工程协议:
{architecture}
{context_instruction}
任务: 生成第 {batch_start} 到 {batch_end} 个镜头 (共 {current_batch_count} 个)。
**⚠️ 时间线与包装死令**:
1. **起始点**:如果包含 Shot 1,**必须是【原料采集/源头】**。
2. **包装禁令**:**在第 {packaging_start_shot} 个镜头之前,绝对禁止进入包装环节!**
**⚠️ 强制格式锚点 (Format Anchor)**:
必须严格执行提示词中的输出格式,每个镜头前必须加上 `[[SHOT_ID: 序号]]`。
**警告:必须真实生成 {current_batch_count} 个独立的提示词块!**
输出: 结构化镜头列表,全部使用中文。
"""
url = f"{MERCHANT_BASE_URL}/v1/chat/completions"
headers = {"Authorization": f"Bearer {api_key.strip()}", "Content-Type": "application/json"}
data = {"model": TEXT_MODEL, "messages": [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_content}]}
success = False
for attempt in range(5):
try:
res = requests.post(url, headers=headers, json=data, timeout=300)
if res.status_code != 200: raise Exception(f"API Error: {res.text}")
new_content = res.json()['choices'][0]['message']['content']
# 🔥 增加空内容防御
if not new_content or len(new_content) < 50:
raise Exception("返回内容为空或过短,可能被安全拦截")
# 🔥 存入列表
v1_all_results.append(new_content)
# 🔥 强制拼合并返回
yield "\n\n".join(v1_all_results)
success = True
break
except Exception as e:
yield f"⚠️ Batch 失败 (尝试 {attempt+1}/5): {e}"
time.sleep(2)
if not success:
yield "❌ 致命错误:多次重试失败,流程终止。"
return
# 3. 导演 2.0 API (带 prev_context_shots)
def call_director_v2_batch_api(batch_plans, start_idx, total, prev_state, prev_context_shots, api_key, system_prompt):
joined_plans = "\n\n".join(batch_plans)
packaging_start_shot = int(total * 0.8) + 1
# 🔥 核心修改:回溯指令 (首批次豁免 + N-1 自动逻辑)
context_instruction = ""
if prev_context_shots and start_idx > 1:
# 如果是 Shot 7,list slice [-10:] 会返回 Shot 1-6,符合 "N-1" 要求
context_instruction = f"""
**👁️ 扩展视觉记忆 (Extended Visual Memory - Previous Shots)**:
以下是该镜头之前 **最多10个镜头** 的记录 (用于确保物理状态连续)。
---
{prev_context_shots}
---
**强制约束**: Shot {start_idx} 必须继承[产品]的真实物理状态,并延续视觉风格。
"""
else:
# 🔥 Shot 1 不回溯
context_instruction = "**【初始批次】:这是视频的开头(Shot 1),无需回溯历史。请建立基准物理状态(通常为原料采集)。**"
user_content = f"""
**批量任务**: 正在处理第 {start_idx} 到第 {start_idx + len(batch_plans) - 1} 个镜头 (共 {len(batch_plans)} 个)。
**⚠️ 包装时间线检查**:
- 包装允许开始点: Shot {packaging_start_shot}
🧬 **初始状态**: "{prev_state}"
{context_instruction}
📜 **导演 1 原始计划组**:
{joined_plans}
**你的任务**:
请**依次**处理这 {len(batch_plans)} 个镜头。
**输出格式要求**:
必须输出 {len(batch_plans)} 个完整的 7D 格式块,每个都要带 `[[SHOT_ID]]` 锚点。
在所有镜头输出完毕后,**必须**输出一行最终状态:
`[BATCH_END_STATE]: <这里写最后一个镜头完成后的物理状态>`
"""
url = f"{MERCHANT_BASE_URL}/v1/chat/completions"
headers = {"Authorization": f"Bearer {api_key.strip()}", "Content-Type": "application/json"}
data = {"model": TEXT_MODEL, "messages": [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_content}], "temperature": 0.3}
try:
res = requests.post(url, headers=headers, json=data, timeout=300)
content = res.json()['choices'][0]['message']['content']
final_state = prev_state
if "[BATCH_END_STATE]" in content:
parts = content.split("[BATCH_END_STATE]")
content_body = parts[0].strip()
state_raw = parts[1].strip()
if state_raw.startswith(":") or state_raw.startswith(":"):
final_state = state_raw[1:].strip()
else:
content_body = content
return content_body, final_state
except Exception as e: return None, str(e)
# 3. 导演 2.0 循环 (结果缓存 + 10段回溯)
def execute_director_v2_loop(v1_text, count, api_key, system_prompt):
if not v1_text: yield "❌ 请先生成导演 1.0 的视觉方案", ""; return
global IS_PAUSED
IS_PAUSED = False
logs = []
# 🔥 核心修正:UI 持久化容器
# 只要生成了,就塞进这里,永远不丢
all_generated_batches = []
full_results_history = []
raw_text = v1_text.strip()
# 🔥 核心修复:双重锚点解析 (兼容性 MAX)
header_pattern = re.compile(r'(?:\[\[\s*SHOT_ID\s*[::]\s*(\d+)\s*\]\]|###\s*镜头\s*(\d+))', re.IGNORECASE)
matches = list(header_pattern.finditer(raw_text))
if not matches:
yield log_msg(logs, f"❌ 严重错误:未找到锚点。请确认导演 1.0 输出包含 [[SHOT_ID]] 或 ### 镜头。", "ERROR"), ""
return
visual_plans = []
seen_ids = set() # 去重,防止同一镜头被重复解析
for i in range(len(matches)):
start = matches[i].start()
end = matches[i+1].start() if i+1 < len(matches) else len(raw_text)
block = raw_text[start:end].strip()
try:
val1 = matches[i].group(1)
val2 = matches[i].group(2)
shot_id = int(val1) if val1 else int(val2)
except:
shot_id = i + 1
if shot_id not in seen_ids and len(block) > 5:
visual_plans.append({"id": shot_id, "content": block})
seen_ids.add(shot_id)
# 排序以防万一
visual_plans.sort(key=lambda x: x['id'])
yield log_msg(logs, f"🚀 导演 2.0 启动:状态进化引擎 (7D 旗舰版 | Batch={BATCH_SIZE})", "SUCCESS"), ""
yield log_msg(logs, f"📊 成功解析 {len(visual_plans)} 个镜头 (首镜ID: {visual_plans[0]['id']})", "INFO"), ""
prev_state = "原材料,脏的,完整的,未处理的 (自然状态)"
yield log_msg(logs, f"🧬 [初始状态] {prev_state}", "STATE"), ""
total_shots = len(visual_plans)
for i in range(0, total_shots, BATCH_SIZE):
batch_items = visual_plans[i : i + BATCH_SIZE]
batch_texts = [item['content'] for item in batch_items]
start_real_id = batch_items[0]['id']
end_real_id = batch_items[-1]['id']
# 强制显式返回一次全量数据
full_text_snapshot = "\n\n".join(all_generated_batches)
if IS_PAUSED:
yield log_msg(logs, f"⏸️ 已暂停...", "PAUSE"), full_text_snapshot
while IS_PAUSED: time.sleep(1)
yield log_msg(logs, f"▶️ 恢复", "PROCESS"), full_text_snapshot
# 回溯逻辑
prev_context_shots = ""
if full_results_history:
last_10 = full_results_history[-10:]
prev_context_shots = "\n\n".join(last_10)
yield log_msg(logs, f"👁️ [视觉审阅] 回溯 Shot {max(1, start_real_id-10)} - {start_real_id-1}...", "AUDIT"), full_text_snapshot
else:
yield log_msg(logs, f"👁️ [视觉审阅] 初始批次 (Shot 1),无前序记忆", "INFO"), full_text_snapshot
yield log_msg(logs, f"⚙️ [批量处理] 镜头 {start_real_id} - {end_real_id} ({len(batch_items)}个)...", "PROCESS"), full_text_snapshot
yield log_msg(logs, f"🧬 [思维链继承] 起始状态: {prev_state}", "STATE"), full_text_snapshot
success = False
for attempt in range(5):
if IS_PAUSED:
yield log_msg(logs, "⏸️ 暂停中...", "PAUSE"), full_text_snapshot
while IS_PAUSED: time.sleep(1)
try:
if attempt > 0: yield log_msg(logs, f" 🔄 重试 ({attempt}/5)...", "RETRY"), full_text_snapshot
# 🔥 传入真实的 start_real_id
batch_result, next_state = call_director_v2_batch_api(
batch_texts, start_real_id, count, prev_state, prev_context_shots, api_key, system_prompt
)
if batch_result:
success = True
yield log_msg(logs, f" 📥 Batch 完成", "SUCCESS"), full_text_snapshot
# 🔥 核心修正:追加到全局列表
all_generated_batches.append(batch_result)
full_text_snapshot = "\n\n".join(all_generated_batches) # 更新快照
split_regex = r'(?:\[\[\s*SHOT_ID\s*[::]\s*\d+\s*\]\]|###\s*镜头\s*\d+)'
current_blocks = re.split(split_regex, batch_result)
current_blocks = [b.strip() for b in current_blocks if len(b) > 20]
full_results_history.extend(current_blocks)
if prev_state != next_state:
yield log_msg(logs, f" 🌊 [进化] ... -> {next_state}", "STATE"), full_text_snapshot
else:
yield log_msg(logs, f" 🧬 [保持] {next_state}", "STATE"), full_text_snapshot
prev_state = next_state
break
time.sleep(1.5 * (attempt + 1))
except Exception as e:
yield log_msg(logs, f" ⚠️ Batch 错误: {e}", "WARN"), full_text_snapshot
time.sleep(1.5 * (attempt + 1))
if not success:
yield log_msg(logs, f" ❌ Batch 失败 (跳过)", "ERROR"), full_text_snapshot
# 🔥 每一批次完成后,必须显式返回全量文本
yield "\n".join(logs[-20:]), full_text_snapshot
time.sleep(0.2)
yield log_msg(logs, "🎉 全片逻辑闭环完成", "SUCCESS"), "\n\n".join(all_generated_batches)
# ===============================================
# --- 渲染逻辑升级 (7要素拼接 + 格式化展示) ---
# ===============================================
def inject_satisfaction_physics(satisfaction_text):
booster = ""
if not satisfaction_text: return booster
if any(k in satisfaction_text for k in ["填满", "堆叠", "丰盛", "空虚", "数量"]):
booster += ",极高密度填充,画面无任何空隙,形成视觉实体墙,"
if any(k in satisfaction_text for k in ["秩序", "同步", "整齐", "治愈", "强迫症"]):
booster += ",完美几何对齐,毫秒级机械同步运动,无任何误差,"
if any(k in satisfaction_text for k in ["剥离", "去除", "刮", "磨", "分离", "炸开"]):
booster += ",表面污垢被物理暴力剥离,瞬间露出完美内部,"
if any(k in satisfaction_text for k in ["净化", "洁净", "丝滑", "顺滑", "粘稠"]):
booster += ",极度洁净的表面,丝滑的高光反射,无暇质感,"
return booster
def assemble_veo_prompt(raw_block):
"""提取+拼接+转译 (适配新的 7D 格式)"""
def extract(key_pattern, text):
match = re.search(key_pattern, text)
return match.group(1).strip().rstrip("。") if match else ""
f_subject = extract(r"01\. 主体与密度:(.*?)\n", raw_block)
f_texture = extract(r"02\. 材质与光影:(.*?)\n", raw_block)
f_action = extract(r"03\. 动作演变:(.*?)\n", raw_block)
f_physics = extract(r"04\. 物理反馈:(.*?)\n", raw_block)
f_env = extract(r"05\. 环境构建:(.*?)\n", raw_block)
f_camera = extract(r"06\. 镜头语言:(.*?)\n", raw_block)
physics_booster = inject_satisfaction_physics(f_physics)
parts = [
"BBC纪录片风格,无字幕,无说话声,8k分辨率,超写实电影质感",
f"{f_camera}",
f"{f_subject}",
f"{f_texture}",
f"{f_action}{physics_booster}",
f"{f_physics}",
f"位于{f_env}"
]
final_prompt = ",".join([p for p in parts if p and p.strip() != ","])
final_prompt = re.sub(r",+", ",", final_prompt)
final_prompt = f"{final_prompt} --ar 16x9"
return final_prompt
def render_videos(script, topic, video_key, progress=gr.Progress()):
if not script: yield "无脚本", None, ""; return
session_dir = os.path.join("AutoSaved_Videos", f"{topic}_{int(time.time())}")
os.makedirs(session_dir, exist_ok=True)
# 🔥 核心修复:兼容双重锚点
raw_blocks = []
matches = list(re.finditer(r'(?:\[\[\s*SHOT_ID\s*[::]\s*(\d+)\s*\]\]|###\s*镜头\s*(\d+))', script, re.IGNORECASE))
if not matches:
logs = ["❌ 无法解析脚本:未找到任何 '[[SHOT_ID]]' 标记"]
yield "\n".join(logs), None, ""; return
for i in range(len(matches)):
start = matches[i].start()
if i < len(matches) - 1:
end = matches[i+1].start()
else:
end = len(script)
block = script[start:end].strip()
if "Veo 提示词" in block or "01. 主体" in block:
raw_blocks.append(block)
logs = [f"🚀 全量并发渲染启动: 检测到 {len(raw_blocks)} 个独立镜头任务...", "✨ 已启用 [7大要素拼接] + [心理物理转译]"]
yield "\n".join(logs), None, ""
if len(raw_blocks) == 0:
logs.append("❌ 未找到可渲染提示词块 (请确保脚本包含 7D 格式)"); yield "\n".join(logs), None, ""; return
formatted_prompts_display = ""
parsed_tasks = []
for i, block in enumerate(raw_blocks):
title_match = re.search(r'(?:镜头|Shot)\s*(\d+)(?:/\d+)?\s*[\||]\s*(.*)', block)
title = title_match.group(2).strip() if title_match else f"Untitled"
safe_title = re.sub(r'[\\/*?:"<>|]', "", title).replace(" ", "_")
final_p = assemble_veo_prompt(block)
if len(final_p) < 50:
pattern = r"(?:Veo 提示词|Veo Prompt).*?[::]\s*(.*?)(?=\[状态连接\]|\[NEXT_LINK\]|镜头|Shot|$)"
match = re.search(pattern, block, re.DOTALL | re.IGNORECASE)
if match:
final_p = f"BBC纪录片风格,无字幕,无说话声,8k分辨率,超写实,{match.group(1).strip().replace(chr(10), ' ')} --ar 16x9"
shot_num = f"{i+1:03d}"
formatted_entry = f"=== Shot {shot_num}/{len(raw_blocks)}: {title} ===\nveo Prompt (English): {final_p}\n\n"
formatted_prompts_display += formatted_entry
fname = f"Shot_{shot_num}_{safe_title}"
parsed_tasks.append((final_p, fname))
yield "\n".join(logs), None, formatted_prompts_display
files = []
with ThreadPoolExecutor(max_workers=len(parsed_tasks)) as executor:
futures = {}
for final_p, fname in parsed_tasks:
futures[executor.submit(simple_veo_call, final_p, fname, session_dir, video_key)] = fname
done = 0
for f in as_completed(futures):
res = f.result()
done += 1
progress(done/len(parsed_tasks))
if res['ok']:
logs.append(f"✅ {res['name']} 完成")
files.append(res['path'])
else:
error_msg = res.get('error', '未知错误')
logs.append(f"❌ {res['name']} 失败: {error_msg}")
yield "\n".join(logs[-15:]), None, formatted_prompts_display
if files:
shutil.make_archive(session_dir, 'zip', session_dir)
yield "\n".join(logs), f"{session_dir}.zip", formatted_prompts_display
def simple_veo_call(prompt, name, folder, key):
last_error = ""
for attempt in range(5):
try:
url = f"{MERCHANT_BASE_URL}/v1/chat/completions"
headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
data = {"model": VEO_MODEL, "messages": [{"role": "user", "content": prompt}], "size": VIDEO_SIZE}
r = requests.post(url, headers=headers, json=data, timeout=300)
if r.status_code != 200:
raise Exception(f"API Error {r.status_code}: {r.text[:100]}")
content = r.json()['choices'][0]['message']['content']
v_url = re.search(r'(https?://[^\s)"]+)', content).group(1)
path = os.path.join(folder, name + ".mp4")
with open(path, "wb") as f: f.write(requests.get(v_url).content)
return {"ok": True, "name": name, "path": path}
except Exception as e:
last_error = str(e)
time.sleep(2 * (attempt + 1))
continue
return {"ok": False, "name": name, "error": last_error}
# ===============================================
# --- UI 界面构造 ---
# ===============================================
with gr.Blocks(title="Veo 终极全能引擎 (全链路覆盖版)") as app:
init_eng, init_v1, init_v2 = load_prompts()
gr.Markdown("## 🏭 Veo 终极全能引擎 (全链路覆盖版:微观拆解 + 多机位重复 + 7D)")
gr.Markdown("**提示**:此版本已强制工程师覆盖从【源头收获】到【包装物流】的全流程,并保留了所有历史铁律。")
# 0. 全局配置 & 保存按钮
with gr.Row(variant="panel"):
with gr.Column(scale=2):
with gr.Row():
llm_key = gr.Textbox(label="🔑 LLM Key", value=DEFAULT_LLM_API_KEY, type="password")
video_key = gr.Textbox(label="🎬 Video Key", value=DEFAULT_VIDEO_API_KEY, type="password")
with gr.Column(scale=1):
btn_save_config = gr.Button("💾 保存所有配置", variant="primary")
save_status = gr.Textbox(label="状态", lines=1, interactive=False)
topic_input = gr.Textbox(label="📦 产品名称", placeholder="例如:菠萝")
# 1. 工程师
with gr.Row():
with gr.Column():
gr.Markdown("### 🛠️ 步骤 1: 工程师 (全链路拆解)")
# 🔥 修改点:open=True,强制展开
with gr.Accordion("提示词设定 (可修改)", open=True):
prompt_eng_input = gr.Textbox(value=init_eng, lines=10, label="工程师提示词", interactive=True)
btn_eng = gr.Button("1. 生成海量素材日志", variant="secondary")
with gr.Column():
arch_output = gr.Textbox(label="工艺架构输出", lines=10)
gr.HTML("<hr>")
# 2. 导演 1.0
with gr.Row():
with gr.Column():
gr.Markdown("### 🎨 步骤 2: 导演 1.0 (最终剪辑与铺排)")
# 🔥 默认值改为 200,最大值改为 300
count_slider = gr.Slider(1, 300, 200, step=1, label="镜头数量")
# 🔥 修改点:open=True,强制展开
with gr.Accordion("提示词设定 (可修改)", open=True):
prompt_v1_input = gr.Textbox(value=init_v1, lines=15, label="导演1.0提示词", interactive=True)
btn_v1 = gr.Button("2. 生成完整剧本", variant="primary")
with gr.Column():
v1_output = gr.Textbox(label="V1 剧本输出", lines=15)
gr.HTML("<hr>")
# 3. 导演 2.0
with gr.Row():
with gr.Column():
gr.Markdown("### 🧬 步骤 3: 导演 2.0 (状态进化)")
# 🔥 修改点:open=True,强制展开
with gr.Accordion("提示词设定 (可修改)", open=True):
prompt_v2_input = gr.Textbox(value=init_v2, lines=10, label="导演2.0提示词", interactive=True)
with gr.Row():
btn_v2 = gr.Button("3. 启动逻辑变异 (Batch=10)", variant="primary")
btn_pause = gr.Button("⏸️ 暂停/继续", variant="secondary")
log_output = gr.Textbox(label="演算日志", lines=15, elem_id="log_box", autoscroll=True)
with gr.Column():
v2_output = gr.Textbox(label="最终脚本输出", lines=20, autoscroll=True)
gr.HTML("<hr>")
# 4. 渲染
with gr.Row(variant="panel"):
with gr.Column(scale=1):
gr.Markdown("### 🚀 步骤 4: 渲染")
btn_render = gr.Button("4. 全量并发渲染", variant="stop")
zip_output = gr.File(label="下载 ZIP")
with gr.Column(scale=2):
final_prompts_output = gr.Textbox(label="拼接后的提示词 (Assembled Prompts)", lines=20, interactive=False)
# 绑定加载事件
app.load(refresh_config_on_load, inputs=None, outputs=[prompt_eng_input, prompt_v1_input, prompt_v2_input])
# 绑定按钮事件
btn_save_config.click(save_prompts, [prompt_eng_input, prompt_v1_input, prompt_v2_input], [save_status])
btn_eng.click(execute_engineer, [topic_input, llm_key, prompt_eng_input], [arch_output])
btn_v1.click(execute_director_v1, [topic_input, arch_output, count_slider, llm_key, prompt_v1_input], [v1_output])
btn_pause.click(toggle_pause, [], [btn_pause])
btn_v2.click(execute_director_v2_loop, [v1_output, count_slider, llm_key, prompt_v2_input], [log_output, v2_output])
btn_render.click(render_videos, [v2_output, topic_input, video_key], [log_output, zip_output, final_prompts_output])
app.launch() |