Changed from os.rename() to shutil.copy2() when creating backups. This prevents the "No such file or directory" error that occurred when trying to write the resampled file after the original was renamed. Now the script: - Copies original to .backup (instead of renaming) - Reads from .backup file - Writes resampled audio to original filename 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
157 lines
5.0 KiB
Python
157 lines
5.0 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
|
|
import shutil
|
|
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'
|
|
source_path = input_path
|
|
|
|
if not os.path.exists(backup_path):
|
|
# Copy to backup instead of rename, so original still exists
|
|
shutil.copy2(input_path, backup_path)
|
|
print(f" Backup created: {filename}.backup")
|
|
source_path = backup_path
|
|
else:
|
|
print(f" Using existing backup")
|
|
source_path = backup_path
|
|
|
|
# Resample
|
|
output_path = os.path.join(sounds_dir, filename)
|
|
if resample_wav(source_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()
|