x

重构 Pixelle-Video:公有 AI 视频生成方案

用公有云 API(MiniMax / 通义千问 / 豆包 / MiniMax-Music)替代本地文生图/视频模型,降低硬件门槛,实现「输入标题→全自动生成视频」的多 Agent 协作流水线。


一、项目背景与目标

1.1 现状痛点

  • 现有 Pixelle-Video 依赖本地 ComfyUI + 文生图/视频模型,需要 16-24GB 显存 GPU 显卡
  • 硬件投入成本高(五金门店/小团队难以承担)
  • 模型更新维护复杂,需要持续跟进开源社区
  • 本地部署对网络要求低,但运维成本高

1.2 重构目标

  • 去除所有本地 AI 模型(文生图、视频生成),改用公有云 API
  • 保留并强化:文案生成编排、多 Agent 协作调度、本地 ffmpeg 合成
  • 硬件要求降至:CPU(无需 GPU)+ 8GB RAM + 50GB 磁盘
  • 保持「输入标题→全自动出片」的核心体验
  • 支持私有化部署,API Key 可配置

1.3 预期收益

  • 硬件成本降低 90%(无需 GPU 服务器)
  • 上线周期从「月」缩短到「天」
  • 可对接任意公有 AI 服务商(MiniMax / 通义千问 / 豆包 / 混元)
  • 适合 SaaS 化输出,多商户共用一套流水线

二、整体架构设计

2.1 系统分层

采用五层架构,将 AI 推理全部委托给公有云,本地只负责编排和合成:

┌─────────────────────────────────────────────────────┐
│  接入层:用户输入(标题/关键词/主题)                 │
└────────────────┬────────────────────────────────────┘
                 ▼
┌─────────────────────────────────────────────────────┐
│  编排层:多 Agent 协作调度(Hermes / LangGraph)     │
│  ├── Agent-文案  →  MiniMax LLM / 通义千问         │
│  ├── Agent-配图  →  豆包视觉 / 通义万相 / 混元      │
│  ├── Agent-视频  →  MiniMax Video / 通义万相        │
│  ├── Agent-音乐  →  MiniMax-Music                  │
│  ├── Agent-配音  →  edge-tts / MiniMax TTS         │
│  └── Agent-合成  →  本地 ffmpeg                   │
└────────────────┬────────────────────────────────────┘
                 ▼
┌─────────────────────────────────────────────────────┐
│  合成层:ffmpeg 拼接 + 字幕 + 背景音乐 → MP4        │
└────────────────┬────────────────────────────────────┘
                 ▼
┌─────────────────────────────────────────────────────┐
│  输出层:成品视频 + 预览链接 / 微信/钉钉推送         │
└─────────────────────────────────────────────────────┘

2.2 技术选型

  • 编排框架:MiniMax 原生 + Hermes 多租户(复用现有基础设施)
  • 公有 API:MiniMax 全家桶(文案/视频/音乐/TTS)+ 豆包/通义千问(图片)
  • 本地合成:ffmpeg(字幕压制 + 音频混音 + 拼接)
  • 配置管理:config.yaml(API Key / 模型选择 / 尺寸参数)
  • 消息队列:Redis(可选,批量生成时解耦各 Agent)

2.3 与现有 Pixelle-Video 的区别

维度 原版 Pixelle-Video 重构版
文生图 本地 ComfyUI 公有云 API
图生视频 本地 VideoGen 模型 公有云 API
显存要求 16-24GB 0(纯 CPU)
部署难度 高(依赖模型下载) 低(pip install 即可)
运维成本 高(模型更新/显存管理) 低(API 版本由厂商维护)

三、模块拆解:公有 API 集成

3.1 文案生成模块

选用方案:MiniMax LLM 或 通义千问

核心流程

  1. 接收用户输入的主题(1-2句话)
  2. 调用 LLM API 生成:标题 + 5-10 个分镜脚本 + BGM 描述
  3. 输出结构化 JSON,供后续 Agent 消费

Prompt 设计模板

你是一个专业的短视频文案师。请根据以下主题,生成短视频脚本:

主题:{user_topic}

请以 JSON 格式输出:
{
  "title": "视频标题(10字以内)",
  "hook": "开场抓人句子(5字)",
  "scenes": [
    {
      "scene_id": 1,
      "duration": 5,
      "narration": "旁白文案(20字以内)",
      "visual_prompt": "画面描述词(供 AI 生成图片/视频用,15字以内)",
      "bgm_mood": "欢快/紧张/温柔/史诗"
    }
  ],
  "ending": "结尾行动号召"
}

API 调用示例(MiniMax)

import requests

def generate_script(topic: str) -> dict:
    response = requests.post(
        "https://api.minimaxi.com/v1/text/chatcompletion_v2",
        headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"},
        json={
            "model": "MiniMax-Text-01",
            "messages": [{"role": "user", "content": prompt_template.format(user_topic=topic)}]
        }
    )
    return response.json()["choices"][0]["message"]["content"]

视频时长估算

  • 每个分镜:5-8 秒
  • 总分镜数:根据总时长动态分配(30s视频≈5分镜,60s≈8分镜)

3.2 图片生成模块

推荐方案:豆包视觉(dart-vision)或 通义万相(Wan2.1-I2V)或 混元

选型对比

服务 模型 优势 劣势
豆包 dart-vision 字节自研,价格低,响应快 国内需要 API 代理
通义万相 Wan2.1-I2V 阿里自研,中文理解强 需开通阿里云百炼
混元 Hunyuan-DiT 腾讯自研,与微信生态打通 单独接入流程较复杂

核心流程

  1. 接收文案 Agent 输出的 scenes[].visual_prompt
  2. 每个分镜调用图片生成 API(可并发)
  3. 下载图片到本地临时目录
  4. 检查图片尺寸,不合格则重试(最多 3 次)

API 调用示例(豆包 dart-vision)

import requests

def generate_image(prompt: str, output_path: str) -> str:
    response = requests.post(
        "https://ark.cn-beijing.volces.com/api/v3/images/generations",
        headers={"Authorization": f"Bearer {DOUBAN_API_KEY}"},
        json={
            "model": "doubao-vision-01",
            "prompt": prompt,
            "size": "1080x1920",
            "response_format": "url"
        }
    )
    image_url = response.json()["data"][0]["url"]
    img_data = requests.get(image_url).content
    with open(output_path, "wb") as f:
        f.write(img_data)
    return output_path

图片规格

  • 分辨率:1080x1920(竖屏 9:16,适配抖音/视频号)
  • 也支持 1920x1080(横屏 16:9,适配B站/YouTube)
  • 格式:PNG → ffmpeg 前转 JPG 降低体积

