AI在线 AI在线

EasyDub 配音视频生成平台:SpringBoot + Thymeleaf + Spring AI 实战开发

本项目旨在构建一个 Web 端一键生成 AI 配音视频的系统,提供从“上传视频 → 提取语音 → 翻译 → 合成音频 → 合成字幕与数字人 → 下载结果”的完整流程。 后端基于 SpringBoot,前端使用 Thymeleaf Bootstrap,结合 Redis 实现异步任务状态跟踪与进度轮询,支持多用户并发任务处理。 功能亮点🔁 全流程:上传原视频 → 翻译 → 配音合成 → 视频输出💬 Spring AI:调用 AI 模型实现翻译、合成🎞️ Web UI:Thymeleaf Bootstrap 实现进度轮询🔧 Redis Spring Task 实现异步任务与进度管理💡 实际 DEMO:上传 original_video.mp4 → 下载 linly_dubbing.mp4项目结构复制SpringBoot 构建 REST 接口视频上传与任务创建接口复制Spring Task Redis 实现任务调度配置异步线程池复制后台任务处理服务复制Redis 进度服务封装复制Web 前端 Thymeleaf Bootstrapindex.html复制本地 DEMO 流程启动 SpringBoot 应用浏览器打开 http://localhost:8080上传 original_video.mp4等待进度提示,后台完成:视频 → 音频提取 → Whisper识别 → 翻译 → 合成配音 → 视频合成下载生成的 linly_dubbing.mp4结语通过整合 Spring Boot、Thymeleaf、Redis、FFmpeg 与 AI 模型接口(Whisper、XTTSv2 等),我们构建了一个功能强大且易用的 EasyDub Web 配音系统,支持异步处理、状态轮询、数字人合成与完整视频输出。

本项目旨在构建一个 Web 端一键生成 AI 配音视频的系统,提供从“上传视频 → 提取语音 → 翻译 → 合成音频 → 合成字幕与数字人 → 下载结果”的完整流程。后端基于 SpringBoot,前端使用 Thymeleaf + Bootstrap,结合 Redis 实现异步任务状态跟踪与进度轮询,支持多用户并发任务处理。

功能亮点

  • 🔁 全流程:上传原视频 → 翻译 → 配音合成 → 视频输出
  • 💬 Spring AI:调用 AI 模型实现翻译、合成
  • 🎞️ Web UI:Thymeleaf + Bootstrap 实现进度轮询
  • 🔧 Redis + Spring Task 实现异步任务与进度管理
  • 💡 实际 DEMO:上传 original_video.mp4 → 下载 linly_dubbing.mp4

项目结构

复制
com.icoderoad.easydub
├── controller
│   └── DubbingController.java
├── service
│   ├── DubbingService.java
│   └── ProgressService.java
├── config
│   └── TaskConfig.java
├── model
│   └── TaskStatus.java
├── templates
│   └── index.html
├── static
│   └── bootstrap + js
├── application.yml
└── EasyDubApplication.java

SpringBoot 构建 REST 接口

视频上传与任务创建接口

复制
package com.icoderoad.easydub.controller;


import com.icoderoad.easydub.service.DubbingService;
import com.icoderoad.easydub.service.ProgressService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;


@RestController
@RequestMapping("/api")
public class DubbingController {


    @Autowired
    private DubbingService dubbingService;


    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file) {
        return dubbingService.handleUpload(file);
    }


    @GetMapping("/progress/{taskId}")
    public String getProgress(@PathVariable String taskId) {
        return dubbingService.getProgress(taskId);
    }


    @GetMapping("/download/{taskId}")
    public String getDownloadUrl(@PathVariable String taskId) {
        return dubbingService.getDownloadUrl(taskId);
    }
}

Spring Task + Redis 实现任务调度

配置异步线程池

复制
package com.icoderoad.easydub.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;


import java.util.concurrent.Executor;


@Configuration
public class TaskConfig {
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("DubbingTask-");
        executor.initialize();
        return executor;
    }
}

后台任务处理服务

复制
package com.icoderoad.easydub.service;


import com.icoderoad.easydub.model.TaskStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import redis.clients.jedis.Jedis;


