1
0
dwelling-radio/pkg/oggtag/oggtag.go

105 lines
2.5 KiB
Go

package oggtag
/* oggtag is a naive implementation of OGG tag's reader that is just looking
for certain tag names ending with an = character, e.g. artist= and title=.
*/
import (
"bytes"
"io"
"os"
"strings"
"time"
)
const (
bufferLength = 6144
OggS = "OggS"
OggSLen = len(OggS)
Vorbis = "vorbis"
VorbisLen = len(Vorbis)
)
// OggFile holds a head of a file and a tail part conatining last granule.
type OggFile struct {
bufHead, bufTail []byte
}
// NewOggFile reads a file and returns a new OggFile.
func NewOggFile(path string) (*OggFile, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
of := &OggFile{}
of.bufHead = make([]byte, bufferLength)
if _, err := f.Read(of.bufHead); err != nil {
return nil, err
}
of.bufTail = make([]byte, bufferLength)
if _, err := f.Seek(-bufferLength, io.SeekEnd); err != nil {
return nil, err
}
fst, err := f.Stat()
if err != nil {
return nil, err
}
for offset := int64(bufferLength); offset <= fst.Size(); offset += bufferLength {
if _, err := f.Seek(-offset, io.SeekEnd); err != nil {
return nil, err
}
if _, err := f.Read(of.bufTail); err != nil {
return nil, err
}
if bytes.Contains(of.bufTail, []byte(OggS)) {
break
}
}
return of, nil
}
// GetTag is searching for a certain tag and returns its value or an empty string.
func (of *OggFile) GetTag(tag string) string {
tagIdx := bytes.Index(of.bufHead, append([]byte{0, 0, 0}, (tag+"=")...))
if tagIdx == -1 {
if tagIdx = bytes.Index(of.bufHead, append([]byte{0, 0, 0}, (strings.ToUpper(tag)+"=")...)); tagIdx == -1 {
return ""
}
}
tagIdx += 3
tagNameLen := len(tag) + 1
valStart := tagIdx + tagNameLen
valLen := int(of.bufHead[tagIdx-4]) - tagNameLen
return string(of.bufHead[valStart : valStart+valLen])
}
// GetDuration returns song's duration in milliseconds.
func (of *OggFile) GetDuration() time.Duration {
rateIdx := bytes.Index(of.bufHead, []byte(Vorbis)) +
VorbisLen + 5
rateBytes := of.bufHead[rateIdx : rateIdx+4]
rate := int32(rateBytes[0]) + int32(rateBytes[1])<<8 +
int32(rateBytes[2])<<16 + int32(rateBytes[3])<<24
granuleIdx := bytes.LastIndex(of.bufTail, []byte(OggS)) +
OggSLen + 2
granuleBytes := of.bufTail[granuleIdx : granuleIdx+8]
granule := int64(granuleBytes[0]) + int64(granuleBytes[1])<<8 +
int64(granuleBytes[2])<<16 + int64(granuleBytes[3])<<24 +
int64(granuleBytes[4])<<32 + int64(granuleBytes[5])<<40 +
int64(granuleBytes[6])<<48 + int64(granuleBytes[7])<<56
return time.Duration(granule*1000/int64(rate)) * time.Millisecond
}