Skip to content

Instantly share code, notes, and snippets.

@ericdfields
Last active November 24, 2024 04:46
Show Gist options
  • Save ericdfields/c0c0d3b57a7d32e5f2a03d3b5f2ed701 to your computer and use it in GitHub Desktop.
Save ericdfields/c0c0d3b57a7d32e5f2a03d3b5f2ed701 to your computer and use it in GitHub Desktop.
A python script to take a bunch of session stems and mix them down for quick reference listening
#
# For when you have a cigar box of SD cards that get dumped onto an external hard drive
# https://chat.openai.com/share/7090a9c0-2618-445e-a706-a232cf1d1a29
#
import argparse
from pydub import AudioSegment
from pydub.effects import normalize, compress_dynamic_range
import os
from multiprocessing import Pool
# Define the root directory where your audio stems are located
root_directory = "/Volumes/HD1-B/2023"
# Function to mix down stems in a folder and apply mastering effects
def mix_and_master_stems_in_folder(folder_path):
# Create an empty AudioSegment to hold the mix
mix = AudioSegment.silent(duration=0)
# Iterate through files in the folder
for file_name in os.listdir(folder_path):
# Check if the file has a ".wav" extension (case-insensitive)
if file_name.lower().endswith(".wav"):
# Load the stem audio file
stem = AudioSegment.from_wav(os.path.join(folder_path, file_name))
# Mix it with the existing mix
mix = mix + stem
# Apply mastering effects
mix = normalize(mix) # Normalize the audio
mix = compress_dynamic_range(mix, threshold=-20.0, ratio=2.0) # Apply compression (adjust threshold and ratio as needed)
return mix
# Function to check if a file already exists and remove it if needed
def check_and_remove_if_exists(file_path, remove_preexisting):
if os.path.exists(file_path):
if remove_preexisting:
os.remove(file_path)
else:
print(f"Skipping '{file_path}' as requested.")
return False
return True
def process_session(datestamped_folder, remove_preexisting, skip_preexisting, destination_path):
folder_path = os.path.join(root_directory, datestamped_folder)
if os.path.isdir(folder_path):
if skip_preexisting:
# If skipping preexisting, check if the files already exist and skip the processing
wav_output_path = os.path.join(destination_path, f"{datestamped_folder}-mixed.wav")
mp3_output_path = os.path.join(destination_path, f"{datestamped_folder}-mixed.mp3")
if os.path.exists(wav_output_path) and os.path.exists(mp3_output_path):
print(f"Skipping '{datestamped_folder}' as requested.")
return
# Mix and master the stems for each session
session_mix = mix_and_master_stems_in_folder(folder_path)
# Create the output file paths with the datestamp in the destination folder
wav_output_path = os.path.join(destination_path, f"{datestamped_folder}-mixed.wav")
mp3_output_path = os.path.join(destination_path, f"{datestamped_folder}-mixed.mp3")
# If the files already exist, check and remove or skip based on the arguments
if remove_preexisting:
check_and_remove_if_exists(wav_output_path, True)
check_and_remove_if_exists(mp3_output_path, True)
# Export the mixed and mastered session as WAV and MP3 files
session_mix.export(wav_output_path, format="wav")
session_mix.export(mp3_output_path, format="mp3")
print(f"Mixed and mastered {datestamped_folder} and saved as {wav_output_path} and {mp3_output_path}")
def main():
# Create an argument parser
parser = argparse.ArgumentParser(description="Mix and master audio stems")
# Add a flag to remove preexisting files
parser.add_argument("--remove-preexisting", action="store_true", help="Remove preexisting mixed audio files if they exist")
# Add a flag to skip preexisting files
parser.add_argument("--skip-preexisting", action="store_true", help="Skip processing of preexisting mixed audio files")
# Add an optional parameter for a specific folder name
parser.add_argument("--folder-name", type=str, help="Process a specific folder by name")
# Add an optional parameter for the destination path
parser.add_argument("--destination-path", type=str, help="Specify the destination path for exported files")
# Add an option to specify the number of processes
parser.add_argument("--num-processes", type=int, default=4, help="Number of processes to use for parallel processing (default: 4)")
args = parser.parse_args()
# Get the list of datestamped folders
datestamped_folders = [folder for folder in os.listdir(root_directory) if os.path.isdir(os.path.join(root_directory, folder))]
# If a specific folder name is provided, process only that folder
if args.folder_name:
if args.folder_name in datestamped_folders:
datestamped_folders = [args.folder_name]
else:
print(f"Folder '{args.folder_name}' not found in the root directory.")
return
# Process each session in parallel with the specified number of processes
with Pool(processes=args.num_processes) as pool:
# Pass the destination path to the process_session function
pool.starmap(process_session, [(folder, args.remove_preexisting, args.skip_preexisting, args.destination_path) for folder in datestamped_folders])
if __name__ == "__main__":
main()
@ericdfields
Copy link
Author

Audio Mixing and Mastering Script

This Python script allows you to mix and master audio stems, providing a convenient way to combine multiple audio tracks and apply mastering effects. It supports processing audio sessions stored in date-stamped folders and provides options for managing preexisting mixed audio files and specifying a destination path for the exported files.

Prerequisites

Before running this script, make sure you have the following:

  • Python 3.x installed on your system.

Usage

Running the Script

To use the script, follow these steps:

  1. Clone or download this repository to your local machine.

  2. Open a terminal or command prompt.

  3. Navigate to the directory containing the script using the cd command.

  4. Run the script with the desired options. Here are the available command-line options:

    • --remove-preexisting: Remove preexisting mixed audio files if they exist.
    • --skip-preexisting: Skip processing of preexisting mixed audio files.
    • --folder-name: Process a specific folder by name.
    • --destination-path: Specify the destination path for exported files.
    • --num-processes: Number of processes to use for parallel processing (default: 4).

    Example usage:

    python mix_and_master.py --remove-preexisting --destination-path /path/to/export

Input Audio

The script expects audio stems to be organized within date-stamped folders, typically within a root directory. For example:

/Volumes/HD1-B/2023/
   ├── 210801_062039/
   │     ├── audio_1.wav
   │     ├── audio_2.wav
   │     ├── ...
   ├── 210802_071520/
   │     ├── audio_1.wav
   │     ├── audio_2.wav
   │     ├── ...
   ├── ...

Output

The script will mix and master the audio stems, and the resulting mixed audio files will be saved in the specified destination-path or the same folder as the original stems.

Example Command

python mix_and_master.py --remove-preexisting --destination-path /path/to/export

Notes

  • Make sure to configure the script by setting the root_directory variable in the script to the root directory containing your audio stems.

  • You can adjust the mastering effects in the script to suit your preferences.

  • This script uses the pydub library for audio processing.

License

This script is provided under the MIT License. See LICENSE for details.


You can replace placeholders like /path/to/export with actual file paths or adjust the content to fit your specific script and use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment