Skip to content

v0.27.0

Choose a tag to compare

@github-actions github-actions released this 25 Feb 20:03
· 45 commits to release since this release
b577da7

Breaking Changes

Default paths moved to platform directories

Config, cache, and output no longer default to the current working directory. They now use standard platform directories. fdb675b

Setting Old Default New Default (Linux) Docker
config ./config.yml $XDG_CONFIG_HOME/caesura/config.yml /config.yml
cache ./cache $XDG_CACHE_HOME/caesura/ /cache
output ./output $XDG_DATA_HOME/caesura/output/ /output
What to do

Move your config file and directories to the new locations, or pass the old paths explicitly via --config, --cache, --output. If old paths are detected at the current working directory, a migration hint will be shown.

Refer to the setup guide for the full default paths table.

content directories are now required

content no longer defaults to ["./content"]. It must be set explicitly via CLI or config file. 0dfb7cf

Docker installations still default to ["/content"].

What to do:

Docker users don't need to do anything. But if you are using a native install then, add to your config.yml:

content:
  - /path/to/your/downloads

Or pass --content /path/to/your/downloads on the CLI.

Docker image now runs as non-root user

The Docker image now runs as a non-root user (uid 65532) instead of root.

The updated docker-compose.yml includes a user: "1000:1000" directive so mounted host directories remain accessible.

What to do

Determine your host UID:GID pair:

echo "$(id -u):$(id -g)"

Then add it to docker-compose.yml or pass to docker via --user 1000:1000

services:
  caesura:
    ...
    # Run as host user so mounted volumes are accessible.
    # Replace with your own UID:GID
    user: "1000:1000"

SoX_ng is now recommended

SoX_ng is now the recommended SoX binary. The original SoX repository is no longer maintained and has known issues. caesura will auto-detect which binary is available, preferring sox_ng over sox. ab70b7e aeaa6dd #185

What to do

Docker users don't need to do anything. But if you are using a native install then:

Install sox_ng and add the following to your config.yml:

sox_ng: true
sox_path: sox_ng

Setting these explicitly avoids unnecessary version detection calls at startup.

Refer to the dependencies guide for install instructions.


New Features

New commands

version

Display caesura version plus versions of all external dependencies (FLAC, LAME, SoX). Useful for verifying your installation and debugging issues. bfdebfc #184

inspect

Native audio metadata inspection with ANSI color output. Prints a table of properties for each file followed by all tags and embedded images. c09c1fa #182

New options

  • --rename-tracks -- Standardize output filenames during transcoding. 57f5c23 #58
  • --exclude-vorbis-comments -- Strip specified Vorbis comment tags from transcoded output. Defaults to stripping COMMENT, ENCODER, RATING, and WORK. 2e96540
  • --sox-path -- Custom path to the sox binary. e9bbcd1 #185
  • --sox-ng -- Whether to use sox_ng behavior. Auto-detected by default. e9bbcd1 #185

New install methods

caesura can now be installed via Homebrew and Nix.

brew install rogueoneecho/tap/caesura
nix profile install github:RogueOneEcho/nix#caesura

Refer to the install guide for all methods.

80f581e c0238bf

External dependencies

imdl, eyeD3, and ImageMagick have been replaced with native Rust implementations.

Only FLAC, LAME, and SoX are required.

Refer to the dependencies guide.

5c895db c09c1fa d405898 #140 #182 #180

Documentation

Documentation has been restructured into focused guides 07365ce

Refer to README.md as the starting point.

Docker

  • Multi-arch images with native ARM64 builds 473cc3f #97
  • FLAC 1.5.0 and SoX_ng 14.7.1 built from source 9752fb2 b438785 #161
  • Smaller image: imdl, eyeD3, and ImageMagick no longer included
  • Runtime image hardened 297a3d7 b438785
  • docker-compose.yml rewritten with inline config, security hardening, and shared mount for hard-link support

Refer to the Docker guide.

Deterministic outputs

Transcoding is now deterministic. Provided the same builds of caesura, sox, flac, and lame are used the transcodes will be identical.

That has enabled us to overhaul the test suite to verify the exact files that each command produces.

caesura is now backed by an extensive suite of 300+ unit and integration tests.

Refer to the Testing guide.

Other improvements

  • Error reporting now uses rich diagnostic chains. When a dependency is missing or a command fails, the full error chain is displayed including the program name and exact cause. fd260aa #166
  • Cross-tracker torrent fallback: existing torrents are duplicated with the new tracker suffix instead of re-transcoding 6bb2ba6 #132
  • Torrent files now only use the indexer suffix (e.g., .red.torrent) instead of a generic .torrent 6bb2ba6 #132
  • verify command checks for unnecessary subdirectories in FLAC sources 68cbf9a #170
  • Letter-only vinyl numbering (e.g., A, B, as well as A1, B1) handled correctly 1a33796 #179
  • Source directories matched using libtorrent's path sanitization rules, fixing invisible character mismatches 7964c46 #169 #163
  • Invisible characters in API metadata (artist, album, media, remaster title) and source paths are detected and reported with human-readable names 40622c6
  • Spectrogram comment updated to caesura fbdcdde #171
  • Deterministic SoX dithering via repeatable mode (-R) c2d77cb
  • ANSI color styling in inspect and config output 7ffeb87 889b954
  • Extended version information in upload descriptions and torrent created_by field c628127
  • Zoom spectrograms use 50% mark for tracks shorter than 62 seconds defa0e0

