127 lines
3.3 KiB
Racket
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].
|
|
|