documentation and small bug fixes
This commit is contained in:
@@ -17,3 +17,6 @@ compiled/
|
|||||||
|
|
||||||
/private/*.bak
|
/private/*.bak
|
||||||
/*.bak
|
/*.bak
|
||||||
|
scribblings/*.html
|
||||||
|
scribblings/*.css
|
||||||
|
scribblings/*.js
|
||||||
|
|||||||
@@ -1,134 +1,216 @@
|
|||||||
# audio-library-manager
|
# audio-library-manager
|
||||||
|
|
||||||
Tools for maintaining audio library trees.
|
`audio-library-manager` maintains a music library in two forms:
|
||||||
|
|
||||||
The package currently contains two managers:
|
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.
|
||||||
|
|
||||||
- `flac-48khz-manager.rkt`: keeps a FLAC tree at a maximum sample rate of 48 kHz.
|
The current entry point is `audio-manager.rkt`, exported as the function
|
||||||
- `flac2opus-manager.rkt`: mirrors a FLAC source tree to an Opus target tree.
|
`audio-manager` from `main.rkt`.
|
||||||
|
|
||||||
Both managers use the same administration files in the source tree:
|
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.
|
||||||
|
|
||||||
- `.music-info.db`: keystore state database
|
## Requirements
|
||||||
- `.flac-48khz-manager.ini`: configuration
|
|
||||||
- `.flac-48khz-manager.log`: log file
|
|
||||||
|
|
||||||
The `flac2opus` state uses its own key prefix in `.music-info.db`, so it can share
|
Racket packages:
|
||||||
the same database with the 48 kHz manager without mixing state entries. State
|
|
||||||
keys use only normalized paths relative to the configured source/base directory:
|
|
||||||
absolute mount points are not stored, and path separators are always `/`.
|
|
||||||
|
|
||||||
## FLAC 48 kHz manager
|
- `racket-audio`
|
||||||
|
- `simple-ini`
|
||||||
|
- `simple-log`
|
||||||
|
- `net-lib`
|
||||||
|
|
||||||
The command keeps a FLAC directory tree at a maximum sample rate of 48 kHz.
|
Native libraries are provided or loaded by `racket-audio`. For the current audio
|
||||||
Files above the configured threshold are converted in place by a direct call to
|
path this normally means FLAC, Opus/Opusenc, TagLib and SoXR. FFmpeg is not part
|
||||||
`racket-audio/audio-encoder`. The conversion code is deliberately synchronous:
|
of the intended FLAC-to-Opus path; it is only relevant for `racket-audio` features
|
||||||
no places, channels or worker protocol are used.
|
that need FFmpeg-based decoding.
|
||||||
|
|
||||||
Run:
|
## Usage
|
||||||
|
|
||||||
```sh
|
From a Racket REPL or a small wrapper script:
|
||||||
racket flac-48khz-manager.rkt /path/to/flac-tree
|
|
||||||
```
|
|
||||||
|
|
||||||
When a FLAC file has no embedded picture and the same directory contains one of
|
|
||||||
`cover.jpg`, `folder.jpg`, `cover.png` or `folder.png`, the manager embeds that
|
|
||||||
image as front-cover picture before fingerprinting/conversion. This keeps FLAC
|
|
||||||
artwork consistent before a later Opus mirror is made.
|
|
||||||
|
|
||||||
The `flac-48khz-manager` scanner only processes `.flac` files. Other files such
|
|
||||||
as `.mp3`, booklets and cover images are ignored by this manager.
|
|
||||||
|
|
||||||
## FLAC to Opus mirror manager
|
|
||||||
|
|
||||||
The command mirrors a source directory to a target directory. FLAC files are
|
|
||||||
converted to Ogg Opus files with extension `.opus`; all other regular files are
|
|
||||||
copied unchanged, preserving their relative path and modification time. Examples
|
|
||||||
include `booklet.pdf`, `cover.jpg`, `folder.png`, cue sheets, text files and
|
|
||||||
other sidecar files.
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
racket flac2opus-manager.rkt /path/to/flac-tree /path/to/opus-tree
|
|
||||||
racket flac2opus-manager.rkt --kbps 192 /path/to/flac-tree /path/to/opus-tree
|
|
||||||
```
|
|
||||||
|
|
||||||
The default Opus bitrate is 224 kbps. The library API exposes the same setting
|
|
||||||
as keyword argument:
|
|
||||||
|
|
||||||
```racket
|
```racket
|
||||||
(manage-flac2opus-tree source-directory target-directory #:kbps 224)
|
#lang racket/base
|
||||||
|
|
||||||
|
(require audio-library-manager)
|
||||||
|
|
||||||
|
(audio-manager "/mnt/music" "/mnt/music-opus")
|
||||||
```
|
```
|
||||||
|
|
||||||
Metadata is copied through `racket-audio/taglib` and `racket-audio/audio-encoder`:
|
The first argument is the source music tree. The second argument is the target
|
||||||
ordinary TagLib properties are transferred, embedded pictures are transferred,
|
Opus mirror tree.
|
||||||
and an additional `FLAC2OPUS` comment is written to mark the conversion. The
|
|
||||||
batch converter itself remains a simple synchronous function call.
|
|
||||||
|
|
||||||
The manager removes target files that belonged to source files which disappeared
|
The optional log level defaults to `debug`:
|
||||||
since the previous run. It deliberately does not mirror its own root-level
|
|
||||||
administration files: `.music-info.db`, `.flac-48khz-manager.ini` and
|
|
||||||
`.flac-48khz-manager.log`.
|
|
||||||
|
|
||||||
Paths are handled as Racket paths rather than by splitting on `/`, so Windows
|
```racket
|
||||||
absolute paths and UNC paths such as `\\panderleou\music` are preserved by the
|
(audio-manager "/mnt/music" "/mnt/music-opus" #:log-level 'info)
|
||||||
platform path implementation.
|
```
|
||||||
|
|
||||||
|
## 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
|
## Configuration
|
||||||
|
|
||||||
Important configuration keys are created automatically in `.flac-48khz-manager.ini`.
|
The manager expects a configuration file at:
|
||||||
The same file is used by both managers. The implementation now reads this file as a simple-ini value directly; there is no separate configuration struct layered on top of the ini model.
|
|
||||||
|
|
||||||
```ini
|
```text
|
||||||
[manager]
|
<source-tree>/.audio-manager.ini
|
||||||
max-sample-rate=48000
|
|
||||||
hash-algorithm="sha256"
|
|
||||||
change-detection="flac-taglib"
|
|
||||||
dry-run=#f
|
|
||||||
display-log=#t
|
|
||||||
log-file=".flac-48khz-manager.log"
|
|
||||||
compression-level=5
|
|
||||||
|
|
||||||
[opus-manager]
|
|
||||||
log-file=".flac2opus-manager.log"
|
|
||||||
|
|
||||||
[mail]
|
|
||||||
enabled=#f
|
|
||||||
send-on-success=#f
|
|
||||||
send-on-error=#t
|
|
||||||
host=""
|
|
||||||
port=25
|
|
||||||
tls=#f
|
|
||||||
username=""
|
|
||||||
password=""
|
|
||||||
from=""
|
|
||||||
to=""
|
|
||||||
cc=""
|
|
||||||
bcc=""
|
|
||||||
subject-prefix="[flac-48khz-manager]"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
SMTP reports are HTML and summarize counters and errors.
|
Example:
|
||||||
|
|
||||||
## Change detection
|
```ini
|
||||||
|
[flac]
|
||||||
|
max-khz=48000
|
||||||
|
|
||||||
The default change detection is `flac-taglib`:
|
[opus]
|
||||||
|
kbps=224
|
||||||
|
|
||||||
1. unchanged files are skipped with a cheap `size` + `mtime` comparison;
|
[mail]
|
||||||
2. new or visibly changed files get a semantic fingerprint;
|
from=Audio Manager <audio@example.org>
|
||||||
3. for FLAC files, the semantic fingerprint consists of FLAC STREAMINFO data,
|
to=Hans <hans@example.org>
|
||||||
including the FLAC audio MD5 signature, plus TagLib properties and embedded
|
server=smtp.example.org
|
||||||
picture metadata/content hash;
|
port=25
|
||||||
4. for non-FLAC files in the Opus mirror, the default is `mtime` + `size`;
|
user=
|
||||||
5. full-file hashing is available by setting `change-detection="hash"`.
|
passwd=
|
||||||
|
```
|
||||||
|
|
||||||
This avoids reading every complete audio file during a normal run, which matters
|
The mail section is currently required. If the mail configuration is missing or
|
||||||
on Windows and network shares.
|
incomplete, the manager fails at startup with a configuration error. This is
|
||||||
|
intentional: a batch run should not silently skip reporting.
|
||||||
|
|
||||||
## FLAC files with ID3v2 prefixes
|
## Processing model
|
||||||
|
|
||||||
Version 0.1.4 accepts native FLAC files that start directly with `fLaC`, and FLAC
|
The manager uses a file walker with an ordered processing pipeline. Each step
|
||||||
files with an ID3v2 tag before the `fLaC` marker. The latter occurs in some
|
receives:
|
||||||
libraries and is accepted by players such as foobar2000/libFLAC. The fast
|
|
||||||
STREAMINFO reader skips the ID3v2 prefix before reading the FLAC metadata.
|
```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`.
|
||||||
|
|||||||
+1
-1
@@ -256,7 +256,7 @@
|
|||||||
;; Kopieer bestanden rechtstreeks indien nodig en geen flac
|
;; Kopieer bestanden rechtstreeks indien nodig en geen flac
|
||||||
(define (copy-other base-path path info)
|
(define (copy-other base-path path info)
|
||||||
(when (and (needs-copying? path info)
|
(when (and (needs-copying? path info)
|
||||||
(not (flac? path info)))
|
(not (presumed-flac? info)))
|
||||||
(with-handlers ([exn? (λ (e)
|
(with-handlers ([exn? (λ (e)
|
||||||
(set! failed-copies (+ failed-copies 1))
|
(set! failed-copies (+ failed-copies 1))
|
||||||
(err-am "Copy file ~a: ~a" path e))])
|
(err-am "Copy file ~a: ~a" path e))])
|
||||||
|
|||||||
@@ -10,5 +10,5 @@
|
|||||||
(define scribblings '(("scribblings/audio-library-manager.scrbl" ())))
|
(define scribblings '(("scribblings/audio-library-manager.scrbl" ())))
|
||||||
(define build-deps '("rackunit-lib" "scribble-lib" "racket-doc"))
|
(define build-deps '("rackunit-lib" "scribble-lib" "racket-doc"))
|
||||||
(define pkg-desc "Audio library maintenance tools for FLAC and Opus trees")
|
(define pkg-desc "Audio library maintenance tools for FLAC and Opus trees")
|
||||||
(define version "0.2.6")
|
(define version "0.2.9")
|
||||||
(define pkg-authors '(hans-dijkema))
|
(define pkg-authors '(hans-dijkema))
|
||||||
|
|||||||
@@ -91,9 +91,11 @@
|
|||||||
(if (file-info-equal? info in-db)
|
(if (file-info-equal? info in-db)
|
||||||
(hash-set! in-db 'file-db 'unchanged)
|
(hash-set! in-db 'file-db 'unchanged)
|
||||||
(begin
|
(begin
|
||||||
|
(hash-set! in-db 'type (hash-ref info 'type))
|
||||||
(hash-set! in-db 'file-db 'changed)
|
(hash-set! in-db 'file-db 'changed)
|
||||||
(hash-set! in-db 'size (hash-ref info 'size))
|
(when (eq? (hash-ref info 'type #f) 'file)
|
||||||
(hash-set! in-db 'mtime (hash-ref info 'mtime))))
|
(hash-set! in-db 'size (hash-ref info 'size))
|
||||||
|
(hash-set! in-db 'mtime (hash-ref info 'mtime)))))
|
||||||
(hash-set! db-hash normalized-sub-path in-db)
|
(hash-set! db-hash normalized-sub-path in-db)
|
||||||
(values base-path path in-db)
|
(values base-path path in-db)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,7 +33,12 @@
|
|||||||
(encoder-settings (hash 'target-sample-rate rate
|
(encoder-settings (hash 'target-sample-rate rate
|
||||||
'compression-level 8))
|
'compression-level 8))
|
||||||
)
|
)
|
||||||
(with-handlers ([exn? (λ (e)
|
(with-handlers ([exn:break? (λ (e)
|
||||||
|
(err-am "Ctrl-c used")
|
||||||
|
(when (file-exists? tmp-file)
|
||||||
|
(delete-file tmp-file))
|
||||||
|
(raise e))]
|
||||||
|
[exn? (λ (e)
|
||||||
(err-am (format "~a" e))
|
(err-am (format "~a" e))
|
||||||
(when (file-exists? tmp-file)
|
(when (file-exists? tmp-file)
|
||||||
(delete-file tmp-file))
|
(delete-file tmp-file))
|
||||||
|
|||||||
@@ -1,149 +1,196 @@
|
|||||||
#lang scribble/manual
|
#lang scribble/manual
|
||||||
|
|
||||||
@(require (for-label racket/base
|
@(require (for-label racket/base
|
||||||
(file "../main.rkt")))
|
"../main.rkt"))
|
||||||
|
|
||||||
@title{audio-library-manager}
|
@title{audio-library-manager}
|
||||||
@author{Hans Dijkema}
|
@author{Hans Dijkema}
|
||||||
|
|
||||||
The @racketmodname[audio-library-manager] package contains command-line tools for
|
@defmodule[audio-library-manager]
|
||||||
maintaining audio library trees.
|
|
||||||
|
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}
|
@section{Administration files}
|
||||||
|
|
||||||
The managers use these files below the source directory:
|
The manager stores these files in the source directory:
|
||||||
|
|
||||||
@itemlist[
|
@itemlist[
|
||||||
@item{@filepath{.music-info.db}: keystore database with file state.}
|
@item{@filepath{.audio-manager.ini}: configuration read through
|
||||||
@item{@filepath{.flac-48khz-manager.ini}: configuration.}
|
@racketmodname[simple-ini].}
|
||||||
@item{@filepath{.flac-48khz-manager.log}: log file.}]
|
@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].}]
|
||||||
|
|
||||||
The FLAC-to-Opus manager uses a separate key prefix in @filepath{.music-info.db},
|
State keys are relative to the configured source tree. Absolute mount points are
|
||||||
so it can share the same database with the 48 kHz manager.
|
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{FLAC 48 kHz manager}
|
@section{Usage}
|
||||||
|
|
||||||
The tool @filepath{flac-48khz-manager.rkt} keeps a FLAC tree at a maximum sample
|
From a Racket program or REPL:
|
||||||
rate of 48 kHz. Files with a higher sample rate are converted in place by a
|
|
||||||
direct call to @racketmodname[racket-audio/audio-encoder]. The batch conversion
|
|
||||||
path is deliberately synchronous; it does not use places, channels or a worker
|
|
||||||
protocol.
|
|
||||||
|
|
||||||
Run the manager as:
|
@racketblock[
|
||||||
|
(require audio-library-manager)
|
||||||
|
|
||||||
@verbatim{racket flac-48khz-manager.rkt <base-directory>}
|
(audio-manager "/mnt/music" "/mnt/music-opus")
|
||||||
|
]
|
||||||
|
|
||||||
When a FLAC file has no embedded picture and its directory contains
|
The optional log level defaults to @racket['debug]:
|
||||||
@filepath{cover.jpg}, @filepath{folder.jpg}, @filepath{cover.png} or
|
|
||||||
@filepath{folder.png}, the manager embeds that image as front-cover picture
|
|
||||||
before fingerprinting and conversion.
|
|
||||||
|
|
||||||
The manager only processes @filepath{.flac} files; @filepath{.mp3} files and
|
@racketblock[
|
||||||
other sidecar files are ignored by this command.
|
(audio-manager "/mnt/music" "/mnt/music-opus" #:log-level 'info)
|
||||||
|
]
|
||||||
@section{FLAC to Opus mirror manager}
|
|
||||||
|
|
||||||
The tool @filepath{flac2opus-manager.rkt} mirrors a source directory to a target
|
|
||||||
directory. FLAC files are converted to Ogg Opus files with extension
|
|
||||||
@filepath{.opus}. Other regular files are copied unchanged, preserving their
|
|
||||||
relative path and modification time. This includes sidecar files such as
|
|
||||||
@filepath{booklet.pdf}, @filepath{cover.jpg}, cue sheets and text files.
|
|
||||||
|
|
||||||
Run the manager as:
|
|
||||||
|
|
||||||
@verbatim{racket flac2opus-manager.rkt <source-directory> <target-directory>}
|
|
||||||
|
|
||||||
The default Opus bitrate is 224 kbps. A different bitrate can be selected with:
|
|
||||||
|
|
||||||
@verbatim{racket flac2opus-manager.rkt --kbps 192 <source-directory> <target-directory>}
|
|
||||||
|
|
||||||
Metadata is copied through @racketmodname[racket-audio/taglib] and
|
|
||||||
@racketmodname[racket-audio/audio-encoder]. TagLib properties and embedded
|
|
||||||
pictures are transferred to the Opus file. The manager also writes a
|
|
||||||
@tt{FLAC2OPUS} comment indicating that the file was converted by the manager.
|
|
||||||
The conversion function is a normal synchronous function call.
|
|
||||||
|
|
||||||
When a source file disappears, the corresponding target file is removed on the
|
|
||||||
next run. The manager does not mirror its own root-level administration files.
|
|
||||||
Paths are handled as Racket paths instead of by splitting on @litchar{/}, so
|
|
||||||
Windows absolute paths and UNC paths are left to the platform path
|
|
||||||
implementation.
|
|
||||||
|
|
||||||
@section{Configuration}
|
@section{Configuration}
|
||||||
|
|
||||||
The configuration file is created automatically when it does not exist. The managers read it directly as sections and keys through @racketmodname[simple-ini]; there is no separate configuration struct layered on top of the ini model. The main settings are:
|
The manager expects @filepath{.audio-manager.ini} in the source tree. A minimal
|
||||||
|
configuration looks like this:
|
||||||
|
|
||||||
@verbatim{
|
@verbatim|{
|
||||||
[manager]
|
[flac]
|
||||||
max-sample-rate=48000
|
max-khz=48000
|
||||||
hash-algorithm="sha256"
|
|
||||||
change-detection="flac-taglib"
|
|
||||||
dry-run=#f
|
|
||||||
display-log=#t
|
|
||||||
log-file=".flac-48khz-manager.log"
|
|
||||||
compression-level=5
|
|
||||||
|
|
||||||
[opus-manager]
|
[opus]
|
||||||
log-file=".flac2opus-manager.log"
|
kbps=224
|
||||||
|
|
||||||
[mail]
|
[mail]
|
||||||
enabled=#f
|
from=Audio Manager <audio@example.org>
|
||||||
send-on-success=#f
|
to=Hans <hans@example.org>
|
||||||
send-on-error=#t
|
server=smtp.example.org
|
||||||
host=""
|
|
||||||
port=25
|
port=25
|
||||||
tls=#f
|
user=
|
||||||
username=""
|
passwd=
|
||||||
password=""
|
}|
|
||||||
from=""
|
|
||||||
to=""
|
|
||||||
cc=""
|
|
||||||
bcc=""
|
|
||||||
subject-prefix="[flac-48khz-manager]"
|
|
||||||
}
|
|
||||||
|
|
||||||
When mail is enabled, the report is sent as HTML. The message contains the
|
The mail configuration is required in the current implementation. If it is
|
||||||
summary counters and the error table; it does not dump the full log by default.
|
missing or incomplete, the manager fails at startup. This is intentional for a
|
||||||
|
batch process where reporting should not silently disappear.
|
||||||
|
|
||||||
@section{Change detection}
|
@section{Processing model}
|
||||||
|
|
||||||
The default @tt{change-detection} mode is @tt{flac-taglib}. Unchanged files are
|
The file walker produces entries with an @racket[info] hash. The manager then
|
||||||
first skipped by comparing @tt{size} and @tt{mtime} from the keystore state. New
|
runs an ordered list of steps. Each step receives three values:
|
||||||
or visibly changed files get a semantic fingerprint made from FLAC STREAMINFO
|
|
||||||
and TagLib metadata. The FLAC part includes the STREAMINFO audio MD5 signature;
|
|
||||||
the TagLib part includes properties and an embedded-picture content hash.
|
|
||||||
|
|
||||||
For non-FLAC files mirrored by @filepath{flac2opus-manager.rkt}, the default
|
@racketblock[
|
||||||
signature is @tt{mtime} plus @tt{size}. A full-file hash remains available by
|
base-path
|
||||||
setting @tt{change-detection="hash"}.
|
path
|
||||||
|
info
|
||||||
|
]
|
||||||
|
|
||||||
@section{Library API}
|
and returns the same three values, possibly with @racket[info] enriched. The
|
||||||
|
important states in @racket[info] are:
|
||||||
|
|
||||||
@defproc[(manage-flac-tree
|
@itemlist[
|
||||||
[base-directory path-string?]
|
@item{@racket['new]: the relative path is not present in the saved state.}
|
||||||
[#:inspect-flac-proc inspect-flac-proc procedure? inspect-flac-sample-rate]
|
@item{@racket['changed]: the path exists, but type, size or modification time
|
||||||
[#:fingerprint-proc fingerprint-proc procedure? flac-taglib-fingerprint]
|
changed.}
|
||||||
[#:convert-proc convert-proc procedure? convert-flac-to-target-in-place])
|
@item{@racket['unchanged]: the path exists and its basic filesystem state is
|
||||||
list?]{
|
unchanged.}
|
||||||
Scans @racket[base-directory], updates the keystore state, converts FLAC files
|
@item{@racket['deleted]: the path existed in the saved state but no longer
|
||||||
above the configured maximum sample rate, sends the optional HTML mail report,
|
exists in the source tree.}]
|
||||||
and returns a summary association list.
|
|
||||||
|
|
||||||
The keyword arguments are intended for tests and dry integration work. In normal
|
For @racket['unchanged] entries, the previous enriched @racket[info] hash is
|
||||||
use, the default inspector, fingerprint function and converter are used.}
|
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.
|
||||||
|
|
||||||
@defproc[(manage-flac2opus-tree
|
Conceptually, the pipeline has three groups:
|
||||||
[source-directory path-string?]
|
|
||||||
[target-directory path-string?]
|
|
||||||
[#:kbps kbps exact-positive-integer? 224]
|
|
||||||
[#:convert-proc convert-proc procedure? convert-flac-to-opus])
|
|
||||||
list?]{
|
|
||||||
Mirrors @racket[source-directory] to @racket[target-directory]. FLAC files are
|
|
||||||
converted to Opus at @racket[kbps] kbps; all other regular files are copied.
|
|
||||||
The state is stored in the source directory's @filepath{.music-info.db} using a
|
|
||||||
separate @tt{flac2opus} prefix. Keystore keys and stored target paths use
|
|
||||||
normalized paths relative to the configured source or target base directory;
|
|
||||||
absolute mount points are not stored and separators are always @tt{/}.}
|
|
||||||
|
|
||||||
@defproc[(summary->lines [summary list?]) (listof string?)]{
|
@itemlist[
|
||||||
Formats the summary association list as display lines.}
|
@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].
|
||||||
|
|||||||
Reference in New Issue
Block a user