Thanks

Thanks to @vilohuhu, @finevan and @Flip7413 for their pull requests, and to everyone who submitted ideas, reported issues, and provided feedback in discussions. caesura doesn't contain any telemetry so user reports are essential feedback to encourage further improvements.


Full Changes

New Features

naming: log invisible characters found in metadata and source paths 40622c6
  • Sanitize API metadata fields (artist, album, media) and warn when
    invisible characters are stripped
  • Warn when libtorrent-unsafe characters are found in source paths
  • Extract character constants into SanitizerChar enum covering C0/C1
    control and Unicode formatting characters
  • Replace static Sanitizer with rule-based builder (invisible(),
    name(), libtorrent()) returning SanitizerResult with found chars
inspect: compact multi-line headers, kHz formatting, and column reorder e2c33d8
  • Add multi_line_headers() and newline_after_headers() to TableBuilder
  • Split wide column headers across rows: "Bit Rate kbps", "Sample Rate kHz",
    "Bit Depth"
  • Format sample rate as kHz (44100 -> "44.1", 48000 -> "48")
  • Reorder tag columns: item key first, native key last
  • Reorder picture columns: type name first, native key last
  • Add style_info() (cyan) for native keys and headers
  • Rename bitrate to bit_rate on TrackInfo
  • Add mock_flac() and mock_mp3() test constructors on TrackInfo
  • Add unit test for mixed-format properties table and 48 kHz integration test

Warning

Breaking Change: sox: replace `--no-sox-ng` boolean with `--sox-variant` enum and auto-detection aeaa6dd
  • Add SoxVariant enum (Sox / SoxNg) with serde and clap aliases
  • Auto-detect variant via LazyLock by probing for sox_ng --version
  • Add SoxVariant::binary() method, used by SoxFactory and tests
  • Pin SoxOptions in config test for deterministic snapshots
  • Update CONFIG.md to match generated docs
add `--exclude-vorbis-comments` option to strip tags from transcoded output 2e96540

Add configurable Vorbis comment exclusion during transcode, defaulting
to COMMENT, ENCODER, RATING, and WORK. Tags are stripped via two paths:
in-memory ID3v2 removal for MP3 output, and on-disk Vorbis comment
removal for FLAC resamples.

inspect: add ANSI color styling to inspect output 7ffeb87
  • Style keys yellow, paths green, headers and units dimmed, dividers black
  • Use DisableStyleGuard (drop-based) to produce unstyled output for upload BBCode
  • Reorder imports in upload_command.rs
version: improve error formatting for missing dependencies 580e205

Use yellow instead of red ? for missing dependencies and
consolidate error output into the table. Also make TableBuilder
ANSI-aware by measuring visible terminal columns via unicode-width
instead of byte length.

extend version information for version command, upload description, and torrent created by c628127

Use git describe output captured at build time to provide richer version
strings that distinguish release builds, unmodified source builds, and
modified/development builds.

  • Replace built crate with a minimal build script that captures git describe
  • Add app_info module with BuildStatus, app_version_or_describe(), and app_user_agent()
  • Add homepage to workspace metadata for CARGO_PKG_HOMEPAGE
  • Add .git/packed-refs as a build script rerun trigger
config: add doc comments and ANSI colors to `config` command output 889b954

Render # comment lines from Documented trait metadata above each YAML key,
with colored output (dimmed green comments, yellow keys) that auto-disables
when piping to a file. Replace serde_json with serde_yaml for value
serialization and add missing ConfigOptions/CopyOptions to the output.

Warning

Breaking Change: replace SoX with sox-ng ab70b7e

Use sox-ng as the default sox binary, with --no-sox-ng flag to fall
back to the original sox binary when needed. A SoxFactory handles
binary selection and base flags via dependency injection.

use simple default paths in Docker containers 16459ec

Default to /config.yml, /cache, /output, and /content when
running in Docker so users only need volume mounts without explicit
CLI flags.

Warning

Breaking Change: make `content` directories required 0dfb7cf

content no longer defaults to ["./content"] — users must explicitly
set it via CLI or config. Also removes .gitignore files from the
legacy cache/, content/, and output/ directories.

Warning

Breaking Change: use platform user directories for default config, cache, and output paths fdb675b

Replace relative defaults (config.yml, ./cache, ./output) with
XDG/platform directories via the dirs crate. Legacy path migration
hints guide users who have existing files at the old locations.

add `version` command showing caesura and dependency versions bfdebfc

Replace clap's built-in --version flag with a custom VersionCommand
that displays version info for caesura, flac, lame, and sox in a table.
Accessible via caesura version, caesura --version, or caesura -V.

Warning

Breaking Change: replace `imdl` CLI with native `lava_torrent` for torrent operations 5c895db

Remove the external imdl binary dependency and implement torrent
creation, reading, and verification natively using lava_torrent and
sha1.

inspect: add native audio metadata inspection command c09c1fa

Improve upload description: show properties inline, tags in collapsible.
Add inspect command using lofty crate for native FLAC/MP3 metadata
reading, replacing external eyed3 and metaflac CLI dependencies.

  • Add InspectCommand with TrackInfo, PictureInfo, and formatting
  • Add TableBuilder utility for column-aligned CLI output
  • Update upload command to use native get_details_split()
  • Remove eyed3 module entirely; make metaflac test-only
  • Remove eye-d3 from CI, Docker, and build docs
  • Add AlbumConfig::multi_disc_flat() for flat source directory tests
transcode: replace ImageMagick with native Rust image resizing d405898

Replace the convert external dependency with fast_image_resize and
image crates for in-process image resizing. Add FileOptions default
constants and tests for RGBA, RGB16, and small-image code paths.

match source directories sanitized by libtorrent 7964c46

handle letter-only vinyl numbering #179 1a33796

simplify torrent file handling with cross-tracker support 6bb2ba6
  • Remove generic .torrent files; create only tracker-specific ones
    (e.g., .red.torrent, .ops.torrent)
  • Add cross-tracker fallback to duplicate existing torrents when
    switching between RED/OPS/PTH without re-transcoding
  • Add SharedOptions::indexer_lowercase() for consistent case handling
  • Normalize all torrent filenames to lowercase indexer suffix
  • Add 8 tests for torrent file creation and cross-tracker duplication
add `--rename-tracks` option for standardized output filenames 57f5c23

Adds a new CLI option that renames transcoded files from source filenames
to a standardized format: {track:0>N} {title}.{ext}. Multi-disc releases
are organized into CD1/, CD2/ subfolders.
Key changes:

  • Add --rename-tracks flag to FileOptions with YAML config support
  • Add DiscContext struct to compute track padding and detect multi-disc
  • Add Collector::get_flacs_with_context() for callers needing disc info
  • Cache ID3 tags on FlacFile via thread-safe OnceCell
  • Update verify command to check for missing disc_number on multi-disc
  • Add snapshot tests for single-disc, multi-disc, double-digit, and vinyl
add SoX repeatable mode (`-R`) for deterministic dithering c2d77cb

Add sox_random_dither option to TargetOptions (default: false).
When false, SoX runs with -R flag which seeds the dither PRNG with
a fixed value, producing deterministic output across runs.

  • Add repeatable field to Decode and Resample structs
  • Update TranscodeJobFactory to pass option through
  • Enable 24-bit transcode tests (previously ignored due to non-determinism)
  • Add snapshots for transcode_command_flac24_441/48/96

verify: Implement flac directory validation against unnecessary subdirs #170 68cbf9a

Bug Fixes

options: auto-detect `sox_ng` and `sox_path` in Docker 098ae50

Add is_docker() checks to default both sox_path and sox_ng
when running in a Docker container.

docker: add CA certificates and update SoX_ng to 14.7.1 b438785
  • Install ca-certificates-bundle in runtime image so reqwest can
    make HTTPS connections to tracker APIs
  • Update SoX_ng from 14.7.0.7 to 14.7.1 (old release removed from
    Codeberg)
handle `Torrent.remastered` becoming optional in `gazelle_api` 0.14 2adde66

OPS API no longer returns remastered, recordLabel, or catalogueNumber.
Updated dependency versions and adapted the verify check to treat None
as not-unconfirmed rather than flagging it.

torrent: cap lava_torrent piece-hashing thread pool to avoid exhausting OS process limit 4f98790

options: fix regression where `queue_add_path` in config.yml was ignored 0e52811

Migrate QueueAddArgs to the Options derive macro for config file merging and validation.
Additional changes:

  • Add MatchPath variant to QueueAction for nonexistent path errors
  • Remove manual validate() call from execute_cli() (now handled by
    the options pipeline before command execution)
  • Add QueueAddArgs to ConfigCommand and DocsCommand output
  • Extend the docs macro to handle positional args (#[arg(value_name)]
    without long) and respect explicit #[arg(long = "name")] values
  • Update nonexistent path test to assert error instead of Ok(false)
options: replace clap `SetTrue` with `num_args` so bool config file values are applied 343f76a

Clap's SetTrue action injects default_value("false") for Option<bool>
fields, making them Some(false) when absent from the CLI. This prevented
the config file merge (is_none() check) from ever applying bool values
like rename_tracks: true.
Switch to num_args = 0..=1 with default_missing_value = "true" which
keeps absent flags as None, allowing the merge to work correctly.
Also updates stale macro snapshots for SharedOptions and CacheOptions.

extract `ConfigOptions` from `SharedOptions` so `config` command loads the config file 544e7ac

