Python and ffmpeg

PJ Carroll
8 min readDec 20, 2020

--

Say goodbye to directory woes

“Nagra SNST Recorder” by Matt Blaze is licensed under CC BY-NC-ND 2.0

I’ve become fairly adept at bash scripting, apart from one aspect: when I want to get files from one directory, manipulate them, then send them to another directory — I end up in knots.

Each time I go through the pain of StackOverflow Q&A, opening a couple of hundred tabs in the process, I think to myself that there’s got to be a better way.

Enter: python

To give you an idea of how easy python makes dealing with directories, recently I had a housekeeping problem crop up (again) where I wanted to normalize the sound from a bunch of mp4 videos and put them into another directory as mp3s. Hence the tenuous connection with the image of a tape machine.

Yes, I’ve done it with a bash script, thank you, but this is to show you how it’s done in python.

Here’s my explorer:

… and just to make sure you know where you’re going to run the code, here’s the same view from the console:

We’re going to run this code from the tmp directory. It’ll go into the video directory, grab each mp4 file, do the conversion stuff and then stick the mp3 version into the audio directory. Clear?

First up, install ffmpeg-python. This is a cracking utility from Karl Kroening:

pip install ffmpeg-python

Once that’s run, add it to the top of your file along with the ‘os’ library:

import ffmpeg, os

I have my video files in a directory called video and an adjacent empty directory called audio.

Let’s tell folk what this code does:

import ffmpeg, os
print("This takes mp4 files from ./video and converts them into mp3 files in ./audio")
print("\nyou are here:\n")
current_directory = os.getcwd()
print(current_directory + "\n")

os.getcwd gets the current working directory and puts it in current_directory. Now let’s walk through the current directory and print what files are available:

current_directory = os.getcwd()
print(current_directory + "\n")
print("files available are:\n")for root, dirs, files in os.walk(current_directory):
for file in files:
print(os.path.join(root, file))

And lastly, insert an input statement to allow users to bail if need be:

print("files available are:\n")for root, dirs, files in os.walk(current_directory):      
for file in files:
print(os.path.join(root, file))
input('\nHit enter when ready or CRTL-c to escape...\n')print('running...\n')

This will output the following:

This takes mp4 files from ./video and converts them into mp3 files in ./audio you are here:
/home/pc/data/portable/tmp
files available are: /home/pc/data/portable/tmp/video/bishbashbosh.mp4
/home/pc/data/portable/tmp/video/gurtcha.mp4
/home/pc/data/portable/tmp/video/diddlydee.mp4
/home/pc/data/portable/tmp/video/winkywonky.mp4
Hit enter when ready or CRTL-c to escape…

os.path.splitext()

Now what we want is to read the mp4 filename but split off the name part from the extension so we can add .mp3 to it.

os.path.splitext makes this a doddle. namepart and extension will contain the bits needed:

namepart, extension = os.path.splitext(filename)
print(filename)
print(namepart)
print(extension)

This gives us e.g.

bishbashbosh.mp4
bishbashbosh
.mp4

Note the extension bit includes the dot — if you’re going to use the extension then this can catch you out sometimes. In this case we’re not. Looking good, so how do we use this with ffmpeg?

ffmpeg-python

We’re going to iterate through all the files in current_directory/video, run ffmpeg on it, split off the name part (see above), rename it .mp3 and put it into current_directory/audio.

We need a source with directory (infile) and a destination with directory (outfile):

for filename in os.listdir(current_directory + '/video'):
namepart, extension = os.path.splitext(filename)
# print(filename)
# print(namepart)
# print(extension)
infile = current_directory + '/video' +'/' + filename
outfile = current_directory + '/audio' + '/' + namepart + '.n.mp3'
print('infile: ' + infile)
print('outfile: ' + outfile)

Notice we’re just using the namepart of the filename. This gives us:

running… infile: /home/pc/data/portable/tmp/video/bishbashbosh.mp4
outfile: /home/pc/data/portable/tmp/audio/bishbashbosh.n.mp3
infile: /home/pc/data/portable/tmp/video/gurtcha.mp4
outfile: /home/pc/data/portable/tmp/audio/gurtcha.n.mp3
infile: /home/pc/data/portable/tmp/video/diddlydee.mp4
outfile: /home/pc/data/portable/tmp/audio/diddlydee.n.mp3
infile: /home/pc/data/portable/tmp/video/winkywonky.mp4
outfile: /home/pc/data/portable/tmp/audio/winkywonky.n.mp3

If you’ve used ffmpeg before you’ll be aware that it can take a while to trundle through a conversion. We should check that the output doesn’t exist first:

if(not os.path.isfile(outfile)):
# code...

not is the python way of doing the opposite of a binary command. The full ffmpeg command looks like this:

if(not os.path.isfile(outfile)):
stream = ffmpeg.input(infile)
stream = ffmpeg.output(stream, outfile)
ffmpeg.run(stream)

Job done. Now a problem I have with scraping audio from video is that the volumes for different tracks can be all over the place. Hence I use the loudnorm filter from ffmpeg:

if(not os.path.isfile(outfile)):
stream = ffmpeg.input(infile)
stream = ffmpeg.filter(stream, 'loudnorm')
stream = ffmpeg.output(stream, outfile)
ffmpeg.run(stream)

And to make sure I remember whether a track has been run through the loudnorm filter I add a .n. for ‘normalized volume’ to the file extension:

outfile = current_directory + '/audio' + '/' +  namepart + '.n.mp3'

Finally, these ffmpeg commands can also be chained together:

ffmpeg.input(infile).filter('loudnorm').output(outfile).run()

That’s it. my explorer now looks like this:

… and the console looks like this:

For the full code look here:

https://gist.github.com/petercz1/b45844c8edf21af15431d67d010fd0fc

