Skip to content

Instantly share code, notes, and snippets.

@shawnsmithdev
Created January 20, 2019 23:44
Show Gist options
  • Save shawnsmithdev/e5ccd15a5d4124a429a3a58df22225d1 to your computer and use it in GitHub Desktop.
Save shawnsmithdev/e5ccd15a5d4124a429a3a58df22225d1 to your computer and use it in GitHub Desktop.
Extract the MD5 field from a FLAC file
package main
import (
"encoding/binary"
"encoding/hex"
"log"
"os"
)
const (
// First 4 bytes is FLAC file header
flacHeader = "fLaC"
// Block header is also 4 bytes
blockHeaderSize = 4
// Ignore the first bit of a FLAC block header, which is set on the last metadata block.
// STREAMINFO is the only required block, so the bit may or may not be set
blockHeaderIdMask = 0x7F
// STREAMINFO sets remaining 7 bits as 0
streamInfoId = 0x00
// STREAMINFO size is 34
streamInfoBytes = 34
// Overwrite a byte to 0 in buffer to read as a 4 byte uint32
blockSizeBytes = 4
// MD5 hash starts after flac header (4), STREAMINFO header(4),
// and 18 bytes of other STREAMINFO data
hashStart = 26
// MD5 hashes are 128 bits = 16 bytes
hashBytes = 16
)
func flacMd5(songFile *os.File) string {
var buffer [hashBytes]byte
// Read the flac header and first block header
if _, err := songFile.ReadAt(buffer[:len(flacHeader)+blockHeaderSize], 0); err != nil {
log.Println("FLAC read error", err)
return ""
}
// check flac header
header := string(buffer[:len(flacHeader)])
if flacHeader != header {
log.Printf("FLAC header is wrong, expected %x, got %x", flacHeader, header)
return ""
}
// check first block id is STREAMINFO
bufOffset := len(flacHeader)
if streamInfoId != buffer[bufOffset]&blockHeaderIdMask {
log.Println("FLAC first block id not STREAMINFO")
return ""
}
// check block size is correct for STREAMINFO
buffer[bufOffset] = 0
size := binary.BigEndian.Uint32(buffer[bufOffset : bufOffset+blockSizeBytes])
if streamInfoBytes != size {
log.Println("FLAC STREAMINFO block size not 34")
return ""
}
// read MD5
if _, err := songFile.ReadAt(buffer[:], hashStart); err != nil {
log.Println("FLAC read error", err)
return ""
}
// ignore if all zero (not set)
for _, b := range buffer {
if b != 0 {
hash := hex.EncodeToString(buffer[:])
log.Printf("FLAC MD5=%s for file at %q", hash, songFile.Name())
return hash
}
}
log.Printf("FLAC file %q does not have an MD5 checksum set in its STREAMINFO block\n", songFile.Name())
return ""
}
func main() {
args := os.Args
if len(args) != 2 {
log.Println("flac file argument missing")
return
}
if f, err := os.Open(args[1]); err == nil {
defer func() {
if err := f.Close(); err != nil {
panic(err)
}
}()
flacMd5(f)
} else {
panic(err)
}
}
@Nomen-Luni
Copy link

Thanks for sharing, Shawn. Just what I needed mate. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment