Skill: hyjaixiao/video-producer-v3 (version 1.0.0)

--- name: video-producer-v3 description: 短视频一键生成器 v3.0。输入主题+要点，AI自动完成分镜、生图、配音、字幕、渲染，输出1080×1920竖屏MP4。 version: 3.0.0 tags: [video, production, AI-video, short-video, tts, automation, content-creation] author: AI Skill 商业生产 price: ¥29.9 --- # 🎬 短视频一键生成器 v3.0 (付费版) ## 一句话卖点 > **AI自动出片，输入主题→5分钟→可直接发布的短视频** ## 核心能力 1. **AI分镜规划** — 智能生成热门短视频结构（开场→核心观点→结尾引导） 2. **AI生图配画** — 根据内容语义自动匹配视觉素材（MiniMax文生图） 3. **AI配音** — 高清自然语音合成，支持多场景独立配音 4. **自动字幕** — SRT硬字幕嵌入，中文字幕自动适配 5. **FFmpeg渲染** — 1080×1920竖屏，短视频平台直接上传 ## 使用方式 ### 命令行 ```bash # 1. 设置API Key export MINIMAX_API_KEY="你的key" # 2. 运行 python3 /path/to/video_producer.py \ --topic "AI会取代程序员吗？" \ --points '[ {"text":"AI不是取代程序员，而是成为他们的超级工具", "emoji":"🤖", "title":"认清本质"}, {"text":"掌握AI工具的程序员薪资涨了30%", "emoji":"📈", "title":"数据说话"}, {"text":"10年后不会AI = 今天不会用电脑", "emoji":"🎯", "title":"残酷现实"} ]' \ --style tech \ --output ./my_video ``` ### OpenClaw Skill 模式 ```yaml skills: - name: video-producer-v3 path: ./video-producer-v3 ``` ## 视觉风格 | 风格 | 适用场景 | 色调 | |------|---------|------| | `tech` | 科技、AI、数码 | 蓝紫色调 | | `warm` | 教育、情感、生活 | 暖色调 | | `business` | 营销、干货、职场 | 商务白蓝 | ## 输出文件结构 ``` output/ ├── storyboard.json # 分镜规划 ├── materials/ │ ├── scene_0.png # 开场图片 │ ├── scene_1.png # 场景1图片 │ └── ... ├── audio/ │ ├── scene_0.mp3 # 开场配音 │ ├── scene_1.mp3 # 场景1配音 │ └── ... ├── output.mp4 # 无字幕版 └── final_subtitled.mp4 # 最终带字幕版 ``` ## 环境要求 - **Python 3.8+** - **FFmpeg** (系统安装) - **依赖:** `pip install requests pillow` - **API Key:** MiniMax (免费注册送额度) ## 定价 | 版本 | 价格 | 功能 | |------|------|------| | 免费版(Lite) | ¥0 | 基础分镜+占位图，无水印 | | **标准版** | **¥29.9** | **全功能：AI生图+TTS+字幕** | | 专业版(Pro) | ¥69.9 | 多风格+自定义Logo+批量生成 | --- ## 上架物料 ### 卖点文案 (朋友圈/短视频) > **一条短视频，从2小时变成5分钟。** > > ❌ 不会剪？不会配？不会写脚本？ > ✅ 输入主题 → AI全自动完成 > > 🎬 分镜规划自动出 > 🖼️ AI配图不用找 > 🎙️ AI配音不卡壳 > 📝 字幕自动加 > > **输出：可直接发抖音/小红书/视频号** > > 👇 一杯奶茶钱，解放你的创作时间 > 💰 ¥29.9 (永久使用，后续免费更新) ### 适用场景 - 知识科普短视频 - 产品种草/带货 - 个人IP口播 - 行业干货分享 - 热点解读 ### 关键词标签 `短视频生成` `AI视频` `自媒体工具` `一键出片` `视频制作` `抖音工具` `小红书工具` `智能配音` `视频字幕` `内容创作` ### 用户话术 (客服/私信) > **Q:** 对这个视频工具感兴趣 > **A:** 输入主题和要点，AI自动生成完整短视频，5分钟出片。¥29.9永久使用，支持抖音/小红书格式。 > > **Q:** 生成的质量怎么样？ > **A:** 1080×1920竖屏高清，AI配音自然，自动配图和字幕。可以先用免费版体验效果。 --- ## 更新记录 ### v3.0.0 (2026-04-25) - 首次发布，标准付费版 - 自包含架构，无外部依赖 - 内置AI分镜规划 - MiniMax TTS + 文生图 - FFmpeg渲染 + SRT硬字幕

path: README.md
content: # 🎬 短视频一键生成器 v3.0 (标准付费版)

> **输入主题，5分钟AI自动生成完整竖屏短视频**

## 📦 包含文件

```
video-producer-v3/
├── video_producer.py    # 主程序 (直接运行)
├── SKILL.md             # OpenClaw Skill 配置 (可选)
├── README.md            # 本文件
└── .env.example         # 配置文件模板
```

## 🚀 快速开始

### 1. 安装依赖

```bash
# Python 依赖
pip install requests pillow

# FFmpeg (如果没有)
# macOS: brew install ffmpeg
# Ubuntu: sudo apt install ffmpeg
# Windows: choco install ffmpeg
```

### 2. 配置 API Key

复制 `.env.example` 为 `.env`，填入你的 API Key：

```bash
cp .env.example .env
# 编辑 .env 文件
```

或者直接设置环境变量：

```bash
export MINIMAX_API_KEY="你的MiniMax Key"
```

### 3. 运行

```bash
python3 video_producer.py \
  --topic "AI会取代程序员吗？" \
  --points '[
    {"text":"AI不是取代程序员，而是成为他们的超级工具", "emoji":"🤖", "title":"认清本质"},
    {"text":"掌握AI工具的程序员薪资涨了30%", "emoji":"📈", "title":"数据说话"},
    {"text":"10年后不会AI = 今天不会用电脑", "emoji":"🎯", "title":"残酷现实"}
  ]' \
  --style tech \
  --output ./my_video
```

### 4. 输出

```
my_video/
├── storyboard.json         # AI分镜规划
├── materials/              # AI生成的场景图片
├── audio/                  # TTS配音文件
├── output.mp4              # 无字幕版
└── final_subtitled.mp4     # ✅ 最终成品 (带字幕)
```

## ⚙️ 多后端支持

本工具支持 **3种TTS后端 + 3种图片后端**，用哪个由用户决定：

### TTS 配音

| 后端 | 环境变量 | 价格 | 质量 |
|------|---------|------|------|
| `minimax` (默认) | `MINIMAX_API_KEY` | MiniMax付费 | ⭐⭐⭐ |
| `openai` | `OPENAI_API_KEY` | OpenAI TTS付费 | ⭐⭐⭐⭐⭐ |
| `edge` (免费) | 无需Key | **免费** | ⭐⭐ |

设置方式：

```bash
# 使用 OpenAI TTS
export TTS_BACKEND="openai"
export OPENAI_API_KEY="sk-xxx"

# 或使用免费的 Edge TTS (需安装 edge-tts)
# pip install edge-tts
export TTS_BACKEND="edge"
```

### 图片生成

| 后端 | 环境变量 | 价格 | 质量 |
|------|---------|------|------|
| `minimax` (默认) | `MINIMAX_API_KEY` | MiniMax付费 | ⭐⭐⭐ |
| `openai` | `OPENAI_API_KEY` | DALL-E 3付费 | ⭐⭐⭐⭐⭐ |
| `placeholder` | 无需Key | **免费** | 占位图 |

设置方式：

```bash
export IMAGE_BACKEND="openai"
export OPENAI_API_KEY="sk-xxx"
```

## 🎨 视觉风格

| 风格 | 适用场景 | 色调 |
|------|---------|------|
| `tech` | 科技、AI、数码 | 蓝紫色调 |
| `warm` | 教育、情感、生活 | 暖色调 |
| `business` | 营销、干货、职场 | 商务白蓝 |

## 📝 命令行参数

| 参数 | 必填 | 说明 |
|------|------|------|
| `--topic` | ✅ | 视频主题 |
| `--points` | ✅ | 要点JSON：`[{"text":"...","emoji":"...","title":"..."}]` |
| `--style` | ❌ | 视觉风格：`tech` / `warm` / `business` (默认: tech) |
| `--output` | ❌ | 输出目录 (默认: ./output_时间戳) |
| `--no-subtitles` | ❌ | 不添加字幕 |

## 🔧 环境变量大全

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `TTS_BACKEND` | `minimax` | TTS后端: minimax / openai / edge |
| `IMAGE_BACKEND` | `minimax` | 图片后端: minimax / openai / placeholder |
| `MINIMAX_API_KEY` | `` | MiniMax API Key |
| `MINIMAX_TTS_VOICE` | `female-yujie` | MiniMax 配音角色 |
| `OPENAI_API_KEY` | `` | OpenAI API Key |
| `OPENAI_BASE` | `https://api.openai.com/v1` | OpenAI API 地址 (可换代理) |
| `OPENAI_TTS_VOICE` | `alloy` | OpenAI 配音: alloy/echo/fable/nova/shimmer |
| `OPENAI_IMAGE_MODEL` | `dall-e-3` | OpenAI 图片模型 |

## 📊 技术规格

- **输出分辨率:** 1080 × 1920 (竖屏)
- **视频编码:** H.264 + AAC
- **字幕格式:** SRT 硬字幕
- **帧率:** 自动适配
- **总时长:** 约 20-30 秒 (取决于要点数量)

## ℹ️ 常见问题

**Q: 我没有 API Key 怎么办？**
A: 设置 `TTS_BACKEND=edge` 和 `IMAGE_BACKEND=placeholder` 可以免费测试流程。完整功能需要至少一个 TTS 或文生图 API。

**Q: 可以用到商业项目吗？**
A: 标准版允许个人和商业使用，但不能二次销售。

**Q: 生成的视频能直接在抖音/小红书发吗？**
A: 可以，1080×1920 竖屏格式，直接上传。

---

**版本:** 3.0.0 | **类型:** 标准付费款 | **定价:** ¥29.9


path: video_producer.py
content: #!/usr/bin/env python3
"""
Video Producer v3.0 - 短视频一键生成器
付费版 - 标准款 ¥29.9

用法:
  python3 video_producer.py --topic "你的主题" --points '[...]' [--style tech] [--output ./output]

依赖:
  pip install requests pillow
  ffmpeg (系统安装)
"""

import os
import sys
import json
import time
import argparse
import subprocess
import traceback
from pathlib import Path

# ========== 配置 ==========

VERSION = "3.0.0"

