將目錄及子目錄下所有 .mp3 文件按文件名排序,拼接為單一 MP3 文件,適用於有聲書章節合併、會議錄音整理等場景。
.mp3 絕對路徑關鍵點:MP3 由幀組成,直接字節拼接在多數播放器中可正常播放,但元數據(ID3 標籤)會殘留在文件頭/尾,建議合併後用工具重寫標籤。
package com.example.mp3;
import java.io.*;
import java.nio.file.*;
import java.util.*;
public class Mp3Merger {
public static void main(String[] args) throws IOException {
if (args.length < 2) {
System.err.println("用法: java Mp3Merger <輸入目錄> <輸出文件.mp3>");
return;
}
Path inputDir = Paths.get(args[0]);
Path outputFile = Paths.get(args[1]);
List mp3Files = collectMp3Files(inputDir);
if (mp3Files.isEmpty()) {
System.out.println("未發現 .mp3 文件");
return;
}
// 自然排序:1.mp3, 2.mp3, 10.mp3 (而非 1, 10, 2)
mp3Files.sort(Comparator.comparing(Path::getFileName,
Comparator.comparing(String::toString,
(a, b) -> naturalCompare(a, b))));
mergeFiles(mp3Files, outputFile);
System.out.printf("合併完成:%d 個文件 → %s%n", mp3Files.size(), outputFile);
}
/** 遞歸收集 .mp3 文件 */
private static List collectMp3Files(Path dir) throws IOException {
List result = new ArrayList<>();
Files.walkFileTree(dir, new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (file.toString().toLowerCase().endsWith(".mp3")) {
result.add(file.toAbsolutePath());
}
return FileVisitResult.CONTINUE;
}
});
return result;
}
/** 自然排序比較器(簡易版) */
private static int naturalCompare(String a, String b) {
int i = 0, j = 0;
while (i < a.length() && j < b.length()) {
char ca = a.charAt(i), cb = b.charAt(j);
if (Character.isDigit(ca) && Character.isDigit(cb)) {
int numA = 0, numB = 0;
while (i < a.length() && Character.isDigit(a.charAt(i))) numA = numA * 10 + (a.charAt(i++) - '0');
while (j < b.length() && Character.isDigit(b.charAt(j))) numB = numB * 10 + (b.charAt(j++) - '0');
if (numA != numB) return Integer.compare(numA, numB);
} else {
if (ca != cb) return Character.compare(ca, cb);
i++; j++;
}
}
return Integer.compare(a.length(), b.length());
}
/** 字節流拼接(1MB 緩衝) */
private static void mergeFiles(List files, Path output) throws IOException {
try (FileOutputStream fos = new FileOutputStream(output.toFile(), true)) { // 追加模式
byte[] buffer = new byte[1024 * 1024];
for (Path file : files) {
try (FileInputStream fis = new FileInputStream(file.toFile())) {
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
System.out.println("已合併:" + file.getFileName());
}
}
}
}
| 痛點 | 優化方案 |
|---|---|
| ID3 標籤重複/錯亂 | 合併前用 jaudiotagger 清除標籤,合併後重寫統一標籤 |
| 大文件內存壓力 | 已使用 1MB 緩衝流式讀寫,常數內存 |
| 合併順序不可控 | 文件名前綴數字(001_, 002_)或外部清單文件控制 |
| 格式不一致(采樣率/碼率) | 合併前用 FFmpeg 統一轉碼:ffmpeg -i in.mp3 -ar 44100 -b:a 128k out.mp3 |
# 1. 生成文件列表 filelist.txt
for f in *.mp3; do echo "file '$f'" >> filelist.txt; done
# 2. 無損合併(極快,不重新編碼)
ffmpeg -f concat -safe 0 -i filelist.txt -c copy merged.mp3
Java 方案適合「嵌入應用、跨平台、無外部依賴」場景;追求極致性能與音質,FFmpeg 才是終極答案。