363 lines
7.9 KiB
Go
363 lines
7.9 KiB
Go
package metadata
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"git.gammaspectra.live/S.O.N.G/Hibiki/utilities/audio/format"
|
|
"github.com/minio/sha256-simd"
|
|
"hash"
|
|
"hash/crc32"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
const chanBuf = 16
|
|
|
|
type HasherChannel chan *format.AnalyzerPacket
|
|
|
|
func (c HasherChannel) Split(n int) (channels []HasherChannel) {
|
|
channels = make([]HasherChannel, n)
|
|
for i := range channels {
|
|
channels[i] = make(HasherChannel, chanBuf)
|
|
}
|
|
|
|
go func() {
|
|
defer func() {
|
|
for _, channel := range channels {
|
|
close(channel)
|
|
}
|
|
}()
|
|
|
|
for packet := range c {
|
|
for _, channel := range channels {
|
|
channel <- packet
|
|
}
|
|
}
|
|
}()
|
|
|
|
return
|
|
}
|
|
|
|
func (c HasherChannel) PrependGap(samples, sampleRate, channels, bitDepth int) (channel HasherChannel) {
|
|
return MergeHasherChannels(NewHasherAudioGap(samples, sampleRate, channels, bitDepth), c)
|
|
}
|
|
|
|
func (c HasherChannel) AppendGap(samples, sampleRate, channels, bitDepth int) (channel HasherChannel) {
|
|
return MergeHasherChannels(c, NewHasherAudioGap(samples, sampleRate, channels, bitDepth))
|
|
}
|
|
|
|
func (c HasherChannel) SkipStartSamples(samples int) (channel HasherChannel) {
|
|
channel = make(HasherChannel, chanBuf)
|
|
go func() {
|
|
defer close(channel)
|
|
|
|
for samples > 0 {
|
|
packet, ok := <-c
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if len(packet.Samples)/packet.Channels > samples {
|
|
startIndex := samples * packet.Channels
|
|
channel <- &format.AnalyzerPacket{
|
|
Samples: packet.Samples[startIndex:],
|
|
Channels: packet.Channels,
|
|
SampleRate: packet.SampleRate,
|
|
BitDepth: packet.BitDepth,
|
|
}
|
|
samples = 0
|
|
break
|
|
} else {
|
|
samples -= len(packet.Samples) / packet.Channels
|
|
continue
|
|
}
|
|
}
|
|
|
|
for packet := range c {
|
|
channel <- packet
|
|
}
|
|
}()
|
|
|
|
return
|
|
}
|
|
|
|
func (c HasherChannel) SkipEndSamples(samples int) (channel HasherChannel) {
|
|
channel = make(HasherChannel, chanBuf)
|
|
go func() {
|
|
defer close(channel)
|
|
|
|
var buffer []*format.AnalyzerPacket
|
|
bufferSamples := 0
|
|
|
|
for packet := range c {
|
|
for len(buffer) > 0 && (bufferSamples-len(buffer[0].Samples)/buffer[0].Channels) > samples {
|
|
channel <- buffer[0]
|
|
bufferSamples -= len(buffer[0].Samples) / buffer[0].Channels
|
|
buffer = buffer[1:]
|
|
}
|
|
|
|
bufferSamples += len(packet.Samples) / packet.Channels
|
|
buffer = append(buffer, packet)
|
|
}
|
|
|
|
for _, packet := range buffer {
|
|
//TODO: check this
|
|
leftSamples := bufferSamples - len(packet.Samples)/packet.Channels
|
|
|
|
if leftSamples <= samples {
|
|
endIndex := len(packet.Samples) - (samples-leftSamples)*packet.Channels
|
|
channel <- &format.AnalyzerPacket{
|
|
Samples: packet.Samples[:endIndex],
|
|
Channels: packet.Channels,
|
|
SampleRate: packet.SampleRate,
|
|
BitDepth: packet.BitDepth,
|
|
}
|
|
samples = 0
|
|
break
|
|
} else {
|
|
channel <- packet
|
|
bufferSamples -= len(packet.Samples) / packet.Channels
|
|
}
|
|
}
|
|
}()
|
|
|
|
return
|
|
}
|
|
|
|
func (c HasherChannel) SkipEndSamplesMultiple(wg *sync.WaitGroup, offset *uint32, samples int) (channel HasherChannel) {
|
|
channel = make(HasherChannel, chanBuf)
|
|
go func() {
|
|
defer close(channel)
|
|
|
|
var buffer []*format.AnalyzerPacket
|
|
bufferSamples := 0
|
|
|
|
maxSamples := samples * 2
|
|
|
|
samplesRead := 0
|
|
for packet := range c {
|
|
for len(buffer) > 0 && (bufferSamples-len(buffer[0].Samples)/buffer[0].Channels) > maxSamples {
|
|
channel <- buffer[0]
|
|
samplesRead += len(buffer[0].Samples) / buffer[0].Channels
|
|
bufferSamples -= len(buffer[0].Samples) / buffer[0].Channels
|
|
buffer = buffer[1:]
|
|
}
|
|
|
|
bufferSamples += len(packet.Samples) / packet.Channels
|
|
buffer = append(buffer, packet)
|
|
}
|
|
|
|
wg.Wait()
|
|
totalSampleOffset := samplesRead + int(atomic.LoadUint32(offset))
|
|
|
|
if len(buffer) > 0 {
|
|
p := &format.AnalyzerPacket{
|
|
Channels: buffer[0].Channels,
|
|
SampleRate: buffer[0].SampleRate,
|
|
BitDepth: buffer[0].BitDepth,
|
|
}
|
|
for _, packet := range buffer {
|
|
p.Samples = append(p.Samples, packet.Samples...)
|
|
}
|
|
nsamples := samples + (((len(p.Samples) / p.Channels) + totalSampleOffset) % samples)
|
|
|
|
if len(p.Samples)/p.Channels > nsamples {
|
|
endIndex := len(p.Samples) - nsamples*p.Channels
|
|
channel <- &format.AnalyzerPacket{
|
|
Samples: p.Samples[:endIndex],
|
|
Channels: p.Channels,
|
|
SampleRate: p.SampleRate,
|
|
BitDepth: p.BitDepth,
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
return
|
|
}
|
|
|
|
func NewHasherAudioGap(samples, sampleRate, channels, bitDepth int) (channel HasherChannel) {
|
|
channel = make(HasherChannel, 1)
|
|
channel <- &format.AnalyzerPacket{
|
|
Samples: make([]int32, samples*channels),
|
|
Channels: channels,
|
|
SampleRate: sampleRate,
|
|
BitDepth: bitDepth,
|
|
}
|
|
close(channel)
|
|
|
|
return
|
|
}
|
|
|
|
func MergeHasherChannels(channels ...HasherChannel) (channel HasherChannel) {
|
|
channel = make(HasherChannel, chanBuf)
|
|
|
|
go func() {
|
|
defer close(channel)
|
|
|
|
for _, c := range channels {
|
|
for packet := range c {
|
|
channel <- packet
|
|
}
|
|
}
|
|
}()
|
|
|
|
return
|
|
}
|
|
|
|
type HashType int
|
|
|
|
const (
|
|
HashtypeCrc32 = HashType(iota)
|
|
HashtypeSha256
|
|
HashtypeAccurateRipV1
|
|
HashtypeAccurateRipV1Start
|
|
HashtypeAccurateRipV2
|
|
HashtypeAccurateRipV2Start
|
|
)
|
|
|
|
type Hasher struct {
|
|
hash HashType
|
|
hasher hash.Hash
|
|
result []byte
|
|
channel HasherChannel
|
|
wg sync.WaitGroup
|
|
samples int
|
|
duration float64
|
|
sampleRate int
|
|
bitDepth int
|
|
channels int
|
|
buffer [][]int32
|
|
}
|
|
|
|
func NewHasher(channel HasherChannel, hashType HashType) (h *Hasher) {
|
|
h = &Hasher{
|
|
hash: hashType,
|
|
channel: channel,
|
|
}
|
|
|
|
switch hashType {
|
|
case HashtypeCrc32:
|
|
h.hasher = crc32.NewIEEE()
|
|
case HashtypeSha256:
|
|
h.hasher = sha256.New()
|
|
case HashtypeAccurateRipV1:
|
|
h.hasher = NewAccurateRipV1(0)
|
|
case HashtypeAccurateRipV1Start:
|
|
h.hasher = NewAccurateRipV1(Int16SamplesPerSector*5 - 1)
|
|
case HashtypeAccurateRipV2:
|
|
h.hasher = NewAccurateRipV2(0)
|
|
case HashtypeAccurateRipV2Start:
|
|
h.hasher = NewAccurateRipV2(Int16SamplesPerSector*5 - 1)
|
|
|
|
}
|
|
|
|
h.startRoutine()
|
|
|
|
return
|
|
}
|
|
|
|
func (h *Hasher) startRoutine() {
|
|
h.wg.Add(1)
|
|
go func() {
|
|
defer h.wg.Done()
|
|
|
|
for packet := range h.channel {
|
|
h.handlePacket(packet)
|
|
}
|
|
|
|
h.result = h.hasher.Sum([]byte{})
|
|
|
|
}()
|
|
}
|
|
|
|
func (h *Hasher) handlePacket(packet *format.AnalyzerPacket) {
|
|
samples := len(packet.Samples) / packet.Channels
|
|
|
|
h.samples += samples
|
|
|
|
if h.sampleRate == 0 {
|
|
h.sampleRate = packet.SampleRate
|
|
} else if h.sampleRate != packet.SampleRate {
|
|
h.sampleRate = -1
|
|
}
|
|
if h.bitDepth == 0 {
|
|
h.bitDepth = packet.BitDepth
|
|
} else if h.bitDepth != packet.BitDepth {
|
|
h.bitDepth = -1
|
|
}
|
|
if h.channels == 0 {
|
|
h.channels = packet.Channels
|
|
} else if h.channels != packet.Channels {
|
|
h.channels = -1
|
|
}
|
|
|
|
h.duration += float64(samples) / float64(packet.SampleRate)
|
|
|
|
var buf []byte
|
|
switch packet.BitDepth {
|
|
case 8:
|
|
buf = make([]byte, len(packet.Samples))
|
|
for i := range packet.Samples {
|
|
buf[i] = byte(packet.Samples[i])
|
|
}
|
|
case 16:
|
|
buf = make([]byte, len(packet.Samples)*2)
|
|
for i := range packet.Samples {
|
|
binary.LittleEndian.PutUint16(buf[i*2:], uint16(int16(packet.Samples[i])))
|
|
}
|
|
case 24:
|
|
buf = make([]byte, len(packet.Samples)*3)
|
|
for i := range packet.Samples {
|
|
buf[i*3] = byte((packet.Samples[i] >> 16) & 0xFF)
|
|
buf[i*3+1] = byte((packet.Samples[i] >> 8) & 0xFF)
|
|
buf[i*3+2] = byte(packet.Samples[i] & 0xFF)
|
|
}
|
|
default:
|
|
buf = make([]byte, len(packet.Samples)*4)
|
|
for i := range packet.Samples {
|
|
binary.LittleEndian.PutUint32(buf[i*4:], uint32(packet.Samples[i]))
|
|
}
|
|
|
|
}
|
|
|
|
h.hasher.Write(buf)
|
|
}
|
|
|
|
func (h *Hasher) GetSampleCount() int {
|
|
return h.samples
|
|
}
|
|
|
|
func (h *Hasher) GetChannels() int {
|
|
return h.channels
|
|
}
|
|
|
|
func (h *Hasher) GetSampleRate() int {
|
|
return h.sampleRate
|
|
}
|
|
|
|
func (h *Hasher) GetHashType() HashType {
|
|
return h.hash
|
|
}
|
|
|
|
func (h *Hasher) GetResult() []byte {
|
|
return h.result
|
|
}
|
|
|
|
func (h *Hasher) GetDuration() time.Duration {
|
|
if h.sampleRate > 0 {
|
|
return time.Duration(float64(time.Second) * (float64(h.samples) / float64(h.sampleRate)))
|
|
}
|
|
|
|
//Fallback calculated duration
|
|
return time.Duration(float64(time.Second) * h.duration)
|
|
}
|
|
|
|
func (h *Hasher) GetWaitGroup() *sync.WaitGroup {
|
|
return &h.wg
|
|
}
|
|
|
|
func (h *Hasher) Wait() {
|
|
h.wg.Wait()
|
|
}
|