Files
racket-sound/scrbl/audio-sniffer.rkt
T
2026-04-28 17:59:48 +02:00

127 lines
3.3 KiB
Racket

#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].