diff --git a/internal/http/dj_handlers.go b/internal/http/dj_handlers.go index 5045f43..d0e7a89 100644 --- a/internal/http/dj_handlers.go +++ b/internal/http/dj_handlers.go @@ -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()) diff --git a/pkg/oggtag/oggtag.go b/pkg/oggtag/oggtag.go index e6eda11..5eb3175 100644 --- a/pkg/oggtag/oggtag.go +++ b/pkg/oggtag/oggtag.go @@ -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 + diff --git a/pkg/oggtag/oggtag_test.go b/pkg/oggtag/oggtag_test.go index 74f64a8..69f6b90 100644 --- a/pkg/oggtag/oggtag_test.go +++ b/pkg/oggtag/oggtag_test.go @@ -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()) }