Home

Previous Entry | Next Entry

Linux Audio Conversion Trick

  • Sep. 26th, 2009 at 7:32 PM
Digi-Jason
I'm in the middle of transcoding much of my audio library to move onto a portable player.

For those who don't know me well enough, I archive my audio files in FLAC as much as possible, and on my computer, play those. I won't go into too much detail, but FLAC is to MP3 as PSD is to JPG. One is lossless (albeit with compression, as opposed to raw WAV files which aren't), the other is lossy. Lossy formats will discard data it feels unnecessary.
The main upside to this is that I can transcode the music any way I want based off the FLAC files, and I'll always have the same full quality base to start with.

I'm transcoding a bunch of FLAC files into AAC. AAC > MP3, and that's the last I'll say that.

To make this job easier and have it running like an assembly line, I have three shells/terminals open via the `screen` command. One for copying, one for FLAC -> WAV decoding, one for WAV -> AAC encoding. Remember that you'll need lots of hard drive space for this, because you're taking a compressed raw file, decoding it to an uncompressed raw file, and only afterwards will the post-AAC encode free up all the raw space.

First Terminal - Copying files into a scratch space.
This will obviously be rather specific to my setup, but I'll explain my concepts and leave the task of adopting them to you.

I store my music in /music/extension/Artist/Album/(music files here)
Where extension is one of the formats I have, mp3, flac, ogg, m4a, etc.

Quite obviously, I'm cherry picking albums, admittedly more en massé than cherry picking, but I have no need for the overall artist folder.

The first think to note is that I'm sitting in ~/tmp, a temporary directory created inside my home directory. So I copy them from their storage space, into my tmp directory on the desktop I'm using for this.
cp -rv /music/flac/artist1/album1 /music/flac/artist1/album2 .

This says, copy (cp) the entire folder and any subfolders (-r, needed to actually copy the directory itself) and tell me explicitly what you're copying when you're copying it (-v) into the current directory (.). That'll start churning away immediately.

Move over to the second terminal, move into the temporary directory, and issue this command:
while(ls */*.flac) ; do for x in */*.flac ; do flac --decode --delete-input-file --verify "${x}" ; done ; done

Nothing like a little bit of BASH scripting to kick your logical side into action.
First:
while(ls */*.flac)

This opens a while loop, a loop that runs as long as the expression inside remains "true". Long story short, this command will execute once and if successful, it will move onto the second command/loop in the chain. The brilliant part of using this while loop, is that once there are no more flac files (when `ls` returns "not found"), the whole command will stop and you will be returned to a shell. But since new files will invariably be copied in after it runs the first time, it will succeed the second time `ls */*.flac` has to run.

The ; do bit has to exist as part to set up the loop.

Second:
for x in */*.flac

The part where this command is dumb is that the `ls` portion of the while loop didn't send it's output to the next command. It's really not a big deal, because listing candidates is dead simple. Again, the while loop serves only to run when it needs to, and stop when it doesn't.

for sets up a for loop, x is the variable to assign the iterations too, and in */*.flac are the candidates. So, for each item that is in */*.flac, run the following command.

Once again, ; do has to exist as part to set up the for loop.

Third, the meat and potatoes:
flac --decode --delete-input-file --verify "${x}"

Explaining this probably won't be very hard:
flac is the command for operating with (or creating) flac files. Real shocking, I know.
--decode means to decode the flac file into a raw wav file.
--delete-input-file means to delete the flac file once the decoding is done. Again, this is why I made a copy. I could have very easily omitted that part and just specified an output path for the decoded wav file, but certain storage infrastructure and general computer setup 'isms in my house made just creating a straight up copy a much smarter idea.
--verify means to ... verify the file.
"${x}" I could have easily done "$x" and it would have worked the same. Lately I've become more explicit in defining what a variable is. Let's just say that with some of the variable work you do, sometimes you have to do something that requires you to add text immediately after the variable.

If I were to have said "$xm4a" (explained in the encoding section), this process would fail because the variable $xm4a doesn't exist. But, if you said ${x}m4a, then it would understand that ${x} is the variable, and m4a is plain text on the end.

Lastly, you must double quote the variable because my files have spaces in them, quoting them treats them as one argument, not multiple.

And finally, the pair of ; done statements signify the end of the loops. You need two, because you ran two loops.

This process will run and run and run until no more flac files exist in the path to encode.

Finally, we can move onto our third terminal:
This one takes the decoded raw WAV files and turns them into AAC audio files. I'm using the Nero AAC codec binaries, because they have linux versions, they work, and because the "faac" AAC encoder plainly states on their website that:
FAAC is an MPEG-4 and MPEG-2 AAC encoder.
Note that the quality of FAAC is not up to par with the currently best AAC encoders available.


The third terminal runs the following command, and it is a doozy:
while(ls */*.wav) ; do for x in */*.wav ; do echo "$x" && neroAacEnc -if "${x}" -of "${x:: ${#x}-3}m4a" && rm -fv "${x}" ; done ; done

Again we see the return of the while loop:
while(ls */*.wav)
Again, when run from my temporary directories, this executes the loop when wav files exist, and stops when it doesn't.

for x in */*.wav
Also again, this sets up iteration over all of the wav files I want to encode.

This part is messy, and I have to be a bit explicit because of the way the AAC encoder works.

echo "$x"
The biggest pain in the ass with the Nero encoder, decoder, and everything it ships, is that it doesn't report what file isn't working on, and I like to know that for progress reasons.

Thus, I simply echo the candidate file. You should note that I used $x and not ${x}. It was the first thing that came out when setting this all up, and it does work, I wasn't worried. You'll see where that's different later in the execution.

neroAacEnc -if "${x}" -of "${x:: ${#x}-3}m4a"
This may be deducible until the absolute madness occurring at the end.

neroAacEnc is the name of the binary.
-if "${x}", -if means input file, and I'm telling it to use the WAV file ("${x}").
-of "${x:: ${#x}-3}m4a", -of means output file (where to save the AAC file), and... ...
Ok, stay with me here. BASH (and likely other shells) have a means of "sub-selecting" variable content.
I said stay with me here.

So I input the WAV file, and output the AAC file (extension: m4a), and for some ridiculous reason I don't even want to know, it doesn't change the extension. I could very easily have a step I run after everything's done to change extensions en massé, but why bother?

BASH to the rescue.

Firstly, the entire operation is wrapped in double quotes because the content has spaces, and because it is one long variable plus text appending on the end.

Stat with just the ${x:: ${#x}-3} part.
${x:: means that we're operating on the "x" variable, as you should understand by now.
The :: portion is the part that sub-selects.

As described by the man page, the general overview is:
${variable:offset:length}
variable is x
offset is not set in my example, but it means to start offset characters into the variable text. In this case, I want to start at the very first character, so I simply don't use it.
That leaves us with length and it's a doozy. But it's about to all come together, I promise.
${#x} simply means, instead of using the actual variable content, return the length of the variable. Tack on that -3 immediately afterwards, and that means that if we have a 20 character string, return the number 17. That number 17 gets passed into the first variable selector as the length value.

Why -3? What is usually 3 characters in filenames? The extension.
So we have: ${x:: ${#x}-3} tells bash to return the entire length of the file name minus the last three characters. That leaves us with the straggling "m4a" on the end. What does that do? Makes that the extension of the output file.

Voìla.

So, consider $x = "20 - Mansun - I Can Only Disappoint U (Perfecto mix).wav"
Side Note: This is the last song on the first Dancemania "FantasiA" album, and it's hilarious. Check it out if possible.
All that ${x:: ${#x}-3}m4a madness does is specify the output file name is instead "20 - Mansun - I Can Only Disappoint U (Perfecto mix).m4a"

Makes sense? I sure hope so!

And of course, there's lastly the:
rm -fv "${x}"
This just deletes the WAV file because the encoder won't do it for me.

And one last time, you have ; done ; done to set the end points of the while and for loops.

Let that churn as long as it needs to, and you have compressed audio files suitable for an iPod, Sansa, or whatever plays AAC files. (Does a Zune? I honestly am curious.)

Believe me, this process took a long time to figure out. It was not easy, but here's hoping I made similar tasks a bit easier on you.

I'd like to thank the mysterious man behind the curtain (whose name I cannot find) at BASH Cures Cancer for having been an inspiration for doing more with less, and reminding me that Shell Scripting is damn powerful, and can be leveraged in more ways than I can imagine.

Comments

( 6 comments — Leave a comment )
[info]sherl0k wrote:
Sep. 27th, 2009 04:14 am (UTC)
I'm gonna be really honest here and I hope I don't come off as a jerk.

But Windows has dbPowerAmp to do audio conversions and it's as simple as right-clicking a group of files, choosing "convert" and selecting your desired codec. It keeps all the tags and everything. And will delete the source files when done.

Those with script-fu like you will scoff at the idea, but to an everyday person this will seem like a much easier idea. Also, personally I think AAC is a better codec than MP3, it provides better quality at the same bitrate. Maybe in your case less than three isn't < but > ? :)
[info]vxjasonxv wrote:
Sep. 27th, 2009 04:25 am (UTC)
Well that's just fine and dandy if you have Windows. But guess what! I don't!
And yes, this is hard, I know. I could have learned and used gstreamer, ffmpeg, or something else entirely. But I've tried, and I get really annoyed with software sometime.

This works for me, it works nicely. I could save tags, but I really don't care. My files are named sufficiently well enough that I throw it into MusicBrainz Picard and it works just fine.

I've used CDex, I've used dbPowerAmp (shit, I BOUGHT dbPowerAmp), and yes, they worked.
But I don't use Windows. There's probably just as good a utility in Linux, I just don't know what it is. Add to the fact that I my desktop is headless, it'd have to be a command line utility. Again, ffmpeg, gstreamer and all those exist, I've just... had troubles.


This works, and it's not just about encoding audio. TONS of this knowledge can be reused for purposes that don't have an application dedicated to the task, that's actually more the reason I wrote this up than the audio conversion itself.

It's awesome knowledge in a practical example. No, I don't scoff at it. I just say that I don't use Windows. Because it's true.




BTW, AAC > MP3, I have no idea why you're going on about it.
[info]cutriss wrote:
Sep. 27th, 2009 10:26 am (UTC)
I bought it years ago too. I dunno if my license still affords me updates to it, I honestly haven't tried since the whole "digitizing my music collection" project is on the back burner until I can get a good storage solution on the cheap for it.
[info]sherl0k wrote:
Sep. 27th, 2009 03:04 pm (UTC)
Maybe I was reading it wrong but originally I thought you had the greater than bracket going the other way :P

It really sucks there's no easier alternative to doing this except from a shell, maybe it's time we collaborate on a project to do just this, converting from one format to another?
(Anonymous) wrote:
Sep. 28th, 2009 04:00 am (UTC)
You could try SoundKonverter (http://www.kde-apps.org/content/show.php?content=29024). It's a nice GUI frontend for transcoding to and from a lot of different audio formats.
[info]vxjasonxv wrote:
Sep. 28th, 2009 04:01 am (UTC)
I don't have a GUI installed, I run entirely headless.

Regardless, I'll take a gander at it. Thanks for the recommendation.
( 6 comments — Leave a comment )

Profile

HugUrMod
[info]vxjasonxv
/home/Jason

Latest Month

September 2009
S M T W T F S
  12345
6789101112
13141516171819
20212223242526
27282930   

Other Places to Find Me

Page Summary

Powered by LiveJournal.com
Designed by Tiffany Chow