The config command was showing only defaults because SharedOptionsPartial::from_args
returned None for it, preventing the config file from being read. Moving the config
path field to its own ConfigOptions type lets OptionsProvider discover the config
file path for all commands (including config) without triggering SharedOptions
validation.

skip config file read for commands without shared options b9a79e3

read_config_file now returns None early when the command does not
use shared options, and silently returns None on read failure since
validation reports missing config errors.

upload: render full diagnostic chain for track details warning 3c57b83

docker: update `Dockerfile` for `crates/macros` workspace member a39a28a

Replace markdown_help with macros crate and add proper proc-macro
stub that exports the Options derive. Clear caesura fingerprints after
dependency caching to ensure rebuild with real source.

spectrogram: use 50% mark for zoom spectrogram on short tracks defa0e0

Previously, zoom spectrograms always started at 1:00, causing truncated
output for tracks shorter than 62 seconds. Now tracks shorter than the
typical start position use the 50% mark instead, centering the 2-second
capture window.
Add duration_secs field to SpectrogramJob and calculate_zoom_start()
function with unit tests. Add TrackConfig.duration_secs field and
AlbumConfig::track_30s()/track_1s() test fixtures. Refactor
spectrogram_command_helper to accept AlbumConfig for flexibility.

revise cli host to get services as immutable f66f1c4

Mutability was removed a while ago. Apparently, while testing is now incredibly robust, there's nothing that calls the CLI entry itself.

spectrogram: Set spectrogram comment to `caesura` fbdcdde
Closes: #171

Build

docker: harden runtime image and `docker-compose.yml` 297a3d7
Dockerfile: remove apk, docs, and man pages from runtime image;
add non-root user (65532) following the DHI convention.
docker-compose.yml: use published image, Docker configs, read-only
Caddy root, `no-new-privileges`, and drop all capabilities.
docker: build FLAC v1.5.0 from source and update imdl to v0.1.15 9752fb2

FLAC v1.5.0 adds multithreaded encoding support (-j/--threads flag),
though encoding defaults to single-threaded and decoding remains
single-threaded only. Caesura's semaphore-based concurrency model
continues to work correctly without changes.
Alpine packages lag behind at 1.4.3, so we now build from source in
a multi-stage build. Also updates imdl from 0.1.14 to 0.1.15.

Dependencies

update dependencies fa8a5ad

update dependencies 2d211ec

Documentation

update features and comparison table 56c7f75

add COMPARISON.md comparing related transcoding and upload tools 80c4585

rename `USE.md` to `COMMANDS.md` and `CONFIG.md` to `OPTIONS.md` fac5a85

Update titles, index descriptions, inline references, and next footers
across all docs. Regenerate OPTIONS.md from updated render source.

add DEPENDENCIES.md and GitHub Releases install method af81e44

restructure and rewrite README.md into focused guides 07365ce
  • Split monolithic README into standalone guides under docs/
  • Add CONTRIBUTING.md
  • GIF screencasts with VHS tape sources
  • Use Git LFS for GIF assets
options: improve doc comments for `rename_tracks` and `queue_add_path` 7417a3c

Clarify behavior of the rename_tracks option with examples and adjust documentation for queue_add_path to include specific paths for torrent clients (qBittorrent and Deluge).

add missing doc comments and fix stale type references b32cb1b

Add doc comments to undocumented pub and pub(crate) methods,
structs, enums, traits, and fields across queue commands, transcode
jobs, Host, OptionsProvider, OptionRule, RegisterOptions,
and Source. Fix stale references to CommandRunner/Command in
job subscriber docs (now JobRunner/Job). Correct copy-paste
error in QueueSummaryCommand struct doc.

add comprehensive documentation across codebase 4295159
  • Fix incorrect docs: Encode.output field, EyeD3Command::display,
    TranscodeFormatStatus.format, UploadStatus.completed, EYED3 constant,
    ConfigCommand, BatchCommand, and SOX domain in SpectrogramJob
  • Add module-level docs to all modules and submodules
  • Document all pub and pub(crate) structs with their fields
  • Document enum variants for Variant, Size, Status, and Job
  • Document Options trait and helper functions
  • Update CLI facades (ImdlCommand, EyeD3Command, MetaflacCommand)
    to clarify subprocess invocation
  • Update factory docs to reference created types explicitly

Code Style

fix clippy warnings 1a4b427

Refactor get_year to use pattern matching instead of is_none() with
expect(). Add clippy::doc_markdown allow for auto-generated built.rs.

Refactor

logging: add rustls_platform_verifier exclude filter and alphabetize 2a02020

naming: improve clarity of invisible character warnings 616042a

inspect: replace global styling state with `InspectFactory` instance 04814f6
  • Eliminate AtomicBool + DisableStyleGuard in favor of InspectFactory with a style: bool field
  • Merge format.rs into inspect_factory.rs
  • Consolidate format_tests into inspect_tests
options: replace `SoxVariant` enum with `sox_path` and `sox_ng` options 0def14e

