Last active
August 26, 2024 12:25
-
-
Save PeterDekkers/30e5fc85b756c9079c3b58a6158439c2 to your computer and use it in GitHub Desktop.
Simple macOS script to convert AIFF and WAV files to 44.1kHz 16bit mono PCM. Ideal for use with Polyend Play.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python3 | |
""" | |
Simple macOS script to recursively convert AIFF and WAV files to 44.1kHz 16bit mono PCM. | |
Ideal for use with Polyend Play. | |
Only files that are not already in the right format will be converted. | |
Requires Python 3. | |
Usage: ./polyend-play-convert.py ~/Samples | |
""" | |
import os | |
import re | |
import sys | |
import subprocess | |
from pathlib import Path | |
from xml.etree.ElementTree import fromstring, ParseError | |
from dataclasses import dataclass | |
AUDIO_EXTENSION = ".wav" | |
AUDIO_CHANNELS = 1 | |
AUDIO_SAMPLE_RATE = 44100 | |
AUDIO_BIT_DEPTH = 16 | |
AUDIO_FORMAT_TYPE = "lpcm" | |
@dataclass | |
class FileToConvert: | |
path: Path | |
audio_channels: int | |
sample_rate: int | |
bit_depth: int | |
format_type: str | |
def get_files_recursive( | |
target_path: Path, | |
) -> list[Path]: | |
""" | |
Recursively gets audio files anywhere in `path`. | |
""" | |
waves = target_path.rglob(f"**/*.wav") | |
aiffs = target_path.rglob(f"**/*.aiff") | |
aifs = target_path.rglob(f"**/*.aif") | |
return [ | |
child for child in list(waves) + list(aiffs) + list(aifs) if child.is_file() | |
] | |
def get_namespace_from_tag(tag: str) -> str: | |
m = re.match(r"{.*}", tag) | |
return m.group(0) if m else "" | |
def convert(): | |
try: | |
target_folder = sys.argv[1] | |
if not target_folder: | |
raise ValueError() | |
except (IndexError, ValueError): | |
raise SystemExit(f"Usage: {sys.argv[0]} <target_folder>") | |
target_path = Path(target_folder) | |
if not target_path.is_dir(): | |
raise SystemExit(f"Not a valid directory: {sys.argv[1]}") | |
# Loop all the files and get only those that need to be converted | |
file_paths = get_files_recursive(target_path) | |
files_to_convert: list[FileToConvert] = [] | |
for file_path in file_paths: | |
cmd_output = subprocess.check_output(["afinfo", "-x", str(file_path)]) | |
try: | |
xml_tree = fromstring(cmd_output) | |
except ParseError: | |
raise SystemExit(f"PARSE ERROR: '{file_path}' - Possibly an invalid file?") | |
xml_namespace = get_namespace_from_tag(xml_tree.tag) | |
xml_track = ( | |
xml_tree.find( | |
f"{xml_namespace}audio_file", | |
) | |
.find(f"{xml_namespace}tracks") | |
.find(f"{xml_namespace}track") | |
) | |
audio_channels = int(xml_track.find(f"{xml_namespace}num_channels").text) | |
sample_rate = int(xml_track.find(f"{xml_namespace}sample_rate").text) | |
bit_depth = int(xml_track.find(f"{xml_namespace}bit_depth").text) | |
format_type = xml_track.find(f"{xml_namespace}format_type").text | |
if ( | |
file_path.suffix != AUDIO_EXTENSION | |
or audio_channels != AUDIO_CHANNELS | |
or sample_rate != AUDIO_SAMPLE_RATE | |
or bit_depth != AUDIO_BIT_DEPTH | |
or format_type != AUDIO_FORMAT_TYPE | |
): | |
files_to_convert.append( | |
FileToConvert( | |
path=file_path, | |
audio_channels=audio_channels, | |
sample_rate=sample_rate, | |
bit_depth=bit_depth, | |
format_type=format_type, | |
) | |
) | |
if not files_to_convert: | |
raise SystemExit(f"No WAV/AIFF files found that need conversion.") | |
# Confirm | |
proceed = input( | |
f"Are you sure that you want to convert all {len(files_to_convert)} files " | |
f"in the following directory to 44.1kHz 16bit mono PCM WAV?" | |
f"\n\n{target_path}\n\ny/N\n\n" | |
) | |
if proceed.lower() not in ["y", "yes"]: | |
raise SystemExit("Cancelled.") | |
# Convert | |
for file_to_convert in files_to_convert: | |
file_path = str(file_to_convert.path) | |
subprocess.run( | |
[ | |
"afconvert", | |
"-f", | |
"WAVE", | |
"--mix", | |
"-c", | |
"1", | |
"-d", | |
"LEI16@44100", | |
file_path, | |
file_to_convert.path.with_suffix(AUDIO_EXTENSION), | |
] | |
) | |
if file_to_convert.path.suffix != AUDIO_EXTENSION: | |
os.unlink(file_to_convert.path) | |
print(f"- {str(file_to_convert.path).replace(target_folder, '')}") | |
print("\n\nAll done.\n\n") | |
if __name__ == "__main__": | |
convert() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment