Added hasher support for Crc32 / Sha256 / AccurateRipV1 / AccurateRipV2
This commit is contained in:
parent
bc5e87d095
commit
9011364103
|
@ -99,7 +99,7 @@ type AccurateRipMetadata struct {
|
|||
}
|
||||
|
||||
func (s *Source) Test() {
|
||||
meta := s.FindMetadataByTOC(metadata.NewTOCFromString("267453 150 24647 71194 95579 139576 174573 199089 244305"))
|
||||
meta := s.FindMetadataByTOC(metadata.NewTOCFromString("290543 150 27572 69794 109235 133506 177855 214680 252471"))
|
||||
|
||||
log.Print(meta)
|
||||
}
|
||||
|
|
|
@ -259,7 +259,7 @@ func (s *Source) FindMetadataByTOC(toc metadata.TOC) *CueToolsMetadata {
|
|||
}
|
||||
|
||||
func (s *Source) Test() {
|
||||
album := s.FindByTocID(metadata.NewTOCFromString("267453 150 24647 71194 95579 139576 174573 199089 244305").GetTocID())
|
||||
album := s.FindByTocID(metadata.NewTOCFromString("290543 150 27572 69794 109235 133506 177855 214680 252471").GetTocID())
|
||||
//album := s.FindByTOC(metadata.NewTOCFromString("267453 150 24647 71194 95579 139576 174573 199089 244305"))
|
||||
//album := s.GetRelease("9ca2748b-88fd-44a7-bc5c-036574148571")
|
||||
|
||||
|
|
82
metadata/hash.go
Normal file
82
metadata/hash.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"hash"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type accurateRipDigestV1 struct {
|
||||
crc uint32
|
||||
pos uint32
|
||||
offset uint32
|
||||
}
|
||||
|
||||
func NewAccurateRipV1(offset uint32) hash.Hash32 {
|
||||
return &accurateRipDigestV1{0, offset + 1, offset}
|
||||
}
|
||||
|
||||
func (d *accurateRipDigestV1) Size() int { return 4 }
|
||||
|
||||
func (d *accurateRipDigestV1) BlockSize() int { return 1 }
|
||||
|
||||
func (d *accurateRipDigestV1) Reset() { d.crc = 0; d.pos = d.offset + 1 }
|
||||
|
||||
func (d *accurateRipDigestV1) Sum32() uint32 { return d.crc }
|
||||
|
||||
func (d *accurateRipDigestV1) Sum(in []byte) []byte {
|
||||
s := d.Sum32()
|
||||
return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
|
||||
}
|
||||
|
||||
func (d *accurateRipDigestV1) Write(p []byte) (n int, err error) {
|
||||
numWords := uintptr(len(p)) * unsafe.Sizeof(p[0]) / unsafe.Sizeof(uint32(0))
|
||||
words := unsafe.Slice((*uint32)(unsafe.Pointer(&p[0])), numWords)
|
||||
|
||||
for _, w := range words {
|
||||
//this can wrap
|
||||
d.crc += d.pos * w
|
||||
d.pos++
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
type accurateRipDigestV2 struct {
|
||||
crc uint32
|
||||
pos uint32
|
||||
offset uint32
|
||||
}
|
||||
|
||||
func NewAccurateRipV2(offset uint32) hash.Hash32 {
|
||||
return &accurateRipDigestV2{0, offset + 1, offset}
|
||||
}
|
||||
|
||||
func (d *accurateRipDigestV2) Size() int { return 4 }
|
||||
|
||||
func (d *accurateRipDigestV2) BlockSize() int { return 1 }
|
||||
|
||||
func (d *accurateRipDigestV2) Reset() { d.crc = 0; d.pos = d.offset + 1 }
|
||||
|
||||
func (d *accurateRipDigestV2) Sum32() uint32 { return d.crc }
|
||||
|
||||
func (d *accurateRipDigestV2) Sum(in []byte) []byte {
|
||||
s := d.Sum32()
|
||||
return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
|
||||
}
|
||||
|
||||
func (d *accurateRipDigestV2) Write(p []byte) (n int, err error) {
|
||||
numWords := uintptr(len(p)) * unsafe.Sizeof(p[0]) / unsafe.Sizeof(uint32(0))
|
||||
words := unsafe.Slice((*uint32)(unsafe.Pointer(&p[0])), numWords)
|
||||
|
||||
for _, w := range words {
|
||||
crcNew := uint64(w) * uint64(d.pos)
|
||||
LO := crcNew & 0xFFFFFFFF
|
||||
HI := crcNew / 0x100000000
|
||||
//this can wrap
|
||||
d.crc += uint32(HI)
|
||||
d.crc += uint32(LO)
|
||||
d.pos++
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
|
@ -1,13 +1,170 @@
|
|||
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"
|
||||
"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 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
|
||||
HashtypeAccurateRipV2
|
||||
)
|
||||
|
||||
type Hasher struct {
|
||||
options HasherOptions
|
||||
hash HashType
|
||||
hasher hash.Hash
|
||||
result []byte
|
||||
channel chan *format.AnalyzerPacket
|
||||
wg sync.WaitGroup
|
||||
samples int
|
||||
|
@ -18,41 +175,22 @@ type Hasher struct {
|
|||
buffer [][]int32
|
||||
}
|
||||
|
||||
type HasherOptions struct {
|
||||
PrefixSilenceSamples int
|
||||
AppendSilenceSamples int
|
||||
SkipStartSamples int
|
||||
SkipEndSamples int
|
||||
}
|
||||
|
||||
func NewMuxedHasher(channel chan *format.AnalyzerPacket, options ...HasherOptions) (h []*Hasher) {
|
||||
channels := make([]chan *format.AnalyzerPacket, len(options))
|
||||
for i := range channels {
|
||||
channels[i] = make(chan *format.AnalyzerPacket, 16)
|
||||
h = append(h, NewHasher(channels[i], options[i]))
|
||||
func NewHasher(channel chan *format.AnalyzerPacket, hashType HashType) (h *Hasher) {
|
||||
h = &Hasher{
|
||||
hash: hashType,
|
||||
channel: channel,
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
for _, c := range channels {
|
||||
close(c)
|
||||
}
|
||||
}()
|
||||
switch hashType {
|
||||
case HashtypeCrc32:
|
||||
h.hasher = crc32.NewIEEE()
|
||||
case HashtypeSha256:
|
||||
h.hasher = sha256.New()
|
||||
case HashtypeAccurateRipV1:
|
||||
h.hasher = NewAccurateRipV1(0)
|
||||
case HashtypeAccurateRipV2:
|
||||
h.hasher = NewAccurateRipV2(0)
|
||||
|
||||
for packet := range channel {
|
||||
for _, c := range channels {
|
||||
c <- packet
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewHasher(channel chan *format.AnalyzerPacket, options HasherOptions) (h *Hasher) {
|
||||
h = &Hasher{
|
||||
options: options,
|
||||
channel: channel,
|
||||
}
|
||||
|
||||
h.startRoutine()
|
||||
|
@ -66,29 +204,11 @@ func (h *Hasher) startRoutine() {
|
|||
defer h.wg.Done()
|
||||
|
||||
for packet := range h.channel {
|
||||
if h.samples == 0 {
|
||||
h.buffer = make([][]int32, h.options.SkipStartSamples, h.options.SkipEndSamples)
|
||||
|
||||
if h.options.PrefixSilenceSamples > 0 {
|
||||
h.handlePacket(&format.AnalyzerPacket{
|
||||
Samples: make([]int32, packet.Channels*h.options.PrefixSilenceSamples),
|
||||
Channels: packet.Channels,
|
||||
SampleRate: packet.SampleRate,
|
||||
BitDepth: packet.BitDepth,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
h.handlePacket(packet)
|
||||
}
|
||||
if h.options.AppendSilenceSamples > 0 {
|
||||
h.handlePacket(&format.AnalyzerPacket{
|
||||
Samples: make([]int32, h.channels*h.options.AppendSilenceSamples),
|
||||
Channels: h.channels,
|
||||
SampleRate: h.sampleRate,
|
||||
BitDepth: h.bitDepth,
|
||||
})
|
||||
}
|
||||
|
||||
h.result = h.hasher.Sum([]byte{})
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
|
@ -114,6 +234,35 @@ func (h *Hasher) handlePacket(packet *format.AnalyzerPacket) {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -128,8 +277,12 @@ func (h *Hasher) GetSampleRate() int {
|
|||
return h.sampleRate
|
||||
}
|
||||
|
||||
func (h *Hasher) GetOptions() HasherOptions {
|
||||
return h.options
|
||||
func (h *Hasher) GetHashType() HashType {
|
||||
return h.hash
|
||||
}
|
||||
|
||||
func (h *Hasher) GetResult() []byte {
|
||||
return h.result
|
||||
}
|
||||
|
||||
func (h *Hasher) GetDuration() time.Duration {
|
||||
|
|
|
@ -12,6 +12,10 @@ import (
|
|||
const TocPregap = 150
|
||||
const SectorsPerSecond = 75
|
||||
const DataTrackGap = 11400
|
||||
const BytesPerSector = 2352
|
||||
const CDChannels = 2
|
||||
const Int16SamplesPerSector = BytesPerSector / (2 * CDChannels)
|
||||
const CDSampleRate = Int16SamplesPerSector * SectorsPerSecond
|
||||
|
||||
//TOC includes a list, index 0 being total sectors/end, then start times follow, with TocPregap added
|
||||
type TOC []int
|
||||
|
|
Loading…
Reference in a new issue