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

View File

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

View File

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