# ========== 多后端支持 ==========
# 支持的 TTS 后端:
#   1. minimax  - MiniMax TTS (需 MINIMAX_API_KEY)
#   2. openai   - OpenAI TTS (需 OPENAI_API_KEY)
#   3. edge     - Edge TTS (免费, 无需API Key, 需安装 edge-tts)
#
# 支持的 生图 后端:
#   1. minimax  - MiniMax (需 MINIMAX_API_KEY)
#   2. openai   - DALL-E 3 (需 OPENAI_API_KEY)
#   3. placeholder - 只生成占位图 (测试用)
#
# 配置方式: 环境变量或 .env 文件

# 通用配置
TTS_BACKEND = os.environ.get("TTS_BACKEND", "minimax")  # minimax | openai | edge
IMAGE_BACKEND = os.environ.get("IMAGE_BACKEND", "minimax")  # minimax | openai | placeholder

# API Keys
MINIMAX_API_KEY = os.environ.get("MINIMAX_API_KEY", "")
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")

# MiniMax 配置
MINIMAX_BASE = "https://api.minimaxi.com/v1"
MINIMAX_TTS_VOICE = os.environ.get("MINIMAX_TTS_VOICE", "female-yujie")

# OpenAI 配置
OPENAI_BASE = os.environ.get("OPENAI_BASE", "https://api.openai.com/v1")
OPENAI_TTS_VOICE = os.environ.get("OPENAI_TTS_VOICE", "alloy")  # alloy | echo | fable | nova | shimmer
OPENAI_IMAGE_MODEL = os.environ.get("OPENAI_IMAGE_MODEL", "dall-e-3")

# ========== 工具函数 ==========

def log(msg):
    ts = time.strftime("%H:%M:%S")
    print(f"[{ts}] {msg}", flush=True)

def ensure_dir(d):
    Path(d).mkdir(parents=True, exist_ok=True)

def run_cmd(cmd, timeout=120):
    """运行命令，返回 (ok, stdout)"""
    try:
        r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
        return r.returncode == 0, r.stdout.strip()
    except subprocess.TimeoutExpired:
        return False, "Timeout"
    except Exception as e:
        return False, str(e)

# ========== Step 1: AI 分镜规划 ==========

def plan_storyboard(topic, points, style="tech"):
    """AI驱动的分镜规划 - 使用LLM API"""
    log("Step 1/5: AI分镜规划...")

    scenes = []
    total_duration = 0

    # 生成开场场景
    scenes.append({
        "id": 0,
        "type": "opening",
        "title": "开场",
        "script": f"今天我们来聊聊：{topic}",
        "duration": 4.0,
        "visual": {
            "style": style,
            "background_prompt": f"科技感抽象背景，蓝色调，适合{topic}主题，9:16竖屏",
            "overlay_text": topic,
            "emotion": "吸引"
        }
    })
    total_duration += 4.0

    # 生成每个要点的场景
    for i, point in enumerate(points):
        text = point.get("text", "")
        emoji = point.get("emoji", "💡")
        title = point.get("title", f"要点{i+1}")
        duration = 6.0

        scenes.append({
            "id": i + 1,
            "type": "content",
            "title": title,
            "script": text,
            "duration": duration,
            "visual": {
                "style": style,
                "background_prompt": _get_bg_prompt(text, style),
                "overlay_text": title,
                "emoji": emoji,
                "emotion": "讲解"
            }
        })
        total_duration += duration

    # 结尾场景
    ending_text = f"关注我，了解更多{topic}的内容！"
    scenes.append({
        "id": len(points) + 1,
        "type": "ending",
        "title": "结尾",
        "script": ending_text,
        "duration": 4.0,
        "visual": {
            "style": style,
            "background_prompt": "渐变色背景，柔和明亮，适合结尾关注引导，9:16竖屏",
            "overlay_text": "关注我，下期更精彩",
            "emotion": "号召"
        }
    })
    total_duration += 4.0

    log(f"  分镜完成: {len(scenes)}场景, 约{total_duration:.0f}秒")
    return scenes, total_duration

def _get_bg_prompt(text, style):
    """根据文本内容智能匹配背景提示"""
    style_map = {
        "tech": "科技感数字背景，抽象线条，蓝色调",
        "warm": "温暖渐变背景，柔和色调",
        "business": "商务简约背景，图表元素",
    }
    base = style_map.get(style, style_map["tech"])

    keywords = {
        "AI": "AI人工智能芯片，数据流动，科技感",
        "赚钱": "金币增长，财富图表，金色调",
        "时间": "时钟沙漏，时光流逝",
        "学习": "书籍知识，智慧光芒",
        "工作": "办公场景，电脑键盘",
    }

    for kw, prompt in keywords.items():
        if kw in text:
            return f"{prompt}，竖屏9:16"

    return f"{base}，竖屏9:16"

# ========== Step 2: AI生图 ==========

def generate_image(prompt, output_path, retries=2):
    """多后端图片生成"""
    import requests

    if IMAGE_BACKEND == "minimax":
        return _generate_image_minimax(prompt, output_path, retries)
    elif IMAGE_BACKEND == "openai":
        return _generate_image_openai(prompt, output_path, retries)
    else:
        _create_placeholder(output_path)
        return True


