217 lines
6.7 KiB
Markdown
217 lines
6.7 KiB
Markdown
# audio-library-manager
|
|
|
|
`audio-library-manager` maintains a music library in two forms:
|
|
|
|
1. a source FLAC tree, optionally normalised so FLAC files do not exceed a
|
|
configured sample rate; and
|
|
2. a mirrored Opus tree, where FLAC files are converted to `.opus` and
|
|
non-FLAC sidecar files are copied unchanged.
|
|
|
|
The current entry point is `audio-manager.rkt`, exported as the function
|
|
`audio-manager` from `main.rkt`.
|
|
|
|
The implementation is intentionally simple. It is a command-line batch process:
|
|
scan files, compare with the saved state, run a small ordered set of processing
|
|
steps, write the state, send a report. It does not use places, channels,
|
|
threads or a worker protocol.
|
|
|
|
## Requirements
|
|
|
|
Racket packages:
|
|
|
|
- `racket-audio`
|
|
- `simple-ini`
|
|
- `simple-log`
|
|
- `net-lib`
|
|
|
|
Native libraries are provided or loaded by `racket-audio`. For the current audio
|
|
path this normally means FLAC, Opus/Opusenc, TagLib and SoXR. FFmpeg is not part
|
|
of the intended FLAC-to-Opus path; it is only relevant for `racket-audio` features
|
|
that need FFmpeg-based decoding.
|
|
|
|
## Usage
|
|
|
|
From a Racket REPL or a small wrapper script:
|
|
|
|
```racket
|
|
#lang racket/base
|
|
|
|
(require audio-library-manager)
|
|
|
|
(audio-manager "/mnt/music" "/mnt/music-opus")
|
|
```
|
|
|
|
The first argument is the source music tree. The second argument is the target
|
|
Opus mirror tree.
|
|
|
|
The optional log level defaults to `debug`:
|
|
|
|
```racket
|
|
(audio-manager "/mnt/music" "/mnt/music-opus" #:log-level 'info)
|
|
```
|
|
|
|
## Files created in the source tree
|
|
|
|
The manager stores its administration files in the source directory:
|
|
|
|
- `.audio-manager.ini` — configuration, read with `simple-ini`.
|
|
- `.audio-manager.db` — serialized state hash, keyed by normalized relative
|
|
paths.
|
|
- `.audio-manager.log` — log file.
|
|
|
|
State keys are normalized relative paths from the source tree. Absolute mount
|
|
points are not stored, and path separators are normalized to `/`. This makes the
|
|
state less dependent on the local mount point used on a particular machine.
|
|
|
|
## Configuration
|
|
|
|
The manager expects a configuration file at:
|
|
|
|
```text
|
|
<source-tree>/.audio-manager.ini
|
|
```
|
|
|
|
Example:
|
|
|
|
```ini
|
|
[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 section is currently required. If the mail configuration is missing or
|
|
incomplete, the manager fails at startup with a configuration error. This is
|
|
intentional: a batch run should not silently skip reporting.
|
|
|
|
## Processing model
|
|
|
|
The manager uses a file walker with an ordered processing pipeline. Each step
|
|
receives:
|
|
|
|
```racket
|
|
base-path path info
|
|
```
|
|
|
|
and returns the same three values, with `info` possibly enriched. The `info` hash
|
|
contains the relative path, type, extension, state and any metadata discovered by
|
|
earlier steps.
|
|
|
|
The important file states are:
|
|
|
|
- `new` — the relative path was not present in the saved state.
|
|
- `changed` — the path existed before, but type, size or modification time
|
|
changed.
|
|
- `unchanged` — the path exists and its basic filesystem state is unchanged.
|
|
- `deleted` — the path existed in the saved state but no longer exists in the
|
|
source tree.
|
|
|
|
For unchanged entries, the previous enriched `info` hash is reused. For changed
|
|
entries, the previous `info` hash is reused but marked as `changed` and updated
|
|
with the current type, size and modification time. Stages must therefore treat
|
|
`changed` as the signal to re-inspect content-dependent data.
|
|
|
|
The pipeline is grouped conceptually as:
|
|
|
|
1. enrichment: normalize/identify known extensions, sniff audio files, detect
|
|
FLAC-with-ID3, and read FLAC sample rate/bit depth when needed;
|
|
2. conversion/actions: downsample FLAC files above the configured rate, embed a
|
|
sidecar cover image when a FLAC has no embedded picture, convert FLAC to
|
|
Opus, and copy non-FLAC sidecar files;
|
|
3. cleanup/logging: remove target files for deleted source entries and log
|
|
progress.
|
|
|
|
## FLAC handling
|
|
|
|
FLAC files are detected by extension and confirmed by `racket-audio` sniffing.
|
|
A file with extension `.flac` but an ID3 prefix is reported as a FLAC-with-ID3
|
|
case. Such files are not treated as ordinary sidecar files.
|
|
|
|
For valid FLAC files:
|
|
|
|
- sample rate and bit depth are read through the FLAC decoder;
|
|
- files with a sample rate greater than `[flac] max-khz` are converted in place
|
|
through `racket-audio/audio-encoder`;
|
|
- conversion is synchronous and direct;
|
|
- if the file has no embedded picture, `cover.jpg`, `cover.jpeg`, `folder.jpg`,
|
|
`folder.jpeg`, `cover.png` or `folder.png` in the same directory is embedded
|
|
as a front-cover picture.
|
|
|
|
Temporary FLAC files produced by interrupted conversions should be ignored by
|
|
the scanner. The implementation uses manager-specific temporary names for this
|
|
purpose.
|
|
|
|
## Opus mirror handling
|
|
|
|
For every processable FLAC source file, the manager writes a corresponding Opus
|
|
file under the target tree:
|
|
|
|
```text
|
|
source: album/track.flac
|
|
target: album/track.opus
|
|
```
|
|
|
|
The default bitrate is 224 kbps and can be changed with `[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. Typical copied
|
|
sidecar files include cover images, PDFs, cue sheets and logs.
|
|
|
|
When a source entry is deleted, the corresponding target entry is removed. For a
|
|
deleted FLAC source, the target extension is changed from `.flac` to `.opus`.
|
|
Renames are deliberately represented as `deleted` plus `new`; no rename detection
|
|
is attempted.
|
|
|
|
## Logging and report
|
|
|
|
The manager logs to:
|
|
|
|
```text
|
|
<source-tree>/.audio-manager.log
|
|
```
|
|
|
|
The report contains sections for:
|
|
|
|
- FLAC files with ID3 tags;
|
|
- FLAC conversions and cover updates;
|
|
- Opus conversions;
|
|
- file counters for processed, new, changed, unchanged and deleted files;
|
|
- failed conversions and failed copies.
|
|
|
|
At the end of a successful scan the serialized state is written to
|
|
`.audio-manager.db` and the report is sent by mail.
|
|
|
|
## API
|
|
|
|
```racket
|
|
(audio-manager music-path opus-path #:log-level [log-level 'debug])
|
|
```
|
|
|
|
Runs the complete batch process. `music-path` is the source tree and `opus-path`
|
|
is the target Opus mirror tree. The function returns `'done` after a completed
|
|
run.
|
|
|
|
## Design notes
|
|
|
|
The package intentionally keeps responsibilities separated:
|
|
|
|
- `audio-manager.rkt` orchestrates the batch run and owns the pipeline;
|
|
- `private/file-walker.rkt` scans the filesystem and maintains the state model;
|
|
- `private/flac-handling.rkt` contains FLAC inspection and FLAC downsampling;
|
|
- `private/opus-handling.rkt` contains FLAC-to-Opus conversion;
|
|
- `private/mail.rkt` sends reports;
|
|
- `private/log.rkt` defines the logger;
|
|
- `private/util.rkt` contains small path/date/cover helpers.
|
|
|
|
The manager does not implement an audio pipeline itself. Audio decoding,
|
|
encoding, metadata and resampling are delegated to `racket-audio`.
|