成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

EasyDub 配音視頻生成平臺:SpringBoot + Thymeleaf + Spring AI 實戰開發

人工智能
通過整合 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" />
</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 配音系統,支持異步處理、狀態輪詢、數字人合成與完整視頻輸出。

責任編輯:武曉燕 來源: 路條編程
相關推薦

2025-05-14 07:35:27

UVR5合成管道集成

2025-04-16 09:20:00

虛擬模型數字

2024-02-28 08:22:07

2018-04-27 11:21:14

2024-10-28 07:30:00

2024-06-17 08:03:51

2024-10-15 13:30:03

2023-03-03 15:40:43

抖音視頻編碼器

2024-11-08 17:34:38

2022-08-29 10:39:32

FFmpeg多媒體框架開源

2025-06-18 14:40:22

2023-11-20 22:02:54

開源模型

2024-10-05 08:10:01

2018-04-13 17:00:21

騰訊云音視頻

2022-09-21 11:48:40

端到端音視頻測試用戶體驗

2018-04-26 14:57:24

騰訊云音視頻

2022-06-20 05:59:35

5G技術音視頻技術安卓系統

2024-11-18 10:50:39

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 午夜在线观看视频 | 久久精品网 | 狠狠躁天天躁夜夜躁婷婷老牛影视 | 精品人伦一区二区三区蜜桃网站 | 精品国产99 | 蜜桃视频在线观看免费视频网站www | 久久久久亚洲精品国产 | 国产精品久久国产精品 | 久久精品久久久久久 | 久久久国产一区二区三区四区小说 | 日韩毛片在线视频 | 在线看片网站 | 久草精品视频 | 日韩成人精品一区 | 久久久高清| 亚洲成人精品免费 | 亚洲视频免费在线播放 | 国产婷婷精品av在线 | 天天久久| 亚洲高清av在线 | 国产精品久久久久久久久久免费看 | 精品国产乱码久久久久久丨区2区 | 久久久久网站 | 蜜桃av鲁一鲁一鲁一鲁 | 欧美亚洲高清 | 亚洲精品一区二区三区中文字幕 | 久久精品久久久久久 | 精品欧美一区二区三区久久久 | 久久黄色网 | 久久亚洲综合 | 成人av电影在线观看 | 综合久久久久 | 国产综合精品一区二区三区 | 97国产一区二区 | 久久精品国产99国产精品亚洲 | 国产色播av在线 | 成人精品鲁一区一区二区 | 久久亚 | 欧美福利视频一区 | av黄色在线 | 天天操天天干天天曰 |