METANOIA/metadata/toc.go

185 lines
4.1 KiB
Go

package metadata
import (
"crypto/sha1"
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
)
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
var specialBase64Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._").WithPadding('-')
func NewTOCFromString(toc string, split ...string) (r TOC) {
sep := " "
if len(split) > 0 {
sep = split[0]
}
for _, i := range strings.Split(toc, sep) {
item, err := strconv.Atoi(i)
if err == nil {
r = append(r, item)
}
}
return
}
func NewTOCFromCTDBString(toc string) (r TOC) {
t := NewTOCFromString(toc)
r = append(r, t[len(t)-1]+TocPregap)
for i := 0; i < len(t)-1; i++ {
r = append(r, t[i]+TocPregap)
}
return
}
func NewTOCFromCTDB2String(toc string) (r TOC) {
t := NewTOCFromString(toc, ":")
r = append(r, t[len(t)-1]+TocPregap)
for i := 0; i < len(t)-1; i++ {
r = append(r, t[i]+TocPregap)
}
return
}
func (t TOC) GetTrackNumber() int {
return len(t) - 1
}
func (t TOC) GetDuration() time.Duration {
return (time.Second * time.Duration(t[0]-t[1])) / SectorsPerSecond
}
func (t TOC) GetTrackDuration(index int) time.Duration {
if index < 0 || index > len(t)-2 {
return 0
} else if index == len(t)-2 {
return (time.Second * time.Duration(t[0]-t[index+1])) / SectorsPerSecond
} else {
return (time.Second * time.Duration(t[index+2]-t[index+1])) / SectorsPerSecond
}
}
func (t TOC) CTDBString() string {
toc := make([]string, 0, len(t))
for _, o := range t[1:] {
toc = append(toc, fmt.Sprintf("%d", o-TocPregap))
}
toc = append(toc, fmt.Sprintf("%d", t[0]-TocPregap))
return strings.Join(toc, ":")
}
func (t TOC) CDDBString() string {
return fmt.Sprintf("%s %d %s %d", t.GetCDDB1(), t.GetTrackNumber(), t[1:].String(), int(t.GetDuration().Seconds()))
}
func (t TOC) MusicBrainzString() string {
return fmt.Sprintf("1 %d %s", t.GetTrackNumber(), t.String())
}
func (t TOC) String() string {
toc := make([]string, 0, len(t))
for _, o := range t {
toc = append(toc, fmt.Sprintf("%d", o))
}
return strings.Join(toc, " ")
}
func (t TOC) GetCDDB1() CDDB1 {
length := uint32(t.GetDuration().Seconds())
checksum := uint32(0)
for i := 1; i < len(t); i++ {
n := uint32(t[i] / SectorsPerSecond)
for n > 0 {
checksum += n % 10
n /= 10
}
}
return CDDB1(uint32((len(t)-1)&0xFF) | ((length & 0xFFFF) << 8) | ((checksum % 255) << 24))
}
func (t TOC) GetDiscID() DiscID {
hasher := sha1.New()
hasher.Write([]byte(fmt.Sprintf("%02X", 1)))
hasher.Write([]byte(fmt.Sprintf("%02X", len(t)-1)))
for i := 0; i < 100; i++ {
if i < len(t) { //tracks+lead-out
hasher.Write([]byte(fmt.Sprintf("%08X", t[i])))
} else {
hasher.Write([]byte(fmt.Sprintf("%08X", 0)))
}
}
result := hasher.Sum([]byte{})
return DiscID(specialBase64Encoding.EncodeToString(result))
}
func (t TOC) GetTocID() TocID {
hasher := sha1.New()
for i := 2; i < len(t); i++ {
hasher.Write([]byte(fmt.Sprintf("%08X", t[i]-t[1])))
}
hasher.Write([]byte(fmt.Sprintf("%08X", t[0]-t[1])))
for i := len(t) - 1; i < 100; i++ {
hasher.Write([]byte(fmt.Sprintf("%08X", 0)))
}
result := hasher.Sum([]byte{})
return TocID(specialBase64Encoding.EncodeToString(result))
}
func (t TOC) GetAccurateRipData() (byte, uint32, uint32, CDDB1) {
var TrackOffsetsAdded uint32
var TrackOffsetsMultiplied uint32
var num uint32
for i := 1; i < len(t); i++ {
start := uint32(t[i] - TocPregap)
TrackOffsetsAdded += start
if start < 1 {
start = 1
}
num++
TrackOffsetsMultiplied += start * num
}
TrackOffsetsAdded += uint32(t[0] - TocPregap)
num++
TrackOffsetsMultiplied += uint32(t[0]-TocPregap) * num
return byte(t.GetTrackNumber()), TrackOffsetsAdded, TrackOffsetsMultiplied, t.GetCDDB1()
}
func (t TOC) Equals(o TOC) bool {
if len(t) != len(o) {
return false
}
for i, d := range t {
if o[i] != d {
return false
}
}
return true
}