Files
LocalAI/pkg/audio/identify.go
Ettore Di Giacinto 697f6aa71c feat(audio): set audio content type (#8416)
* feat(audio): set audio content type

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* chore: add tests

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-02-05 19:14:12 +01:00

131 lines
3.3 KiB
Go

package audio
import (
"io"
"os"
"path/filepath"
"strings"
"github.com/dhowden/tag"
"github.com/mudler/xlog"
)
// extensionFromFileType returns the file extension for tag.FileType.
func extensionFromFileType(ft tag.FileType) string {
switch ft {
case tag.FLAC:
return "flac"
case tag.MP3:
return "mp3"
case tag.OGG:
return "ogg"
case tag.M4A:
return "m4a"
case tag.M4B:
return "m4b"
case tag.M4P:
return "m4p"
case tag.ALAC:
return "m4a"
case tag.DSF:
return "dsf"
default:
return ""
}
}
// contentTypeFromFileType returns the MIME type for tag.FileType.
func contentTypeFromFileType(ft tag.FileType) string {
switch ft {
case tag.FLAC:
return "audio/flac"
case tag.MP3:
return "audio/mpeg"
case tag.OGG:
return "audio/ogg"
case tag.M4A, tag.M4B, tag.M4P, tag.ALAC:
return "audio/mp4"
case tag.DSF:
return "audio/dsd"
default:
return ""
}
}
// Identify reads from r and returns the detected audio extension and Content-Type.
// It uses github.com/dhowden/tag to identify the format from the stream.
// Returns ("", "", err) if the format could not be identified.
func Identify(r io.ReadSeeker) (ext string, contentType string, err error) {
_, fileType, err := tag.Identify(r)
if err != nil || fileType == tag.UnknownFileType {
return "", "", err
}
ext = extensionFromFileType(fileType)
contentType = contentTypeFromFileType(fileType)
if ext == "" || contentType == "" {
return "", "", nil
}
return ext, contentType, nil
}
// ContentTypeFromExtension returns the MIME type for common audio file extensions.
// Use as a fallback when Identify fails or when the file is not openable.
func ContentTypeFromExtension(path string) string {
ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(path), "."))
switch ext {
case "flac":
return "audio/flac"
case "mp3":
return "audio/mpeg"
case "wav":
return "audio/wav"
case "ogg":
return "audio/ogg"
case "m4a", "m4b", "m4p":
return "audio/mp4"
case "webm":
return "audio/webm"
default:
return ""
}
}
// NormalizeAudioFile opens the file at path, identifies its format with tag.Identify,
// and renames the file to have the correct extension if the current one does not match.
// It returns the path to use (possibly the renamed file) and the Content-Type to set.
// If identification fails, returns (path, ContentTypeFromExtension(path)).
func NormalizeAudioFile(path string) (finalPath string, contentType string) {
finalPath = path
f, err := os.Open(path)
if err != nil {
contentType = ContentTypeFromExtension(path)
return finalPath, contentType
}
defer f.Close()
ext, ct, identifyErr := Identify(f)
if identifyErr != nil || ext == "" || ct == "" {
contentType = ContentTypeFromExtension(path)
return finalPath, contentType
}
contentType = ct
currentExt := strings.ToLower(strings.TrimPrefix(filepath.Ext(path), "."))
if currentExt == ext {
return finalPath, contentType
}
dir := filepath.Dir(path)
base := filepath.Base(path)
baseNoExt := strings.TrimSuffix(base, filepath.Ext(base))
if baseNoExt == "" {
baseNoExt = base
}
newPath := filepath.Join(dir, baseNoExt+"."+ext)
if renameErr := os.Rename(path, newPath); renameErr != nil {
xlog.Debug("Could not rename audio file to match type", "from", path, "to", newPath, "error", renameErr)
return finalPath, contentType
}
return newPath, contentType
}