Extracting MP3 Audio from YouTube Videos (FLVs)

Setup

I've recently started watching youtube videos of people performing instrumental versions of popular songs. Check out David Wong performing Ellie Goulding - Burn for one such example. A problem I have with this is that you can't have the video continue to play on your phone with the screen locked or while using other applications. One solution to this problem is to save only the audio portion of the video as an MP3.

The facts known at point are as follows:
- Youtube Videos can be saved as FLV files
- FLV files are containers of audio and video streams

Execution

Originally I started looking into python libraries that would allow me to parse an FLV file and extract the audio stream. This actually led to several scripts that simply called ffmpeg. As discussed in an earlier post, ffmpeg is a multipurpose tool that can encode audio and video files.

So let's take a look at the contents of an FLV file as seen by ffmpeg:

$ ffmpeg -i Ellie\ Goulding\ -\ Burn\ \(Violin\ Loop\ Cover\ by\ David\ Wong\)\ \(Low\).flv 

... snip ...

Duration: 00:04:19.68, start: 0.000000, bitrate: 347 kb/s
Stream #0:0: Video: flv1, yuv420p, 426x240, 282 kb/s, 23.98 tbr, 1k tbn, 1k tbc
Stream #0:1: Audio: mp3, 22050 Hz, stereo, s16p, 65 kb/s
Stream #0:2: Data: none

Here we can see that the audio is encoded with MP3 and is located in Stream #0:1. Extracting that is actually as easy as providing ffmpeg with an output filename ending in .mp3.

$ ffmpeg -i Ellie\ Goulding\ -\ Burn\ \(Violin\ Loop\ Cover\ by\ David\ Wong\)\ \(Low\).flv Ellie\ Goulding\ -\ Burn\ \(Violin\ Loop\ Cover\ by\ David\ Wong\)\ \(Low\).mp3

Also, as expected, the mp3 file takes up considerably less space:

[22:29 - [email protected] downloads]$ ls -lh Ellie*
-rw------- 1 randy randy  11M Dec 27 21:56 Ellie Goulding - Burn (Violin Loop Cover by David Wong) (Low).flv
-rw-r--r-- 1 randy randy 2.0M Dec 27 22:03 Ellie Goulding - Burn (Violin Loop Cover by David Wong) (Low).mp3

All Together Now

After downloading several videos to a directory, I wanted to quickly perform this action on all of them. It's a perfect job for a script, and as such I've included one below that does just that.

  1 #!/bin/bash
  2 
  3 processFLVs() {
  4     while IFS= read -d $'\0' -r file ; do
  5         printf 'Converting: %s\n' "$file"
  6         basename="${file:0:${#file}-4}"
  7         < /dev/null ffmpeg -i "$file" "$basename.mp3" &
  8         wait
  9         printf 'MP3 Complete!'
 10     done < <(find . -iname '*.flv' -print0)
 11 }
 12 
 13 processFLVs

The script contains 4 neat features:
1. The main loop that deals with troublesome filenames was adapted from a post on StackOverflow
2. The removal of the last 4 characters on line 6 is basic string manipulation
3. The removal of standard input to ffmpeg on line 7 with < /dev/null to prevent errors when ran as a script
4. The & on line 7 and wait on line 8 in order to force the script to convert one flv at a time