Skip to content

Instantly share code, notes, and snippets.

@mochki
Last active February 1, 2022 12:50
Show Gist options
  • Save mochki/4220cf31d5d5795e84cdc7b5c2d01319 to your computer and use it in GitHub Desktop.
Save mochki/4220cf31d5d5795e84cdc7b5c2d01319 to your computer and use it in GitHub Desktop.
ffmpeg multitrack to stems
/*
it's a pain in the ass to manage and using ffmpeg is kinda easiest. so grab your multitracks and just make a comma/semicolon
separated list.
vox=Ld Vocals.mp3,Vocal FX.mp3,BGV 1.mp3,Adlibs.mp3,BGV 2.mp3,BGV 3.mp3;
drums=Snare.mp3,Percussion.mp3,Claps.mp3,Kick.mp3;
bass=808.mp3,Pads.mp3;
other=Guitar.mp3,Strings.mp3
this generates a string (multiple ffmpeg commands) that makes the 4 stems that you can just run in your terminal.
the stems will be named after the label on the left of the '='
run in the directory the files are in. if you need help getting ffmpeg...
make the 'config' into one line:
vox=Ld Vocals.mp3,Vocal FX.mp3,BGV 1.mp3,Adlibs.mp3,BGV 2.mp3,BGV 3.mp3;drums=Snare.mp3,Percussion.mp3,Claps.mp3,Kick.mp3;bass=808.mp3,Pads.mp3;other=Guitar.mp3,Strings.mp3
to make stem files, just use ffmpeg's amerge, it's simple and diverse enough.
> ffmpeg -i a.mp3 -i b.mp3 -i c.mp3 -filter_complex "[0:a][1:a][2:a]amerge=inputs=3,pan=stereo|c0<c0+c2+c4|c1<c1+c3+c5[a]" -map "[a]" "d.mp3"
this is what you run to merge 3 tracks into one stem. figure out the pattern for n tracks
i just open up a terminal -> node or chrome -> inspect and run quick scripts. i realize my life is ratchet
also realized i wrote this fluid enough to support any config, not just 4 stems like what fits my needs. whoops
*/
const stemConfig = 'vox=Ld\ Vocals.mp3,Vocal\ FX.mp3,BGV\ 1.mp3,Adlibs.mp3,BGV\ 2.mp3,BGV\ 3.mp3;drums=Snare.mp3,Percussion.mp3,Claps.mp3,Kick.mp3;bass=808.mp3,Pads.mp3;other=Guitar.mp3,Strings.mp3'
const generateCmds = stemConfig => stemConfig.split(';').map(config => [config.split('=')[0], config.split('=')[1].split(',').map(track => `"${track}"`)]).map(([output, tracks]) => `ffmpeg -i ${tracks.join(' -i ')} -filter_complex "${tracks.map((_,i) => `[${i}:a]`).join('')}amerge=inputs=${tracks.length},pan=stereo|c0<${`c${tracks.map((_,i) => i * 2).join('+c')}`}|c1<${`c${tracks.map((_,i) => i * 2 + 1).join('+c')}`}[a]" -map "[a]" "${output}.mp3"`).join(' && ')
// one liners makes copy pasta easier and it's a sad world we live in that i live by this. here, it's not magic:
stemConfig
.split(';')
.map((config) => [
config.split('=')[0],
config
.split('=')[1]
.split(',')
.map((track) => `"${track}"`),
]) // [['vox', [...]], ['drums', [...]], ...]
.map(
([output, tracks]) =>
`ffmpeg \
-i ${tracks.join(' -i ')} \
-filter_complex "${tracks.map((_, i) => `[${i}:a]`).join('')}amerge=inputs=${tracks.length},pan=stereo|c0<${`c${tracks.map((_, i) => i * 2).join('+c')}`}|c1<${`c${tracks.map((_, i) => i * 2 + 1).join('+c')}`}[a]" \
-map "[a]" \
"${output}.mp3"`
)
.join(' && '); // just to run them all at once
/*
to do
don't hard code mp3 you idiot
lol knowing ffmpeg i could do this all with one run isntead of &&-ing
ehh good enough, im not gonna explain the filter complex. DM for deets hahaha
*/
// ugg for peak lazy (chrome) (plus that prefix thing haha)
const CMDtoClip = (stemConfig, prefix = '') => copy(stemConfig.split(';').map(config => [config.split('=')[0], config.split('=')[1].split(',').map(track => `"${track}"`)]).map(([output, tracks]) => `ffmpeg -i ${tracks.join(' -i ')} -filter_complex "${tracks.map((_,i) => `[${i}:a]`).join('')}amerge=inputs=${tracks.length},pan=stereo|c0<${`c${tracks.map((_,i) => i * 2).join('+c')}`}|c1<${`c${tracks.map((_,i) => i * 2 + 1).join('+c')}`}[a]" -map "[a]" "${prefix}${output}.mp3"`).join(' && '))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment