Skip to content
pierrenelPublic

About

`cp -R`, but every `.flac` it copies arrives as an `.mp3`

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flacpy

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

Install

Needs a Rust toolchain and ffmpeg with libmp3lame.

Debian / Ubuntu

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/flacpy

cargo 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.

macOS

brew install ffmpeg rust
git clone https://github.com/<you>/flacpy.git && cd flacpy
cargo install --path .

Verify

flacpy --help
ffmpeg -encoders 2>/dev/null | grep libmp3lame   # must print a line

If 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.

Usage

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.

How it works

  1. Walks SRC recursively, mirroring the directory tree under DST.
  2. FLAC files are transcoded by ffmpeg directly to the destination, with -map_metadata 0 so title/artist/album tags and cover art carry over. The output extension becomes .mp3.
  3. 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, .m3u playlists and logs come along too.
  4. 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.

Why no local temp file

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.

Errors

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.

Limitations

  • FLAC in, MP3 out only. Other lossless formats (ALAC, WAV, APE) are copied as-is, not converted. Widen the extension check in is_flac if you need them.
  • No deletion of destination files absent from the source — this is cp, not rsync --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.

About

`cp -R`, but every `.flac` it copies arrives as an `.mp3`

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages