GoBlog/pkgs/mp3merge/mp3merge.go

137 lines
2.6 KiB
Go

package mp3merge
import (
"errors"
"io"
"os"
"path/filepath"
"github.com/dmulholl/mp3lib"
"github.com/thoas/go-funk"
)
// Inspired by https://github.com/dmulholl/mp3cat/blob/2ec1e4fe4d995ebd41bf1887b3cab8e2a569b3d4/mp3cat.go
// Merge multiple mp3 files into one file
func MergeMP3(out string, in []string) error {
var totalFrames, totalBytes uint32
var firstBitRate int
var isVBR bool
// Check if output file is included in input files
if funk.ContainsString(in, out) {
return errors.New("the list of input files includes the output file")
}
// Create the output file.
if err := os.MkdirAll(filepath.Dir(out), os.ModePerm); err != nil {
return err
}
outfile, err := os.Create(out)
if err != nil {
return err
}
// Loop over the input files and append their MP3 frames to the output file.
for _, inpath := range in {
infile, err := os.Open(inpath)
if err != nil {
return err
}
isFirstFrame := true
for {
// Read the next frame from the input file.
frame := mp3lib.NextFrame(infile)
if frame == nil {
break
}
// Skip the first frame if it's a VBR header.
if isFirstFrame {
isFirstFrame = false
if mp3lib.IsXingHeader(frame) || mp3lib.IsVbriHeader(frame) {
continue
}
}
// If we detect more than one bitrate we'll need to add a VBR
// header to the output file.
if firstBitRate == 0 {
firstBitRate = frame.BitRate
} else if frame.BitRate != firstBitRate {
isVBR = true
}
// Write the frame to the output file.
_, err := outfile.Write(frame.RawBytes)
if err != nil {
return err
}
totalFrames += 1
totalBytes += uint32(len(frame.RawBytes))
}
_ = infile.Close()
}
_ = outfile.Close()
// If we detected multiple bitrates, prepend a VBR header to the file.
if isVBR {
err = addXingHeader(out, totalFrames, totalBytes)
if err != nil {
return err
}
}
return nil
}
// Prepend an Xing VBR header to the specified MP3 file.
func addXingHeader(filepath string, totalFrames, totalBytes uint32) error {
tmpSuffix := ".mp3merge.tmp"
outputFile, err := os.Create(filepath + tmpSuffix)
if err != nil {
return err
}
inputFile, err := os.Open(filepath)
if err != nil {
return err
}
xingHeader := mp3lib.NewXingHeader(totalFrames, totalBytes)
_, err = outputFile.Write(xingHeader.RawBytes)
if err != nil {
return err
}
_, err = io.Copy(outputFile, inputFile)
if err != nil {
return err
}
_ = outputFile.Close()
_ = inputFile.Close()
err = os.Remove(filepath)
if err != nil {
return err
}
err = os.Rename(filepath+tmpSuffix, filepath)
if err != nil {
return err
}
return nil
}