import java.io.File;
import java.io.FileOutputStream;
import java.util.UUID;


@Service
public class DubbingService {


    @Autowired
    private ProgressService progressService;


    private final String baseDir = "output/";


    public String handleUpload(MultipartFile file) {
        String taskId = UUID.randomUUID().toString();
        File saveFile = new File(baseDir + taskId + "_original.mp4");
        try (FileOutputStream fos = new FileOutputStream(saveFile)) {
            fos.write(file.getBytes());
        } catch (Exception e) {
            return "上传失败:" + e.getMessage();
        }


        progressService.init(taskId);
        processAsync(taskId, saveFile.getAbsolutePath());
        return taskId;
    }


    @Async("taskExecutor")
    public void processAsync(String taskId, String inputPath) {
        try {
            progressService.update(taskId, "提取音频中...");
            String audioPath = extractAudio(inputPath);


            progressService.update(taskId, "识别翻译中...");
            String translatedText = callSpringAIWhisperAndTranslate(audioPath);


            progressService.update(taskId, "合成语音中...");
            String newVoice = synthesizeAudio(translatedText);


            progressService.update(taskId, "合成视频中...");
            String finalVideo = composeVideo(inputPath, newVoice, taskId);


            progressService.complete(taskId, finalVideo);
        } catch (Exception e) {
            progressService.fail(taskId, e.getMessage());
        }
    }


    private String extractAudio(String inputPath) throws Exception {
        String outPath = inputPath.replace(".mp4", ".wav");
        String cmd = String.format("ffmpeg -i %s -vn -acodec pcm_s16le -ar 16000 -ac 1 %s", inputPath, outPath);
        Runtime.getRuntime().exec(cmd).waitFor();
        return outPath;
    }


    private String callSpringAIWhisperAndTranslate(String audioPath) {
        // 伪代码:可以集成 Spring AI Whisper + LLM 翻译
        return "你好,欢迎来到 EasyDub。";
    }


    private String synthesizeAudio(String text) {
        // 伪代码:调用 XTTS 合成中文音频
        return "output/temp_tts.wav";
    }


    private String composeVideo(String originalVideo, String newAudio, String taskId) throws Exception {
        String output = baseDir + taskId + "_linly_dubbing.mp4";
        String cmd = String.format("ffmpeg -i %s -i %s -map 0:v -map 1:a -c:v copy -c:a aac %s",
                originalVideo, newAudio, output);
        Runtime.getRuntime().exec(cmd).waitFor();
        return output;
    }


    public String getProgress(String taskId) {
        return progressService.query(taskId);
    }


    public String getDownloadUrl(String taskId) {
        return progressService.getResultUrl(taskId);
    }
}

Redis 进度服务封装

复制
package com.icoderoad.easydub.service;


import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;


@Service
public class ProgressService {


    private final Jedis redis = new Jedis("localhost", 6379);


    public void init(String taskId) {
        redis.set(taskId, "开始处理...");
    }


    public void update(String taskId, String message) {
        redis.set(taskId, message);
    }


    public void complete(String taskId, String path) {
        redis.set(taskId + ":done", path);
        redis.set(taskId, "处理完成!");
    }


    public void fail(String taskId, String errorMsg) {
        redis.set(taskId, "失败:" + errorMsg);
    }


    public String query(String taskId) {
        return redis.get(taskId);
    }


    public String getResultUrl(String taskId) {
        return redis.get(taskId + ":done");
    }
}

Web 前端 Thymeleaf + Bootstrap

index.html

复制
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>EasyDub 配音生成</title>
    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.3.1/css/bootstrap.min.css"/>
</head>
<body class="container mt-5">
<h2>🎬 EasyDub 配音生成平台</h2>


<form id="uploadForm" enctype="multipart/form-data">
    <input type="file" class="form-control" name="file" required/>
    <button type="submit" class="btn btn-primary mt-2">上传并开始处理</button>
</form>


<div class="mt-3" id="status" style="display:none;">
    <h5>进度:<span id="progressMsg"></span></h5>
</div>


<div class="mt-3" id="download" style="display:none;">
    <a class="btn btn-success" id="downloadLink" href="#">下载结果视频</a>
