Full AI pipeline to create dark motivational TikTok/Reels videos using REAL video footage. Generates script (Claude), voiceover (ElevenLabs), searches real dark/cinematic video clips from Pexels API (no AI image generation), adds animated text overlays (MoviePy), color grading (FFmpeg), and exports final 1080x1920 MP4. Use this skill for: motivation video, dark

基于真实视频素材,自动创建暗黑风格的TikTok/Reels激励视频。

已扫描安全风险
适合谁
自媒体创作者、短视频运营人员
不适合谁
无网络环境用户、无法获取API密钥者
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
中等(★★☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @thuongvh2-python/tiktok-motivation-video

Skill 说明

命令、参数、文件名以原文为准

TikTok 黑暗励志视频制作技能

30-90 秒竖版(9:16)黑暗风格励志视频 —— 模仿 @vuongmilano 风格。

制作流程:脚本生成(Claude)+ 语音合成(ElevenLabs)+ 真实视频素材(Pexels API)+ 文字动画叠加(MoviePy)+ 色彩调色(FFmpeg)

重要提示:所有视觉素材均来自 Pexels API(真实拍摄的库存视频)。

禁止使用 FAL.ai、Replicate 或任何 AI 图像生成工具。仅使用 Pexels。


步骤 0 - 收集输入参数

  • 主题:例如 “自律”、“孤独”、“成为更好的自己”
  • 语气:黑暗而沉静 | 黑暗而激烈 | 黑暗而诗意 (默认:黑暗而沉静)
  • 时长:30 秒 | 60 秒 | 90 秒 (默认:60 秒)
  • 视觉主题:暗夜城市雨景 | 孤独战士 | 黑暗自然 | 极简阴影 (默认:暗夜城市雨景)
  • 语音 ID:ElevenLabs 语音 (默认:Adam = pNInz6obpgDQGcFmaJgB)
  • 语言:英语 | 越南语 | 双语

步骤 1 - 生成脚本(Claude API)

系统提示生成包含台词、时间点及 Pexels 搜索关键词的 JSON 数据。

系统提示:

你是一位擅长创作黑暗美学 TikTok 励志内容的顶尖文案写手。

风格参考 @vuongmilano:短句有力,节奏紧凑,富有哲思。

氛围黑暗、电影感强、诗意浓烈。直击那些孤独却奋进、渴望突破自我的人。

规则:最多 8-15 行;每行不超过 8 个词;杜绝冗余;每个词都掷地有声。

先制造张力,再释放爆发力;结尾必须留下一句令人难忘的收尾。

使用 [PAUSE] 标记以营造戏剧性停顿。

仅返回有效 JSON(不包含 markdown 代码块标记):

{
  "title": "...",
  "lines": [
    {"id": 1, "text": "...", "duration_ms": 3000, "pause_after_ms": 500},
    {"id": 2, "text": "[PAUSE]", "duration_ms": 1200, "pause_after_ms": 0}
  ],
  "total_duration_ms": 60000,
  "hashtags": ["#darkmotivation", "#discipline", "#fyp"],
  "pexels_search_queries": ["dark city rain night", "lone person walking", "shadow dramatic"]
}
import json
import requests

def generate_script(topic, tone, duration, api_key):
    r = requests.post(
        "https://api.anthropic.com/v1/messages",
        headers={
            "x-api-key": api_key,
            "anthropic-version": "2023-06-01",
            "content-type": "application/json"
        },
        json={
            "model": "claude-sonnet-4-20250514",
            "max_tokens": 1000,
            "system": SCRIPT_SYSTEM,
            "messages": [
                {"role": "user", "content": f"Topic: {topic}\nTone: {tone}\nDuration: {duration}s"}
            ]
        }
    )
    raw = r.json()["content"][0]["text"]
    return json.loads(raw.replace("```json", "").replace("```", "").strip())

步骤 2 - 生成语音旁白(ElevenLabs)

语音设置适用于黑暗励志风格:

稳定性:0.75,相似度增强:0.85,风格:0.30,启用说话人增强

模型 ID:eleven_multilingual_v2

推荐语音:

Adam pNInz6obpgDQGcFmaJgB 低沉、权威

Antoni ErXwobaYiN019PkySvjV 温暖、强烈

Josh TxGEqnHWrfWFTfGW9XjX 电影感、戏剧化

from pydub import AudioSegment
import io

def generate_voiceover(script, voice_id, api_key, out="voice.mp3"):
    segs = []
    for line in script["lines"]:
        if line["text"] == "[PAUSE]":
            segs.append(AudioSegment.silent(duration=line["duration_ms"]))
            continue
        r = requests.post(
            f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
            headers={
                "xi-api-key": api_key,
                "Content-Type": "application/json"
            },
            json={
                "text": line["text"],
                "model_id": "eleven_multilingual_v2",
                "voice_settings": {
                    "stability": 0.75,
                    "similarity_boost": 0.85,
                    "style": 0.30,
                    "use_speaker_boost": True
                }
            }
        )
        segs.append(AudioSegment.from_mp3(io.BytesIO(r.content)))
        if line.get("pause_after_ms", 0) > 0:
            segs.append(AudioSegment.silent(duration=line["pause_after_ms"]))
    final = sum(segs)
    final.export(out, format="mp3")
    return out, len(final)

步骤 3 - 获取真实视频片段(Pexels API)

免费 API 密钥获取地址:https://www.pexels.com/api/

按主题默认搜索关键词:

THEME_QUERIES = {
    "dark city rain": [
        "dark city rain night",
        "rain street cinematic",
        "dark alley bokeh",
        "night city slow motion"
    ],
    "lone warrior": [
        "lone person silhouette night",
        "person walking alone dark",
        "shadow warrior",
        "lone figure darkness"
    ],
    "dark nature": [
        "dark forest fog cinematic",
        "storm clouds dramatic",
        "night mountain silhouette",
        "dark ocean storm"
    ],
    "minimal shadow": [
        "shadow dark minimal",
        "dark room single light",
        "dramatic low key lighting",
        "black shadow person"
    ]
}
import os

def fetch_video_clips(theme, duration, pexels_key, output_dir="clips", script=None):
    os.makedirs(output_dir, exist_ok=True)

    # 使用 Claude 生成的关键词,若无则使用默认主题
    queries = (script or {}).get("pexels_search_queries") or THEME_QUERIES.get(theme, THEME_QUERIES["dark city rain"])
    needed = max(3, duration // 15)
    collected = []

    for query in queries:
        if len(collected) >= needed:
            break
        print(f"  Pexels: 搜索 '{query}'")
        r = requests.get(
            "https://api.pexels.com/videos/search",
            headers={"Authorization": pexels_key},
            params={"query": query, "per_page": 5, "orientation": "portrait", "size": "medium"}
        )
        for video in r.json().get("videos", []):
            if len(collected) >= needed:
                break
            # 选择宽度 ≥720px 的 MP4 文件
            files = sorted(video.get("video_files", []), key=lambda f: f.get("width", 0), reverse=True)
            url = next((f["link"] for f in files if f.get("width",0)>=720 and "mp4" in f.get("file_type","")), None)
            if not url:
                continue
            path = os.path.join(output_dir, f"clip_{len(collected):02d}.mp4")
            with open(path, "wb") as f:
                for chunk in requests.get(url, stream=True, timeout=60).iter_content(8192):
                    f.write(chunk)
            collected.append({
                "path": path,
                "duration": video.get("duration", 10),
                "photographer": video.get("user", {}).get("name", "Pexels")
            })
            print(f"  已下载片段 {len(collected)}/{needed}")

    if not collected:
        raise RuntimeError("未获取到任何视频片段。请检查 PEXELS_API_KEY 是否正确。")
    return collected

Pexels 注意事项:

  • 免费额度:每小时 200 次请求,每月 20,000 次
  • orientation=portrait 返回竖屏适配的视频片段
  • TikTok 视频字幕中需注明:「视频素材来自 Pexels」
  • 推荐关键词:黑暗、夜晚、阴影、雨夜、雾气、电影感、戏剧性、剪影、孤独

步骤 4 - 视频合成(MoviePy)

将所有视频片段裁剪为 1080x1920 竖屏格式。叠加暗色图层。文字与语音时间轴同步。

字体:从 fonts.google.com 下载 Bebas Neue 字体 → 保存至 fonts/BebasNeue-Regular.ttf

from moviepy.editor import (
    VideoFileClip, AudioFileClip, ColorClip, TextClip,
    concatenate_videoclips, CompositeVideoClip, CompositeAudioClip
)

def crop_to_vertical(clip, w=1080, h=1920):
    clip = clip.resize(height=h)
    if clip.w < w:
        clip = clip.resize(width=w)
    return clip.crop(x_center=clip.w/2, width=w).resize((w, h))

def compose_video(clips_info, audio_path, script, out="output.mp4", bg_music_path=None):
    audio = AudioFileClip(audio_path)
    total_dur = audio.duration

    # 从 Pexels 片段中拼接背景
    pool = clips_info * 4
    parts, remaining = [], total_dur
    for info in pool:
        if remaining <= 0: break
        try:
            c = crop_to_vertical(VideoFileClip(info["path"], audio=False))
            dur = min(c.duration - 0.3, remaining)
            if dur <= 0: continue
            parts.append(c.subclip(0, dur))
            remaining -= dur
        except: continue
    if not parts:
        raise RuntimeError("无法加载任何视频片段")
    bg = concatenate_videoclips(parts, method="compose").set_duration(total_dur)

    # 暗色图层 —— 营造氛围的关键
    dark = ColorClip((1080,1920), color=(0,0,0)).set_opacity(0.55).set_duration(total_dur)

    # 文字内容按脚本时间点同步显示
    texts = []
    t = 0.0
    for line in script["lines"]:
        if line["text"] == "[PAUSE]":
            t += line["duration_ms"] / 1000
            continue
        dur = line["duration_ms"] / 1000
        try:
            clip = TextClip(line["text"], fontsize=78, font="fonts/BebasNeue-Regular.ttf",
                            color="white", stroke_color="black", stroke_width=3,
                            size=(900, None), method="caption")
        except:
            clip = TextClip(line["text"], fontsize=78, color="white",
                            stroke_color="black", stroke_width=3, size=(900,None), method="caption")
        texts.append(clip.set_duration(dur).crossfadein(0.25).crossfadeout(0.25).set_start(t).set_position("center"))
        t += dur + line.get("pause_after_ms", 0) / 1000

    if bg_music_path and os.path.exists(bg_music_path):
        mus = AudioFileClip(bg_music_path).volumex(0.08).set_duration(total_dur)
        final_audio = CompositeAudioClip([audio, mus])
    else:
        final_audio = audio

    CompositeVideoClip([bg, dark] + texts).set_audio(final_audio).write_videofile(
        out, fps=30, codec="libx264", audio_codec="aac", bitrate="8000k", threads=4, preset="fast"
    )
    return out

步骤 5 - 色调调校(FFmpeg)

ffmpeg -y -i output.mp4 \
  -vf "curves=blue='0/0.05 1/1.1',eq=contrast=1.15:brightness=-0.04:saturation=0.60,vignette=PI/4,noise=alls=6:allf=t+u" \
  -c:v libx264 -crf 17 -c:a copy final_video.mp4

滤镜说明:

  • 蓝色调阴影增强
  • 对比度提升,亮度降低,饱和度微调
  • 边缘暗角(vignette)
  • 添加胶片颗粒感(film grain)

完整工作流管道

import os
import subprocess

def create_motivation_video(
    topic,
    tone="dark & stoic",
    duration=60,
    visual_theme="dark city rain",
    voice_id="pNInz6obpgDQGcFmaJgB",
    output="motivation_video.mp4",
    bg_music_path=None
):
    ak = os.getenv("ANTHROPIC_API_KEY")
    ek = os.getenv("ELEVENLABS_API_KEY")
    pk = os.getenv("PEXELS_API_KEY")

    print("[1/5] 生成脚本..."); script = generate_script(topic, tone, duration, ak)
    print("[2/5] 生成配音..."); audio, _ = generate_voiceover(script, voice_id, ek)
    print("[3/5] 搜索视频片段..."); clips = fetch_video_clips(visual_theme, duration, pk, script=script)
    print("[4/5] 合成视频..."); raw = compose_video(clips, audio, script, "raw.mp4", bg_music_path)
    print("[5/5] 调色...");
    subprocess.run([
        "ffmpeg", "-y", "-i", raw, "-vf",
        "curves=blue='0/0.05 1/1.1',eq=contrast=1.15:brightness=-0.04:saturation=0.60,vignette=PI/4",
        "-c:v", "libx264", "-crf", "17", "-c:a", "copy", output
    ], check=True)
    print(f"\n完成: {output}")
    print(f"标签: {' '.join(script['hashtags'])}")
    print("字幕: 视频素材来自 Pexels")
    return output, script

设置

pip install requests Pillow moviepy pydub numpy
# macOS: brew install ffmpeg
# Ubuntu: apt install ffmpeg -y
# 字体: fonts.google.com/specimen/Bebas+Neue -> fonts/BebasNeue-Regular.ttf

# .env
ANTHROPIC_API_KEY=sk-ant-...
ELEVENLABS_API_KEY=...
PEXELS_API_KEY=...       # 免费注册获取,访问 pexels.com/api

命令行使用

python scripts/pipeline_full.py --topic "自律" --duration 60 --theme "暗城雨夜"

常见问题排查

无 Pexels 搜索结果 → 扩大关键词范围;检查 API 密钥是否正确

画面为横屏 → crop_to_vertical() 会自动处理

文字与音频不同步 → 确保时间参数单位为秒(原代码中使用 /1000 将毫秒转为秒)

配音单调 → 降低稳定性(stability)至 0.5,提高风格化(style)至 0.5

字体加载失败 → 使用 .ttf 文件的完整绝对路径

FFmpeg 未安装 → 使用 brew install ffmpeg 或 apt install ffmpeg 安装

推荐的 Pexels 关键词(适用于黑暗励志主题)

自律:暗城雨夜、阴影剪影夜景、独自行走的人

孤独:雾中孤影、空荡黑暗房间、深夜独处

奋起:风暴后山巅、黎明前暗天、雾中小径

反派弧线:戏剧性低光阴影、暗烟流动、情绪化人像

奋斗:夜城延时、城市雨夜光斑、暗街灯光

参考资料

references/pexels_search_guide.md - 按情绪分类的完整关键词列表

references/elevenlabs_voices.md - 配音角色目录

scripts/pipeline_full.py - 完整可运行脚本

TP
@thuongvh2-python

已收录 1 个 Skill

相关推荐