A containerized service that watches a source folder and automatically transcodes audio files to multiple formats simultaneously.
Perfect for maintaining a music library in multiple formats -- lossless for archival, lossy for portable devices -- without lifting a finger. Drop a FLAC into your source folder and get ALAC, MP3, AAC, and Opus copies instantly.
- Features
- Quick Start
- Configuration
- How It Works
- Performance
- Verification Tool
- Development
- License
- Contributing
- Real-time file watching -- Detects new, modified, renamed, or deleted files via watchdog
- Multiple simultaneous outputs -- Transcode to any number of formats in a single pass
- Six codecs -- ALAC, AAC, MP3, Opus, FLAC, WAV with configurable bitrates
- Synced lyrics -- Auto-fetches
.lrclyrics with Whisper speech-to-text fallback - Artwork preservation -- Optionally embeds cover art in output files
- Atomic writes -- No partial or corrupted files on failure
- Orphan cleanup -- Automatically removes outputs that no longer have a source
- Safety guards -- Prevents accidental mass deletion if folders appear empty
- Unicode safe -- Properly handles special characters in filenames
- Docker-first -- Ships as a lightweight container built on Python 3.14-slim + FFmpeg
1. Create a config.yaml file:
source:
path: /music/flac
outputs:
- name: alac
codec: alac
path: /music/alac
- name: mp3-256
codec: mp3
bitrate: 256k
path: /music/mp3
- name: aac-256
codec: aac
bitrate: 256k
path: /music/aac2. Create a docker-compose.yml:
services:
audio-transcoder:
image: drumsergio/audio-transcoder:0.4.1
container_name: audio_transcoder
environment:
- TZ=Europe/Madrid
- CONFIG_FILE=/app/config.yaml
volumes:
- ./config.yaml:/app/config.yaml:ro
- /path/to/flac:/music/flac:ro
- /path/to/alac:/music/alac
- /path/to/mp3:/music/mp3
- /path/to/aac:/music/aac
restart: unless-stopped3. Start the service:
docker compose up -ddocker run -d \
--name audio_transcoder \
-e TZ=Europe/Madrid \
-e CONFIG_FILE=/app/config.yaml \
-v ./config.yaml:/app/config.yaml:ro \
-v /path/to/flac:/music/flac:ro \
-v /path/to/mp3:/music/mp3 \
--restart unless-stopped \
drumsergio/audio-transcoder:0.4.1Configuration is provided via a YAML file. Set the CONFIG_FILE environment variable to its path inside the container.
# Source folder containing original audio files
source:
path: /music/flac
# Output destinations -- define as many as you need
outputs:
# Lossless ALAC for Apple devices
- name: alac
codec: alac
path: /music/alac
include_artwork: true
# High-quality MP3 for broad compatibility
- name: mp3-320
codec: mp3
bitrate: 320k
path: /music/mp3-320
include_artwork: true
# Balanced MP3 for portable devices
- name: mp3-192
codec: mp3
bitrate: 192k
path: /music/mp3-192
include_artwork: true
# AAC for modern devices
- name: aac-256
codec: aac
bitrate: 256k
path: /music/aac
include_artwork: true
# Opus for streaming (best quality-to-size ratio)
- name: opus-128
codec: opus
bitrate: 128k
path: /music/opus
# Optional settings
settings:
# Delete all outputs and re-encode on startup
force_reencode: false
# Maximum time to wait for a file to become stable (seconds)
stability_timeout: 60
# Minimum time a file must be unchanged before processing (seconds)
min_stable_seconds: 1.0| Codec | Extension | Bitrate | Artwork | Description |
|---|---|---|---|---|
alac |
.m4a |
N/A | Yes | Lossless, Apple compatible |
aac |
.m4a |
64k--320k | Yes | Lossy, excellent quality |
mp3 |
.mp3 |
64k--320k | Yes | Lossy, universal support |
opus |
.opus |
32k--256k | No | Lossy, best quality/size |
flac |
.flac |
N/A | Yes | Lossless, open format |
wav |
.wav |
N/A | No | Lossless, uncompressed |
You can alternatively provide configuration as a JSON string via the CONFIG_JSON environment variable:
environment:
- CONFIG_JSON={"source":{"path":"/music/flac"},"outputs":[{"name":"mp3","codec":"mp3","bitrate":"256k","path":"/music/mp3"}]}- Initial sync -- On startup, scans the source folder and encodes any missing files to all configured outputs.
- Watch mode -- Continuously monitors the source folder for changes:
- New files are encoded to all configured outputs
- Modified files are re-encoded to all outputs
- Renamed files trigger deletion of old outputs and creation of new ones
- Deleted files have their corresponding outputs removed
- Orphan cleanup -- Removes output files that no longer have a matching source.
- Lyrics sync -- Fetches synced
.lrclyrics from online databases; falls back to Whisper transcription when no lyrics are found.
The service includes multiple guards to prevent data loss:
- If the source folder appears empty, no deletions are performed
- If any output folder appears empty, no deletions are performed
- All writes are atomic -- encoding happens to a temporary file that is moved into place only on success
- Parallel processing -- Multiple output formats are encoded concurrently
- Incremental sync -- Only missing or changed files are processed; unchanged files are skipped
- Stability detection -- Files are not processed until they have been stable on disk for a configurable period, avoiding partial reads during large copies or network transfers
- Low idle footprint -- Uses inotify/FSEvents-based watching with minimal CPU usage when idle
A built-in verification tool checks that all outputs are in sync with the source:
# Basic sync check
docker exec audio_transcoder python /app/tools/verify_sync.py --config /app/config.yaml
# Thorough check including duration comparison
docker exec audio_transcoder python /app/tools/verify_sync.py --config /app/config.yaml --check-duration -v- Docker (recommended), or Python 3.14+ with FFmpeg installed
- Hatch build system
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
# Run tests with coverage
pytestdocker build -t audio-transcoder:dev .This project is licensed under the GNU General Public License v3.0 -- see the LICENSE file for details.
Contributions are welcome. Please open an issue to discuss significant changes before submitting a pull request.
- Fork the repository
- Create a feature branch (
git checkout -b feat/amazing-feature) - Run tests (
pytest) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feat/amazing-feature) - Open a Pull Request