Encode Mp3 Files in your Ruby Application

Ryan Rebo
MarmoLabs
Published in
5 min readAug 26, 2017

--

I currently work for a music company, and one thing we need to do on the regs is take high-res files (think WAV, AIFF) and encode an mp3 version, for every song. This process was, until recently, handled by a third-party service called Zencoder. While this worked fairly well for us, when there were errors, we had little control (or information) on what went wrong. So we decided to do away with that dependency and figure out a way to encode our files within our own app so we have more flexibility and control. Here’s what we did:

First, I removed all traces of Zencoder from our app. Not only does it always feel great to remove some legacy code, as a bonus, this saved us over $4,000 per year in costs to Zencoder.

After playing around with some different approaches, we landed on using the free, open-source (and well-maintained) software AvConv and integrated it into our Ruby-based application. This is a fork off of ffmpeg, which we started with but had issues getting it to play nicely with Heroku. I’d try ffmpeg first though, the commands are the same, and it has even more support. Here, I’ll show you the snippet of code that does the heavy lifting for us, then I’ll break it down.

While this method looks like it just returns a string…. wait.. are those back ticks? Yes, that is one way to execute a system command with Ruby code. You can also use Kernel’s ‘system’ method:

system(“avconv -y -i #{input_filepath} -vn -qscale:a 4 -map_metadata -1 #{output_filepath}”)

… either will do.

The first command in this method, as you can see, is avconv. In order for that to execute anything, you need avconv installed! This could easily be done with Homebrew.

brew install libav

Alright, after the avconv command you’ll see -y. This will overwrite output files without asking (no, “are you sure you want to do this?” prompt).

-i is for the input filepath (high res file, like WAV or AIFF), so put that path right after the -i. Note that the filepath needs to be the full path, including the filename, (ex: /Users/yourname/desktop/hey_moon.wav).

-vn is to disable any video recording.

-qscale:a 4 handles the audio quality, and says ‘set it to 4 in the scale of 0 to 9

-map_metadata -1 clears any metadata from the input file so it doesn’t write to the output file (we want to start with a clean slate with metadata)

And finally, let your last argument be your output filepath (where the mp3 will go), and again, a full path, with a name and extension (ex: /Users/yourname/desktop/hey_moon.mp3)

You can easily try this by just running the command from your terminal (no back ticks when running via bash, save those for Ruby). Play around with it and watch it encode high-res files to mp3’s. You just need a WAV or AIFF file to play with and you’re good to go.

Ok, I’ll show you now the full class that handles our encoding process. For us, it lives at app/services/encode_mp3_service.rb:

First, this class method..

allows us to call the service like so:

EncodeMp3Service.encode(@model.id, input_filepath)

It gets instantiated then calls the encode instance method:

This method below first sets a state,

mostly to let the one who is uploading this new song where its at in the process. Then we go straight to encoding, again, using avconv.

After an mp3 version is encoded, it sends it to the output_filepath:

If you are unfamiliar with the way match is used here, play around with it, change $1 to $2 and you’ll see what it’s doing.

This output_filepath method basically says “take the name of the input filepath, append the extension .mp3 on it, and stuff it in the /tmp directory”

If the encoding succeeds and it is placed in /tmp, the send_to_id3_tagger method executes, which calls another class that handles adding Id3 tags to the newly encoded mp3, and then handles storing the said file in AWS S3 for us. Perhaps I’ll share how to Id3 tag your mp3 files in another post, at some point.

If encoding and stuffing in /tmp doesn’t succeed, we have a job to send the dev team some info so we can investigate, via email:

Cool. That’s the meat of it. Roughly 45 lines of code gets the job done!

But how to get this to work in production? Since we installed avconv onto our local machine, won’t we need it on our production server? Yes. Yes we will. Our app runs on Heroku, so for now, I can only share how to do that. A little research and you’ll likely find how to run avconv on other server providers.

Heroku provides buildpacks, a way to get external software to run on a Heroku dyno. A buildpack can be added like so:

heroku buildpacks:set https://github.com/moofmayeda/avconv-buildpack

This buildpack was modified by a coworker to work for us, feel free to use it, or, search avconv builpacks via Bing (ha!) or Google, there are a few others out there from what I can tell.

Welp, if you’re in the business of generating encoded mp3 files from a high-res version, I hope this was helpful to you. Fire away with any questions, issues, or comments, etc.

Hack on, Obie-Wan.

Here, for bonus reading (if you find it helpful), here’s the test coverage for the encoding class. Gotta have dem tests.

--

--