About the author

Latest posts

  • Peter Carroll

python

Leave a Comment

Your email address will not be published. Required fields are marked *

Save my name, email, and website in this browser for the next time I comment.

Categories

I’ve become fairly adept at bash scripting, apart from one aspect: when I want to get files from one directory, manipulate them, then send them to another directory — I end up in knots.

Each time I go through the pain of StackOverflow Q&A, opening a couple of hundred tabs in the process, I think to myself that there’s got to be a better way.

Enter: python

To give you an idea of how easy python makes dealing with directories, recently I had a housekeeping problem crop up (again) where I wanted to normalize the sound from a bunch of mp4 videos and put them into another directory as mp3s. Hence the tenuous connection with the image of a tape machine.

Yes, I’ve done it with a bash script, thank you, but this is to show you how it’s done in python.

Here’s my explorer:

… and just to make sure you know where you’re going to run the code, here’s the same view from the console:

We’re going to run this code from the tmp directory. It’ll go into the video directory, grab each mp4 file, do the conversion stuff and then stick the mp3 version into the audio directory. Clear?

First up, install ffmpeg-python. This is a cracking utility from Karl Kroening:

pip install ffmpeg-python

Once that’s run, add it to the top of your file along with the ‘os’ library:

import ffmpeg, os

I have my video files in a directory called video and an adjacent empty directory called audio.

Let’s tell folk what this code does:

import ffmpeg, os
print("This takes mp4 files from ./video and converts them into mp3 files in ./audio")
print("\nyou are here:\n")
current_directory = os.getcwd()
print(current_directory + "\n")

os.getcwd gets the current working directory and puts it in current_directory. Now let’s walk through the current directory and print what files are available:

current_directory = os.getcwd()
print(current_directory + "\n")
print("files available are:\n")for root, dirs, files in os.walk(current_directory):
for file in files:
print(os.path.join(root, file))

And lastly, insert an input statement to allow users to bail if need be:

print("files available are:\n")for root, dirs, files in os.walk(current_directory):      
for file in files:
print(os.path.join(root, file))
input('\nHit enter when ready or CRTL-c to escape...\n')print('running...\n')

This will output the following:

This takes mp4 files from ./video and converts them into mp3 files in ./audio you are here:
/home/pc/data/portable/tmp
files available are: /home/pc/data/portable/tmp/video/bishbashbosh.mp4
/home/pc/data/portable/tmp/video/gurtcha.mp4
/home/pc/data/portable/tmp/video/diddlydee.mp4
/home/pc/data/portable/tmp/video/winkywonky.mp4
Hit enter when ready or CRTL-c to escape…

os.path.splitext()

Now what we want is to read the mp4 filename but split off the name part from the extension so we can add .mp3 to it.

os.path.splitext makes this a doddle. namepart and extension will contain the bits needed:

namepart, extension = os.path.splitext(filename)
print(filename)
print(namepart)
print(extension)

This gives us e.g.

bishbashbosh.mp4
bishbashbosh
.mp4

Note the extension bit includes the dot — if you’re going to use the extension then this can catch you out sometimes. In this case we’re not. Looking good, so how do we use this with ffmpeg?

ffmpeg-python

We’re going to iterate through all the files in current_directory/video, run ffmpeg on it, split off the name part (see above), rename it .mp3 and put it into current_directory/audio.

We need a source with directory (infile) and a destination with directory (outfile):

for filename in os.listdir(current_directory + '/video'):
namepart, extension = os.path.splitext(filename)
# print(filename)
# print(namepart)
# print(extension)
infile = current_directory + '/video' +'/' + filename
outfile = current_directory + '/audio' + '/' + namepart + '.n.mp3'
print('infile: ' + infile)
print('outfile: ' + outfile)

Notice we’re just using the namepart of the filename. This gives us:

running… infile: /home/pc/data/portable/tmp/video/bishbashbosh.mp4
outfile: /home/pc/data/portable/tmp/audio/bishbashbosh.n.mp3
infile: /home/pc/data/portable/tmp/video/gurtcha.mp4
outfile: /home/pc/data/portable/tmp/audio/gurtcha.n.mp3
infile: /home/pc/data/portable/tmp/video/diddlydee.mp4
outfile: /home/pc/data/portable/tmp/audio/diddlydee.n.mp3
infile: /home/pc/data/portable/tmp/video/winkywonky.mp4
outfile: /home/pc/data/portable/tmp/audio/winkywonky.n.mp3

If you’ve used ffmpeg before you’ll be aware that it can take a while to trundle through a conversion. We should check that the output doesn’t exist first:

if(not os.path.isfile(outfile)):
# code...

not is the python way of doing the opposite of a binary command. The full ffmpeg command looks like this:

if(not os.path.isfile(outfile)):
stream = ffmpeg.input(infile)
stream = ffmpeg.output(stream, outfile)
ffmpeg.run(stream)

Job done. Now a problem I have with scraping audio from video is that the volumes for different tracks can be all over the place. Hence I use the loudnorm filter from ffmpeg:

if(not os.path.isfile(outfile)):
stream = ffmpeg.input(infile)
stream = ffmpeg.filter(stream, 'loudnorm')
stream = ffmpeg.output(stream, outfile)
ffmpeg.run(stream)

And to make sure I remember whether a track has been run through the loudnorm filter I add a .n. for ‘normalized volume’ to the file extension:

outfile = current_directory + '/audio' + '/' +  namepart + '.n.mp3'

Finally, these ffmpeg commands can also be chained together:

ffmpeg.input(infile).filter('loudnorm').output(outfile).run()

That’s it. my explorer now looks like this:

… and the console looks like this:

For the full code look here:

https://gist.github.com/petercz1/b45844c8edf21af15431d67d010fd0fc

--

--