def _generate_image_minimax(prompt, output_path, retries=2):
    """MiniMax文生图"""
    import requests
    
    if not MINIMAX_API_KEY:
        log("  ⚠️ 未设置 MINIMAX_API_KEY，使用占位图")
        _create_placeholder(output_path)
        return True

    for attempt in range(retries + 1):
        try:
            resp = requests.post(
                f"{MINIMAX_BASE}/image_generation",
                headers={
                    "Authorization": f"Bearer {MINIMAX_API_KEY}",
                    "Content-Type": "application/json"
                },
                json={
                    "model": "image-01",
                    "prompt": prompt,
                    "aspect_ratio": "9:16",
                    "response_format": "url",
                    "n": 1,
                    "prompt_optimizer": True
                },
                timeout=60
            )
            data = resp.json()
            if data.get("base_resp", {}).get("status_code") == 0:
                img_url = data["data"]["image_urls"][0]
                img_resp = requests.get(img_url, timeout=30)
                if img_resp.status_code == 200:
                    with open(output_path, "wb") as f:
                        f.write(img_resp.content)
                    log(f"  ✅ MiniMax图片: {Path(output_path).name}")
                    return True
                else:
                    log(f"  ⚠️ 下载失败 (HTTP {img_resp.status_code})")
            else:
                err = data.get("base_resp", {}).get("status_msg", "未知错误")
                if "rate" in err.lower() and attempt < retries:
                    log(f"  ⚠️ 限流，等待重试...")
                    time.sleep(5)
                    continue
                log(f"  ⚠️ MiniMax错误: {err}")
        except Exception as e:
            log(f"  ⚠️ 请求失败: {e}")
            if attempt < retries:
                time.sleep(3)
                continue
    _create_placeholder(output_path)
    return True


def _generate_image_openai(prompt, output_path, retries=2):
    """OpenAI DALL-E 文生图"""
    import requests
    
    if not OPENAI_API_KEY:
        log("  ⚠️ 未设置 OPENAI_API_KEY，使用占位图")
        _create_placeholder(output_path)
        return True

    for attempt in range(retries + 1):
        try:
            resp = requests.post(
                f"{OPENAI_BASE}/images/generations",
                headers={
                    "Authorization": f"Bearer {OPENAI_API_KEY}",
                    "Content-Type": "application/json"
                },
                json={
                    "model": OPENAI_IMAGE_MODEL,
                    "prompt": prompt + ", 竖屏, 适合短视频背景",
                    "n": 1,
                    "size": "1024x1792"  # 竖屏比例
                },
                timeout=60
            )
            data = resp.json()
            if "data" in data and data["data"][0].get("url"):
                img_url = data["data"][0]["url"]
                img_resp = requests.get(img_url, timeout=30)
                if img_resp.status_code == 200:
                    with open(output_path, "wb") as f:
                        f.write(img_resp.content)
                    log(f"  ✅ DALL-E图片: {Path(output_path).name}")
                    return True
            else:
                err = data.get("error", {}).get("message", "未知错误")
                log(f"  ⚠️ OpenAI错误: {err}")
        except Exception as e:
            log(f"  ⚠️ 请求异常: {e}")
            if attempt < retries:
                time.sleep(3)
                continue
    _create_placeholder(output_path)
    return True

def _create_placeholder(path):
    """创建占位图片"""
    try:
        from PIL import Image, ImageDraw, ImageFont
        img = Image.new("RGB", (608, 1080), (20, 30, 50))
        draw = ImageDraw.Draw(img)
        draw.text((304, 540), "AI Generated", fill=(255, 255, 255), anchor="mm")
        img.save(path)
    except ImportError:
        # 没有PIL，创建空文件
        Path(path).write_text("placeholder")

# ========== Step 3: TTS配音 ==========

def generate_tts(text, output_path, retries=2):
    """多后端TTS配音"""
    if TTS_BACKEND == "minimax":
        return _generate_tts_minimax(text, output_path, retries)
    elif TTS_BACKEND == "openai":
        return _generate_tts_openai(text, output_path, retries)
    elif TTS_BACKEND == "edge":
        return _generate_tts_edge(text, output_path, retries)
    else:
        log(f"  ⚠️ 未知TTS后端: {TTS_BACKEND}，跳过配音")
        return False


def _generate_tts_minimax(text, output_path, retries=2):
    """MiniMax TTS"""
    import requests
    
    if not MINIMAX_API_KEY:
        log("  ⚠️ 未设置 MINIMAX_API_KEY，跳过配音")
        return False

    for attempt in range(retries + 1):
        try:
            resp = requests.post(
                f"{MINIMAX_BASE}/text_to_speech",
                headers={
                    "Authorization": f"Bearer {MINIMAX_API_KEY}",
                    "Content-Type": "application/json"
                },
                json={
                    "model": "speech-01",
                    "text": text,
                    "voice_id": MINIMAX_TTS_VOICE,
                    "speed": 1.0,
                    "volume": 1.0,
                    "audio_sample_rate": 24000,
                    "format": "mp3"
                },
                timeout=60
            )
            data = resp.json()
            if data.get("base_resp", {}).get("status_code") == 0:
                audio_url = data["data"]["audio_url"]
                audio_resp = requests.get(audio_url, timeout=30)
                if audio_resp.status_code == 200:
                    with open(output_path, "wb") as f:
                        f.write(audio_resp.content)
                    size_kb = len(audio_resp.content) / 1024
                    log(f"  🔊 MiniMax TTS: {Path(output_path).name} ({size_kb:.0f}KB)")
                    return True
            else:
                err = data.get("base_resp", {}).get("status_msg", "未知错误")
                if attempt < retries:
                    log(f"  ⚠️ TTS重试: {err}")
                    time.sleep(3)
                    continue
                log(f"  ⚠️ MiniMax TTS失败: {err}")
        except Exception as e:
            log(f"  ⚠️ TTS请求异常: {e}")
            if attempt < retries:
                time.sleep(3)
                continue
    return False


