package main import ( "encoding/hex" "flag" "git.gammaspectra.live/S.O.N.G/Kirika/audio" "git.gammaspectra.live/S.O.N.G/Kirika/audio/format" "git.gammaspectra.live/S.O.N.G/Kirika/audio/format/guess" "git.gammaspectra.live/S.O.N.G/Kirika/hasher" "log" "os" "strconv" "time" ) func main() { inputFile := flag.String("input", "", "Input file to decode") flag.Parse() fp, err := os.Open(*inputFile) if err != nil { log.Panic(err) return } defer fp.Close() log.Printf("File: %s\n", *inputFile) decoders, err := guess.GetDecoders(fp, *inputFile) if err != nil { log.Panic(err) return } log.Print("Available decoders:\n") for _, d := range decoders { if ad, ok := d.(format.AnalyzerDecoder); ok { log.Printf("\t%s: %s (analyzer)", ad.Name(), ad.DecoderDescription()) } else { log.Printf("\t%s: %s", d.Name(), d.DecoderDescription()) } } startTime := time.Now() source, analyzer, err := guess.OpenAnalyzer(fp, decoders) if err != nil { source, analyzer, err = format.NewAnalyzerChannel(guess.Open(fp, decoders)) } if err != nil { log.Panic(err) return } analyzers := analyzer.Split(3) hCRC32 := hasher.NewHasher(analyzers[0], hasher.HashtypeCrc32) hSHA256 := hasher.NewHasher(analyzers[1], hasher.HashtypeSha256) hMD5 := hasher.NewHasher(analyzers[2], hasher.HashtypeMd5) var sampleCount, blockCount uint64 if f32Source, ok := source.(audio.TypedSource[float32]); ok { log.Printf("Decoding as float32 @ %dHz %d channel(s)", f32Source.GetSampleRate(), f32Source.GetChannels()) for b := range f32Source.GetBlocks() { blockCount++ sampleCount += uint64(len(b)) } sampleCount /= uint64(f32Source.GetChannels()) } else if i16Source, ok := source.(audio.TypedSource[int16]); ok { log.Printf("Decoding as int16 %d-bit @ %dHz %d channel(s)", i16Source.GetBitDepth(), i16Source.GetSampleRate(), i16Source.GetChannels()) for b := range i16Source.GetBlocks() { blockCount++ sampleCount += uint64(len(b)) } sampleCount /= uint64(i16Source.GetChannels()) } else if i32Source, ok := source.(audio.TypedSource[int32]); ok { log.Printf("Decoding as int32 %d-bit @ %dHz %d channel(s)", i32Source.GetBitDepth(), i32Source.GetSampleRate(), i32Source.GetChannels()) for b := range i32Source.GetBlocks() { blockCount++ sampleCount += uint64(len(b)) } sampleCount /= uint64(i32Source.GetChannels()) } else { f32Source = source.ToFloat32() log.Printf("Decoding as float32 (generic) %d-bit @ %dHz %d channel(s)", f32Source.GetBitDepth(), f32Source.GetSampleRate(), f32Source.GetChannels()) for b := range f32Source.GetBlocks() { blockCount++ sampleCount += uint64(len(b)) } sampleCount /= uint64(f32Source.GetChannels()) } log.Printf("Decoded %d sample(s)", sampleCount) if blockCount > 0 { log.Printf("Processed %d blocks(s) (avg %0.2f sample(s) per block)", blockCount, float64(sampleCount)/float64(blockCount)) } log.Printf("Duration %s", ((time.Duration(sampleCount) * time.Second) / time.Duration(source.GetSampleRate())).String()) hCRC32.Wait() hSHA256.Wait() hMD5.Wait() log.Printf("CRC32 hash: %s", hex.EncodeToString(hCRC32.GetResult())) log.Printf("SHA256 hash: %s", hex.EncodeToString(hSHA256.GetResult())) log.Printf("MD5 hash: %s", hex.EncodeToString(hMD5.GetResult())) if _, ok := source.(audio.TypedSource[float32]); ok { log.Printf("\tCheck via $ ffmpeg -hide_banner -loglevel error -i %s -vn -c:a pcm_f32le -f md5 -", strconv.Quote(*inputFile)) log.Printf("\tNote that you might need to specify a decoder with -c:a before -i to perfectly match the hash on lossy decoders, for example, libopus instead of opus") } else { switch source.GetBitDepth() { case 8, 16, 24, 32: log.Printf("\tCheck via $ ffmpeg -hide_banner -loglevel error -i %s -vn -c:a pcm_s%dle -f md5 -", strconv.Quote(*inputFile), source.GetBitDepth()) default: log.Printf("\tCheck via $ ffmpeg -hide_banner -loglevel error -i %s -vn -c:a pcm_s32le -f md5 -", strconv.Quote(*inputFile)) } } log.Printf("Took %s", time.Now().Sub(startTime).String()) }