import scipy.signal import scipy.io.wavfile as wavfile import numpy as np import os class AudioClip: def __init__(self, metadata, target_sample_rate=44100): """ metadata: dict with keys 'filename', 'start', 'end' (seconds) target_sample_rate: sample rate for playback """ self.metadata = metadata self.file_path = metadata['filename'] self.start = metadata.get('startTime', 0) self.end = metadata.get('endTime', None) self.target_sample_rate = target_sample_rate self.volume = metadata.get('volume', 1.0) self.finished = False self.audio_data, self.sample_rate = self._load_and_process_audio() print(f"AudioClip created for {self.file_path} with start={self.start}s, end={self.end}s, sample_rate={self.sample_rate}Hz, length={len(self.audio_data)/self.sample_rate:.2f}s") self.position = 0 # sample index for playback def _load_and_process_audio(self): # Load audio file sample_rate, data = wavfile.read(self.file_path) # Convert to float32 if data.dtype != np.float32: data = data.astype(np.float32) / np.max(np.abs(data)) # Convert to mono if needed if len(data.shape) > 1: data = np.mean(data, axis=1) # Resample if needed if sample_rate != self.target_sample_rate: num_samples = int(len(data) * self.target_sample_rate / sample_rate) data = scipy.signal.resample(data, num_samples) sample_rate = self.target_sample_rate # Cache only the clip region start_sample = int(self.start * sample_rate) end_sample = int(self.end * sample_rate) if self.end else len(data) cached = data[start_sample:end_sample] cached *= self.volume # Apply volume return cached, sample_rate def get_samples(self, num_samples): # Return next chunk for playback if self.position >= len(self.audio_data): self.finished = True return np.zeros(num_samples, dtype=np.float32) end_pos = min(self.position + num_samples, len(self.audio_data)) chunk = self.audio_data[self.position:end_pos] self.position = end_pos if self.position >= len(self.audio_data): self.finished = True # Pad if chunk is short if len(chunk) < num_samples: chunk = np.pad(chunk, (0, num_samples - len(chunk)), mode='constant') return chunk def is_finished(self): return self.finished def reset(self): self.position = 0 self.finished = False