失败处理

  • 重试机制:指数退避,3次失败则用 fallback 纯色图
  • 降级策略:图片生成连续失败时,改用纯色+文字字幕作为替代

3.3 视频生成模块

推荐方案:MiniMax Video + 通义万相 Wan2.1(双保险)

选型对比

服务 模型 生成长度 优势 劣势
MiniMax minimax-video-01 5-15秒 速度快,支持中文 prompt 国内访问需代理
通义万相 Wan2.1-VACE 5秒/10秒/15秒 阿里官方,稳定性高 等待较长

核心流程

  1. 接收图片(3.2 生成的本地图片路径)
  2. 将图片上传到图生视频 API
  3. 轮询任务状态(每3秒一次)
  4. 视频就绪后下载到本地

MiniMax Video API 示例

import requests, time

def generate_video(image_path: str) -> str:
    # 1. 上传图片获取 asset_id
    with open(image_path, "rb") as f:
        upload_resp = requests.post(
            "https://api.minimaxi.com/v1/files/upload",
            headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"},
            files={"file": f}
        )
    asset_id = upload_resp.json()["file"]["file_id"]

    # 2. 发起视频生成任务
    task_resp = requests.post(
        "https://api.minimaxi.com/v1/video_generation",
        headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"},
        json={"model": "minimax-video-01", "asset_id": asset_id, "duration": 5}
    )
    task_id = task_resp.json()["task_id"]

    # 3. 轮询直到完成
    while True:
        status = requests.get(
            f"https://api.minimaxi.com/v1/video_generation/{task_id}",
            headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"}
        ).json()
        if status["status"] == "completed":
            return status["video"]["url"]
        elif status["status"] == "failed":
            raise Exception("视频生成失败")
        time.sleep(3)

视频规格

  • 分辨率:720p 或 1080p(由 API 决定)
  • 竖屏优先(适配短视频平台)
  • 格式:MP4(H.264)

降级策略

  • MiniMax Video 失败时 → 切换通义万相 Wan2.1
  • 都失败时 → 使用静态图片 + 字幕 + 配音 代替视频片段

3.4 音乐生成模块

推荐方案:MiniMax-Music API

选型对比

服务 模型 生成长度 优势 劣势
MiniMax minimax-music-01 15-60秒 字节自研,质量高,响应快 国内需代理
豆包 (暂无音乐生成) - - -
通义万相 Wan2.1 (图像/视频) - 阿里官方 无音乐模块

核心流程

  1. 接收文案 Agent 输出的 scenes[].bgm_mood(欢快/紧张/温柔/史诗等)
  2. 根据 mood 调用音乐生成 API
  3. 下载 MP3 到本地临时目录
  4. 可选:生成多段不同 mood 的音乐,最终 ffmpeg 拼接

MiniMax-Music API 示例

import requests, time

def generate_music(mood: str, duration: int = 30) -> str:
    response = requests.post(
        "https://api.minimaxi.com/v1/music_generation",
        headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"},
        json={
            "model": "minimax-music-01",
            "prompt": f"生成一段{mood}风格的背景音乐",
            "duration": duration,
            "format": "mp3"
        }
    )
    task_id = response.json()["task_id"]
    # 轮询直到完成
    while True:
        status = requests.get(
            f"https://api.minimaxi.com/v1/music_generation/{task_id}",
            headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"}
        ).json()
        if status["status"] == "completed":
            music_url = status["music"]["audio_url"]
            mp3_data = requests.get(music_url).content
            output_path = f"/tmp/bgm_{mood}_{int(time.time())}.mp3"
            with open(output_path, "wb") as f:
                f.write(mp3_data)
            return output_path
        elif status["status"] == "failed":
            raise Exception("音乐生成失败")
        time.sleep(3)

音乐时长策略

  • 总时长 = 视频总时长
  • 音乐 < 视频时长:循环 + 淡出
  • 音乐 > 视频时长:截断 + 淡出
  • 音量:背景音乐降低 30%,确保配音清晰

情感标签体系

mood 标签 适用场景
欢快 五金产品展示、促销活动
史诗/震撼 品牌宣传片
温柔 产品讲解、使用教程
紧张 限时促销、倒计时
科技感 新品发布、技术介绍

3.5 语音配音模块

推荐方案:edge-tts(免费)或 MiniMax TTS

选型对比

服务 模型 费用 质量 中文支持
edge-tts 微软 Edge 在线 免费 优(晓晓、云扬等)
MiniMax TTS minimax-tts-01 按 token 计费 很高 优(多种音色)
通义千问 qwen-tts 按调用计费

核心流程

  1. 接收文案 Agent 输出的 scenes[].narration
  2. 调用 TTS API 生成音频
  3. 下载 WAV/MP3 到本地
  4. 检查音频时长是否与预期分镜时长匹配

edge-tts 示例(免费方案)

import asyncio, edge_tts

async def generate_voice(narration: str, output_path: str, voice: str = "zh-CN-XiaoxiaoNeural") -> str:
    communicate = edge_tts.Communicate(narration, voice)
    await communicate.save(output_path)
    return output_path

# 常用中文音色
VOICES = {
    "女-晓晓": "zh-CN-XiaoxiaoNeural",
    "男-云扬": "zh-CN-YunyanNeural",
    "女-云希": "zh-CN-YunxiNeural",
}

分镜时长对齐

  • 每个分镜预期时长 = scenes[].duration(来自文案 Agent)
  • TTS 生成后用 pydub 检测实际时长
  • 如果实际时长 > 预期 20%:加速播放(speed up)
  • 如果实际时长 < 预期 20%:添加静音 padding

音效增强

  • 添加轻微混响(reverb)让声音更有空间感
  • 开头和结尾 0.3s 淡入淡出

3.6 品牌素材库模块

功能定位:为每个租户建立私有品牌素材库,存储 Logo、产品图/视频、门店照片、员工照片,在视频生成时将品牌素材作为场景背景或水印嵌入成品。

3.6.1 素材分类体系

类型 目录 文件格式 典型用途
品牌 Logo logo/ PNG(透明通道)/SVG/AI 固定水印嵌入右下角
产品图片 products/ JPG/PNG/WebP 场景背景、分镜主体
产品视频 products/ MP4/MOV(≤30秒) 直接作为分镜片段
门店照片 stores/ JPG/PNG 到店场景、门头展示
员工照片 employees/ JPG/PNG 员工推荐分镜、团队展示

3.6.2 本地存储结构

