Files
audio-library-manager/scribblings/audio-library-manager.scrbl
T

197 lines
6.9 KiB
Racket

#lang scribble/manual
@(require (for-label racket/base
"../main.rkt"))
@title{audio-library-manager}
@author{Hans Dijkema}
@defmodule[audio-library-manager]
The @racketmodname[audio-library-manager] package contains a simple batch manager
for maintaining a FLAC source library and an Opus mirror tree.
The current public entry point is @racket[audio-manager]. The manager scans a
source tree, compares the scan with a saved state hash, enriches file information
through a small ordered pipeline, performs the necessary FLAC and Opus actions,
writes the updated state, logs progress, and sends a mail report.
The design is deliberately synchronous. It does not use places, channels,
threads or a worker protocol. Conversion is performed by ordinary function calls
to @racketmodname[racket-audio].
@section{Administration files}
The manager stores these files in the source directory:
@itemlist[
@item{@filepath{.audio-manager.ini}: configuration read through
@racketmodname[simple-ini].}
@item{@filepath{.audio-manager.db}: serialized state hash, keyed by normalized
relative paths.}
@item{@filepath{.audio-manager.log}: log file written through
@racketmodname[simple-log].}]
State keys are relative to the configured source tree. Absolute mount points are
not stored, and path separators are normalized to @tt{/}. This makes the state
usable across machines where the same library may be mounted at different base
paths.
@section{Usage}
From a Racket program or REPL:
@racketblock[
(require audio-library-manager)
(audio-manager "/mnt/music" "/mnt/music-opus")
]
The optional log level defaults to @racket['debug]:
@racketblock[
(audio-manager "/mnt/music" "/mnt/music-opus" #:log-level 'info)
]
@section{Configuration}
The manager expects @filepath{.audio-manager.ini} in the source tree. A minimal
configuration looks like this:
@verbatim|{
[flac]
max-khz=48000
[opus]
kbps=224
[mail]
from=Audio Manager <audio@example.org>
to=Hans <hans@example.org>
server=smtp.example.org
port=25
user=
passwd=
}|
The mail configuration is required in the current implementation. If it is
missing or incomplete, the manager fails at startup. This is intentional for a
batch process where reporting should not silently disappear.
@section{Processing model}
The file walker produces entries with an @racket[info] hash. The manager then
runs an ordered list of steps. Each step receives three values:
@racketblock[
base-path
path
info
]
and returns the same three values, possibly with @racket[info] enriched. The
important states in @racket[info] are:
@itemlist[
@item{@racket['new]: the relative path is not present in the saved state.}
@item{@racket['changed]: the path exists, but type, size or modification time
changed.}
@item{@racket['unchanged]: the path exists and its basic filesystem state is
unchanged.}
@item{@racket['deleted]: the path existed in the saved state but no longer
exists in the source tree.}]
For @racket['unchanged] entries, the previous enriched @racket[info] hash is
reused. For @racket['changed] entries, the previous @racket[info] hash is reused
but marked as changed and updated with the current filesystem data. Content-aware
steps should therefore use @racket['changed] as the signal to re-inspect data
such as sample rate, bit depth and sniffed audio format.
Conceptually, the pipeline has three groups:
@itemlist[
@item{Enrichment: extension normalization, audio sniffing, FLAC-with-ID3
detection, and FLAC sample-rate/bit-depth inspection.}
@item{Actions: FLAC downsampling, embedding sidecar cover art, FLAC-to-Opus
conversion, and copying non-FLAC sidecar files.}
@item{Cleanup and logging: removal of target files for deleted source entries
and progress logging.}]
@section{FLAC handling}
FLAC source files are presumed by extension and then confirmed by audio sniffing.
A file with extension @filepath{.flac} but an ID3 prefix is reported as a
FLAC-with-ID3 case. Such a file is not copied to the Opus tree as an ordinary
sidecar file.
For valid FLAC files, the manager reads sample rate and bit depth through the
FLAC decoder. Files above the configured @tt{[flac] max-khz} value are converted
in place by @racketmodname[racket-audio/audio-encoder]. The conversion is a
normal synchronous function call.
If a FLAC file has no embedded picture, the manager looks in the same directory
for @filepath{cover.jpg}, @filepath{cover.jpeg}, @filepath{folder.jpg},
@filepath{folder.jpeg}, @filepath{cover.png}, or @filepath{folder.png}. When one
is found, it is embedded as front-cover artwork.
@section{Opus mirror handling}
For every processable FLAC source file, the manager writes an Opus target file
with the same relative path and extension @filepath{.opus}:
@verbatim|{
source: album/track.flac
target: album/track.opus
}|
The default bitrate is 224 kbps and can be changed with @tt{[opus] kbps}.
Non-FLAC files are copied unchanged to the target tree when they are new,
changed, or missing from the target. Hidden files are ignored. This is intended
for sidecar files such as cover images, PDFs, cue sheets and logs.
When a source entry disappears, the corresponding target entry is removed. For a
deleted FLAC source, the target extension is changed from @filepath{.flac} to
@filepath{.opus}. Renames are deliberately represented as a deleted old path and
a new path; no rename detection is attempted.
@section{Logging and report}
Logging goes to @filepath{.audio-manager.log} in the source tree and is also
shown on the console according to the selected log level. At the end of a
completed run the manager writes the serialized state to @filepath{.audio-manager.db}
and sends a report by mail.
The report contains sections for FLAC-with-ID3 cases, FLAC actions, Opus actions,
file counters, failed conversions and failed copies.
@section{API}
@defproc[(audio-manager
[music-path path-string?]
[opus-path path-string?]
[#:log-level log-level symbol? 'debug])
symbol?]{
Runs the complete batch process. @racket[music-path] is the source music tree.
@racket[opus-path] is the target Opus mirror tree. The function returns
@racket['done] after a completed run.}
@section{Module responsibilities}
The package keeps the responsibilities separated:
@itemlist[
@item{@filepath{audio-manager.rkt}: orchestration, counters, report text and
the ordered processing pipeline.}
@item{@filepath{private/file-walker.rkt}: filesystem scan and state transition
model.}
@item{@filepath{private/flac-handling.rkt}: FLAC metadata inspection and FLAC
downsampling.}
@item{@filepath{private/opus-handling.rkt}: FLAC-to-Opus conversion.}
@item{@filepath{private/mail.rkt}: SMTP report sending.}
@item{@filepath{private/log.rkt}: logger definition.}
@item{@filepath{private/util.rkt}: small path, date and cover-art helpers.}]
The manager does not implement audio codecs itself. Decoding, encoding,
metadata, cover handling and resampling are delegated to @racketmodname[racket-audio].