Garden Sound

Python – Prepend 100ms Bumper

I had the occasion to put some of the scripting I’ve been using to good use.

A recent voice-over editing project involved cutting e-learning material in several foreign language. The clips were destined to play back programmatically and the client required a ~100ms buffer before and after each clip to avoid stuttering during playback or having the end lopped off. Apparently this had been an issue before with the audio engine they’re using in their app.

The first edit session I did in Pro Tools, I was pasting and aligning a 100ms clip of silence where appropriate, but since I had multiple sessions of several hundred clips to edit, I figured there had to be some way to automate this process and save myself the repetitive stress.

Dependencies

The Python Standard Library includes all the functions necessary for simple audio manipulation like this, but the syntax is a bit over my head still. To make my task easier, I called on a third-party module called PyDub which provides a simplified interface for the PSL and ffmpeg library.

Code

Below is the annotated code from my script. In essence, this is what it’s doing:

  • establishing source and target paths for the pre- and post-processed clips.
  • loading a pre-rendered WAV file, 100ms long, dead silence.
  • iterating over the source folder, individually loading clips and concatenating them with the 100ms bumper file
  • exporting each clip individually, with the same filename, to a separate directory.
# Evan Desjardins
# Development started 20190311

import os
# pydub is a module for simplifying calls to python's internal wave module
from pydub import AudioSegment

# Set a variable to the current working directory of the script
basedir = os.getcwd()

# defines a simple 'clear terminal' function.
# If the OS is recognized as Windows, the win shell command 'cls' will run.
# *NIX systems will execute the usual 'clear' command.
def cls():
    os.system('cls' if os.name=='nt' else 'clear')

# Define a function that will request user input for directory paths.
def paths_prompt():
    # The purpose of this script is to prompt the user for the names of the dir-
    # ectory containing the pre-processed audio clips, as well as the directory
    # to which we'll save the processed clips.
    # If the target directory doesn't yet exist, we'll create one with the name
    # given by the user in their input.

    # First we remind the user what directory they're calling the script from,
    # in  case there's any uncertainty.
    print("The current directory is:")
    print(basedir, "\n")

    # Must be in the same parent directory as the script
    print("Enter the name of the source directory below.")
    source_input = input("Source>>>")
    # Must be in the same parent directory as the script
    print("Enter the name of the target directory below.")
    print("If a directory with that name doesn't yet exist, one will be created.")
    target_input = input("Target>>>")

    # Below we verify that the two given directories aren't the same, as that
    # would cause filename conflicts and likely crash out.
    if target_input == source_input:
        print("Target path cannot match source path. Try again.")
        input("Hit any key to continue.")
        # We call the paths_prompt() function recursively to give the user the
        # chance to correct their mistake.
        paths_prompt()
    return source_input, target_input


def init_paths(cwd, source_input, target_input):
    # The purpose of this function is to convert the user's inputted directory
    # names and expand them into a full path for passing to out audio handling
    # functions later in the script.

    # Add sourcedir user input to the current working directory for a complete path.
    source_path = cwd + "/" + source_input
    # Add targetdir user input to the current working directory for a compolete path
    target_path = cwd + "/" + target_input

    # Check whether the directory exists already
    if not os.path.exists(target_input):
        os.makedirs(target_input)

    return source_path, target_path


# Below we declare the function for iterating over the contents of a given
# directory and concatenating the clips therein with our 100ms bumper clip.
def add_bumpers(bumper, source_path, target_path):
    for filename in os.listdir(source_path):
        if filename.endswith(".wav"):
            # Complete the path from the filename by adding cwd and '/'
            clip_path = source_path + '/' + filename

            # The below print statement is meant for debugging only.
            # print(">>>",clip_path)

            # Instantiate our clip as an AudioSegment (see pydub)
            clip = AudioSegment.from_wav(clip_path)
            export_path = target_path + '/' + filename
            #The below print statement is meant for debugging only.
            print(">>>Saving to: ", export_path)
            # Concatenate our bumper with the current clip, then the bumper again
            newclip = bumper + clip + bumper
            # Export the concatenated clip to the export_path as a WAV file.
            newclip.export(export_path, format="wav")
            continue
        else:
            continue

###
# First a little housekeeping:
##


# Clear the screen to simplify the user's view.
cls()

# Remind the user what's going on here.
print("""
This program will add a 100ms bumper to the tops and tails of every clip in
a given directory.

Note:  In its current form, this program can only accommodate the following
format:
WAV | 24bit | 48kHz
""")

# Call our paths_prompt() function above to get input from the user regarding
# what directories the script will refer to.
source_input, target_input = paths_prompt()

#
source_path, target_path = init_paths(basedir, source_input, target_input)

print("The source directory is:\n", source_path, "\n")
print("The target directory path is:\n", target_path, "\n")

print("Is this correct?")
scriptconfirm = input("y/n?\n")
scriptconfirm = scriptconfirm.lower()

if scriptconfirm == "n":
    print("The script will now exit.")
    exit()

##
# Now the real fun starts. . .
##
# Instantiate our bumper clip as an AudioSegment (see pydub)
bumperclip = AudioSegment.from_wav("100ms_24b_48kHz.wav")

# Run our add_bumpers loop, iterating for each file in the given directory.
add_bumpers(bumperclip, source_path, target_path)

# Wave bye-bye
print("\nProcess complete.")

0 Comments

Leave a Reply

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