mcpField
E
elevenlabs

elevenlabs

github.com/calesthio创意与媒体73.0k1.1k 次安装1 天前更新
代码仓库

关于 elevenlabs

通过 OpenMontage 使用 ElevenLabs 生成语音、音效、音乐并克隆语音的技能,也涵盖不同模型的控制方式,以及将旁白与 Remotion 场景对齐的工作流。

主要功能

  1. 01通过 OpenMontage 路由提供方
  2. 02选择文本转语音模型
  3. 03语音克隆
  4. 04生成音效和音乐
  5. 05带时间戳的语音对齐
  6. 06Remotion 场景旁白工作流

适用场景

  1. 01为视频场景生成旁白
  2. 02让字幕和提示与口语词语对齐
  3. 03制作音效或背景音乐
  4. 04复用克隆语音进行旁白

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/calesthio/openmontage --skill elevenlabs

安装后在 Claude Code 中输入/elevenlabs确认已加载。

附带文件reference.md

详细介绍

ElevenLabs 音频生成

当前 v4 传输方式

对于 v4、v4 Turbo、Instant Soundtracks 和实时模型,请阅读 10 月份的集成指南。 v4 使用 Text to Dialogue;Turbo 使用对话 WebSocket。 不要将这些模型用于旧版文本转语音端点。

OpenMontage 提供方路由

选择身份验证方式之前,请检查 OpenMontage 注册表。

  • 如果 fal_elevenlabs_tts 可用,优先使用它。它通过集中管理的 fal.ai 连接提供 Eleven v3、Multilingual v2 和 Turbo v2.5;无需单独的 ElevenLabs 凭据。
  • 仅当注册表报告直接提供方已可用时,才使用 elevenlabs_tts。
  • 在共享安装中,绝不要告诉用户创建 .env、导出密钥或粘贴凭据。若无法使用直接提供方,请将其报告为管理员设置请求。

以下直接 API 示例要求集中配置 ELEVENLABS_API_KEY;如果 fal.ai 提供方可用,这并非默认路径。

文本转语音

from elevenlabs.client import ElevenLabs
from elevenlabs import save, VoiceSettings
import os

client = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))

audio = client.text_to_speech.convert(
    text="Welcome to my video!",
    voice_id="JBFqnCBsd6RMkjVDRZzb",
    model_id="eleven_multilingual_v2",
    voice_settings=VoiceSettings(
        stability=0.5,
        similarity_boost=0.75,
        style=0.5,
        speed=1.0
    )
)
save(audio, "voiceover.mp3")
模型
模型质量SSML 支持备注
eleven_multilingual_v2一致性最高无稳定,可用于生产环境,支持 29 种语言
eleven_flash_v2_5良好<break>、<phoneme>速度快,支持停顿/发音标签
eleven_turbo_v2_5良好<break>、<phoneme>延迟最低
eleven_v3表现力最强无Alpha 版——不可靠,需要提示词工程

**选择建议:**追求可靠性时选 multilingual_v2;需要控制 SSML 时选 flash/turbo;追求最大表现力时选 v3(预计需要反复重录)。

按风格设置语音参数
风格stabilitysimilaritystylespeed
自然/专业0.75-0.850.90.0-0.11.0
对话式0.5-0.60.850.3-0.40.9-1.0
活力/YouTuber0.3-0.50.750.5-0.71.0-1.1
章节间的停顿

**使用 flash/turbo 模型时:**在文本中内联使用 SSML break 标签:

...end of section. <break time="1.5s" /> Start of next...

每个 break 最长 3 秒。过多的 break 可能造成语速异常。

**使用 multilingual_v2 / v3 时:**不支持 SSML。可选方案:

  • 分段(空行)——会产生约 0.3-0.5 秒的自然停顿
  • 使用 ffmpeg 后期处理:分割音频并插入静音

警告:...(省略号)不是可靠的停顿方式——它可能会被读成一个词或声音。不要用省略号来实现停顿。

发音控制

**语音拼写(适用于任何模型):**按期望的发音拼写单词:

  • Janus → Jan-us
  • nginx → engine-x
  • 使用连字符、大写字母和撇号来引导发音

SSML phoneme 标签(仅限 flash/turbo):

<phoneme alphabet="ipa" ph="ˈdʒeɪnəs">Janus</phoneme>
迭代工作流
  1. 生成 → 聆听 → 找出发音/语速问题
  2. 调整:语音拼写、break 标签、语音参数
  3. 重新生成。如果停顿不够精确,请用 ffmpeg 在后期添加静音,而不是反复尝试控制 TTS 引擎。

语音克隆

即时语音克隆
with open("sample.mp3", "rb") as f:
    voice = client.voices.ivc.create(
        name="My Voice",
        files=[f],
        remove_background_noise=True
    )
