Created
January 20, 2019 23:44
-
-
Save shawnsmithdev/e5ccd15a5d4124a429a3a58df22225d1 to your computer and use it in GitHub Desktop.
Extract the MD5 field from a FLAC file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for sharing, Shawn. Just what I needed mate. 👍