/data/brand_assets/{tenant_id}/
├── logo/
│   ├── primary.png          # 主 Logo(透明背景)
│   └── icon.png             # 方形图标(App/头像用)
├── products/
│   ├── {product_id}/
│   │   ├── main.jpg         # 产品主图(白底)
│   │   ├── scene.jpg        # 场景图(使用中)
│   │   └── video.mp4        # 产品视频(可选)
├── stores/
│   ├── {store_id}/
│   │   ├── facade.jpg       # 门头照
│   │   └── interior.jpg     # 店内照
└── employees/
    ├── {employee_id}/
    │   ├── avatar.jpg       # 个人头像
    │   └── team.jpg         # 团队合照(可选)

3.6.3 OSS 云端备份

本地存储使用 SSD,OSS(阿里云/兼容S3协议)作为冷备:

import oss2, hashlib
from pathlib import Path

def upload_to_oss(local_path: str, tenant_id: str, category: str) -> str:
    """上传本地素材到OSS,返回CDN URL"""
    oss_config = get_oss_config(tenant_id)
    bucket = oss2.Bucket(oss2.Auth(oss_config['key'], oss_config['secret']),
                         oss_config['endpoint'], oss_config['bucket'])

    # 计算文件MD5去重
    with open(local_path, 'rb') as f:
        file_hash = hashlib.md5(f.read()).hexdigest()

    oss_key = f"brand_assets/{tenant_id}/{category}/{file_hash}_{Path(local_path).name}"

    # 已存在则跳过
    if bucket.object_exists(oss_key):
        return f"https://{oss_config['cdn_domain']}/{oss_key}"

    bucket.put_object_from_file(oss_key, local_path)
    return f"https://{oss_config['cdn_domain']}/{oss_key}"

3.6.4 视频生成融合方式

Logo 水印:ffmpeg overlay 固定在右下角(见 4.7 节)

产品图片:作为分镜背景,visual_prompt 中引用 {product_id},系统自动从素材库加载对应图片路径替换 AI 生成:

def resolve_product_image(product_id: str, scene_prompt: str) -> str:
    """将 prompt 中的 {product_id} 替换为实际素材路径"""
    if product_id in scene_prompt:
        local_path = get_product_image(product_id)  # 从素材库获取
        return scene_prompt.replace(f"{{{product_id}}}", local_path)
    return scene_prompt  # 无 product_id,使用 AI 生成

门店/员工照片:用于特定分镜类型(如"员工推荐"、"门店环境"),分镜类型映射到对应素材目录。

3.6.5 多租户隔离

每个租户独立目录,API 请求必须携带 tenant_id,跨租户访问抛出 403。


3.7 克隆音色模块

功能定位:使用 MiniMax 声音克隆 API,将店主/员工的声音克隆后用于配音,替代标准 TTS 音色,增加品牌真实感。

3.7.1 克隆流程总览

Step 1: 采集样本音频(3-5分钟纯净人声)
Step 2: 上传样本 → 调用克隆API → 获得 voice_id
Step 3: 生成配音时用 voice_id 替代标准 TTS
Step 4: voice_id 存入租户音色库(最多10个)

3.7.2 样本音频要求

参数 要求
时长 3-5 分钟(越多越好,最少 1 分钟)
格式 WAV/MP3,16bit/44.1kHz
环境 安静无噪声、无背景音乐、无混响
内容 纯人声朗读,语速中等,情感稳定
格式校验 预处理时用 pydub 检测峰值电平(>-30dBFS)

3.7.3 MiniMax 声音克隆 API

import requests, time, os

MINIMAX_API_KEY = os.environ["MINIMAX_API_KEY"]

def clone_voice(audio_path: str, voice_name: str, tenant_id: str) -> str:
    """
    上传样本音频,发起克隆任务,返回 voice_id
    """
    # Step 1: 上传音频文件
    with open(audio_path, "rb") as f:
        upload_resp = requests.post(
            "https://api.minimaxi.com/v1/files/upload",
            headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"},
            files={"file": f}
        )
    file_id = upload_resp.json()["file"]["file_id"]

    # Step 2: 发起克隆任务
    clone_resp = requests.post(
        "https://api.minimaxi.com/v1/voice_clone",
        headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"},
        json={
            "file_id": file_id,
            "name": voice_name,
            "model": "minimax-voice-clone-01"
        }
    )
    task_id = clone_resp.json()["task_id"]

    # Step 3: 轮询直到完成(每5秒一次,超时30分钟)
    for _ in range(360):
        status = requests.get(
            f"https://api.minimaxi.com/v1/voice_clone/{task_id}",
            headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"}
        ).json()
        if status["status"] == "completed":
            voice_id = status["voice_id"]
            save_voice_to_library(tenant_id, voice_id, voice_name)
            return voice_id
        elif status["status"] == "failed":
            raise Exception(f"音色克隆失败: {status.get('error', 'unknown')}")
        time.sleep(5)

    raise Exception("音色克隆超时")


def generate_voice_with_clone(narration: str, voice_id: str, output_path: str) -> str:
    """使用克隆音色生成配音"""
    response = requests.post(
        "https://api.minimaxi.com/v1/tts_with_voice_id",
        headers={"Authorization": f"Bearer {MINIMAX_API_KEY}"},
        json={
            "text": narration,
            "voice_id": voice_id,
            "model": "minimax-tts-01",
            "response_format": "mp3"
        }
    )
    audio_url = response.json()["audio_url"]
    audio_data = requests.get(audio_url).content
    with open(output_path, "wb") as f:
        f.write(audio_data)
    return output_path

3.7.4 音色库管理

# 音色库表结构
"""
CREATE TABLE voice_library (
    id SERIAL PRIMARY KEY,
    tenant_id TEXT NOT NULL,
    voice_id TEXT NOT NULL,
    voice_name TEXT NOT NULL,
    sample_duration INT,
    created_at TIMESTAMP DEFAULT NOW(),
    status TEXT DEFAULT 'active',
    usage_count INT DEFAULT 0
);
"""

def list_voices(tenant_id: str) -> list:
    return db.query(
        "SELECT * FROM voice_library WHERE tenant_id = ? AND status = 'active'",
        tenant_id
    )

def delete_voice(tenant_id: str, voice_id: str) -> bool:
    affected = db.execute(
        "DELETE FROM voice_library WHERE tenant_id = ? AND voice_id = ?",
        tenant_id, voice_id
    )
    return affected > 0

3.7.5 Fallback 机制

克隆音色生成失败
    → 回退到 edge-tts 标准音色(zh-CN-XiaoxiaoNeural)
    → 记录失败日志,标记 voice_id 为 unavailable

3.7.6 费用参考

项目 单价
样本音频克隆(训练) ~¥0.5/分钟音频
克隆音色生成配音 按 MiniMax TTS 标准计费
edge-tts(fallback) 免费

3.7.7 隐私与合规

  • 克隆音色仅限本租户使用,不可跨租户共享
  • 样本音频加密存储(OSS SSE-KMS),不留存克隆后删除
  • 克隆前需用户确认《声音授权书》(电子签)

