File size: 3,385 Bytes
750bbe6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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
}