2023-10-01 03:34:16 +04:00
|
|
|
package oggtag
|
|
|
|
|
|
|
|
/* oggtag is a naive implementation of OGG tag's reader that is just looking
|
|
|
|
for certain tag names ending with an = character, e.g. artist= and title=.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2023-10-02 01:08:24 +04:00
|
|
|
"io"
|
|
|
|
"os"
|
2023-10-01 06:36:47 +04:00
|
|
|
"strings"
|
2023-10-02 01:08:24 +04:00
|
|
|
"time"
|
2023-10-01 03:34:16 +04:00
|
|
|
)
|
|
|
|
|
2023-10-02 01:08:24 +04:00
|
|
|
const bufferLength = 4096
|
2023-10-02 01:25:43 +04:00
|
|
|
const MagicSequence = "OggS"
|
|
|
|
const MagicSequenceLen = len(MagicSequence)
|
|
|
|
const VorbisSequence = "vorbis"
|
|
|
|
const VorbisSequenceLen = len(VorbisSequence)
|
2023-10-02 01:08:24 +04:00
|
|
|
|
|
|
|
// OggReadFile returns a head of an OGG file, and a buffer contains last frame.
|
2023-10-02 01:25:43 +04:00
|
|
|
func ReadFile(path string) (buf_start, buf_end []byte, _ error) {
|
2023-10-02 01:08:24 +04:00
|
|
|
f, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
buf_start = 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 {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
fst, err := f.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for offset := int64(bufferLength); offset <= fst.Size(); offset += bufferLength {
|
|
|
|
if _, err := f.Seek(-offset, io.SeekEnd); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := f.Read(buf_end); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-02 01:25:43 +04:00
|
|
|
if bytes.Contains(buf_end, []byte(MagicSequence)) {
|
2023-10-02 01:08:24 +04:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-10-01 03:34:16 +04:00
|
|
|
// 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.
|
2023-10-02 01:25:43 +04:00
|
|
|
func GetTag(buf []byte, tag string) string {
|
2023-10-01 06:36:47 +04:00
|
|
|
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 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tagIdx += 3
|
2023-10-01 03:34:16 +04:00
|
|
|
tagNameLen := len(tag) + 1
|
|
|
|
valStart := tagIdx + tagNameLen
|
|
|
|
valLen := int(buf[tagIdx-4]) - tagNameLen
|
|
|
|
return string(buf[valStart : valStart+valLen])
|
|
|
|
}
|
2023-10-02 01:08:24 +04:00
|
|
|
|
|
|
|
// OggGetDuration returns song's duration in milliseconds.
|
2023-10-02 01:25:43 +04:00
|
|
|
func GetDuration(buf_start, buf_end []byte) time.Duration {
|
|
|
|
rateIdx := bytes.Index(buf_start, []byte(VorbisSequence)) +
|
|
|
|
VorbisSequenceLen + 5
|
2023-10-02 01:08:24 +04:00
|
|
|
rateBytes := buf_start[rateIdx : rateIdx+4]
|
|
|
|
rate := int32(rateBytes[0]) + int32(rateBytes[1])<<8 +
|
|
|
|
int32(rateBytes[2])<<16 + int32(rateBytes[3])<<24
|
|
|
|
|
2023-10-02 01:25:43 +04:00
|
|
|
granuleIdx := bytes.LastIndex(buf_end, []byte(MagicSequence)) +
|
|
|
|
MagicSequenceLen + 2
|
2023-10-02 01:08:24 +04:00
|
|
|
granuleBytes := buf_end[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 +
|
|
|
|
int64(granuleBytes[6])<<48 + int64(granuleBytes[7])<<56
|
|
|
|
|
|
|
|
return time.Duration(granule*1000/int64(rate)) * time.Millisecond
|
|
|
|
}
|