四、本地合成层:ffmpeg 流水线

4.1 合成流程总览

输入阶段
├── 图片:分镜图 N 张(JPG)
├── 音频:配音 N 段(WAV/MP3)
├── 音乐:背景音乐 1 段(MP3)
└── 字幕:SRT 格式

第一步:图片 → 视频片段(ffmpeg -loop)
第二步:配音 + 音乐混音(ffmpeg amix)
第三步:视频片段拼接(ffmpeg concat)
第四步:字幕烧录(ffmpeg subtitles)
第五步:音频混音绑定 → final_output.mp4

4.2 分镜图转视频片段

ffmpeg -loop 1 \
  -i scene_01.jpg -t 5 \
  -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" \
  -r 30 -c:v libx264 -preset fast -crf 23 \
  -pix_fmt yuv420p -movflags +faststart \
  scene_01.mp4

4.3 音频混音

ffmpeg -i voice.wav -i bgm.mp3 \
  -filter_complex "[0:a]volume=0.8[voice];[1:a]volume=0.25[bgm];[voice][bgm]amix=inputs=2:duration=first[mixed]" \
  -map "[mixed]" -t {total_duration} audio_mixed.mp3

4.4 字幕压制(SRT)

def generate_srt(scenes, output_path):
    def ms_to_srt(ms):
        h, rem = divmod(int(ms), 3600000)
        m, rem = divmod(rem, 60000)
        s, ms = divmod(rem, 1000)
        return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
    with open(output_path, "w", encoding="utf-8") as f:
        for i, scene in enumerate(scenes, 1):
            start = sum(s["duration"] for s in scenes[:i-1])
            end = start + scene["duration"]
            f.write(f"{i}\n{ms_to_srt(start*1000)} --> {ms_to_srt(end*1000)}\n{scene['narration']}\n\n")

4.5 完整合成脚本

import subprocess
from pathlib import Path

def synthesize(video_or_images, voices, music_path, scenes, output_path):
    # Step 1: 图片/视频转片段
    for i, src in enumerate(video_or_images):
        ext = Path(src).suffix
        if ext in [".jpg", ".jpeg", ".png"]:
            subprocess.run(["ffmpeg", "-y", "-loop", "1", "-i", src,
                "-t", str(scenes[i]["duration"]),
                "-vf", "scale=1080:1920,crop=1080:1920",
                "-r", "30", "-c:v", "libx264", "-preset", "fast",
                f"/tmp/scene_{i}.mp4"])
        else:
            subprocess.run(["cp", src, f"/tmp/scene_{i}.mp4"])

    # Step 2: 拼接
    with open("/tmp/filelist.txt", "w") as f:
        for i in range(len(video_or_images)):
            f.write(f"file '/tmp/scene_{i}.mp4'\n")
    subprocess.run(["ffmpeg", "-y", "-f", "concat", "-safe", "0",
        "-i", "/tmp/filelist.txt", "-c", "copy", "/tmp/video_only.mp4"])

    # Step 3: 字幕
    generate_srt(scenes, "/tmp/subtitle.srt")
    subprocess.run(["ffmpeg", "-y", "-i", "/tmp/video_only.mp4",
        "-vf", "subtitles=subtitle.srt", "/tmp/video_with_subs.mp4"])

    # Step 4: 混音绑定
    total_dur = sum(s["duration"] for s in scenes)
    subprocess.run(["ffmpeg", "-y", "-i", "/tmp/video_with_subs.mp4",
        "-i", music_path, "-c:v", "copy", "-c:a", "aac",
        "-t", str(total_dur), output_path])
    return output_path

4.6 ffmpeg 依赖

# Ubuntu
apt install ffmpeg
# macOS
brew install ffmpeg

4.7.1 叠加位置(9点布局)

位置 ffmpeg 参数
左上 overlay=20:20
右上 overlay=W-w-20:20
左下 overlay=20:H-h-20
右下(默认) overlay=W-w-20:H-h-20
居中 overlay=(W-w)/2:(H-h)/2

4.7.2 透明度处理

PNG Alpha 通道(推荐):透明区域自动保留,无需额外滤镜。

透明度滤镜

ffmpeg -i input.mp4 -i logo.png \
  -filter_complex "[1:v]format=rgba,colorchannelmixer=aa=0.35[logo];[0:v][logo]overlay=W-w-20:H-h-20[out]" \
  -map "[out]" output.mp4

4.7.3 固定水印命令模板

LOGO="logo.png"
ffmpeg -i input_video.mp4 -i $LOGO \
  -filter_complex "[1:v]scale=${LOGO_W}:${LOGO_H}[logo];\
[0:v][logo]overlay=W-w-20:H-h-20:format=auto[out]" \
  -map "[out]" -c:v libx264 -preset fast -crf 23 \
  output_with_logo.mp4

4.7.4 动态水印(渐入渐出)

# 前3秒淡入,第58秒开始淡出
ffmpeg -i input_video.mp4 -i logo.png \
  -filter_complex "[1:v]format=rgba,fade=t=in:st=0:d=1:alpha=1[logo];\
[0:v][logo]overlay=W-w-20:H-h-20:enable='between(t,1,57)'[out]" \
  -map "[out]" output.mp4

4.7.5 水印配置持久化

# config.yaml
watermark:
  enabled: true
  logo_path: /data/brand_assets/{tenant_id}/logo/primary.png
  position: 右下        # 左上/右上/左下/右下/居中
  offset_x: 20
  offset_y: 20
  opacity: 0.35        # 0.0~1.0
  fade_in: 1.0         # 秒
  fade_out: 2.0        # 秒
  scale: 0.12          # 相对视频宽度的比例

4.8 品牌素材融合合成

4.8.1 素材融合场景

场景 素材类型 合成方式
产品特写 products/{id}/main.jpg 背景叠加 + 主体居中
门店环境 stores/{id}/interior.jpg 虚化背景 + 分镜字幕
员工推荐 employees/{id}/avatar.jpg 画中画 PiP + 字幕说明
绿幕视频 自带 Alpha 通道 色键抠像 + 背景叠加

4.8.2 产品图作为分镜背景

def blend_product_image(product_img: str, duration: int, output: str):
    """将产品图作为固定背景,持续 duration 秒"""
    subprocess.run([
        "ffmpeg", "-y",
        "-loop", "1", "-i", product_img,
        "-i", f"/tmp/voice_{idx}.wav",
        "-i", f"/tmp/bgm.mp3",
        "-t", str(duration),
        "-vf", (
            "scale=1080:1920:force_original_aspect_ratio=increase,"
            "crop=1080:1920,"
            "boxblur=3"
        ),
        "-i", "logo.png",
        "-filter_complex",
        "[0:v][3:v]overlay=W-w-20:H-h-20[bg];"
        "[bg]ass=subtitle.ass[out]",
        "-map", "[out]", "-map", "1:a", "-map", "2:a",
        "-c:v", "libx264", "-preset", "fast",
        "-shortest", output
    ], check=True)

