In oggtag.go a struct named OggFIle was created, ReadFile turned into a constructor NewOggFile. And GetTag and GetDuration turned into methods.
This commit is contained in:
parent
10bc3a3785
commit
6ae8a40493
@ -56,7 +56,7 @@ func (dj *DJHandlers) PlaylistNext(w http.ResponseWriter, _ *http.Request) {
|
||||
log.Println("the end of a playlist has been reached")
|
||||
} else {
|
||||
go func() {
|
||||
bs, be, err := oggtag.ReadFile(nxt)
|
||||
oggf, err := oggtag.NewOggFile(nxt)
|
||||
if err != nil {
|
||||
log.Println("cannot read an OGG file", nxt, ":", err)
|
||||
http.Error(w, "cannot read an OGG file", http.StatusInternalServerError)
|
||||
@ -64,9 +64,9 @@ func (dj *DJHandlers) PlaylistNext(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
|
||||
song := radio.Song{
|
||||
Artist: oggtag.GetTag(bs, "artist"),
|
||||
Title: oggtag.GetTag(bs, "title"),
|
||||
Duration: oggtag.GetDuration(bs, be),
|
||||
Artist: oggf.GetTag("artist"),
|
||||
Title: oggf.GetTag("title"),
|
||||
Duration: oggf.GetDuration(),
|
||||
MaxListeners: dj.listeners.Current(),
|
||||
StartAt: time.Now()}
|
||||
radio.CheckAndUpdateMostListenedSong(dj.songList.Current())
|
||||
|
@ -12,85 +12,89 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const bufferLength = 4096
|
||||
const MagicSequence = "OggS"
|
||||
const MagicSequenceLen = len(MagicSequence)
|
||||
const VorbisSequence = "vorbis"
|
||||
const VorbisSequenceLen = len(VorbisSequence)
|
||||
const (
|
||||
bufferLength = 6144
|
||||
OggS = "OggS"
|
||||
OggSLen = len(OggS)
|
||||
Vorbis = "vorbis"
|
||||
VorbisLen = len(Vorbis)
|
||||
)
|
||||
|
||||
// OggReadFile returns a head of an OGG file, and a buffer contains last frame.
|
||||
func ReadFile(path string) (buf_start, buf_end []byte, _ error) {
|
||||
// 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, nil, err
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
of := &OggFile{}
|
||||
|
||||
of.bufHead = make([]byte, bufferLength)
|
||||
|
||||
if _, err := f.Read(of.bufHead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf_start = make([]byte, bufferLength)
|
||||
|
||||
if _, err := f.Read(buf_start); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
buf_end = make([]byte, bufferLength)
|
||||
of.bufTail = make([]byte, bufferLength)
|
||||
|
||||
if _, err := f.Seek(-bufferLength, io.SeekEnd); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fst, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for offset := int64(bufferLength); offset <= fst.Size(); offset += bufferLength {
|
||||
if _, err := f.Seek(-offset, io.SeekEnd); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := f.Read(buf_end); err != nil {
|
||||
return nil, nil, err
|
||||
if _, err := f.Read(of.bufTail); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Contains(buf_end, []byte(MagicSequence)) {
|
||||
if bytes.Contains(of.bufTail, []byte(OggS)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return of, nil
|
||||
}
|
||||
|
||||
// OggGetTag is searching for a certain tag in a given buffer buf and returns
|
||||
// its value.
|
||||
//
|
||||
// It is a simple implementation that doesn't parse a header and instead just
|
||||
// looking for a certain tag preceded with three zero bytes and ends with
|
||||
// an = character.
|
||||
func GetTag(buf []byte, tag string) string {
|
||||
tagIdx := bytes.Index(buf, append([]byte{0, 0, 0}, (tag+"=")...))
|
||||
// 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 {
|
||||
tagIdx = bytes.Index(buf, append([]byte{0, 0, 0}, (strings.ToUpper(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(buf[tagIdx-4]) - tagNameLen
|
||||
return string(buf[valStart : valStart+valLen])
|
||||
valLen := int(of.bufHead[tagIdx-4]) - tagNameLen
|
||||
return string(of.bufHead[valStart : valStart+valLen])
|
||||
}
|
||||
|
||||
// OggGetDuration returns song's duration in milliseconds.
|
||||
func GetDuration(buf_start, buf_end []byte) time.Duration {
|
||||
rateIdx := bytes.Index(buf_start, []byte(VorbisSequence)) +
|
||||
VorbisSequenceLen + 5
|
||||
rateBytes := buf_start[rateIdx : rateIdx+4]
|
||||
// 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(buf_end, []byte(MagicSequence)) +
|
||||
MagicSequenceLen + 2
|
||||
granuleBytes := buf_end[granuleIdx : granuleIdx+8]
|
||||
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 +
|
||||
|
@ -1,66 +1,57 @@
|
||||
package oggtag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const bufferSize = 4096
|
||||
const sampleSong = "/mnt/data/appdata/radio/fallback.ogg"
|
||||
const sampleArtist = "breskina"
|
||||
const sampleTitle = "Песня про мечты"
|
||||
|
||||
func TestGetTag(t *testing.T) {
|
||||
f, _ := os.Open(sampleSong)
|
||||
buf := make([]byte, bufferSize)
|
||||
f.Read(buf)
|
||||
tag := GetTag(buf, "artist")
|
||||
oggf, err := NewOggFile(sampleSong)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tag := oggf.GetTag("artist")
|
||||
if tag != sampleArtist {
|
||||
t.Error(tag, "!=", sampleArtist)
|
||||
}
|
||||
tag = GetTag(buf, "title")
|
||||
tag = oggf.GetTag("title")
|
||||
if tag != sampleTitle {
|
||||
t.Error(tag, "!=", sampleTitle)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetTag(b *testing.B) {
|
||||
f, _ := os.Open(sampleSong)
|
||||
buf := make([]byte, bufferSize)
|
||||
f.Read(buf)
|
||||
oggf, err := NewOggFile(sampleSong)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
GetTag(buf, "artist")
|
||||
oggf.GetTag("artist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
bs, bf, err := ReadFile(sampleSong)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
t.Log("bs = ", len(bs), "; bf =", len(bf), "bf(S) =", bytes.LastIndex(bf, []byte("S")))
|
||||
}
|
||||
|
||||
func BenchmarkGetDuration(b *testing.B) {
|
||||
bs, bf, err := ReadFile(sampleSong)
|
||||
oggf, err := NewOggFile(sampleSong)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
GetDuration(bs, bf)
|
||||
oggf.GetDuration()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDuration(t *testing.T) {
|
||||
bs, bf, err := ReadFile(sampleSong)
|
||||
oggf, err := NewOggFile(sampleSong)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
dur := GetDuration(bs, bf)
|
||||
dur := oggf.GetDuration()
|
||||
t.Log(dur, ((dur)/time.Second)*time.Second, dur.Milliseconds())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user