Split the single sox_variant enum into two independent options:

  • sox_path: optional custom path to the sox binary
  • sox_ng: boolean controlling --single-threaded behavior, auto-detected from sox --version output
options: extract options framework into `caesura_options` crate a48af71
  • Move traits, OptionsProvider, registration, rules, and doc metadata
    from crates/core into new crates/options workspace crate
  • Replace concrete ArgumentsProvider with generic ArgsProvider<P, C>
    erased behind ArgsProviderContract trait object
  • Move resolve() from inherent methods into CommandEnumContract trait
  • Update macro codegen to emit ::caesura_options:: paths

docs: merge OPTIONS.md into a single sorted table with inline commands column 34d6c4e

macros: auto-register options via `inventory` crate faba933

The Options derive macro now emits an inventory::submit! call that
registers both doc metadata and DI registration for each options type.
This eliminates three manually-maintained lists that previously required
updating in lockstep when adding new option types.

macros: add `CommandEnum` derive macro replacing manual `FromArgs` impls 78ef5a2

Replace hand-written CommandArguments/QueueCommandArguments enums and all
manual FromArgs implementations with a CommandEnum derive macro. The
Command enum becomes the single source of truth for CLI structure via
declarative #[options(...)] attributes.
Key changes:

  • New CommandEnum derive macro generates clap Subcommand enums, Cli
    parser struct, CommandEnumContract trait impls, and argument resolution
  • Eliminate FromArgs trait; OptionsProvider uses clap::FromArgMatches
    with Command::uses_options() to determine applicability
  • New ArgumentsProvider replaces ArgumentsParser, stores resolved
    Command + ArgMatches in DI
  • Convert SourceArg, QueueRemoveArgs, InspectArg to #[derive(Options)]
    with non-optional required fields
  • Centralize validation in OptionsProvider instead of per-command checks
  • Docs command now renders which commands use each options type
  • Reorganize macro crate into command/ and options/ modules
  • Log errors when CLI argument extraction unexpectedly fails
  • Add validation tests for QueueRemoveArgs and InspectArg
docs: render `OPTIONS.md` tables via `TableBuilder` markdown mode c20da99
  • Add markdown output mode to TableBuilder with GFM pipe-delimited
    tables, <br> joined multi-line headers, and pipe/newline escaping
  • Right-aligned columns use ---: separator syntax
  • Fix last column ignoring right-alignment configuration
adapt to `rogue_logging` `InitLog` trait separating creation from initialization 39b1275
  • Consolidate duplicated logger setup into shared utils/logging.rs module
  • Explicitly call InitLog::init() after creating the logger in host_builder.rs
  • Replace let _ = init_logger() with init_logger() across all test files

Warning

Breaking Change: replace generic `Error` with domain-specific `miette` error types fd260aa

Use Failure<T> from rogue_logging for rich error reporting. Each
module now has its own action type defining domain-specific failure
modes with structured context.
New action types:

  • BatchAction, ConfigAction, QueueAction, SpectrogramAction
  • TranscodeAction, UploadAction, VerifyAction
  • Eyed3Action, ImdlAction, MetaflacAction
  • FsAction, TagsAction, JobAction, SourceAction
  • ProcessAction, SampleAction
    New error types:
  • BatchError, TranscodeError, UploadError, TagsError
    Replace ProcessError enum with Failure<ProcessAction>, making
    ProcessOutput implement Error as the source for non-zero exits.
    Replace SampleError enum with Failure<SampleAction>, propagating
    errors from sample generation instead of panicking.
    Remove redundant path context from call sites where internal functions
    already attach it.
    Add DiagnosticExt trait with render() method for fancy miette output
    in warn/error log messages.
    Update Queue::get to async to match flat_db 0.4.0 API.
clean up `libtorrent_safe_path` and add `SourceProvider` tests ce4337d
  • Add doc comment with libtorrent source link
  • Remove redundant exists() check (already covered by is_dir())
  • Remove unnecessary .clone() in join() call
  • Replace dedup() with explicit conditional for clarity
  • Add unit tests for libtorrent_safe_path
  • Add integration tests for SourceProvider::get() covering exact match,
    bidi character fallback, and missing directory error
  • Extract HostBuilder::with_mock_client from with_mock_api for reuse
use `LazyLock` statics instead of recompiling on each call f38323f

Replace all 7 inline Regex::new calls with module-level LazyLock<Regex>
statics so patterns are compiled once. Deduplicate the shared group URL
pattern in url_helpers.rs. Add doc comments to all modified items.

Warning

Breaking Change: clean up options derive macro with trait contracts 78c423f

