mirror of https://github.com/jlelse/GoBlog
137 lines
2.6 KiB
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
|
||
|
|
||
|
}
|