mirror of
https://github.com/kemko/icecast-ripper.git
synced 2026-01-01 15:55:42 +03:00
Refactor GetDuration for improved MP3 duration estimation (#7)
* Refactor GetDuration to improve MP3 duration estimation using bitrate sampling and file size * Improve error message for bitrate sampling failure in GetDuration
This commit is contained in:
@@ -11,8 +11,31 @@ import (
|
||||
"github.com/tcolgate/mp3"
|
||||
)
|
||||
|
||||
// GetDuration returns the actual duration of an MP3 file by analyzing its frames
|
||||
// Maximum number of frames to sample for bitrate calculation
|
||||
const maxFramesToSample = 100
|
||||
|
||||
// GetDuration returns the estimated duration of an MP3 file by sampling frames
|
||||
// and using file size and bitrate for calculation to avoid processing very large files
|
||||
func GetDuration(filePath string) (time.Duration, error) {
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to stat MP3 file: %w", err)
|
||||
}
|
||||
fileSize := fileInfo.Size()
|
||||
|
||||
// First try to calculate duration by sampling bitrate
|
||||
duration, err := getDurationByBitrateSampling(filePath, fileSize)
|
||||
if err == nil {
|
||||
return duration, nil
|
||||
}
|
||||
|
||||
// If bitrate sampling fails, fall back to size-based estimation
|
||||
return getDurationByFileSize(fileSize), nil
|
||||
}
|
||||
|
||||
// getDurationByBitrateSampling calculates MP3 duration by sampling a limited number of frames
|
||||
// to determine the average bitrate, then uses that with the file size to estimate duration
|
||||
func getDurationByBitrateSampling(filePath string, fileSize int64) (time.Duration, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to open MP3 file: %w", err)
|
||||
@@ -27,11 +50,11 @@ func GetDuration(filePath string) (time.Duration, error) {
|
||||
decoder := mp3.NewDecoder(file)
|
||||
var frame mp3.Frame
|
||||
var skipped int
|
||||
var totalSamples int
|
||||
sampleRate := 0
|
||||
var totalBitrate int
|
||||
var frameCount int
|
||||
|
||||
// Process the frames to calculate the total duration
|
||||
for {
|
||||
// Sample a limited number of frames to calculate average bitrate
|
||||
for frameCount < maxFramesToSample {
|
||||
if err := decoder.Decode(&frame, &skipped); err != nil {
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
break
|
||||
@@ -39,17 +62,46 @@ func GetDuration(filePath string) (time.Duration, error) {
|
||||
return 0, fmt.Errorf("failed to decode MP3 frame: %w", err)
|
||||
}
|
||||
|
||||
if sampleRate == 0 {
|
||||
sampleRate = frame.Samples()
|
||||
// Get bitrate from the frame header in bits per second
|
||||
bitrate := int(frame.Header().BitRate())
|
||||
if bitrate <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
totalSamples += frame.Samples()
|
||||
totalBitrate += bitrate
|
||||
frameCount++
|
||||
}
|
||||
|
||||
if totalSamples == 0 || sampleRate == 0 {
|
||||
return 0, errors.New("could not determine MP3 duration")
|
||||
if frameCount == 0 {
|
||||
return 0, errors.New("could not read any MP3 frames with a valid bitrate")
|
||||
}
|
||||
|
||||
durationSeconds := float64(totalSamples) / float64(sampleRate)
|
||||
// Calculate average bitrate in bits per second
|
||||
averageBitrate := totalBitrate / frameCount
|
||||
|
||||
if averageBitrate <= 0 {
|
||||
return 0, errors.New("invalid bitrate detected")
|
||||
}
|
||||
|
||||
// Calculate duration: file size (bits) / bitrate (bits per second)
|
||||
// Subtract ~10KB for headers and metadata (conservative estimate)
|
||||
metadataEstimate := int64(10 * 1024)
|
||||
adjustedSize := fileSize
|
||||
if fileSize > metadataEstimate {
|
||||
adjustedSize -= metadataEstimate
|
||||
}
|
||||
|
||||
durationSeconds := float64(adjustedSize*8) / float64(averageBitrate)
|
||||
return time.Duration(durationSeconds * float64(time.Second)), nil
|
||||
}
|
||||
|
||||
// getDurationByFileSize provides a rough estimate of MP3 duration based only on file size
|
||||
// using the assumption of ~128kbps average bitrate for most MP3 files
|
||||
func getDurationByFileSize(fileSize int64) time.Duration {
|
||||
// Assume average bitrate of 128kbps (common for MP3)
|
||||
assumedBitrate := 128 * 1024 // bits per second
|
||||
|
||||
// Calculate duration: file size (bits) / bitrate (bits per second)
|
||||
durationSeconds := float64(fileSize*8) / float64(assumedBitrate)
|
||||
return time.Duration(durationSeconds * float64(time.Second))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user