Using FFmpeg Merge All MP3 Files To One

2023-03-26

筆記如何使用 FFmpeg 將眾多 mp3 檔案合成為單一 mp3 檔以及將 mp3 檔案加入封面成為 mp4 檔。

logo

說明

foreach ($i in (48..57 + 97..102)) {
    $c = [char]$i
    $files = Get-ChildItem -Path . -Filter $c*.mp3 | Sort-Object Name | Select-Object -ExpandProperty FullName
    .\ffmpeg.exe -i ('concat:' + ($files -join '|')) -acodec copy "$c.mp3"
}

因為 ffmpeg.exe 不支援 wildcard 的方式輸入檔案名稱,而必須使用 | 連結所有的 input 檔案名稱,所以透過 PowerShell 來協助處理,但此方法會受限於最大字串長度,替代的方式是迭代產生檔案,使用 reduce 的演算方式來進行合併。

而本次實作為了簡化操作,就以 group by 的方式,將檔案依照 0 到 f 開頭,進行分類,而 48..57 表示 ascii 的 0 到 9,97..102 則是 a 到 f,用以迭代檔案的名稱。

.\ffmpeg.exe -loop 1 -framerate 2 -i cover.png -i $c.mp3 -c:a copy -preset:v ultrafast -shortest $c-series.mp4

使用 ffmpeg.exe 將 mp3 轉換為 mp4,且只使用靜態封面的做插圖的轉換,轉換的時間比預期的久很多,後來藉由 -preset 以及 framerate 去做調校才有改善,但兩個小時的 mp3 檔案,仍需要約 5 分鐘的轉換時間。

進階分類檔案

當使用檔名第一碼檔案數量太多,但使用第二碼的時候又太少的折衷處理知道,根據第二碼分成兩組的處理方式:

static readonly string FolderPath = @"D:\Files";

static void Main(string[] args)
{
    string folderPath = FolderPath;
    string[] fileNames = Directory.GetFiles(folderPath).Select(Path.GetFileName).ToArray();

    var groups = fileNames.GroupBy(
        fileName => $"{fileName[0]}{GetGroupType(fileName[1])}",
        (prefix, fileName) => new
        {
            Prefix = prefix,
            FileNames = fileName.ToArray()
        });

    foreach (var group in groups)
    {
        Console.WriteLine($"Prefix: {group.Prefix}, Count: {group.FileNames.Length}");
        string destination = Path.Combine(FolderPath, group.Prefix);
        GenerateFolder(destination);
  
        foreach (var file in group.FileNames)
        {
          MoveFile(file, destination);
        }
    }
}

static void GenerateFolder(string destinationFolderPath)
{
  if (!Directory.Exists(destinationFolderPath))
  {
    Directory.CreateDirectory(destinationFolderPath);
  }
}

static void MoveFile(string fileSource, string fileDestination)
{
  string sourceFilePath = Path.Combine(FolderPath, fileSource);
  string destinationFilePath = Path.Combine(fileDestination, fileSource);
  File.Move(sourceFilePath, destinationFilePath);
}

static string GetGroupType(char prefix)
{
  return prefix >= '0' && prefix <= '7' ? "A" : "B";
}

接著使用 PowerShell 搭配 ffmpeg 開始進行檔案轉換:

for ($i = 0; $i -le 15; $i++) {
    for ($j = 0; $j -le 1; $j++) {
        $prefix = if ($i -lt 10) {"$i"} else {([char]($i + 87))}
        $suffix = if ($j -eq 0) {"A"} else {"B"}
        echo $prefix$suffix
        cd $prefix$suffix

        $files = Get-ChildItem -Path . -Filter *.mp3 | Sort-Object Name | Select-Object -ExpandProperty FullName
        .\ffmpeg.exe -i ('concat:' + ($files -join '|')) -acodec copy "$prefix$suffix.mp3"

        cd ..
        move "$prefix$suffix\$prefix$suffix.mp3" "D:\Files\$prefix$suffix.mp3"
    }
}