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" "io" "os" "strings" "time" ) const ( bufferLength = 6144 OggS = "OggS" OggSLen = len(OggS) Vorbis = "vorbis" VorbisLen = len(Vorbis) ) // OggFile holds a head of a file and a tail part conatining last granule. type OggFile struct { bufHead, bufLast []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, err } defer f.Close() of := &OggFile{} of.bufHead = make([]byte, bufferLength) if _, err := f.Read(of.bufHead); err != nil { return nil, err } of.bufLast = make([]byte, bufferLength) if _, err := f.Seek(-bufferLength, io.SeekEnd); err != nil { return nil, err } fst, err := f.Stat() if err != nil { return nil, err } for offset := int64(bufferLength); offset <= fst.Size(); offset += bufferLength { if _, err := f.Seek(-offset, io.SeekEnd); err != nil { return nil, err } if _, err := f.Read(of.bufLast); err != nil { return nil, err } if bytes.Contains(of.bufLast, []byte(OggS)) { break } } return of, nil } // 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 { if tagIdx = bytes.Index(of.bufHead, append([]byte{0, 0, 0}, (strings.Title(tag)+"=")...)); tagIdx == -1 { return "" } } } tagIdx += 3 tagNameLen := len(tag) + 1 valStart := tagIdx + tagNameLen valLen := int(of.bufHead[tagIdx-4]) - tagNameLen return string(of.bufHead[valStart : valStart+valLen]) } // 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(of.bufLast, []byte(OggS)) + OggSLen + 2 granuleBytes := of.bufLast[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 }