4.8.3 画中画模式(员工/门店素材)

# 员工头像 PIP:左下角小窗(占画面 15%)
ffmpeg -i store_bg.mp4 -i employee_avatar.jpg \
  -filter_complex "[1:v]scale=160:160[avatar];\
[0:v][avatar]overlay=20:H-h-180[out]" \
  -map "[out]" -c:v libx264 -preset fast \
  pip_with_employee.mp4

4.8.4 绿幕抠像融合

# 色键抠像 + 品牌背景叠加
ffmpeg -i green_screen.mp4 -i brand_bg.jpg \
  -filter_complex "[0:v]chromakey=0x00ff00:0.15:0.02[fg];\
[1:v][fg]overlay=0:0[out]" \
  -map "[out]" -c:v libx264 -preset fast \
  keying_output.mp4

4.8.5 批量素材合成脚本

#!/bin/bash
# synthesize_with_brand.sh
TENANT_ID=$1
STORE_ID=$2
PRODUCT_ID=$3

LOGO="/data/brand_assets/${TENANT_ID}/logo/primary.png"
STORE_BG="/data/brand_assets/${TENANT_ID}/stores/${STORE_ID}/interior.jpg"
PRODUCT_IMG="/data/brand_assets/${TENANT_ID}/products/${PRODUCT_ID}/main.jpg"
VOICE="/tmp/voice.wav"
BGM="/tmp/bgm.mp3"
SUBS="/tmp/subtitle.ass"

OUTPUT="/data/videos/${TENANT_ID}/${STORE_ID}_${PRODUCT_ID}_$(date +%Y%m%d%H%M%S).mp4"

mkdir -p "$(dirname $OUTPUT)"

ffmpeg -y \
  -loop 1 -i "$PRODUCT_IMG" \
  -i "$VOICE" \
  -i "$BGM" \
  -i "$LOGO" \
  -filter_complex "
    [0:v]scale=1080:1920,crop=1080:1920,boxblur=2[bg];
    [bg][3:v]overlay=W-w-20:H-h-20[bg_logo];
    [bg_logo]ass=${SUBS}[out]
  " \
  -map "[out]" -map 1:a -map 2:a \
  -c:v libx264 -preset fast -crf 23 \
  -t 30 -shortest \
  "$OUTPUT"

echo "输出: $OUTPUT"

4.8.6 合成配置字段

# config.yaml - 合成配置
synthesis:
  brand_integration:
    logo:
      enabled: true
      path: /data/brand_assets/{tenant_id}/logo/primary.png
      position: 右下
      opacity: 0.35
    product_as_bg: true      # 产品图作为分镜背景
    store_photo_as_bg: true  # 门店照作为背景
    employee_pip: false      # 员工头像画中画
    subtitle_ass: true       # 烧录 ASS 字幕

五、多 Agent 编排层设计

5.1 编排框架选型

方案 优势 劣势 推荐场景
MiniMax 原生 + Hermes 复用现有架构,多租户支持 需要熟悉 Hermes 配置 推荐:五金门店 SaaS
LangGraph 细粒度状态机,可视化好 学习曲线较陡 复杂条件分支
AutoGen 微软开源,生态成熟 资源占用较高 企业内部知识库

5.2 Hermes Profile 设计

platforms:
  api_server:
    enabled: true
    port: 8646
  redis:
    enabled: true
    host: localhost
    port: 6379
agent:
  model: MiniMax-Text-01
skills:
  - name: video_pipeline
    config:
      image_api: doubao
      video_api: minimax
      music_api: minimax
      tts_api: edge-tts

5.3 任务状态机

IDLE → 收到主题 → SCRIPTING(文案生成)
    ↓ 文案完成
ASSET_GEN(图片/音乐/配音并发生成)
    ↓ 素材就绪
SYNTHESIZING(ffmpeg 合成)
    ↓ 完成
DELIVERING(推送微信/存储)
    ↓ 完成 → DONE

5.4 Agent 职责

Agent 输入 输出 调用工具
文案 Agent 用户主题 结构化脚本 JSON MiniMax LLM
配图 Agent visual_prompt 本地图片路径 豆包/通义万相 API
视频 Agent 本地图片路径 本地视频路径 MiniMax Video API
音乐 Agent mood 标签 本地 MP3 路径 MiniMax-Music API
配音 Agent narration 本地音频路径 edge-tts / MiniMax TTS
合成 Agent 所有素材路径 最终 MP4 本地 ffmpeg

5.5 并发控制

import asyncio

async def generate_assets_concurrently(scenes, music_mood):
    image_tasks = [generate_image(s["visual_prompt"]) for s in scenes]
    voice_tasks = [generate_voice(s["narration"]) for s in scenes]
    music_task = generate_music(music_mood)
    images, voices, music = await asyncio.gather(
        asyncio.gather(*image_tasks),
        asyncio.gather(*voice_tasks),
        music_task
    )
    return list(images), list(voices), music

5.6 错误恢复与重试

MAX_RETRIES = 3

def generate_with_retry(func, *args, **kwargs):
    for attempt in range(MAX_RETRIES):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            if attempt == MAX_RETRIES - 1:
                raise
            time.sleep(5 * (2 ** attempt))

六、用户界面与 API 接口

6.1 三种接入方式

方式 受众 体验
Web 管理后台 运营人员 点选主题,一键生成,预览下载
企业微信机器人 销售/店员 发标题给机器人,自动出片推送
REST API 第三方系统 接口调用,批量自动化

6.2 Web 管理后台

技术栈:Streamlit(快速原型)或 Next.js + Tailwind(正式版)

核心页面

  1. 首页:输入标题,选择视频类型(产品展示/促销/教程)
  2. 生成页:显示进度(文案→配图→配音→音乐→合成)
  3. 预览页:内置视频播放器,支持下载 MP4
  4. 历史页:查看历史生成记录

页面示例(Streamlit 简化版)

import streamlit as st

st.title("🎬 AI 全自动视频生成")

topic = st.text_input("输入视频主题", placeholder="例如:五金工具促销,冲击钻特价活动")
video_type = st.selectbox("视频类型", ["产品展示", "促销活动", "使用教程"])

