Using Make to Transcode Your Music Library

Date: October 26, 2024


Back

On my computer, I have a bunch of lossless music files that I want to convert to opus for listening on my phone. After looking online and not really finding anything, my first thought was to use make, but unfortunately, it turned out to be a lot harder than I thought because make really doesn't like spaces in filenames... for a while, I wasn't really able to figure it out, but I tried again recently and was able to get it working!

Basically, the trick is you have to use a shell command to find the files, not the make wildcard, and then you have to pipe it through sed to escape the spaces. Here's my complete makefile:

FLAC_DIR = Flac
OPUS_DIR = Opus
OGG_DIR  = Ogg

LOSSLESS_SRC  = $(shell find "$(FLAC_DIR)" -type f -name "*.flac" -o -name "*.wav"        | sed -E 's:([ ;]):\\\1:g')
LOSSY_SRC     = $(shell find "$(FLAC_DIR)" -type f -name "*.mp3" -o -name "*.m4a"         | sed -E 's:([ ;]):\\\1:g')
ALBUM_ART_SRC = $(shell find "$(FLAC_DIR)" -type f -name "cover.jpg" -o -name "cover.png" | sed -E 's:([ ;]):\\\1:g')

OPUS_LOSSLESS_DST  = $(shell echo "$(LOSSLESS_SRC)"  | sed 's:$(FLAC_DIR):$(OPUS_DIR):g' | sed -E 's:\.(flac|wav):\.opus:g')
OPUS_LOSSY_DST     = $(shell echo "$(LOSSY_SRC)"     | sed 's:$(FLAC_DIR):$(OPUS_DIR):g')
OPUS_ALBUM_ART_DST = $(shell echo "$(ALBUM_ART_SRC)" | sed 's:$(FLAC_DIR):$(OPUS_DIR):g')

OGG_LOSSLESS_DST  = $(shell echo "$(LOSSLESS_SRC)"  | sed 's:$(FLAC_DIR):$(OGG_DIR):g' | sed -E 's:\.(flac|wav):\.ogg:g')
OGG_LOSSY_DST     = $(shell echo "$(LOSSY_SRC)"     | sed 's:$(FLAC_DIR):$(OGG_DIR):g')
OGG_ALBUM_ART_DST = $(shell echo "$(ALBUM_ART_SRC)" | sed 's:$(FLAC_DIR):$(OGG_DIR):g')

.PHONY: all
all: opus ogg

.PHONY: opus
.PHONY: ogg
opus: $(OPUS_LOSSLESS_DST) $(OPUS_LOSSY_DST) $(OPUS_ALBUM_ART_DST)
ogg: $(OGG_LOSSLESS_DST) $(OGG_LOSSY_DST) $(OGG_ALBUM_ART_DST)

define opusEncode
    @echo "Encoding \033[1;35m$<\033[0m => \033[1;35m$@\033[0m"
    @mkdir -p "$(shell dirname "$@")"
    @ffmpeg -loglevel quiet -i "$<" -c:a libopus -b:a 128k "$@"
endef
define oggEncode
    @echo "Encoding \033[1;35m$<\033[0m => \033[1;35m$@\033[0m"
    @mkdir -p "$(shell dirname "$@")"
    @ffmpeg -loglevel quiet -i "$<" -c:a libvorbis -q:a 5.0 "$@"
endef
define copy
    @echo "Copying \033[1;36m$<\033[0m => \033[1;36m$@\033[0m"
    @mkdir -p "$(shell dirname "$@")"
    @cp "$<" "$@"
endef

$(OPUS_DIR)/%.opus : $(FLAC_DIR)/%.flac ; $(opusEncode)
$(OPUS_DIR)/%.opus : $(FLAC_DIR)/%.wav  ; $(opusEncode)
$(OPUS_DIR)/%.mp3  : $(FLAC_DIR)/%.mp3  ; $(copy)
$(OPUS_DIR)/%.m4a  : $(FLAC_DIR)/%.m4a  ; $(copy)
$(OPUS_DIR)/%.png  : $(FLAC_DIR)/%.png  ; $(copy)
$(OPUS_DIR)/%.jpg  : $(FLAC_DIR)/%.jpg  ; $(copy)

$(OGG_DIR)/%.ogg : $(FLAC_DIR)/%.flac ; $(oggEncode)
$(OGG_DIR)/%.ogg : $(FLAC_DIR)/%.wav  ; $(oggEncode)
$(OGG_DIR)/%.mp3 : $(FLAC_DIR)/%.mp3  ; $(copy)
$(OGG_DIR)/%.m4a : $(FLAC_DIR)/%.m4a  ; $(copy)
$(OGG_DIR)/%.png : $(FLAC_DIR)/%.png  ; $(copy)
$(OGG_DIR)/%.jpg : $(FLAC_DIR)/%.jpg  ; $(copy)

.PHONY: clean
clean:
    @rm -rf "$(OPUS_DIR)" "$(OGG_DIR)"

What it does is it takes all the music files in FLAC_DIR and converts them to opus files in OPUS_DIR and ogg vorbis files in OGG_DIR. Any lossy files will just get copied over.

What I like about this is:

I'm not a makefile expert, so it's possible some of this stuff could be improved, but I think it's overall pretty good! :3