print(f"Voice ID: {voice.voice_id}")
  • 使用 client.voices.ivc.create()(而非 client.voices.clone())
  • 以二进制模式("rb")传入文件句柄,而不是路径
  • 先转换 m4a:ffmpeg -i input.m4a -codec:a libmp3lame -qscale:a 2 output.mp3
  • 使用多个样本(2-3 段音频)可提高准确度
  • 保存语音 ID 以便复用

**专业语音克隆:**需要 Creator 套餐或更高版本,以及 30 分钟以上的音频。请参阅 reference.md。

音效

每次生成最长 22 秒。

result = client.text_to_sound_effects.convert(
    text="Thunder rumbling followed by heavy rain",
    duration_seconds=10,
    prompt_influence=0.3
)
with open("thunder.mp3", "wb") as f:
    for chunk in result:
        f.write(chunk)

**提示词建议:**描述要具体——“木地板上的沉重脚步声,缓慢而坚定,并伴有吱嘎声”

音乐生成

时长为 10 秒至 5 分钟。使用 client.music.compose()(而非 .generate())。

result = client.music.compose(
    prompt="Upbeat indie rock, catchy guitar riff, energetic drums, travel vlog",
    music_length_ms=60000,
    force_instrumental=True
)
with open("music.mp3", "wb") as f:
    for chunk in result:
        f.write(chunk)

**提示词结构:**流派、情绪、乐器、节奏、用途。背景音乐可添加“no vocals”,或使用 force_instrumental=True。

Remotion 集成

完整工作流:从脚本到同步场景
VOICEOVER-SCRIPT.md → voiceover.py → public/audio/ → Remotion composition
        ↓                  ↓               ↓                 ↓
  Scene narration    Generate MP3    Audio files     <Audio> component
  with durations     per scene       with timing     synced to scenes
步骤 1:生成每个场景的音频

使用工具包的配音工具为每个场景生成音频:

# Generate voiceover files for each scene
python tools/voiceover.py --scene-dir public/audio/scenes --json

# Output:
# public/audio/scenes/
#   ├── scene-01-title.mp3
#   ├── scene-02-problem.mp3
#   ├── scene-03-solution.mp3
#   └── manifest.json  (durations for each file)

manifest.json 包含时间信息:

{
  "scenes": [
    { "file": "scene-01-title.mp3", "duration": 4.2 },
    { "file": "scene-02-problem.mp3", "duration": 12.8 },
    { "file": "scene-03-solution.mp3", "duration": 15.3 }
  ],
  "totalDuration": 32.3
}
步骤 2:在 Remotion 合成中使用音频
// src/Composition.tsx
import { Audio, staticFile, Series, useVideoConfig } from 'remotion';

// Import scene components
import { TitleSlide } from './scenes/TitleSlide';
import { ProblemSlide } from './scenes/ProblemSlide';
import { SolutionSlide } from './scenes/SolutionSlide';

// Scene durations (from manifest.json, converted to frames at 30fps)
const SCENE_DURATIONS = {
  title: Math.ceil(4.2 * 30),      // 126 frames
  problem: Math.ceil(12.8 * 30),   // 384 frames
  solution: Math.ceil(15.3 * 30),  // 459 frames
};

export const MainComposition: React.FC = () => {
  return (
    <>
      {/* Scene sequence */}
      <Series>
        <Series.Sequence durationInFrames={SCENE_DURATIONS.title}>
          <TitleSlide />
        </Series.Sequence>
        <Series.Sequence durationInFrames={SCENE_DURATIONS.problem}>
          <ProblemSlide />
        </Series.Sequence>
        <Series.Sequence durationInFrames={SCENE_DURATIONS.solution}>
          <SolutionSlide />
        </Series.Sequence>
      </Series>

      {/* Audio track - plays continuously across all scenes */}
      <Audio src={staticFile('audio/voiceover.mp3')} volume={1} />

      {/* Optional: Background music at lower volume */}
      <Audio src={staticFile('audio/music.mp3')} volume={0.15} />
    </>
  );
};
步骤 3:为每个场景单独添加音频(替代方案)

如需更多控制,可为每个场景单独添加音频:

// src/scenes/ProblemSlide.tsx
import { Audio, staticFile, useCurrentFrame } from 'remotion';

export const ProblemSlide: React.FC = () => {
  const frame = useCurrentFrame();

  return (
    <div style={{ /* slide styles */ }}>
      <h1>The Problem</h1>
      {/* Scene content */}

      {/* Audio starts when this scene starts (frame 0 of this sequence) */}
      <Audio src={staticFile('audio/scenes/scene-02-problem.mp3')} />
    </div>
  );
};
将画面与配音同步

根据音频计算场景时长,而不是反过来:

// src/config/timing.ts
import manifest from '../../public/audio/scenes/manifest.json';

const FPS = 30;