if st.button("🚀 一键生成"):
    with st.spinner("文案生成中..."):
        script = generate_script(topic)

    col1, col2, col3 = st.columns(3)
    with col1:
        st.info("✅ 文案完成")
    with col2:
        with st.spinner("配图+配音+音乐生成中..."):
            assets = generate_assets(script)
    with col3:
        with st.spinner("视频合成中..."):
            output = synthesize_video(script, assets)

    st.success("生成完成!")
    st.video(output)
    st.download_button("📥 下载视频", open(output, "rb"), f"{topic}.mp4")

6.3 企业微信机器人接入

@hermes.on_message(channel="wecom")
async def handle_video_request(message: str, user_id: str):
    topic = message.strip()
    await hermes.send_text(
        f"收到!正在为您生成视频:{topic}\n预计 3-5 分钟完成,请稍候...",
        to=user_id
    )
    loop = asyncio.get_event_loop()
    loop.run_in_executor(None, generate_video_task, topic, user_id)

def generate_video_task(topic: str, user_id: str):
    try:
        output_path = main_pipeline(topic)
        hermes.send_file(output_path, to=user_id)
    except Exception as e:
        hermes.send_text(f"生成失败:{str(e)}", to=user_id)

6.4 REST API

POST /api/v1/video/generate
  Body: { "topic": "冲击钻促销", "type": "product_showcase" }
  Response: { "task_id": "vid_abc123", "status": "queued" }

GET /api/v1/video/{task_id}
  Response: {
    "task_id": "vid_abc123",
    "status": "synthesizing",
    "progress": 75,
    "video_url": null
  }

GET /api/v1/video/{task_id}/download
  Response: MP4 文件流

6.5 Webhook 回调

def on_video_completed(task_id: str, video_path: str):
    requests.post(
        "https://your-crm.com/webhook/video",
        json={
            "task_id": task_id,
            "video_path": video_path,
            "completed_at": datetime.now().isoformat()
        }
    )

10.6 品牌素材管理后台

10.6.1 功能概览

素材管理后台提供 Web 界面,支持品牌 Logo、产品图/视频、门店照片、员工照片的上传、查看、删除与标签管理。

10.6.2 素材上传

上传限制

类型 格式 单文件上限 说明
Logo PNG/SVG/JPG 10MB PNG 必须有透明通道
产品图 JPG/PNG/WebP 20MB 建议 1080×1920
产品视频 MP4/MOV 100MB ≤30秒
门店照 JPG/PNG 30MB 建议横版
员工头像 JPG/PNG 5MB 建议 1:1 正方形

上传流程:选择分类 → 拖拽文件 → 自动校验格式/尺寸/MD5去重 → 缩略图生成 → 写入本地+OSS备份 → 返回 CDN URL

上传接口

# POST /api/v1/materials/upload
from fastapi import UploadFile, File
import hashlib, uuid, asyncio

async def upload_material(tenant_id: str, category: str, file: UploadFile = File(...)):
    ext = file.filename.split('.')[-1].lower()
    content = await file.read()
    file_hash = hashlib.md5(content).hexdigest()

    # MD5去重
    existing = db.query(
        "SELECT id FROM materials WHERE tenant_id=? AND hash=?", tenant_id, file_hash
    )
    if existing:
        return {"id": existing[0]['id'], "duplicate": True}

    material_id = str(uuid.uuid4())
    path = f"/data/brand_assets/{tenant_id}/{category}/{material_id}.{ext}"
    Path(path).parent.mkdir(parents=True, exist_ok=True)
    with open(path, 'wb') as f:
        f.write(content)

    db.execute(
        "INSERT INTO materials(id,tenant_id,category,hash,path,filename,size) VALUES(?,?,?,?,?,?,?)",
        material_id, tenant_id, category, file_hash, path, file.filename, len(content)
    )

    # OSS备份(异步)
    asyncio.create_task(upload_to_oss(path, tenant_id, category))
    return {"id": material_id, "url": f"/materials/{material_id}"}

10.6.3 素材查看与检索

# GET /api/v1/materials?category=product&page=1&page_size=20
def list_materials(tenant_id: str, category: str = None,
                   tag: str = None, page: int = 1, page_size: int = 20):
    query = "SELECT * FROM materials WHERE tenant_id=? AND deleted_at IS NULL"
    params = [tenant_id]
    if category:
        query += " AND category=?"
        params.append(category)
    if tag:
        query += " AND tags LIKE ?"
        params.append(f"%{tag}%")
    query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
    offset = (page - 1) * page_size
    params.extend([page_size, offset])
    items = db.query(query, *params)
    total = db.query(
        "SELECT COUNT(*) FROM materials WHERE tenant_id=? AND deleted_at IS NULL"
        + (" AND category=?" if category else ""),
        tenant_id, *([category] if category else [])
    )[0]['count']
    return {"items": items, "total": total, "page": page, "page_size": page_size}

10.6.4 素材删除

# DELETE /api/v1/materials/{id}
def delete_material(tenant_id: str, material_id: str):
    # 软删除(保留30天)
    db.execute(
        "UPDATE materials SET deleted_at=NOW() WHERE id=? AND tenant_id=?",
        material_id, tenant_id
    )
    ref_count = db.query(
        "SELECT COUNT(*) FROM video_tasks WHERE tenant_id=? AND materials LIKE ?",
        tenant_id, f"%{material_id}%"
    )[0]['count']
    if ref_count > 0:
        return {"warning": f"该素材被{ref_count}个任务引用,30天内可恢复"}
    return {"status": "deleted"}

10.7 克隆音色管理

10.7.1 音色库界面

Web 端提供音色管理页面,展示所有克隆音色列表:音色名称、创建时间、状态(训练中/可用)、使用次数、操作(预览/删除)。

10.7.2 上传音频样本

格式要求:WAV/MP3,1-5分钟,≥16kHz采样,安静无噪声。

// POST /api/v1/voices/upload-sample
const formData = new FormData();
formData.append('audio', audioFile);
formData.append('voice_name', '老板张总');
formData.append('tenant_id', 'tenant_001');

const res = await fetch('/api/v1/voices/upload-sample', {
  method: 'POST',
  body: formData
});
const { sample_id, quality_score, status } = await res.json();
// status: 'processing' → 后台自动分析音质

10.7.3 创建克隆音色

# POST /api/v1/voices/clone
def create_voice_clone(tenant_id: str, sample_id: str, voice_name: str):
    sample = db.query("SELECT * FROM voice_samples WHERE id=?", sample_id)[0]
    voice_id = clone_voice(sample['file_path'], voice_name, tenant_id)

    voice_record = {
        'id': str(uuid.uuid4()),
        'tenant_id': tenant_id,
        'voice_id': voice_id,
        'voice_name': voice_name,
        'sample_duration': sample['duration'],
        'status': 'active',
        'created_at': datetime.now()
    }
    db.execute("INSERT INTO voice_library(...) VALUES(...)", *voice_record.values())
    return voice_record