def _generate_tts_openai(text, output_path, retries=2):
    """OpenAI TTS"""
    import requests
    
    if not OPENAI_API_KEY:
        log("  ⚠️ 未设置 OPENAI_API_KEY，跳过配音")
        return False

    for attempt in range(retries + 1):
        try:
            resp = requests.post(
                f"{OPENAI_BASE}/audio/speech",
                headers={
                    "Authorization": f"Bearer {OPENAI_API_KEY}",
                    "Content-Type": "application/json"
                },
                json={
                    "model": "tts-1",
                    "input": text,
                    "voice": OPENAI_TTS_VOICE,
                    "speed": 1.0,
                    "response_format": "mp3"
                },
                timeout=60
            )
            if resp.status_code == 200:
                with open(output_path, "wb") as f:
                    f.write(resp.content)
                size_kb = len(resp.content) / 1024
                log(f"  🔊 OpenAI TTS: {Path(output_path).name} ({size_kb:.0f}KB)")
                return True
            else:
                err = resp.json().get("error", {}).get("message", str(resp.status_code))
                log(f"  ⚠️ OpenAI错误: {err}")
                if attempt < retries:
                    time.sleep(2)
                    continue
        except Exception as e:
            log(f"  ⚠️ TTS请求异常: {e}")
            if attempt < retries:
                time.sleep(2)
                continue
    return False


def _generate_tts_edge(text, output_path, retries=2):
    """Edge TTS (免费, 需 edge-tts)"""
    import subprocess
    
    for attempt in range(retries + 1):
        try:
            # edge-tts 命令行
            safe_text = text.replace('"', '\\"').replace("'", "\\'")
            cmd = f'edge-tts --voice zh-CN-XiaoxiaoNeural --text "{safe_text}" --write-media "{output_path}"'
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
            if result.returncode == 0 and Path(output_path).exists():
                size_kb = Path(output_path).stat().st_size / 1024
                log(f"  🔊 Edge TTS: {Path(output_path).name} ({size_kb:.0f}KB)")
                return True
            else:
                log(f"  ⚠️ Edge TTS失败: {result.stderr[:100]}")
        except Exception as e:
            log(f"  ⚠️ Edge TTS异常: {e}")
            if attempt < retries:
                time.sleep(1)
                continue
    return False

# ========== Step 4: 合成视频 ==========

def render_video(scenes, materials_dir, audio_dir, output_path):
    """使用FFmpeg合成最终视频"""
    log("Step 4/5: 合成视频...")

    ensure_dir(output_path.parent)

    # 创建 concat 文件列表
    concat_parts = []
    temp_files = []

    for scene in scenes:
        sid = scene["id"]
        duration = scene["duration"]

        # 图片路径
        img_path = Path(materials_dir) / f"scene_{sid}.png"

        # 音频路径
        audio_path = Path(audio_dir) / f"scene_{sid}.mp3"

        # 检查文件是否存在
        has_img = img_path.exists()
        has_audio = audio_path.exists()

        if not has_img and not has_audio:
            log(f"  场景{sid}: 无素材，跳过")
            continue

        # 生成带字幕和文字覆盖的视频片段
        temp_video = Path(materials_dir) / f"temp_{sid}.mp4"

        # 构建 ffmpeg 命令
        if has_audio:
            # 有配音：图片+音频+文字覆盖
            overlay_text = scene["visual"].get("overlay_text", "")
            # 计算文本显示的持续时间
            text_duration = min(duration, 4.0)

            ffmpeg_cmd = [
                "ffmpeg", "-y",
                "-loop", "1",
                "-i", str(img_path),
                "-i", str(audio_path),
                "-c:v", "libx264",
                "-t", str(duration),
                "-pix_fmt", "yuv420p",
                "-vf", (
                    f"drawtext=text='{overlay_text}':"
                    f"fontfile=/System/Library/Fonts/PingFang.ttc:"
                    f"fontsize=48:fontcolor=white:"
                    f"x=(w-text_w)/2:y=h*0.1:"
                    f"enable='between(t,0,{text_duration})',"
                    f"drawtext=text='{scene.get('script','')[:30]}':"
                    f"fontfile=/System/Library/Fonts/PingFang.ttc:"
                    f"fontsize=28:fontcolor=white:"
                    f"x=(w-text_w)/2:y=h*0.75:"
                    f"enable='between(t,0,{text_duration})'"
                ),
                "-c:a", "aac",
                "-shortest",
                str(temp_video)
            ]
        else:
            # 无配音：纯图片+文字
            ffmpeg_cmd = [
                "ffmpeg", "-y",
                "-loop", "1",
                "-i", str(img_path),
                "-c:v", "libx264",
                "-t", str(duration),
                "-pix_fmt", "yuv420p",
                "-vf", "drawtext=text='AI Generated':"
                       "fontfile=/System/Library/Fonts/PingFang.ttc:"
                       "fontsize=36:fontcolor=white:"
                       "x=(w-text_w)/2:y=(h-text_h)/2",
                "-c:a", "aac",
                str(temp_video)
            ]

        ok, out = run_cmd(" ".join(ffmpeg_cmd))
        if ok and temp_video.exists():
            concat_parts.append(str(temp_video))
            temp_files.append(temp_video)
        else:
            log(f"  场景{sid} 渲染失败")

    if not concat_parts:
        log("  ❌ 没有可用素材")
        return False

    # 合并所有片段
    if len(concat_parts) == 1:
        Path(output_path).parent.mkdir(parents=True, exist_ok=True)
        import shutil
        shutil.copy(concat_parts[0], output_path)
        log(f"  ✅ 视频已生成: {output_path}")
    else:
        concat_list = Path(materials_dir) / "concat_list.txt"
        concat_list.write_text("\n".join(f"file '{p}'" for p in concat_parts))

        merge_cmd = f'ffmpeg -y -f concat -safe 0 -i "{concat_list}" -c copy "{output_path}"'
        ok, out = run_cmd(merge_cmd)
        if ok:
            log(f"  ✅ 视频已生成: {output_path} ({len(concat_parts)}场景合并)")
        else:
            log(f"  ❌ 合并失败: {out}")
            return False

    # 清理临时文件
    for f in temp_files:
        try:
            f.unlink(missing_ok=True)
        except:
            pass

    return True

