Files
wedding-phone/resample_audio.py
grabowski 30ac7e89e9 Add audio resampling utility and scipy dependency
- Created resample_audio.py utility script
  - Automatically reads target sample rate from config.json
  - Resamples all WAV files in sounds directory
  - Creates .backup files before modifying originals
  - Handles both mono and stereo audio
  - Uses scipy.signal.resample for high-quality resampling

- Added scipy>=1.7.0 dependency to pyproject.toml
- Updated Makefile sync command to include scipy
- Updated README.md with sample rate troubleshooting section
- Updated config example in README to show 48kHz default
- Added beep_sound configuration to README system section

This resolves sample rate mismatch errors when audio files
don't match the configured rate in config.json.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 13:02:39 +07:00

152 lines
4.9 KiB
Python

#!/usr/bin/env python3
"""
Audio Resampling Utility for Wedding Phone
Resamples all WAV files in sounds directory to match configured sample rate
"""
import wave
import numpy as np
import os
import json
from scipy import signal
def load_config():
"""Load sample rate from config.json"""
config_path = 'config.json'
if not os.path.exists(config_path):
print("config.json not found, using 48000Hz as default")
return 48000
with open(config_path, 'r') as f:
config = json.load(f)
return config['audio']['sample_rate']
def resample_wav(input_file, output_file, target_rate):
"""Resample a WAV file to target sample rate"""
try:
# Open source file
with wave.open(input_file, 'rb') as wf:
n_channels = wf.getnchannels()
sampwidth = wf.getsampwidth()
source_rate = wf.getframerate()
n_frames = wf.getnframes()
# Read audio data
audio_data = wf.readframes(n_frames)
# Convert to numpy array
if sampwidth == 1:
dtype = np.uint8
elif sampwidth == 2:
dtype = np.int16
elif sampwidth == 4:
dtype = np.int32
else:
print(f"Unsupported sample width: {sampwidth}")
return False
audio_array = np.frombuffer(audio_data, dtype=dtype)
# Handle stereo
if n_channels == 2:
audio_array = audio_array.reshape(-1, 2)
# Resample
if source_rate != target_rate:
print(f" Resampling from {source_rate}Hz to {target_rate}Hz...")
num_samples = int(len(audio_array) * target_rate / source_rate)
if n_channels == 1:
resampled = signal.resample(audio_array, num_samples)
else:
# Resample each channel separately
left = signal.resample(audio_array[:, 0], num_samples)
right = signal.resample(audio_array[:, 1], num_samples)
resampled = np.column_stack((left, right))
# Convert back to original dtype
resampled = np.clip(resampled, np.iinfo(dtype).min, np.iinfo(dtype).max)
audio_array = resampled.astype(dtype)
# Write output file
with wave.open(output_file, 'wb') as wf_out:
wf_out.setnchannels(n_channels)
wf_out.setsampwidth(sampwidth)
wf_out.setframerate(target_rate)
wf_out.writeframes(audio_array.tobytes())
return True
except Exception as e:
print(f" Error: {e}")
return False
def main():
"""Main resampling function"""
print("Wedding Phone Audio Resampler")
print("=" * 50)
# Load target sample rate
target_rate = load_config()
print(f"\nTarget sample rate: {target_rate}Hz")
# Check sounds directory
sounds_dir = './rotary_phone_data/sounds'
if not os.path.exists(sounds_dir):
print(f"\nSounds directory not found: {sounds_dir}")
return
# Get all WAV files
wav_files = [f for f in os.listdir(sounds_dir) if f.endswith('.wav')]
if not wav_files:
print("\nNo WAV files found in sounds directory")
return
print(f"\nFound {len(wav_files)} WAV file(s)")
print("-" * 50)
# Process each file
for filename in wav_files:
input_path = os.path.join(sounds_dir, filename)
print(f"\nProcessing: {filename}")
# Check current sample rate
try:
with wave.open(input_path, 'rb') as wf:
current_rate = wf.getframerate()
print(f" Current rate: {current_rate}Hz")
if current_rate == target_rate:
print(f" ✓ Already at {target_rate}Hz, skipping")
continue
except Exception as e:
print(f" Error reading file: {e}")
continue
# Create backup
backup_path = input_path + '.backup'
if not os.path.exists(backup_path):
os.rename(input_path, backup_path)
print(f" Backup created: {filename}.backup")
else:
input_path = backup_path
print(f" Using existing backup")
# Resample
output_path = os.path.join(sounds_dir, filename)
if resample_wav(input_path, output_path, target_rate):
print(f" ✓ Successfully resampled to {target_rate}Hz")
else:
print(f" ✗ Failed to resample, restoring backup")
if os.path.exists(backup_path):
os.rename(backup_path, output_path)
print("\n" + "=" * 50)
print("Resampling complete!")
print("\nBackup files (.backup) have been created.")
print("If everything works, you can delete them.")
if __name__ == "__main__":
main()