</div>


<script>
    document.getElementById('uploadForm').addEventListener('submit', function (e) {
        e.preventDefault();
        let formData = new FormData(this);
        fetch('/api/upload', {
            method: 'POST',
            body: formData
        }).then(res => res.text()).then(taskId => {
            document.getElementById("status").style.display = "block";
            pollProgress(taskId);
        });
    });


    function pollProgress(taskId) {
        let interval = setInterval(() => {
            fetch('/api/progress/' + taskId).then(res => res.text()).then(msg => {
                document.getElementById("progressMsg").innerText = msg;
                if (msg.includes("完成")) {
                    clearInterval(interval);
                    document.getElementById("download").style.display = "block";
                    fetch('/api/download/' + taskId).then(r => r.text()).then(url => {
                        document.getElementById("downloadLink").href = '/' + url;
                    });
                } else if (msg.includes("失败")) {
                    clearInterval(interval);
                    alert("处理失败:" + msg);
                }
            });
        }, 2000);
    }
</script>
</body>
</html>

本地 DEMO 流程

  1. 启动 SpringBoot 应用
  2. 浏览器打开 http://localhost:8080
  3. 上传 original_video.mp4
  4. 等待进度提示,后台完成:

视频 → 音频提取 → Whisper识别 → 翻译 → 合成配音 → 视频合成

  1. 下载生成的 linly_dubbing.mp4

结语

通过整合 Spring Boot、Thymeleaf、Redis、FFmpeg 与 AI 模型接口(Whisper、XTTSv2 等),我们构建了一个功能强大且易用的 EasyDub Web 配音系统,支持异步处理、状态轮询、数字人合成与完整视频输出。

相关资讯

聊聊SpringAI流式输出的底层实现?

在 Spring AI 中,流式输出(Streaming Output)是一种逐步返回 AI 模型生成结果的技术,允许服务器将响应内容分批次实时传输给客户端,而不是等待全部内容生成完毕后再一次性返回。 这种机制能显著提升用户体验,尤其适用于大模型响应较慢的场景(如生成长文本或复杂推理结果)。 技术实现在 Spring AI 中流式输出的实现有以下两种方式:通过 ChatModel 实现流式输出。
4/24/2025 12:00:00 AM
磊哥

阿里出手了:Spring AI Alibaba正式版发布!

Spring AI Alibaba 是基于 Spring AI 构建的,专门针对阿里云生态(如通义千问、OSS 等)进行深度适配和功能增强。 Spring AI Alibaba 提供高层次的 AI API 抽象与云原生基础设施集成方案,帮助开发者快速构建 AI 应用。 主要功能Spring AI Alibaba 提供的主要功能如下:开发复杂 AI 应用的高阶抽象 Fluent API — ChatClient。
6/13/2025 6:20:02 PM
磊哥

Deepseek4j再更新:Java应用一行代码集成DeepSeek

deepseek4j 是什么deepseek4j() 是一个专为 Java 开发者打造的 DeepSeek 模型集成框架。 通过优雅的 API 设计,只需一行代码,即可实现接入 DeepSeek,并获得以下核心能力:完整思维链保留:完美保留 DeepSeek 模型的推理过程,让 AI 的思考过程可追溯流式输出体验:基于 Reactor 实现的流式响应,带来类 ChatGPT 的打字机效果复制使用 deepseek4j,您可以专注于业务逻辑开发,而无需关心底层细节。 一、v1.3 更新内容1.1 联网搜索支持1739118403新版本最重要的更新是引入了联网搜索能力,这一功能带来三个关键优势:突破时间边界:模型不再受限于预训练数据的时间范围,可以获取和处理最新信息实时信息获取:通过高质量信息源获取实时资讯,提供更精准的问答服务差异化竞争:在大模型同质化严重的当下,联网搜索成为关键的差异化竞争点复制1.2 智能系统提示词1739118117系统提示词(System Prompt)是基于模型开发的应用程序内置的指令,让决定了模型在特定上下文中的表现方式、回答风格和功能范围。
2/10/2025 10:49:51 AM
冷冷
  • 1