cp -R, but every .flac it copies arrives as an .mp3.
Built for loading a digital audio player from a library that's a mix of FLAC and MP3 albums. FLAC gets transcoded on the fly; MP3 and everything else is copied byte-for-byte, so albums already in MP3 are never re-encoded.
$ flacpy ~/Music /media/dap/Music
⠹ [==============>--------------------] 412/1043 (00:03:21 left) track07.flac
Needs a Rust toolchain and ffmpeg with libmp3lame.
sudo apt update
sudo apt install -y ffmpeg build-essential git
# Debian's packaged rustc is often too old; rustup gives you current stable:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
git clone https://github.com/<you>/flacpy.git
cd flacpy
cargo install --path . # binary lands in ~/.cargo/bin/flacpycargo install puts flacpy on your PATH (assuming ~/.cargo/bin is in it —
the rustup installer adds it to your shell profile, so open a new shell or
source ~/.cargo/env if flacpy isn't found).
Prefer not to install? cargo build --release leaves the binary at
target/release/flacpy.
brew install ffmpeg rust
git clone https://github.com/<you>/flacpy.git && cd flacpy
cargo install --path .flacpy --help
ffmpeg -encoders 2>/dev/null | grep libmp3lame # must print a lineIf that grep is empty your ffmpeg was built without the LAME encoder and every
transcode will fail. On Debian, apt install ffmpeg from the main repo includes
it; a stripped ffmpeg from a minimal container image may not.
flacpy <SRC> <DST> [OPTIONS]flacpy ~/Music /media/dap/Music # -> /media/dap/Music/Music/...
flacpy ~/Music/Albums /media/dap # -> /media/dap/Albums/...
flacpy -q 0 ~/Music /media/dap # best VBR quality
flacpy -n ~/Music /media/dap # resume: skip what's already there
flacpy -j 4 ~/Music /media/dap # cap parallel encodes
flacpy -v ~/Music /media/dap # one line per file, no bar| Option | Default | Meaning |
|---|---|---|
-q, --quality |
2 |
LAME VBR quality, 0 (best) to 9. 2 is ~190kbps, transparent for most ears. |
-j, --jobs |
CPU count | Parallel transcodes. |
-n, --skip-existing |
off | Skip files already at the destination. Makes runs resumable. |
-v, --verbose |
off | Print each file instead of the progress bar. Good for logs and cron. |
Path semantics match cp -R: flacpy src/Album dst creates dst/Album.
- Walks
SRCrecursively, mirroring the directory tree underDST. - FLAC files are transcoded by
ffmpegdirectly to the destination, with-map_metadata 0so title/artist/album tags and cover art carry over. The output extension becomes.mp3. - Every other file is copied unchanged — including MP3s. Albums already in MP3
pass through untouched rather than being re-encoded, which would lose quality
for no benefit. Cover art,
.m3uplaylists and logs come along too. - Files are processed in parallel across cores. Encoding is CPU-bound, so this is roughly an n-core speedup until the USB write becomes the bottleneck.
The obvious design is transcode locally → copy → delete. flacpy writes to the
destination directly instead: same result, half the I/O, and nothing left behind
in your working directory if the run dies partway.
To keep that safe, each encode writes to track.mp3.part next to its target and
is renamed to track.mp3 only when ffmpeg exits cleanly. An interrupted run
therefore never leaves a truncated file that --skip-existing would mistake for
finished — it leaves a .part, which the next run simply overwrites. Failed
encodes have their .part removed.
A failed file prints to stderr and does not stop the run; the rest still
copy. Failures are listed again at the end and the exit code is 1, so a clean
exit means every file made it. ffmpeg's own output is captured and shown only
for files that actually fail.
- FLAC in, MP3 out only. Other lossless formats (ALAC, WAV, APE) are copied
as-is, not converted. Widen the extension check in
is_flacif you need them. - No deletion of destination files absent from the source — this is
cp, notrsync --delete. - The ETA assumes files take similar times; a mixed run of copies and transcodes makes it jumpy early on.
- Doesn't downscale oversized embedded cover art, which some DAPs dislike.