提交代码

This commit is contained in:
liu
2026-01-29 18:26:02 +08:00
parent 981b4ecf42
commit 7531b6c466
47 changed files with 7257 additions and 16 deletions

View File

@@ -0,0 +1,219 @@
package com.music.service;
import com.music.dto.ProgressMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@Service
public class ConvertService {
private static final Logger log = LoggerFactory.getLogger(ConvertService.class);
/** 无损格式,需转换为 FLAC */
private static final Set<String> LOSSLESS_EXTENSIONS = new HashSet<>(Arrays.asList(
"wav", "ape", "aiff", "aif", "wv", "tta"
));
/** 有损格式及 FLAC跳过不处理 */
private static final Set<String> SKIP_EXTENSIONS = new HashSet<>(Arrays.asList(
"flac", "mp3", "m4a", "aac", "ogg", "opus", "wma"
));
private static final int FFMPEG_COMPRESSION_LEVEL = 5;
private final SimpMessagingTemplate messagingTemplate;
private final ProgressStore progressStore;
public ConvertService(SimpMessagingTemplate messagingTemplate, ProgressStore progressStore) {
this.messagingTemplate = messagingTemplate;
this.progressStore = progressStore;
}
@Async
public void convert(String taskId, String srcDir, String dstDir, String mode) {
Path srcPath = Paths.get(srcDir);
Path dstPath = Paths.get(dstDir);
try {
// 验证输入目录路径是否填写
if (srcDir == null || srcDir.trim().isEmpty()) {
sendProgress(taskId, 0, 0, 0, 0, null, "输入目录路径不能为空", true);
return;
}
// 验证输入目录是否存在
if (!Files.exists(srcPath) || !Files.isDirectory(srcPath)) {
sendProgress(taskId, 0, 0, 0, 0, null, "输入目录不存在或不是目录", true);
return;
}
// 验证输出目录路径是否填写
if (dstDir == null || dstDir.trim().isEmpty()) {
sendProgress(taskId, 0, 0, 0, 0, null, "输出目录路径不能为空", true);
return;
}
// 移动模式下,输入和输出目录不能相同
if ("move".equalsIgnoreCase(mode) && srcPath.equals(dstPath)) {
sendProgress(taskId, 0, 0, 0, 0, null, "移动模式下,输入目录和输出目录不能相同", true);
return;
}
// 创建输出目录(如果不存在)
if (!Files.exists(dstPath)) {
Files.createDirectories(dstPath);
}
// 扫描输入目录,查找需要转换的文件
List<Path> toConvert = new ArrayList<>();
Files.walkFileTree(srcPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (shouldConvert(file)) {
toConvert.add(file);
}
return FileVisitResult.CONTINUE;
}
});
int total = toConvert.size();
AtomicInteger processed = new AtomicInteger(0);
AtomicInteger success = new AtomicInteger(0);
AtomicInteger failed = new AtomicInteger(0);
log.info("转码任务开始,扫描目录: {}, 待转码文件数: {}", srcPath, total);
sendProgress(taskId, total, 0, 0, 0, null, "扫描完成,开始转码任务...", false);
// 如果目录存在但没有需要转换的文件,正常完成任务
if (total == 0) {
log.info("目录 {} 中未找到需要转码的无损音频文件", srcPath);
sendProgress(taskId, 0, 0, 0, 0, null,
"目录扫描完成未找到需要转码的无损音频文件WAV/APE/AIFF/WV/TTA。目录中可能只有 FLAC 或有损格式文件,已自动跳过。", true);
return;
}
for (Path srcFile : toConvert) {
String fileName = srcFile.getFileName().toString();
String baseName = getBaseName(fileName);
String outFileName = baseName + ".flac";
Path outFile = resolveTargetFile(dstPath, outFileName);
try {
runFfmpeg(srcFile, outFile);
success.incrementAndGet();
if ("move".equalsIgnoreCase(mode)) {
try {
Files.deleteIfExists(srcFile);
} catch (IOException e) {
log.warn("移动模式删除源文件失败: {}", srcFile, e);
}
}
sendProgress(taskId, total, processed.incrementAndGet(),
success.get(), failed.get(), fileName,
"已处理: " + fileName, false);
} catch (Exception e) {
failed.incrementAndGet();
log.warn("转码失败: {} - {}", fileName, e.getMessage());
sendProgress(taskId, total, processed.incrementAndGet(),
success.get(), failed.get(), fileName,
"转码失败: " + fileName + " - " + e.getMessage(), false);
}
}
sendProgress(taskId, total, processed.get(), success.get(), failed.get(),
null, String.format("任务完成!成功: %d, 失败: %d", success.get(), failed.get()), true);
} catch (Exception e) {
log.error("转码任务执行失败", e);
sendProgress(taskId, 0, 0, 0, 0, null,
"任务执行失败: " + e.getMessage(), true);
}
}
private boolean shouldConvert(Path file) {
String ext = getExtension(file.getFileName().toString());
if (ext == null) return false;
return LOSSLESS_EXTENSIONS.contains(ext);
}
private String getExtension(String fileName) {
int i = fileName.lastIndexOf('.');
if (i == -1 || i == fileName.length() - 1) return null;
return fileName.substring(i + 1).toLowerCase();
}
private String getBaseName(String fileName) {
int i = fileName.lastIndexOf('.');
if (i <= 0) return fileName;
return fileName.substring(0, i);
}
private void runFfmpeg(Path input, Path output) throws IOException, InterruptedException {
ProcessBuilder pb = new ProcessBuilder(
"ffmpeg",
"-y",
"-i", input.toAbsolutePath().toString(),
"-compression_level", String.valueOf(FFMPEG_COMPRESSION_LEVEL),
output.toAbsolutePath().toString()
);
pb.redirectErrorStream(true);
Process p = pb.start();
int exit = p.waitFor();
if (exit != 0) {
throw new RuntimeException("ffmpeg 退出码: " + exit);
}
}
private Path resolveTargetFile(Path targetDir, String fileName) throws IOException {
Path target = targetDir.resolve(fileName);
if (!Files.exists(target)) return target;
int lastDot = fileName.lastIndexOf('.');
String base = lastDot > 0 ? fileName.substring(0, lastDot) : fileName;
String ext = lastDot > 0 ? fileName.substring(lastDot) : "";
int n = 1;
while (Files.exists(target)) {
String next = base + " (" + n + ")" + ext;
target = targetDir.resolve(next);
n++;
}
return target;
}
private void sendProgress(String taskId, int total, int processed, int success,
int failed, String currentFile, String message, boolean completed) {
try {
ProgressMessage pm = new ProgressMessage();
pm.setTaskId(taskId);
pm.setType("convert");
pm.setTotal(total);
pm.setProcessed(processed);
pm.setSuccess(success);
pm.setFailed(failed);
pm.setCurrentFile(currentFile);
pm.setMessage(message);
pm.setCompleted(completed);
progressStore.put(pm);
messagingTemplate.convertAndSend("/topic/progress/" + taskId, pm);
log.debug("发送进度消息: taskId={}, total={}, processed={}, success={}, failed={}, completed={}",
taskId, total, processed, success, failed, completed);
} catch (Exception e) {
log.error("发送进度消息失败", e);
}
}
}