Skip to content

Instantly share code, notes, and snippets.

@PeterDekkers
Last active August 20, 2023 22:16
Show Gist options
  • Save PeterDekkers/30e5fc85b756c9079c3b58a6158439c2 to your computer and use it in GitHub Desktop.
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.
#! /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