2025-10-23 13:06:22 +07:00

181 lines
4.2 KiB
Go

package riff
import (
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
"time"
)
type Parser struct {
r io.Reader
Chan chan *Chunk
ChunkParserTimeout time.Duration
Wg *sync.WaitGroup
ID [4]byte
Size uint32
Format [4]byte
wavHeaderSize uint32
WavAudioFormat uint16
NumChannels uint16
SampleRate uint32
AvgBytesPerSec uint32
BlockAlign uint16
BitsPerSample uint16
}
func (c *Parser) ParseHeaders() error {
id, size, err := c.IDnSize()
if err != nil {
return err
}
c.ID = id
if c.ID != RiffID {
return fmt.Errorf("%s - %s", c.ID, ErrFmtNotSupported)
}
c.Size = size
if err := binary.Read(c.r, binary.BigEndian, &c.Format); err != nil {
return err
}
return nil
}
func (c *Parser) Duration() (time.Duration, error) {
if c == nil {
return 0, errors.New("can't calculate the duration of a nil pointer")
}
if c.ID == [4]byte{} || c.AvgBytesPerSec == 0 {
err := c.Parse()
if err != nil {
return 0, nil
}
}
switch c.Format {
case WavFormatID:
return c.wavDuration()
default:
return 0, ErrFmtNotSupported
}
}
func (c *Parser) String() string {
out := fmt.Sprintf("Format: %s - ", c.Format)
if c.Format == WavFormatID {
out += fmt.Sprintf("%d channels @ %d / %d bits - ", c.NumChannels, c.SampleRate, c.BitsPerSample)
d, _ := c.Duration()
out += fmt.Sprintf("Duration: %f seconds", d.Seconds())
}
return out
}
func (c *Parser) NextChunk() (*Chunk, error) {
if c == nil {
return nil, errors.New("can't calculate the duration of a nil pointer")
}
id, size, err := c.IDnSize()
if err != nil {
return nil, err
}
if size%2 == 1 {
size++
}
ch := &Chunk{
ID: id,
Size: int(size),
R: c.r,
}
return ch, nil
}
func (c *Parser) IDnSize() ([4]byte, uint32, error) {
var ID [4]byte
var blockSize uint32
if err := binary.Read(c.r, binary.BigEndian, &ID); err != nil {
return ID, blockSize, err
}
if err := binary.Read(c.r, binary.LittleEndian, &blockSize); err != err {
return ID, blockSize, err
}
return ID, blockSize, nil
}
func (p *Parser) Parse() error {
if p == nil {
return errors.New("can't calculate the wav duration of a nil pointer")
}
if p.Size == 0 {
id, size, err := p.IDnSize()
if err != nil {
return err
}
p.ID = id
if p.ID != RiffID {
return fmt.Errorf("%s - %s", p.ID, ErrFmtNotSupported)
}
p.Size = size
if err := binary.Read(p.r, binary.BigEndian, &p.Format); err != nil {
return err
}
}
var chunk *Chunk
var err error
for err == nil {
chunk, err = p.NextChunk()
if err != nil {
break
}
if chunk.ID == FmtID {
chunk.DecodeWavHeader(p)
} else {
if p.Chan != nil {
if chunk.Wg == nil {
chunk.Wg = p.Wg
}
chunk.Wg.Add(1)
p.Chan <- chunk
chunk.Wg.Wait()
}
}
if !chunk.IsFullyRead() {
chunk.Drain()
}
}
if p.Wg != nil {
p.Wg.Wait()
}
if p.Chan != nil {
close(p.Chan)
}
if err == io.EOF {
return nil
}
return err
}
func (p *Parser) wavDuration() (time.Duration, error) {
if p.Size == 0 || p.AvgBytesPerSec == 0 {
return 0, fmt.Errorf("can't extract the duration due to the file not properly parsed")
}
duration := time.Duration((float64(p.Size) / float64(p.AvgBytesPerSec)) * float64(time.Second))
return duration, nil
}
func (p *Parser) jumpTo(bytesAhead int) error {
var err error
for bytesAhead > 0 {
readSize := bytesAhead
if readSize > 4000 {
readSize = 4000
}
buf := make([]byte, readSize)
err = binary.Read(p.r, binary.LittleEndian, &buf)
if err != nil {
return nil
}
bytesAhead -= readSize
}
return nil
}