// Convert audio durations to frame counts
export const sceneDurations = manifest.scenes.reduce((acc, scene) => {
  const name = scene.file.replace(/^scene-\d+-/, '').replace('.mp3', '');
  acc[name] = Math.ceil(scene.duration * FPS);
  return acc;
}, {} as Record<string, number>);

// Usage in composition:
// <Series.Sequence durationInFrames={sceneDurations.title}>
单词级时间戳(直接使用 elevenlabs_tts)

当字幕、与单词同步的标题或动画提示必须跟随实际语音时,请向直接使用的 elevenlabs_tts 工具传入 with_timestamps: true,而不是事后转录音频:

result = ElevenLabsTTS().execute({
    "text": paragraph,
    "voice_id": voice_id,
    "previous_text": prev_paragraph,   # continuity across separately synthesized paragraphs
    "next_text": next_paragraph,
    "with_timestamps": True,
    "output_path": "projects/<id>/assets/audio/tts/para_03.mp3",
})
result.data["word_timestamps"]   # [{"word": "Доверие.", "start": 0.12, "end": 0.71}, ...]
result.data["alignment_path"]    # para_03.mp3.alignment.json (raw character alignment)
  • 时间以音频文件起始处为基准,单位为秒。将多个段落放在同一时间轴上时,请加上每个段落的放置偏移量。
  • 单词以空白字符分隔,并保留标点,因此与脚本的 text.split() 对齐。
  • 当服务对文本进行了规范化处理(数字、符号)时,alignment_matches_text 为 false;此时,时间戳会遵循对齐结果中的实际发音形式。
  • 按段落合成可获得精确的段落边界,用于场景切换;单词时间戳则可在每个段落内定位字幕和提示。
音频时间安排模式
import { Audio, Sequence, interpolate, useCurrentFrame } from 'remotion';

// Fade in audio
export const FadeInAudio: React.FC<{ src: string; fadeFrames?: number }> = ({
  src,
  fadeFrames = 30
}) => {
  const frame = useCurrentFrame();
  const volume = interpolate(frame, [0, fadeFrames], [0, 1], {
    extrapolateRight: 'clamp',
  });
  return <Audio src={src} volume={volume} />;
};

// Delayed audio start
export const DelayedAudio: React.FC<{ src: string; delayFrames: number }> = ({
  src,
  delayFrames
}) => (
  <Sequence from={delayFrames}>
    <Audio src={src} />
  </Sequence>
);

// Usage:
// <FadeInAudio src={staticFile('audio/music.mp3')} fadeFrames={60} />
// <DelayedAudio src={staticFile('audio/sfx/whoosh.mp3')} delayFrames={45} />
同步配音与演示视频

当场景同时包含配音和演示视频时:

import { Audio, OffthreadVideo, staticFile, useVideoConfig } from 'remotion';

export const DemoScene: React.FC = () => {
  const { durationInFrames, fps } = useVideoConfig();

  // Calculate playback rate to fit demo into voiceover duration
  const demoDuration = 45; // seconds (original demo length)
  const sceneDuration = durationInFrames / fps; // seconds (from voiceover)
  const playbackRate = demoDuration / sceneDuration;

  return (
    <>
      <OffthreadVideo
        src={staticFile('demos/feature-demo.mp4')}
        playbackRate={playbackRate}
      />
      <Audio src={staticFile('audio/scenes/scene-04-demo.mp3')} />
    </>
  );
};
错误处理
import { Audio, staticFile, delayRender, continueRender } from 'remotion';
import { useEffect, useState } from 'react';

export const SafeAudio: React.FC<{ src: string }> = ({ src }) => {
  const [handle] = useState(() => delayRender());
  const [audioReady, setAudioReady] = useState(false);

  useEffect(() => {
    const audio = new window.Audio(src);
    audio.oncanplaythrough = () => {
      setAudioReady(true);
      continueRender(handle);
    };
    audio.onerror = () => {
      console.error(`Failed to load audio: ${src}`);
      continueRender(handle); // Continue without audio rather than hang
    };
  }, [src, handle]);

  if (!audioReady) return null;
  return <Audio src={src} />;
};
工具包命令:/generate-voiceover

/generate-voiceover 命令负责处理完整工作流:

/generate-voiceover

1. Reads VOICEOVER-SCRIPT.md
2. Extracts narration for each scene
3. Generates audio via ElevenLabs API
4. Saves to public/audio/scenes/
5. Creates manifest.json with durations
6. Updates project.json with timing info

常用语音

  • George:JBFqnCBsd6RMkjVDRZzb(温暖的旁白)
  • Rachel:21m00Tcm4TlvDq8ikWAM(清晰的女声)
  • Adam:pNInz6obpgDQGcFmaJgB(专业的男声)

列出全部语音:client.voices.get_all()

完整 API 文档请参阅 reference.md。

评论

支持 Markdown