10.7.4 音色预览与使用

# GET /api/v1/voices/{voice_id}/preview
def preview_voice(voice_id: str, text: str = "欢迎使用品牌配音"):
    return generate_voice_with_clone(
        narration=text[:50],
        voice_id=voice_id,
        output_path=f"/tmp/preview_{voice_id}.mp3"
    )

10.7.5 音色使用限制

每个租户最多 10 个克隆音色,超限需删除旧音色后才能新建。


10.8 多租户权限隔离

10.8.1 隔离策略总览

层级 隔离方式
存储路径 /data/brand_assets/{tenant_id}/
数据库 PostgreSQL RLS(行级安全策略)
Redis Key 前缀 tenant:{tenant_id}:
OSS Bucket 内路径前缀隔离

10.8.2 数据库行级隔离

-- PostgreSQL RLS
ALTER TABLE materials ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_materials ON materials
  FOR ALL USING (tenant_id = current_setting('app.tenant_id', true));

ALTER TABLE voice_library ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_voices ON voice_library
  FOR ALL USING (tenant_id = current_setting('app.tenant_id', true));

10.8.3 中间件注入

# FastAPI 中间件:自动注入 tenant_id
@app.middleware("http")
async def inject_tenant(request: Request, call_next):
    tenant_id = request.headers.get("X-Tenant-ID")
    if not tenant_id:
        raise HTTPException(401, "Missing X-Tenant-ID")
    with get_db_connection() as conn:
        conn.execute(f"SET LOCAL app.tenant_id = '{tenant_id}'")
        response = await call_next(request)
    return response

10.8.4 审计日志

def audit_log(tenant_id: str, user_id: str, action: str, resource: str, result: str):
    db.execute(
        "INSERT INTO audit_logs(tenant_id,user_id,action,resource,result,ip,created_at) VALUES(?,?,?,?,?,?,NOW())",
        tenant_id, user_id, action, resource, result,
        request.client.host if request else 'system'
    )

10.8.5 跨租户访问检测

def check_cross_tenant(tenant_id: str, resource_tenant_id: str):
    if tenant_id != resource_tenant_id:
        audit_log(tenant_id, "system", "CROSS_TENANT_ACCESS_DENIED",
                  f"attempted access to {resource_tenant_id}", "DENIED")
        raise HTTPException(403, "Access denied")

七、部署架构与运维

7.1 推荐部署架构

用户层
├── Web 管理后台(浏览器)
├── 企业微信机器人
└── REST API

网关层(Nginx)
├── 静态文件服务(视频)
├── 反向代理(API)
└── HTTPS

调度层(Hermes + Redis)
├── Hermes Gateway(任务编排)
└── Redis(队列 + 缓存)

公有 API 层
├── 图片(豆包/通义万相)
├── 视频(MiniMax Video)
└── 音乐(MiniMax Music)

合成层
├── ffmpeg
├── 临时存储(SSD)
└── 成品存储(NFS/OSS)

7.2 硬件配置

方案 A:小规模(< 100 并发)
| 组件 | 配置 | 成本 |
|------|------|------|
| CPU | 4 核 | |
| 内存 | 8 GB | ~300元/月 |
| 磁盘 | 100GB SSD + 500GB NAS | |
| 带宽 | 10 Mbps | |

方案 B:中规模(100-500 并发)
| 组件 | 配置 | 成本 |
|------|------|------|
| CPU | 8 核 | |
| 内存 | 16 GB | ~800元/月 |
| 磁盘 | 200GB SSD + 1TB NAS | |
| 带宽 | 50 Mbps | |

7.3 环境变量配置

MINIMAX_API_KEY=your_key
DOUBAN_API_KEY=your_key
WANXIANG_API_KEY=your_key
FFMPEG_PATH=/usr/bin/ffmpeg
TEMP_DIR=/tmp/video_generation
OUTPUT_DIR=/data/videos
REDIS_URL=redis://localhost:6379/0

7.4 监控与告警

@app.get("/health")
def health_check():
    return {
        "status": "healthy",
        "redis": check_redis(),
        "ffmpeg": check_ffmpeg(),
    }

7.5 日志与备份

  • 访问/生成/错误日志,接入 SLS 或 ELK
  • 视频成品每日增量备份到 OSS,保留 30 天
  • 配置文件 Git 管理

八、实施计划与里程碑

8.1 整体路线图

阶段 时间 目标
第一阶段 Week 1-2 核心框架搭建(环境 + 文案 + 图片 + ffmpeg)
第二阶段 Week 3-4 完整流水线(视频 + 音乐 + 配音 + Hermes 编排)
第三阶段 Week 5-6 用户界面(Web 后台 + 企业微信 + REST API)
第四阶段 Week 7-8 生产优化(错误处理 + 限流 + 监控)
第五阶段 Week 9-10 上线与迭代(内部测试 + 试点 + 正式上线)

8.2 详细任务分解

Week 1-2(核心框架)
| 任务 | 产出 |
|------|------|
| Python 环境 + ffmpeg 安装 | 可执行环境 |
| 文案生成模块(MiniMax LLM) | generate_script() |
| 图片生成模块(豆包 API) | generate_image() |
| ffmpeg 合成流程 | synthesize() |

Week 3-4(完整流水线)
| 任务 | 产出 |
|------|------|
| 视频生成模块(MiniMax Video) | generate_video() |
| 音乐生成模块(MiniMax-Music) | generate_music() |
| 语音配音模块(edge-tts) | generate_voice() |
| 多 Agent 并发编排 | asyncio 流水线 |

Week 5-6(用户界面)
| 任务 | 产出 |
|------|------|
| Streamlit 管理后台 | web 页面 |
| 企业微信机器人 | 消息接收/推送 |
| REST API | Swagger 文档 |

Week 7-8(生产优化)
| 任务 | 产出 |
|------|------|
| 错误处理与降级策略 | 熔断机制 |
| Redis 限流 | 滑动窗口限流 |
| Prometheus 监控 | 监控面板 |
| 压力测试 | TPS 报告 |

Week 9-10(上线)
| 任务 | 产出 |
|------|------|
| 内部测试验收 | UAT 报告 |
| 试点门店部署 | 运行日志 |
| 培训文档 | 用户手册 |

8.3 风险评估

风险 影响 缓解措施
公有 API 限流/宕机 多 API 备援 + 降级策略
ffmpeg 合成长时间 异步队列 + 超时中断
API Key 泄露 环境变量 + Vault
视频版权问题 无版权素材 + 水印

8.4 成本估算(月度)