Major refactoring of the options system to make the derive macro's
requirements and outputs explicit through trait contracts:

  • OptionsContract: you implement on resolved type with validate()
    and type Partial: FromArgs - defines what the macro needs
  • OptionsPartialContract: macro generates on partial type with
    resolve(), resolve_without_validation(), merge() - defines
    what the macro produces
  • FromArgs: you implement on partial type, no default impl so
    compiler enforces it
    This replaces the previous scattered approach where requirements
    were implicit and easy to miss.
    Additional improvements:
  • "Parse, don't validate": resolve() returns Result<T, Vec<OptionRule>>
  • #[options(required)] attribute for mandatory fields
  • required checks run after default_fn (so both can be used together)
  • api_key, announce_url, indexer, indexer_url now String with required
  • OptionsProvider moved to HostBuilder, validation in build()
  • Invalid options not registered with DI container
  • Docs: serde JSON for defaults, ~ for null, <br> for breaks
  • Docs: "YAML Key" instead of "Config Key"
  • Test ensuring CONFIG.md matches docs command output
  • Snapshot tests for all real options structs

Conflicts:

crates/core/src/utils/testing/samples/transcode_generator.rs

consolidate process execution into `utils/process` module 03ee750
  • Add ProcessError enum with Spawn, Wait, Failed variants
  • Add ProcessExt trait with .run() method for Command
  • Add require_success() for spawn+wait patterns
  • Remove CommandError, OutputHandler from utils/errors
  • Remove duplicate CommandError/CommandExt from test utilities
  • Update all call sites to use new API
  • Refactor ImdlCommand::verify to match on ProcessError::Failed
  • Remove dead code verify_from_buffer
  • Fix typo: "spawn decode" -> "spawn encode" in transcode job
add `prelude.rs` and `testing_prelude.rs` modules f3ef2e4

Consolidate common imports into prelude modules to reduce boilerplate:

  • prelude.rs re-exports internal modules, external crates (di, log,
    colored, rogue_logging), and std types (collections, fmt, path)
  • testing_prelude.rs extends the prelude with hosting and insta
    Update ~116 files to use the new prelude imports.
options: add `Options` derive macro to reduce boilerplate a1e4d7a

Introduce caesura_macros crate with #[derive(Options)] proc-macro that
generates partial structs, merge/resolve methods, and documentation metadata.
Key changes:

  • All option structs now use the macro instead of manual trait implementations
  • Add OptionsPartial trait for generated partial types
  • Add Documented trait and DocsCommand for auto-generating CONFIG.md
  • Refactor OptionsProvider to validate only options applicable to current command
  • Add Command enum for command-specific validation
  • Add Serialize to OptionRule for snapshot testing
  • Replace markdown_help crate with new docs command

Warning

Breaking Change: reorganize into Cargo workspace with `crates/core` and `crates/markdown_help` d0f934a

Move from single-package structure to workspace:

  • crates/core/ - main library and binary (package name: caesura)
  • crates/markdown_help/ - docs generation helper
    Key changes:
  • Root Cargo.toml now workspace manifest with shared deps/lints
  • default-members set to core for simpler builds
  • Test samples now generate at workspace root ./samples/
  • Updated Dockerfile, TESTING.md, .claude/CLAUDE.md for new paths
BREAKING: Directory structure changed from `src/` to `crates/core/src/`
use internal mutability for `GazelleClient` and `Queue` 8010d51

Update gazelle_api to v0.10.0 which uses internal mutability, allowing
removal of RefMut wrappers from DI registrations. Queue methods now
take &self instead of &mut self since flat_db::Table already handles
mutability internally.

use `#[deprecated]` attributes and add `IdProviderError::NoId` ddc6f48

Replace /// DEPRECATED doc comments with proper #[deprecated] attributes
on SourceIssue::IdError, ApiResponse, and Provider variants. Add new
IdProviderError::NoId variant for missing IDs and use it in batch command.

Tests

unify snapshot macros with `CAESURA_DETERMINISTIC_TESTS` env var 9267ae1
  • Replace assert_transcode_snapshot! with normalize_snapshots! for structural verification
  • Replace assert_inspect_snapshot! non-empty check with line-count assertion
  • Add normalize_snapshots! for spectrogram tests (previously no cross-platform guard)
  • Replace is_nix(), is_macos(), is_aarch64() with is_deterministic()
  • Set CAESURA_DETERMINISTIC_TESTS=1 in CI for x86_64 Linux only
  • Update spectrogram snapshots for SoX_ng 14.7.1
  • Simplify TESTING.md table
replace file-based locking with `OnceCell` caching in sample providers c54af10

The file lock behaviour depended on a timeout that was flaky in nix environments with many cores but proot bottleneck.
Replace with in-process Arc<OnceCell> coordination in AlbumProvider and
TranscodeProvider. Narrow generator visibility to pub(super), add
get_advanced() APIs for custom album configs, and move sample-related tests
into samples/tests/.

replace platform-specific test skips with runtime snapshot guards 00f3e40
  • Extract is_docker, is_nix, is_macos, is_aarch64 into utils/platform.rs
  • Add shared assert_transcode_snapshot! and assert_inspect_snapshot! macros
  • Spectrogram tests: add Nix to existing docker/macOS guard
  • Transcode and inspect tests: replace #[ignore] on aarch64 with runtime guard macros
  • Upload and transcode_provider tests: remove aarch64 ignores (no snapshot assertions)
  • Set CAESURA_NIX=1 env var in nix derivation, removing checkFlags skips
  • Tests now run on all platforms but use simpler assertions where output differs

