1
0

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:
Alexander Andreev 2023-10-05 17:26:13 +04:00
parent 10bc3a3785
commit 6ae8a40493
Signed by: Arav
GPG Key ID: D22A817D95815393
3 changed files with 68 additions and 73 deletions

View File

@ -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())

View File

@ -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+"=")...))
if tagIdx == -1 {
tagIdx = bytes.Index(buf, append([]byte{0, 0, 0}, (strings.ToUpper(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 {
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 +

View File

@ -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())
}