#lang scribble/manual @title{Audio Sniffer} @author{@author+email["Hans Dijkema" "hans@dijkewijk.nl"]} @defmodule[audio-sniffer] This module provides utilities to determine the format of an audio file. It combines lightweight content sniffing with extension-based fallback. The sniffer is designed to be robust against incomplete data and progressively increases the amount of data inspected. @defproc[(audio-sniff-extension [file path-string?]) (or/c string? #f)]{ Returns the file extension (without dot) or @racket[#f] if none could be determined. The result is purely based on the filename and does not inspect file contents. } @defproc[(audio-sniff-format [file path-string?]) symbol?]{ Determines the audio format by inspecting the file contents. The function reads portions of the file (both head and tail) and tries to match known signatures. Returns a symbol such as: @itemlist[ #:style 'compact @item{@racket['mp3]} @item{@racket['flac]} @item{@racket['ogg]} @item{@racket['wav]} @item{@racket['aiff]} @item{@racket['mp4]} @item{@racket['unknown]} ] If the file cannot be read, a filesystem-related symbol is returned instead of raising an exception. } @defproc[(audio-sniff-format/extension [file path-string?]) symbol?]{ Determines the audio format using sniffing, with a fallback to the file extension. If @racket[audio-sniff-format] returns @racket['unknown], the extension is used as a secondary source. This function provides the most practical format detection. } @defproc[(audio-format-known? [fmt symbol?]) boolean?]{ Returns @racket[#t] if the given format symbol is recognized by the sniffer. } @defproc[(audio-format-matches? [file path-string?] [formats (listof symbol?)]) boolean?]{ Returns @racket[#t] if the detected format of @racket[file] is a member of @racket[formats]. Detection is performed using @racket[audio-sniff-format/extension]. } @section{Detection Strategy} The sniffer follows a layered strategy: @itemlist[ #:style 'compact @item{Read a small head section of the file (starting at 4096 bytes)} @item{Inspect tail sections for formats that store metadata at the end (e.g. mp4)} @item{Increase head size exponentially if needed} @item{Fallback to file extension if no signature is found} ] This approach balances performance with robustness, especially for container formats where identifying markers may not be located at the start of the file. @section{Error Handling} Instead of raising exceptions, the sniffer returns symbolic error conditions when the file cannot be inspected: @itemlist[ #:style 'compact @item{@racket['file-not-found]} @item{@racket['file-not-readable]} @item{@racket['not-a-file]} ] These values can be handled by higher-level code without requiring exception handling. @section{Supported Formats} The sniffer recognizes a broad range of formats, including: @itemlist[ #:style 'compact @item{mp3} @item{flac} @item{ogg / opus} @item{wav} @item{aiff} @item{mp4 / m4a} @item{aac} @item{alac} @item{ac3} @item{ape} @item{wavpack} @item{wma} @item{matroska} ] Extension fallback supports the same set of formats. @section{Notes} Sniffing is heuristic in nature. While probably reliable, it is possible that malformed or unusual files are reported as @racket['unknown].