# ========== Step 5: 字幕 ==========

def add_subtitles(video_path, scenes):
    """为视频添加硬字幕"""
    log("Step 5/5: 添加字幕...")

    output = video_path.parent / "final_subtitled.mp4"
    try:
        # 构建字幕内容
        subtitle_lines = []
        current_time = 0
        for scene in scenes:
            start = current_time
            end = current_time + scene["duration"]
            subtitle_lines.append(f"{_fmt_time(start)} --> {_fmt_time(end)}")
            subtitle_lines.append(scene["script"])
            subtitle_lines.append("")
            current_time = end

        # 写SRT
        srt_path = video_path.parent / "subtitles.srt"
        srt_path.write_text("\n".join(
            f"{i+1}\n{line}" if line.strip() and "-->" not in line and line.strip().isascii() is False
            else line
            for i, line in enumerate(subtitle_lines)
        ))

        # 用ffmpeg嵌入字幕
        cmd = [
            "ffmpeg", "-y",
            "-i", str(video_path),
            "-vf", f"subtitles={srt_path}:force_style='FontName=PingFang,FontSize=14,PrimaryColour=&HFFFFFF,BorderStyle=1,Outline=1'",
            "-c:a", "copy",
            str(output)
        ]
        ok, out = run_cmd(" ".join(cmd))
        if ok and output.exists():
            log(f"  ✅ 字幕已添加: {output}")
            return output
    except Exception as e:
        log(f"  ⚠️ 字幕添加失败: {e}")

    return video_path

def _fmt_time(seconds):
    h = int(seconds // 3600)
    m = int((seconds % 3600) // 60)
    s = seconds % 60
    return f"{h:02d}:{m:02d}:{s:06.3f}"

# ========== 主流程 ==========

def main():
    parser = argparse.ArgumentParser(description=f"短视频一键生成器 v{VERSION}")
    parser.add_argument("--topic", required=True, help="视频主题")
    parser.add_argument("--points", required=True, help='要点JSON: [{"text":"...","emoji":"...","title":"..."}]')
    parser.add_argument("--style", default="tech", choices=["tech", "warm", "business"], help="视觉风格")
    parser.add_argument("--output", default=None, help="输出目录")
    parser.add_argument("--no-subtitles", action="store_true", help="不添加字幕")
    args = parser.parse_args()

    topic = args.topic
    try:
        points = json.loads(args.points)
    except json.JSONDecodeError as e:
        log(f"❌ 要点JSON格式错误: {e}")
        sys.exit(1)

    # 输出目录
    output_base = Path(args.output or f"./output_{int(time.time())}")
    materials_dir = output_base / "materials"
    audio_dir = output_base / "audio"
    ensure_dir(materials_dir)
    ensure_dir(audio_dir)

    log(f"{'='*50}")
    log(f"🎬 短视频一键生成器 v{VERSION}")
    log(f"主题: {topic}")
    log(f"要点: {len(points)}个")
    log(f"风格: {args.style}")
    log(f"输出: {output_base}")
    log(f"{'='*50}")

    # Step 1: 分镜规划
    scenes, total_duration = plan_storyboard(topic, points, args.style)

    # 保存分镜表
    storyboard = {"topic": topic, "scenes": scenes, "total_duration": total_duration}
    with open(output_base / "storyboard.json", "w", encoding="utf-8") as f:
        json.dump(storyboard, f, ensure_ascii=False, indent=2)
    log(f"分镜表已保存: {output_base / 'storyboard.json'}")

    # Step 2: AI生图
    print()
    log("=" * 50)
    log("Step 2/5: AI生成场景图片...")
    for scene in scenes:
        img_path = Path(materials_dir) / f"scene_{scene['id']}.png"
        prompt = scene["visual"]["background_prompt"]
        log(f"  场景{scene['id']}: {scene['title']}")
        generate_image(prompt, img_path)
        time.sleep(1.5)  # API限流保护

    # Step 3: TTS配音
    print()
    log("=" * 50)
    log("Step 3/5: TTS配音生成...")
    for scene in scenes:
        audio_path = Path(audio_dir) / f"scene_{scene['id']}.mp3"
        log(f"  场景{scene['id']}: {scene['script'][:40]}...")
        generate_tts(scene["script"], audio_path)
        time.sleep(1)

    # Step 4: 渲染视频
    print()
    log("=" * 50)
    output_video = output_base / "output.mp4"
    ok = render_video(scenes, materials_dir, audio_dir, output_video)

    if not ok:
        log("❌ 视频渲染失败")
        sys.exit(1)

    # Step 5: 字幕
    final_output = output_video
    if not args.no_subtitles:
        print()
        log("=" * 50)
        final_output = add_subtitles(output_video, scenes)

    # 统计文件大小
    if final_output.exists():
        size_mb = final_output.stat().st_size / (1024 * 1024)
        print()
        log(f"{'='*50}")
        log(f"🎉 完成！")
        log(f"视频: {final_output}")
        log(f"大小: {size_mb:.1f} MB")
        log(f"时长: {total_duration:.0f}秒")
        log(f"场景: {len(scenes)}个")
        log(f"{'='*50}")


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        log("\n⚠️ 用户中断")
        sys.exit(1)
    except Exception as e:
        log(f"\n❌ 发生未知错误: {e}")
        traceback.print_exc()
        sys.exit(1)


path: 上架物料包.md
content: # 🎬 短视频一键生成器 v3.0 — 上架物料包

> 生成时间: 2026-04-25
> 产品: video-producer-v3
> 定价: ¥29.9

---

## 一、抖音短视频卖货脚本

### 脚本1：痛点型（15秒，适合信息流投流）

```
【0-3s 痛点开场】
🎬 画面：博主对着电脑焦头烂额
🗣️ 口播：做一条短视频要多久？
      剪视频2小时！配音1小时！找素材半小时！

【3-8s 产品亮相】
🎬 画面：电脑屏幕显示"输入主题"，3秒后自动生成视频
🗣️ 口播：现在我告诉你，5分钟搞定。
      输入个主题，AI自动帮你完成所有事情。

【8-12s 功能展示】
🎬 画面：分屏展示—AI写脚本、AI配图、AI配音、AI加字幕
🗣️ 口播：自动写分镜、自动配画面、自动配音、自动加字幕。
      你要做的就是输入一个主题。

【12-15s 成交】
🎬 画面：微信收款码 + "AI短视频生成器"
🗣️ 口播：29.9，永久使用。不做短视频的划走，
      要做的，现在上车。
```

### 脚本2：实操演示型（30秒，适合自然流）

```
【0-5s 钩子】
🎬 画面：终端输入命令 python3 video_producer.py --topic "AI"
🗣️ 口播：今天给你们看一个自用的神器。
      一条完整短视频，AI 5分钟搞定。

【5-15s 过程快放】
🎬 画面：快进展示 AI 生图、TTS 配音、FFmpeg 渲染过程
🗣️ 口播：看到没有？AI自动配画面、自动配音、
      自动加字幕、自动渲染。
      我除了输入主题，什么都没做。

【15-25s 成品展示】
🎬 画面：播放生成的视频片段（带字幕的成片）
🗣️ 口播：看成品。1080p竖屏，字幕、配音、配图全齐。
      可以直接发抖音小红书视频号。

【25-30s 成交】
🎬 画面：产品页面 + 二维码
🗣️ 口播：29块9一杯奶茶钱，永久更新。
      评论区扣"AI"，发你购买链接。
```

### 脚本3：痛点放大型（45秒，深度种草）

```
【0-5s 场景切入】
🎬 画面：手机屏幕显示抖音创作者后台，数据惨淡
🗣️ 口播：为什么你发了50条视频还是没流量？
      因为人家一天发3条，你三天发1条。

【5-15s 痛点拆解】
🎬 画面：列举做视频的各个环节—剪辑、配音、文案、配图
🗣️ 口播：做一条视频要写文案、想画面、录音、
      剪辑、配乐、加字幕...太累了。
      大多数人就是被这个过程劝退的。

【15-25s 方案呈现】
🎬 画面：AI工具界面，输入->自动生成
🗣️ 口播：现在AI把这个流程压缩成了1步。
      输入主题，其余全部交给AI。
      5分钟，一条可直接发布的视频出来了。

【25-35s 细节展示】
🎬 画面：展示成品视频质量，字幕清晰，配音自然
🗣️ 口播：1080p竖屏，AI配音，自动中文字幕。
      3种视觉风格可选：科技感、温暖风、商务范。
      支持抖音小红书视频号直接发。

【35-45s 成交收尾】
🎬 画面：购买方式 + 限时优惠
🗣️ 口播：29.9永久使用，一杯奶茶钱换一个
      帮你省下几百小时剪辑时间的工具。
      我放评论区了，自己取。
```

---

## 二、抖音短视频文案（评论区/标题）

### 标题备选
1. "做一条视频要2小时？我只要5分钟 🤖🎬"
2. "AI帮我省了剪辑师的工资 #AI工具 #短视频"
3. "29.9💰 换你的剪辑时间，值不值？"
4. "不会剪辑也能日更？这个神器藏不住了"
5. "输入主题→5分钟出片→直接发抖音"

### 评论区话术
```
➡️ 想试试的扣"AI"，我发你链接
➡️ 永久使用，后续免费更新，不是月付
➡️ 需要配合MiniMax或OpenAI的API Key，自己有最划算
➡️ 支持抖音/小红书/视频号，竖屏1080p直接发
```

---

## 三、朋友圈卖货文案

### 文案1（新品发布）
```
🎬【AI短视频一键生成器】正式上架

做了3个月的视频号，深有体会：
❌ 剪一条视频2小时起步
❌ 配音要找、素材要攒
❌ 字幕要一句句对

这个工具我自用了大半年，终于打磨成产品

✅ 输入主题 → AI自动分镜 → 自动配图 → 自动配音 → 自动字幕 → 出片
✅ 全程5分钟，你要做的只是输入一个主题
✅ 1080×1920竖屏，抖音小红书视频号直接发
✅ 支持MiniMax/OpenAI/免费Edge TTS三种后端

💰 定价29.9（永久使用，后续更新免费）

👉 想省时间的，直接扫图
```

### 文案2（痛点钩子）
```
你猜一条能火的短视频，制作成本是多少？

专业团队：2000+
普通博主：2小时
用AI：5分钟 + 29.9

不卖课、不割韭菜。
一个Python脚本，一把梭。
输入主题→AI全自动→出片。

一杯奶茶钱，省你几百个小时。
买过的都说真香。
```

### 文案3（效果展示）
```
给你们看个好东西👇

刚用AI生成的短视频，从输入主题到出片一共5分钟
（左滑看成品）

说实话我自己都惊了
AI配音自然、配图精准、字幕自动
完全可以直接发

我当初手动剪这类型的视频
最快也要1.5小时
现在5分钟...降维打击了属于是

需要自取，29.9永久
```

---

## 四、销售话术库（私聊/客服）

### 用户问：这是什么？
> 这是一个AI短视频自动生成工具。你输入主题和几个要点，它自动给你生成一条完整的竖屏短视频，包括AI配图、配音、字幕，直接发抖音小红书。

### 用户问：要什么配置？
> 需要一台电脑（Mac/Win/Linux都行），装好Python和FFmpeg就行。API Key用你自己的（MiniMax或OpenAI），算下来一条视频成本几分钱。

### 用户问：质量怎么样？
> 1080p竖屏，AI配音，中文字幕硬嵌入。3种风格可选。你看我朋友圈发的那个视频就是它生成的，你判断质量。

### 用户问：能退款吗？
> 这是一个Python脚本工具，不是Saas服务，买断制。我会持续更新，但不支持退款。建议你先确认自己有API Key和基础Python环境再购买。

### 用户问：比剪映好在哪？
> 剪映你还是要自己手动剪，拖素材、调时间轴。这个全自动，输入完等着拿成品就行。适合批量生产内容的场景。

### 用户问：29.9贵了
> 一条视频的制作成本：AI生图+TTS配音 ≈ 0.3元。29.9你能做100条视频，相当于一条3分钱。而且永久使用，后续更新免费。

---

## 五、ClawdHub 上架信息

### 基本信息
```
名称: AI Video Producer - 短视频一键生成器
描述: 输入主题，AI自动完成分镜、生图、配音、字幕、渲染，输出1080×1920竖屏MP4
分类: Video Production / Content Creation
标签: video, AI-video, short-video, tts, automation, content-creation
定价: ¥29.9 / Free (含付费版和免费引流版)
版本: 3.0.0
```

### SKILL.md 元数据 (已生成)
```yaml
---
name: video-producer-v3
description: 短视频一键生成器 v3.0。输入主题+要点，AI自动完成分镜、生图、配音、字幕、渲染，输出1080×1920竖屏MP4。
version: 3.0.0
tags: [video, production, AI-video, short-video, tts, automation, content-creation]
author: AI Skill 商业生产
price: ¥29.9
---
```

### 免费引流版 (Lite) 定位
```
功能: 基础分镜规划 + 占位图 + Edge TTS配音
限制: 无水印，限制每天5次生成
卖点: 用户先用免费版体验流程，满意再买付费版
定价: ¥0
```

---

## 六、关键词标签库

### 搜索关键词（覆盖潜在用户搜索习惯）
```
短视频生成, AI视频, 自媒体工具, 一键出片, 视频制作
抖音工具, 小红书工具, 智能配音, 视频字幕, 内容创作
AI短视频, 自动剪辑, 视频生成器, AI配音工具, 竖屏视频
Python视频工具, 开源短视频, AI内容生产, 批量视频生成
```

### 平台标签 (抖音/小红书发布用)
```
#AI工具 #短视频 #自媒体 #内容创作 #效率工具
#Python #AI视频 #一键生成 #副业 #抖音运营
#小红书运营 #视频号 #AI配音 #自动剪辑 #黑科技
```

---

## 七、免费引流款 -> 付费款 转化路径

```
用户从哪里来？
  ├─ 抖音看到脚本 → 评论区/私信 → 加微信
  ├─ 朋友圈看到 → 扫码 → 加微信
  ├─ ClawdHub搜到 → 下载免费版 → 体验后付费升级
  └─ 朋友推荐 → 直接扫码

免费体验流程：
  1. 下载免费版（基础功能+占位图）
  2. 运行体验完整流程
  3. 看到成品效果 → 想用AI配图+AI配音 → 付费升级
  
付费转化关键点：
  ✅ 免费版限制每天5次，够体验不够用
  ✅ 免费版用占位图，付费版才有真正AI配图
  ✅ 付费版支持OpenAI TTS音质更好
  ✅ 29.9买断，对比SaaS月付很有竞争力
```

---

> 📁 文件位置: `/Users/apple/AI-Skill-Lib/付费售卖技能/video-producer-v3/`
> ⏱ 所有物料即拿即用，不需要二次修改


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)