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")
|
log.Println("the end of a playlist has been reached")
|
||||||
} else {
|
} else {
|
||||||
go func() {
|
go func() {
|
||||||
bs, be, err := oggtag.ReadFile(nxt)
|
oggf, err := oggtag.NewOggFile(nxt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("cannot read an OGG file", nxt, ":", err)
|
log.Println("cannot read an OGG file", nxt, ":", err)
|
||||||
http.Error(w, "cannot read an OGG file", http.StatusInternalServerError)
|
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{
|
song := radio.Song{
|
||||||
Artist: oggtag.GetTag(bs, "artist"),
|
Artist: oggf.GetTag("artist"),
|
||||||
Title: oggtag.GetTag(bs, "title"),
|
Title: oggf.GetTag("title"),
|
||||||
Duration: oggtag.GetDuration(bs, be),
|
Duration: oggf.GetDuration(),
|
||||||
MaxListeners: dj.listeners.Current(),
|
MaxListeners: dj.listeners.Current(),
|
||||||
StartAt: time.Now()}
|
StartAt: time.Now()}
|
||||||
radio.CheckAndUpdateMostListenedSong(dj.songList.Current())
|
radio.CheckAndUpdateMostListenedSong(dj.songList.Current())
|
||||||
|
@ -12,85 +12,89 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bufferLength = 4096
|
const (
|
||||||
const MagicSequence = "OggS"
|
bufferLength = 6144
|
||||||
const MagicSequenceLen = len(MagicSequence)
|
OggS = "OggS"
|
||||||
const VorbisSequence = "vorbis"
|
OggSLen = len(OggS)
|
||||||
const VorbisSequenceLen = len(VorbisSequence)
|
Vorbis = "vorbis"
|
||||||
|
VorbisLen = len(Vorbis)
|
||||||
|
)
|
||||||
|
|
||||||
// OggReadFile returns a head of an OGG file, and a buffer contains last frame.
|
// OggFile holds a head of a file and a tail part conatining last granule.
|
||||||
func ReadFile(path string) (buf_start, buf_end []byte, _ error) {
|
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)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
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)
|
of.bufTail = make([]byte, bufferLength)
|
||||||
|
|
||||||
if _, err := f.Read(buf_start); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf_end = make([]byte, bufferLength)
|
|
||||||
|
|
||||||
if _, err := f.Seek(-bufferLength, io.SeekEnd); err != nil {
|
if _, err := f.Seek(-bufferLength, io.SeekEnd); err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fst, err := f.Stat()
|
fst, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for offset := int64(bufferLength); offset <= fst.Size(); offset += bufferLength {
|
for offset := int64(bufferLength); offset <= fst.Size(); offset += bufferLength {
|
||||||
if _, err := f.Seek(-offset, io.SeekEnd); err != nil {
|
if _, err := f.Seek(-offset, io.SeekEnd); err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := f.Read(buf_end); err != nil {
|
if _, err := f.Read(of.bufTail); err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Contains(buf_end, []byte(MagicSequence)) {
|
if bytes.Contains(of.bufTail, []byte(OggS)) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return of, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OggGetTag is searching for a certain tag in a given buffer buf and returns
|
// GetTag is searching for a certain tag and returns its value or an empty string.
|
||||||
// its value.
|
func (of *OggFile) GetTag(tag string) string {
|
||||||
//
|
tagIdx := bytes.Index(of.bufHead, append([]byte{0, 0, 0}, (tag+"=")...))
|
||||||
// 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+"=")...))
|
|
||||||
if tagIdx == -1 {
|
if tagIdx == -1 {
|
||||||
tagIdx = bytes.Index(buf, append([]byte{0, 0, 0}, (strings.ToUpper(tag)+"=")...))
|
if tagIdx = bytes.Index(of.bufHead, append([]byte{0, 0, 0}, (strings.ToUpper(tag)+"=")...)); tagIdx == -1 {
|
||||||
if tagIdx == -1 {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tagIdx += 3
|
tagIdx += 3
|
||||||
tagNameLen := len(tag) + 1
|
tagNameLen := len(tag) + 1
|
||||||
valStart := tagIdx + tagNameLen
|
valStart := tagIdx + tagNameLen
|
||||||
valLen := int(buf[tagIdx-4]) - tagNameLen
|
valLen := int(of.bufHead[tagIdx-4]) - tagNameLen
|
||||||
return string(buf[valStart : valStart+valLen])
|
return string(of.bufHead[valStart : valStart+valLen])
|
||||||
}
|
}
|
||||||
|
|
||||||
// OggGetDuration returns song's duration in milliseconds.
|
// GetDuration returns song's duration in milliseconds.
|
||||||
func GetDuration(buf_start, buf_end []byte) time.Duration {
|
func (of *OggFile) GetDuration() time.Duration {
|
||||||
rateIdx := bytes.Index(buf_start, []byte(VorbisSequence)) +
|
rateIdx := bytes.Index(of.bufHead, []byte(Vorbis)) +
|
||||||
VorbisSequenceLen + 5
|
VorbisLen + 5
|
||||||
rateBytes := buf_start[rateIdx : rateIdx+4]
|
rateBytes := of.bufHead[rateIdx : rateIdx+4]
|
||||||
rate := int32(rateBytes[0]) + int32(rateBytes[1])<<8 +
|
rate := int32(rateBytes[0]) + int32(rateBytes[1])<<8 +
|
||||||
int32(rateBytes[2])<<16 + int32(rateBytes[3])<<24
|
int32(rateBytes[2])<<16 + int32(rateBytes[3])<<24
|
||||||
|
|
||||||
granuleIdx := bytes.LastIndex(buf_end, []byte(MagicSequence)) +
|
granuleIdx := bytes.LastIndex(of.bufTail, []byte(OggS)) +
|
||||||
MagicSequenceLen + 2
|
OggSLen + 2
|
||||||
granuleBytes := buf_end[granuleIdx : granuleIdx+8]
|
granuleBytes := of.bufTail[granuleIdx : granuleIdx+8]
|
||||||
granule := int64(granuleBytes[0]) + int64(granuleBytes[1])<<8 +
|
granule := int64(granuleBytes[0]) + int64(granuleBytes[1])<<8 +
|
||||||
int64(granuleBytes[2])<<16 + int64(granuleBytes[3])<<24 +
|
int64(granuleBytes[2])<<16 + int64(granuleBytes[3])<<24 +
|
||||||
int64(granuleBytes[4])<<32 + int64(granuleBytes[5])<<40 +
|
int64(granuleBytes[4])<<32 + int64(granuleBytes[5])<<40 +
|
||||||
|
@ -1,66 +1,57 @@
|
|||||||
package oggtag
|
package oggtag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bufferSize = 4096
|
|
||||||
const sampleSong = "/mnt/data/appdata/radio/fallback.ogg"
|
const sampleSong = "/mnt/data/appdata/radio/fallback.ogg"
|
||||||
const sampleArtist = "breskina"
|
const sampleArtist = "breskina"
|
||||||
const sampleTitle = "Песня про мечты"
|
const sampleTitle = "Песня про мечты"
|
||||||
|
|
||||||
func TestGetTag(t *testing.T) {
|
func TestGetTag(t *testing.T) {
|
||||||
f, _ := os.Open(sampleSong)
|
oggf, err := NewOggFile(sampleSong)
|
||||||
buf := make([]byte, bufferSize)
|
if err != nil {
|
||||||
f.Read(buf)
|
t.Fatal(err)
|
||||||
tag := GetTag(buf, "artist")
|
}
|
||||||
|
|
||||||
|
tag := oggf.GetTag("artist")
|
||||||
if tag != sampleArtist {
|
if tag != sampleArtist {
|
||||||
t.Error(tag, "!=", sampleArtist)
|
t.Error(tag, "!=", sampleArtist)
|
||||||
}
|
}
|
||||||
tag = GetTag(buf, "title")
|
tag = oggf.GetTag("title")
|
||||||
if tag != sampleTitle {
|
if tag != sampleTitle {
|
||||||
t.Error(tag, "!=", sampleTitle)
|
t.Error(tag, "!=", sampleTitle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkGetTag(b *testing.B) {
|
func BenchmarkGetTag(b *testing.B) {
|
||||||
f, _ := os.Open(sampleSong)
|
oggf, err := NewOggFile(sampleSong)
|
||||||
buf := make([]byte, bufferSize)
|
if err != nil {
|
||||||
f.Read(buf)
|
b.Fatal(err)
|
||||||
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
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) {
|
func BenchmarkGetDuration(b *testing.B) {
|
||||||
bs, bf, err := ReadFile(sampleSong)
|
oggf, err := NewOggFile(sampleSong)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
GetDuration(bs, bf)
|
oggf.GetDuration()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDuration(t *testing.T) {
|
func TestGetDuration(t *testing.T) {
|
||||||
bs, bf, err := ReadFile(sampleSong)
|
oggf, err := NewOggFile(sampleSong)
|
||||||
if err != nil {
|
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())
|
t.Log(dur, ((dur)/time.Second)*time.Second, dur.Milliseconds())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user