关于 elevenlabs
通过 OpenMontage 使用 ElevenLabs 生成语音、音效、音乐并克隆语音的技能,也涵盖不同模型的控制方式,以及将旁白与 Remotion 场景对齐的工作流。
主要功能
- 01通过 OpenMontage 路由提供方
- 02选择文本转语音模型
- 03语音克隆
- 04生成音效和音乐
- 05带时间戳的语音对齐
- 06Remotion 场景旁白工作流
适用场景
- 01为视频场景生成旁白
- 02让字幕和提示与口语词语对齐
- 03制作音效或背景音乐
- 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(预计需要反复重录)。
按风格设置语音参数
| 风格 | stability | similarity | style | speed |
|---|---|---|---|---|
| 自然/专业 | 0.75-0.85 | 0.9 | 0.0-0.1 | 1.0 |
| 对话式 | 0.5-0.6 | 0.85 | 0.3-0.4 | 0.9-1.0 |
| 活力/YouTuber | 0.3-0.5 | 0.75 | 0.5-0.7 | 1.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-usnginx→engine-x- 使用连字符、大写字母和撇号来引导发音
SSML phoneme 标签(仅限 flash/turbo):
<phoneme alphabet="ipa" ph="ˈdʒeɪnəs">Janus</phoneme>
迭代工作流
- 生成 → 聆听 → 找出发音/语速问题
- 调整:语音拼写、break 标签、语音参数
- 重新生成。如果停顿不够精确,请用 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。