This repository has been archived by the owner on Dec 3, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
flac.go
338 lines (281 loc) · 8.22 KB
/
flac.go
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
package taggolib
import (
"encoding/binary"
"fmt"
"io"
"strconv"
"strings"
"time"
"github.com/eaburns/bit"
)
const (
// flacStreamInfo denotes a STREAMINFO metadata block
flacStreamInfo = 0
// flacVorbisComment denotes a VORBISCOMMENT metadata block
flacVorbisComment = 4
)
var (
// flacMagicNumber is the magic number used to identify a FLAC audio stream
flacMagicNumber = []byte("fLaC")
)
// flacParser represents a FLAC audio metadata tag parser
type flacParser struct {
encoder string
endPos int64
properties *flacStreamInfoBlock
reader io.ReadSeeker
tags map[string]string
// Shared buffer stored as field to prevent unneeded allocations
buffer []byte
}
// Album returns the Album tag for this stream
func (f flacParser) Album() string {
return f.tags[tagAlbum]
}
// AlbumArtist returns the AlbumArtist tag for this stream
func (f flacParser) AlbumArtist() string {
return f.tags[tagAlbumArtist]
}
// Artist returns the Artist tag for this stream
func (f flacParser) Artist() string {
return f.tags[tagArtist]
}
// BitDepth returns the bits-per-sample of this stream
func (f flacParser) BitDepth() int {
return int(f.properties.BitsPerSample)
}
// Bitrate calculates the audio bitrate for this stream
func (f flacParser) Bitrate() int {
// Check for zero duration or end position, to prevent a division-by-zero panic
seconds := f.Duration().Seconds()
if f.endPos == 0 || seconds == 0 {
return 0
}
return int(((f.endPos * 8) / int64(f.Duration().Seconds())) / 1024)
}
// Channels returns the number of channels for this stream
func (f flacParser) Channels() int {
return int(f.properties.ChannelCount)
}
// Checksum returns the checksum for this stream
func (f flacParser) Checksum() string {
return f.properties.MD5Checksum
}
// Comment returns the Comment tag for this stream
func (f flacParser) Comment() string {
return f.tags[tagComment]
}
// Date returns the Date tag for this stream
func (f flacParser) Date() string {
return f.tags[tagDate]
}
// DiscNumber returns the DiscNumber tag for this stream
func (f flacParser) DiscNumber() int {
disc, err := strconv.Atoi(f.tags[tagDiscNumber])
if err != nil {
return 0
}
return disc
}
// Duration returns the time duration for this stream
func (f flacParser) Duration() time.Duration {
return time.Duration(int64(f.properties.SampleCount)/int64(f.SampleRate())) * time.Second
}
// Encoder returns the encoder for this stream
func (f flacParser) Encoder() string {
return f.encoder
}
// Format returns the name of the FLAC format
func (f flacParser) Format() string {
return "FLAC"
}
// Genre returns the Genre tag for this stream
func (f flacParser) Genre() string {
return f.tags[tagGenre]
}
// Publisher returns the Publisher (record-label) tag for this stream
func (f flacParser) Publisher() string {
return f.tags[tagPublisher]
}
// SampleRate returns the sample rate in Hertz for this stream
func (f flacParser) SampleRate() int {
return int(f.properties.SampleRate)
}
// Tag attempts to return the raw, unprocessed tag with the specified name for this stream
func (f flacParser) Tag(name string) string {
return f.tags[strings.ToUpper(name)]
}
// Title returns the Title tag for this stream
func (f flacParser) Title() string {
return f.tags[tagTitle]
}
// TrackNumber returns the TrackNumber tag for this stream
func (f flacParser) TrackNumber() int {
track, err := strconv.Atoi(f.tags[tagTrackNumber])
if err != nil {
return 0
}
return track
}
// newFLACParser creates a parser for FLAC audio streams
func newFLACParser(reader io.ReadSeeker) (*flacParser, error) {
// Create FLAC parser
parser := &flacParser{
buffer: make([]byte, 2048),
reader: reader,
}
// Begin parsing properties
if err := parser.parseProperties(); err != nil {
return nil, err
}
// Seek through the file and attempt to parse tags
if err := parser.parseTags(); err != nil {
return nil, err
}
// Seek to end of file to grab the final position, used to calculate bitrate
n, err := parser.reader.Seek(0, 2)
if err != nil {
return nil, err
}
parser.endPos = n
// Return parser
return parser, nil
}
// flacMetadataHeader represents the header for a FLAC metadata block
type flacMetadataHeader struct {
LastBlock bool
BlockType uint8
BlockLength uint32
}
// flacStreamInfoBlock represents the metadata from a FLAC STREAMINFO block
type flacStreamInfoBlock struct {
SampleRate uint16
ChannelCount uint8
BitsPerSample uint16
SampleCount uint64
MD5Checksum string
}
// parseMetadataHeader retrieves metadata header information from a FLAC stream
func (f *flacParser) parseMetadataHeader() (*flacMetadataHeader, error) {
// Create and use a bit reader to parse the following fields:
// 1 - Last metadata block before audio (boolean)
// 7 - Metadata block type (should be 0, for streaminfo)
// 24 - Length of following metadata (in bytes)
fields, err := bit.NewReader(f.reader).ReadFields(1, 7, 24)
if err != nil {
return nil, err
}
// Generate metadata header
return &flacMetadataHeader{
LastBlock: fields[0] == 1,
BlockType: uint8(fields[1]),
BlockLength: uint32(fields[2]),
}, nil
}
// parseTags retrieves metadata tags from a FLAC VORBISCOMMENT block
func (f *flacParser) parseTags() error {
// Continuously parse and seek through blocks until we discover the VORBISCOMMENT block
for {
header, err := f.parseMetadataHeader()
if err != nil {
return err
}
// Check for VORBISCOMMENT block, break so we can begin parsing tags
if header.BlockType == flacVorbisComment {
break
}
// If last block and no VORBISCOMMENT block found, no tags
if header.LastBlock {
return nil
}
// If nothing found and not last block, seek forward in stream
if _, err := f.reader.Seek(int64(header.BlockLength), 1); err != nil {
return err
}
}
// Parse length fields
var length uint32
// Read vendor string length
if err := binary.Read(f.reader, binary.LittleEndian, &length); err != nil {
return err
}
// Read vendor string
if _, err := f.reader.Read(f.buffer[:length]); err != nil {
return err
}
f.encoder = string(f.buffer[:length])
// Read comment length (new allocation so we can use it as loop counter)
var commentLength uint32
if err := binary.Read(f.reader, binary.LittleEndian, &commentLength); err != nil {
return err
}
// Begin iterating tags, and building tag map
tagMap := map[string]string{}
for i := 0; i < int(commentLength); i++ {
// Read tag string length
if err := binary.Read(f.reader, binary.LittleEndian, &length); err != nil {
return err
}
// Read tag string
n, err := f.reader.Read(f.buffer[:length])
if err != nil {
return err
}
// Split tag name and data, store in map
pair := strings.Split(string(f.buffer[:n]), "=")
tagMap[strings.ToUpper(pair[0])] = pair[1]
}
// Store tags
f.tags = tagMap
return nil
}
// parseProperties retrieves stream properties from a FLAC STREAMINFO block
func (f *flacParser) parseProperties() error {
// Read the metadata header for STREAMINFO block
header, err := f.parseMetadataHeader()
if err != nil {
return err
}
// Ensure that the metadata block type is STREAMINFO
if header.BlockType != flacStreamInfo {
return TagError{
Err: errInvalidStream,
Format: f.Format(),
Details: "first metadata block is not type STREAMINFO",
}
}
// Ensure that STREAMINFO is not the last block
if header.LastBlock {
return TagError{
Err: errInvalidStream,
Format: f.Format(),
Details: "STREAMINFO block is marked as last metadata block in stream",
}
}
// Seek forward past frame information, to sample rate
if _, err := f.reader.Seek(10, 1); err != nil {
return err
}
// Create and use a bit reader to parse the following fields:
// 20 - Sample rate
// 3 - Channel count (+1)
// 5 - Bits per sample (+1)
// 36 - Sample count
fields, err := bit.NewReader(f.reader).ReadFields(20, 3, 5, 36)
if err != nil {
return err
}
// Read the MD5 checksum of the stream
if _, err := f.reader.Read(f.buffer[:16]); err != nil {
return err
}
// Store properties
f.properties = &flacStreamInfoBlock{
SampleRate: uint16(fields[0]),
ChannelCount: uint8(fields[1]) + 1,
BitsPerSample: uint16(fields[2]) + 1,
SampleCount: uint64(fields[3]),
MD5Checksum: fmt.Sprintf("%x", f.buffer[:16]),
}
return nil
}