文创图影 视频生成完整流程
- [第 1 阶段:前端发起请求](#第 1 阶段:前端发起请求)
- [第 2 阶段:上传图片到服务器](#第 2 阶段:上传图片到服务器)
- [第 3 阶段:创建视频生成任务](#第 3 阶段:创建视频生成任务)
- [第 4 阶段:真实的视频生成流程(核心)](#第 4 阶段:真实的视频生成流程(核心))
-
- [4.1 文字转语音 (TTS)](#4.1 文字转语音 (TTS))
- [4.2 生成字幕](#4.2 生成字幕)
- [4.3 处理图片](#4.3 处理图片)
- 视频合成 (FFmpeg)
- [第 5 阶段:任务状态轮询](#第 5 阶段:任务状态轮询)
- 完整时序图
- 总结
第 1 阶段:前端发起请求
用户操作:
在首页填写文案
上传 1-9 张参考图片
可选:选择配音音色、视频风格、背景音乐
点击「一键生成视频」
javascript
async function submit() {
// 1. 验证输入
if (!text.value.trim()) {
uni.showToast({ title: '请先填写文案', icon: 'none' })
return
}
if (!images.value.length) {
uni.showToast({ title: '请至少添加一张参考图', icon: 'none' })
return
}
// 2. 防止重复提交
if (submitting.value) return
submitting.value = true
try {
// 3. 上传图片 + 创建任务
const created = await createJobWithUploads({
text: text.value.trim(),
localPaths: [...images.value], // 本地图片路径
voice: voice.value,
style: style.value,
bgm: bgm.value
})
// 4. 获取任务ID
const jobId = created.jobId || created.id
if (!jobId) throw new Error('未返回任务编号')
// 5. 轮询等待完成
const done = await pollJob(jobId)
// 6. 跳转预览
const url = encodeURIComponent(done.videoUrl)
uni.navigateTo({
url: `/pages/preview/preview?videoUrl=${url}&workId=${done.workId}`
})
} catch (e) {
// 7. 错误处理
uni.showModal({
title: '提示',
content: e.message || '生成失败',
confirmText: '重试',
success(r) {
if (r.confirm) submit()
}
})
} finally {
submitting.value = false
}
}
第 2 阶段:上传图片到服务器
前端调用
javascript
async function createJobWithUploads(param) {
const { text, localPaths, voice, style, bgm } = param
const imageUrls = []
// 逐个上传图片
for (const p of localPaths) {
imageUrls.push(await uploadImage(p))
}
// 创建视频任务
return createVideoJob({ text, imageUrls, voice, style, bgm })
}
后端调用
javascript
app.post('/api/v1/media/upload', upload.single('file'), (req, res) => {
// 保存文件到服务器
const url = `http://127.0.0.1:3000/uploads/${req.file.filename}`
res.json({ code: 0, data: { url, key: req.file.filename } })
})
第 3 阶段:创建视频生成任务
后端调用
javascript
app.post('/api/v1/video/jobs', async (req, res) => {
const { text, imageUrls, voice, style, bgm } = req.body
// 验证参数
if (!text) return res.json({ code: 40001, message: '请提供文案' })
if (!imageUrls || imageUrls.length === 0) {
return res.json({ code: 40001, message: '请提供参考图片' })
}
// 创建任务
const jobId = 'job_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
jobs.set(jobId, {
jobId,
status: 'pending',
progress: 0,
text,
imageUrls,
voice,
style,
bgm,
createdAt: new Date().toISOString()
})
// 启动异步任务
simulateJobProgress(jobId, text, voice)
// 立即返回给前端
res.json({ code: 0, data: { jobId, status: 'pending' } })
})
第 4 阶段:真实的视频生成流程(核心)
当有真实 TTS 和 FFmpeg 时,流程如下:
4.1 文字转语音 (TTS)
javascript
async function generateVoice(text, voice) {
// 1. 调用百度
const audioBuffer = await ttsService.synthesize(text, voice)
// 2. 保存为 MP3 文件
const audioPath = 'audio/job-' + jobId + '.mp3'
fs.writeFileSync(audioPath, audioBuffer)
// 3. 获取音频时长(需要 FFmpeg)
const duration = await getAudioDuration(audioPath)
return { audioPath, duration }
}
4.2 生成字幕
javascript
async function generateSubtitles(text, audioDuration) {
// 1. 智能拆分文案成句子
const sentences = splitIntoSentences(text)
// 例如:['今天天气真好', '我们去公园玩', '非常开心']
// 2. 计算每句字幕的时间轴
const avgTimePerChar = audioDuration / text.length
const subtitles = sentences.map((sentence, index) => {
const startTime = calculateStartTime(sentences, index, avgTimePerChar)
const endTime = startTime + sentence.length * avgTimePerChar
return {
text: sentence,
start: startTime,
end: endTime
}
})
// 3. 生成 SRT 字幕文件
const srtPath = await generateSRTFile(subtitles)
return srtPath
}
字幕格式示例(SRT):
bash
1
00:00:00,000 --> 00:00:02,500
今天天气真好
2
00:00:02,500 --> 00:00:05,000
我们去公园玩
3
00:00:05,000 --> 00:00:07,500
非常开心
4.3 处理图片
javascript
async function processImages(imageUrls) {
const processedImages = []
for (const url of imageUrls) {
// 1. 下载图片
const imagePath = await downloadImage(url)
// 2. 裁剪为 9:16 竖屏比例(如果需要)
const croppedPath = await cropToVertical(imagePath)
// 3. 应用视频风格滤镜
const styledPath = await applyStyle(croppedPath, style)
processedImages.push(styledPath)
}
return processedImages
}
视频合成 (FFmpeg)
javascript
async function合成Video(images, audioPath, subtitlesPath, bgm) {
// 1. 准备图片列表文件
const imageList = prepareImageList(images, audioDuration)
// 2. 如果有背景音乐,混合音频
let finalAudio = audioPath
if (bgm !== 'off') {
finalAudio = await mixAudioWithBGM(audioPath, bgm)
}
// 3. 执行 FFmpeg 合成
const command = `
ffmpeg
-f concat
-safe 0
-i "${imageList}"
-i "${finalAudio}"
-vf "subtitles=${subtitlesPath}"
-c:v libx264
-c:a aac
-shortest
output.mp4
`
await exec(command)
// 4. 上传到云存储(如果有)
const videoUrl = await uploadToCloud('output.mp4')
return videoUrl
}
** FFmpeg 参数解释:**
bash
-f concat # 使用文件列表模式
-i "${imageList}" # 输入图片列表
-i "${audio}" # 输入音频
-vf subtitles=... # 添加字幕滤镜
-c:v libx264 # H.264 视频编码
-c:a aac # AAC 音频编码
-shortest # 以最短的流为基准结束
第 5 阶段:任务状态轮询
** 前端轮询代码:**
javascript
async function pollJob(jobId) {
// 最多轮询 90 次(约 3 分钟)
for (let i = 0; i < 90; i++) {
// 1. 查询任务状态
const job = await getVideoJob(jobId)
const status = job.status || job.state
// 2. 任务完成
if (status === 'completed' || status === 'done') {
const url = job.videoUrl || job.video_url
if (url) return { ...job, videoUrl: url }
}
// 3. 任务失败
if (status === 'failed' || status === 'error') {
throw new Error(job.errorMessage || job.message || '生成失败')
}
// 4. 继续等待(2秒)
await sleep(2000)
}
// 5. 超时
throw new Error('生成超时,请稍后到「作品」中查看')
}
** 后端查询接口:**
javascript
app.get('/api/v1/video/jobs/:jobId', (req, res) => {
const job = jobs.get(req.params.jobId)
if (!job) {
return res.json({ code: 40001, message: '任务不存在' })
}
res.json({ code: 0, data: job })
})
完整时序图

总结
| 步骤 | 功能 | 技术 |
|---|---|---|
| 1 | 收集用户输入 | 前端表单 |
| 2 | 上传图片 | Multer + Express |
| 3 | 创建任务 | Express API |
| 4 | TTS 配音 | 百度/阿里云 TTS API |
| 5 | 生成字幕 | 智能分句 + SRT 格式 |
| 6 | 处理图片 | 图片裁剪 + 滤镜 |
| 7 | 合成视频 | FFmpeg |
| 8 | 云存储 | 腾讯云 COS / 阿里云 OSS |
| 9 | 轮询状态 | 前端定时请求 |
| 10 | 预览播放 | video 组件 |