文创图影 视频生成完整流程

文创图影 视频生成完整流程

  • [第 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 组件
相关推荐
DogDaoDao2 小时前
深入解析 libaom:AV1 开源编解码库技术分析
google·开源·音视频·视频编解码·hevc·av1·libaom
开开心心就好3 小时前
解决图片无页码添加功能的实用工具
javascript·python·安全·智能手机·pdf·音视频·1024程序员节
EasyCVR12 小时前
国标GB28181视频监控平台EasyCVR行业解决方案深度解读——雪亮工程、智慧城市与智慧交通
人工智能·音视频·智慧城市
“码”力全开15 小时前
打破芯片与协议壁垒:基于 Docker + 边缘计算的 GB28181/RTSP 视频智能管理平台架构设计与源码交付方案
docker·音视频·边缘计算
AI服务老曹1 天前
解密企业级视频中台:基于 GB28181/RTSP 统一接入与边缘计算的 AI 视频管理平台(附 Docker 部署与源码交付方案)
人工智能·音视频·边缘计算
shandianchengzi1 天前
【记录】LosslessCut|Linux下配置开源无损剪辑软件 LosslessCut AppImage 命令行启动和设置图标
linux·运维·服务器·音视频·视频·剪辑
zhaoshuzhaoshu1 天前
无线耳机的音频传输时延技术对比总结
音视频
ai产品老杨1 天前
深度解析:基于 Docker 与异构计算的下一代 AI 视频管理平台架构(附 GB28181/RTSP 统一接入与源码交付方案)
人工智能·docker·音视频
hz567891 天前
2026主流RTC音视频SDK选型全解析:性能对比+避坑指南+国产化适配深度横评
云计算·音视频·实时音视频·信息与通信