项目 单价 预估量 月成本
云服务器(方案 A) 300 元/月 1 台 300 元
MiniMax 文案(LLM) ~0.1 元/千 Token 10 万次生成 100 元
豆包图片生成 ~0.1 元/张 1 万张 1000 元
MiniMax 视频 ~1 元/个 500 个 500 元
MiniMax 音乐 ~0.5 元/段 200 段 100 元
edge-tts 免费 - 0 元
合计 ~2000 元/月

九、代码示例与 Prompt 模板

9.1 核心入口脚本(main_pipeline.py)

#!/usr/bin/env python3
"""
Pixelle-Refactor:公有 API 全自动视频生成流水线
输入:标题 / 关键词
输出:完整 MP4 视频
"""
import os, asyncio
from pathlib import Path
from datetime import datetime

from modules.script import generate_script
from modules.image import generate_image_batch
from modules.video import generate_video_batch
from modules.music import generate_music
from modules.voice import generate_voice_batch
from modules.synthesize import synthesize

class VideoPipeline:
    def __init__(self, config: dict):
        self.config = config
        self.temp_dir = Path(config["TEMP_DIR"])
        self.output_dir = Path(config["OUTPUT_DIR"])
        self.temp_dir.mkdir(parents=True, exist_ok=True)
        self.output_dir.mkdir(parents=True, exist_ok=True)

    async def run(self, topic: str) -> str:
        task_id = datetime.now().strftime("%Y%m%d_%H%M%S")
        print(f"[{task_id}] 开始生成视频,主题:{topic}")

        # Step 1: 文案生成
        print(f"[{task_id}] Step 1/4 文案生成中...")
        script_data = await generate_script(topic)
        scenes = script_data["scenes"]
        total_duration = sum(s["duration"] for s in scenes)

        # Step 2: 素材并发生成
        print(f"[{task_id}] Step 2/4 素材生成中(图片+配音+音乐)...")
        image_tasks = [generate_image(s["visual_prompt"]) for s in scenes]
        voice_tasks = [generate_voice(s["narration"]) for s in scenes]
        music_task = generate_music(script_data.get("bgm_mood", "欢快"), duration=total_duration)

        images, voices, music_path = await asyncio.gather(
            asyncio.gather(*image_tasks),
            asyncio.gather(*voice_tasks),
            music_task
        )

        # Step 3: 视频生成(降级:使用静态图)
        print(f"[{task_id}] Step 3/4 视频生成中...")
        video_paths = []
        for i, img_path in enumerate(images):
            try:
                video_path = await generate_video(img_path)
                video_paths.append(video_path)
            except Exception as e:
                print(f"[{task_id}] 视频生成失败,使用静态图片:{e}")
                video_paths.append(img_path)

        # Step 4: ffmpeg 合成
        print(f"[{task_id}] Step 4/4 视频合成中...")
        output_path = self.output_dir / f"{task_id}_{topic[:20]}.mp4"
        await synthesize(video_paths, voices, music_path, scenes, output_path)

        print(f"[{task_id}] ✅ 生成完成:{output_path}")
        return str(output_path)

def main():
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--topic", "-t", required=True)
    parser.add_argument("--output", "-o", default="/data/videos")
    args = parser.parse_args()

    config = {
        "TEMP_DIR": os.environ.get("TEMP_DIR", "/tmp/video_generation"),
        "OUTPUT_DIR": args.output,
        "MINIMAX_API_KEY": os.environ.get("MINIMAX_API_KEY", ""),
    }
    pipeline = VideoPipeline(config)
    result = asyncio.run(pipeline.run(args.topic))
    print(f"视频路径:{result}")

if __name__ == "__main__":
    main()

9.2 Prompt 模板库

文案生成 Prompt

你是一个专业的抖音/快手短视频文案师

请根据以下主题生成一个完整的短视频脚本
主题{user_topic}
视频类型{video_type}产品展示/促销活动/使用教程
目标时长 60 

要求
1. 开场前 3 秒必须有强吸引力悬念/数字/冲突
2. 每个分镜旁白不超过 20 
3. 画面描述词要具体适合 AI 生成
4. BGM 风格选择要贴合内容情绪

输出格式严格 JSON):
{
  "title": "视频标题(10字内)",
  "hook": "开场钩子(5字)",
  "scenes": [{"scene_id": 1, "duration": 5, "narration": "旁白", "visual_prompt": "画面", "bgm_mood": "欢快"}],
  "ending": "结尾行动号召"
}

五金产品专用 Prompt

你是一个五金行业资深销售短视频文案师

主题{user_topic}
产品类型{product_category}电动工具/手动工具/劳保用品等

请生成包含以下要素的脚本
1. 痛点切入使用场景描述
2. 产品亮点核心卖点3 个以内
3. 促销信息如有折扣/活动
4. 行动号召点击购买/咨询客服

开场方式{hook_type}提问式/对比式/场景式/数字式

9.3 配置示例(config.yaml)

pipeline:
  temp_dir: /tmp/video_generation
  output_dir: /data/videos

apis:
  minimax:
    api_key: ${MINIMAX_API_KEY}
    base_url: https://api.minimaxi.com/v1
    models:
      text: MiniMax-Text-01
      video: minimax-video-01
      music: minimax-music-01
  doubao:
    api_key: ${DOUBAN_API_KEY}
    model: doubao-vision-01

video:
  width: 1080
  height: 1920
  fps: 30

audio:
  voice: zh-CN-XiaoxiaoNeural
  voice_volume: 0.8
  bgm_volume: 0.25

rate_limit:
  max_concurrent_generations: 10
  api_retry: 3

9.4 Docker 部署

FROM python:3.11-slim
RUN apt-get update && apt-get install -y ffmpeg curl && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install -r requirements.txt --no-cache-dir
COPY . /app
WORKDIR /app
ENV TEMP_DIR=/tmp/video_generation
ENV OUTPUT_DIR=/data/videos
EXPOSE 8501
CMD ["streamlit", "run", "web/app.py", "--server.port=8501", "--server.address=0.0.0.0"]
# docker-compose.yml
version: "3.8"
services:
  pipeline:
    build: .
    container_name: pixelle-pipeline
    restart: unless-stopped
    environment:
      - MINIMAX_API_KEY=${MINIMAX_API_KEY}
      - DOUBAN_API_KEY=${DOUBAN_API_KEY}
    volumes:
      - /tmp/video_generation:/tmp/video_generation
      - /data/videos:/data/videos
    depends_on:
      - redis
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8501/health"]
      interval: 30s

  redis:
    image: redis:7-alpine
    container_name: pixelle-redis
    restart: unless-stopped
    volumes:
      - redis_data:/data

volumes:
  redis_data:
Left-click: follow link, Right-click: select node, Scroll: zoom
x