inspect: fix divider including style in snapshot c94c8a1

set correct API encoding for 24-bit mock sources 66a87b0

The mock API client always reported encoding: "Lossless" regardless
of bit depth, causing ExistingFormat::from_torrent to return Flac
for 24-bit sources. This prevented TargetFormatProvider from including
the FLAC resample target, so the resample path was never exercised.
Add SampleFormat::encoding() to return the correct Gazelle API
encoding string based on bit depth and use it in build_mock_client.
The 24-bit test snapshots now include FLAC resample output.

relax `created_by` assertions to accept any version format cc6e83e

When no v* tag is reachable (e.g. CI shallow clone or feature branch),
git describe --always returns a bare commit hash without a v prefix.
The torrent created_by tests assumed a v-prefixed version string,
causing failures on CI.

remove build_status_is_not_release that fails on CI release builds 3189941

fix CI failures for version and torrent tests dba54fd
  • Remove snapshot from create_produces_valid_torrent test since the
    torrent file hash changes with each version bump (created_by field)
  • Fix sox version detection on macOS by checking stderr when stdout
    is empty (some tools output version info to stderr)
  • Add debug output to sox test to show first_line on failure
upload: add `get_details` snapshot tests for FLAC, 320, and V0 dfac191

Extract get_details from UploadCommand to a free function so it can
be tested independently without DI setup. Add insta snapshot tests that
verify metaflac and eyeD3 output for each target format.

transcode: add AdditionalJobFactory, AdditionalJob, and Resize tests 087a0de

add RAII `LockGuard` to prevent stale `.lock` files in sample generation 6ff1980

Extract duplicated file-based locking from AlbumGenerator and TranscodeGenerator
into lock_guard.rs with a LockGuard that removes the .lock file on drop, ensuring
cleanup even on failure or panic.

add RAII auto-cleanup to `TempDirectory` and `TestDirectory` 82dd381

Test directories in /tmp/caesura/ were never cleaned up, accumulating
over time. Implement RAII pattern where directories are automatically
deleted when the struct is dropped.

  • Add TempDirectory with automatic cleanup on drop and keep() for debugging
  • Refactor TestDirectory to wrap TempDirectory for inherited cleanup
  • Use atomic counter + ISO timestamps for unique directory paths
  • Update AlbumConfig::single_torrent_dir() to return TempDirectory
  • Remove unused cache_dir and CacheOptions from TranscodeGenerator
  • Add comprehensive tests for both types
fix sample torrent corruption from ephemeral generation 362b3bb
AlbumGenerator: :generate_in_dir() was using config.torrent_path() which
always pointed to SAMPLE_SOURCES_DIR, even when generating in a temp
directory. This caused ephemeral generation to overwrite the cached
sample torrent with one containing a timestamp-based name.
Fix by:
- Remove misleading source_dir() and torrent_path() from AlbumConfig
- Compute torrent path from actual source_dir using append_extension()
- Update callers to use SAMPLE_SOURCES_DIR.join() explicitly
Also add samples/rm-samples helper script for clearing sample cache.
hide progress bar output during tests 1e80cfc

Use ProgressDrawTarget::hidden() in test builds to prevent progress
bar output from cluttering test results.

fix queue add tests to use isolated torrent directories 03a7795

Tests previously read from SAMPLE_SOURCES_DIR directly. Use
single_torrent_dir() to copy torrents to isolated temp directories.
Add queue_add_test_helper to reduce setup duplication and replace
manual assertions with yaml snapshots.

add integration tests for batch, config, queue, and upload commands af7fe66

Add comprehensive test coverage for command modules:

  • BatchCommand: queue processing, limits, indexer filtering, upload status
  • ConfigCommand: options serialization
  • QueueAddCommand, QueueListCommand, QueueRemoveCommand: CRUD operations
  • UploadCommand: success paths, dry run, copy options, API failure handling
  • Queue: set/get/remove operations, filtering by status and indexer
  • JobRunner: concurrency configuration, empty execution
  • IdProvider: numeric ID and URL parsing, error variants
    Infrastructure updates:
  • Add with_upload_torrent() mock response to AlbumConfig::api()
  • Add single_torrent_dir() helper for isolated torrent file tests
  • Add default BatchOptions to with_test_options() for consistent test setup
sort `DirectoryReader` paths for deterministic file selection 4e98a68

DirectoryReader::read() returns files in filesystem order which varies
across platforms. Tests using .first() got different tracks on macOS
vs Linux, causing snapshot mismatches in CI.
Sort paths before selecting to ensure consistent cross-platform behavior.
Also add snapshot assertion to eyed3_display test (ignored on ARM).

Split sample logic into `AlbumGenerator`, `AlbumProvider` and `TestDirectory` a0eb467

