SyncMaster / audio_processor.py
aseelflihan's picture
Update audio_processor.py
9e3f5f9 verified
Raw History Blame Contribute Delete
4.86 kB
# audio_processor.py - Final definitive fix for Numba caching issue
import os
from dotenv import load_dotenv
import tempfile
from typing import List, Dict, Optional, Tuple
import json
import traceback
# --- DEFINITIVE NUMBA FIX ---
# This MUST be done BEFORE importing librosa
os.environ["NUMBA_CACHE_DIR"] = "/tmp"
# Now, import librosa safely
import librosa
# --- END OF FIX ---
from google import genai
from google.genai import types
class AudioProcessor:
def __init__(self):
self.client = None
self.init_error = None
self._initialize_gemini()
def _initialize_gemini(self):
try:
load_dotenv()
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
raise ValueError("GEMINI_API_KEY not found in environment variables.")
self.client = genai.Client(api_key=api_key)
except Exception as e:
self.init_error = f"--- FATAL ERROR during Gemini Init: {str(e)} ---"
self.client = None
def transcribe_audio(self, audio_file_path: str) -> Tuple[Optional[str], Optional[str]]:
"""
Transcribes audio. Returns (text, error_message).
"""
if self.init_error:
return None, self.init_error
if not self.client:
return None, "--- ERROR: Gemini client is not available. ---"
try:
if not os.path.exists(audio_file_path):
return None, f"--- ERROR: Audio file for transcription not found at: {audio_file_path} ---"
with open(audio_file_path, 'rb') as f:
audio_bytes = f.read()
file_ext = os.path.splitext(audio_file_path)[1].lower()
mime_type = {'mp3': 'audio/mpeg', 'wav': 'audio/wav', 'm4a': 'audio/mp4'}.get(file_ext, 'audio/mpeg')
response = self.client.models.generate_content(
model="gemini-2.5-flash",
contents=[types.Part.from_bytes(data=audio_bytes, mime_type=mime_type), "Transcribe this audio."]
)
if response and response.text:
return response.text.strip(), None
else:
return None, "--- WARNING: Gemini returned an empty response for transcription. ---"
except Exception as e:
error_msg = f"--- FATAL ERROR during transcription: {traceback.format_exc()} ---"
return None, error_msg
def get_audio_duration(self, audio_file_path: str) -> Tuple[Optional[float], Optional[str]]:
"""
Gets audio duration. Returns (duration, error_message).
"""
try:
if not os.path.exists(audio_file_path):
return None, f"--- ERROR: Audio file for duration not found at: {audio_file_path} ---"
duration = librosa.get_duration(path=audio_file_path)
if duration is None or duration < 0.1:
return None, f"--- ERROR: librosa returned an invalid duration: {duration}s ---"
return duration, None
except Exception as e:
error_msg = f"--- FATAL ERROR getting audio duration with librosa: {traceback.format_exc()} ---"
return None, error_msg
def get_word_timestamps(self, audio_file_path: str) -> Tuple[List[Dict], List[str]]:
"""
Generates timestamps. Returns (timestamps, log_messages).
"""
logs = ["--- INFO: Starting get_word_timestamps... ---"]
transcription, error = self.transcribe_audio(audio_file_path)
if error:
logs.append(error)
return [], logs
logs.append(f"--- DEBUG: Transcription successful. Text: '{transcription[:50]}...'")
audio_duration, error = self.get_audio_duration(audio_file_path)
if error:
logs.append(error)
return [], logs
logs.append(f"--- DEBUG: Audio duration successful. Duration: {audio_duration:.2f}s")
words = transcription.split()
if not words:
logs.append("--- WARNING: Transcription resulted in zero words. ---")
return [], logs
logs.append(f"--- INFO: Distributing {len(words)} words across the duration. ---")
word_timestamps = []
total_words = len(words)
usable_duration = max(0, audio_duration - 1.0)
for i, word in enumerate(words):
start_time = 0.5 + (i * (usable_duration / total_words))
end_time = 0.5 + ((i + 1) * (usable_duration / total_words))
word_timestamps.append({'word': word.strip(), 'start': round(start_time, 3), 'end': round(end_time, 3)})
logs.append(f"--- SUCCESS: Generated {len(word_timestamps)} word timestamps. ---")
return word_timestamps, logs