Restructure the test sample generation infrastructure for better clarity
and separation of concerns:

  • Rename SampleDataBuilder to AlbumGenerator with AlbumConfig
  • Split SampleProvider into AlbumProvider and TranscodeProvider
  • Add TranscodeGenerator and TranscodeConfig for transcode samples
  • Add TestDirectory utility for test directory management
  • Update all dependent tests and snapshots
enable deterministic tests via mock API and sample generation a1a8744

Replace flaky live API tests with deterministic mocks:

  • Abstract GazelleClient to GazelleClientTrait for mock injection
  • Add HostBuilder::with_mock_api() and with_mock_samples() for testing
  • Delete ignored live API tests that provided little value
    Add programmatic sample generation:
  • Add FlacGenerator, ImageGenerator, SampleDataBuilder for creating
    reproducible test files using SOX and metaflac
  • Add SampleFormat enum for type-safe audio format specifications
  • Add snapshot testing utilities (AudioSnapshot, FileSnapshot, etc.)
    Housekeeping:
  • Remove unused TestOptionsFactory
  • Rename TempDirectory::with_test_name() to for_test()
  • Add TESTING.md documenting test setup and required tool versions
  • Update CI to run determinism tests and remove download-sample script
  • Add insta, image, sha2 dev-dependencies
  • Update gazelle_api to 0.11.0 for mock support

CI

publish all workspace crates to crates.io b577da7
  • Use --workspace for set-version and cargo publish
  • Include crates/macros/Cargo.toml and crates/options/Cargo.toml in cargo-manifests artifact

add --locked to all cargo commands in CI and Docker 435d082

fix unnecessary recompilation of caesura on every cargo invocation 2598153

Two issues caused caesura to recompile every time cargo ran:

  1. build.rs used relative rerun-if-changed paths (.git/HEAD),
    which resolved to crates/core/.git/HEAD — a path that never
    exists. Cargo treated the build script as permanently stale.
  2. CI used cargo auditable build then plain cargo test. The
    cargo-auditable wrapper produces different fingerprints, so
    cargo recompiled for every test step.
    Fixed by using absolute paths in build.rs and switching CI test
    steps to cargo auditable test.
run `caesura_macros` snapshot tests in CI and update stale snapshots 4a60b11
  • Add macro test step to on-push.yml so options struct changes are caught
  • Update stale expand_file_options and expand_target_options snapshots
  • Document macro expansion snapshot workflow in TESTING.md
restrict cache save and delete to main branch only 6084613

Feature branch runs were deleting main's caches and replacing them with
branch-scoped caches that main cannot access, causing cache misses.

prevent buildx cache bloat by deleting stale entries before save 395dd6c

prevent cargo cache bloat by deleting stale entries before save 0a9954a

skip tests on macOS Intel runner e63aa89

The macOS Intel runner is ~2x slower than other runners. Since macOS ARM
already runs the full test suite, Intel only needs to build the binary.

add `cargo-auditable` and `cargo-audit` to `cargo-build` bcf8ff2

Use cargo-auditable to embed Rust dependency metadata in binaries,
then scan with cargo audit bin on x86_64-unknown-linux-gnu for vulnerabilities.

add Docker security scanning and SBOM attestation 9177169

Add docker-scan job to CI using Syft, Grype, and cosign:

  • Syft generates CycloneDX SBOM (Alpine packages + Rust crates)
  • Grype scans for vulnerabilities, fails on critical
  • cosign attests merged SBOM to per-arch images via keyless signing
  • Manual SBOM fragment in .github/sbom/source-deps.cdx.json covers source-built FLAC
    Use cargo-auditable in Dockerfile to embed Rust dependency metadata
    in the binary, making crates visible to SBOM scanners in the final image.

add Nix package publishing support c0238bf

add Homebrew publishing support 80f581e
  • Package release binaries as tar.xz archives instead of raw files
  • Trigger homebrew-tap workflow after release upload

update cargo cache key to save latest version each run 46107d7

build test artifacts before caching to speed up cargo test a5a6297

remove macOS brew caching cfd5ddd

add multi-arch Docker images with native ARM64 builds 473cc3f
  • Update Dockerfile to download arch-specific imdl binary using TARGETARCH
  • Split docker-build into matrix job for ubuntu-24.04 and ubuntu-24.04-arm
  • Create multi-arch manifest combining both architectures in docker-release

add native ARM64 Linux builds with ubuntu-24.04-arm runner ca41eb1

update macOS runners to macos-15 d5843d6

Replace deprecated macos-13 with macos-15-intel for x86_64 builds
and update macos-14 to macos-15 for aarch64 builds.
Also remove /.github from .gitignore.

Chores

remove ImageMagick from CI, Docker, and test docs c45a4ac

Follow-up to d405898 which replaced ImageMagick with native Rust image
resizing. Removes the now-unnecessary ImageMagick dependency from the
GitHub Actions workflow, Dockerfile, and TESTING.md.

use cargo-chef in `Dockerfile`, fix CI build, remove `markdown_help` crate c929628
  • Refactor Dockerfile to use cargo-chef for better dependency caching
  • Add --bins flag to CI cargo build command
  • Delete unused crates/markdown_help workspace member