79 lines
3.7 KiB
Python
79 lines
3.7 KiB
Python
"""Stream local MP3 fixtures through the backend path to the configured ASR model.
|
|
|
|
Run from talkingq-url: .venv/bin/python scripts/smoke_fun_asr.py MANIFEST --output RESULT
|
|
Manifest: [{"name": "short", "audio": "path.mp3", "expected_keywords": ["你好"]}]
|
|
Uses the local ALIYUN_API_KEY. Does not start the app, connect to DB/MQTT,
|
|
invoke LLM/TTS, or send anything to a physical device. Audio must be 16 kbps MP3
|
|
unless --bytes-per-second is supplied. Empty keywords means silence is expected.
|
|
"""
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
import time
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
from implementations.aliyun_asr import AliyunASR
|
|
from services.audio_session import AudioSession
|
|
from handlers.audio_session_handler import handle_audio_packet
|
|
from handlers.service_connection_handler import send_audio_task, end_asr_session
|
|
|
|
|
|
async def run(args):
|
|
cases = json.loads(args.manifest.read_text())
|
|
results = []
|
|
for role in ['Banban', 'Baikeboshi']:
|
|
for case in cases:
|
|
audio = Path(case['audio']).read_bytes()
|
|
asr = AliyunASR(selected_role={'role_key': role})
|
|
asr.device_id = 'LOCAL_ASR_TEST'
|
|
asr.session_id = f"{role}-{case['name']}"
|
|
session = AudioSession(asr_service=asr)
|
|
session.device_id, session.session_id = asr.device_id, asr.session_id
|
|
session.send_queue = asyncio.Queue()
|
|
started = time.perf_counter()
|
|
result = {'role': role, 'case': case['name'], 'model': asr.model, 'bytes': len(audio)}
|
|
try:
|
|
await asr.connect()
|
|
result['ready_seconds'] = round(time.perf_counter() - started, 3)
|
|
session.send_task = asyncio.create_task(send_audio_task(session))
|
|
for offset in range(0, len(audio), 640):
|
|
chunk = audio[offset:offset + 640]
|
|
await handle_audio_packet(session, chunk)
|
|
await asyncio.sleep(len(chunk) / args.bytes_per_second)
|
|
ending = time.perf_counter()
|
|
await end_asr_session(session)
|
|
result['finish_seconds'] = round(time.perf_counter() - ending, 3)
|
|
result['transcript'] = session.transcript
|
|
result['error'] = session.asr_error
|
|
expected = case['expected_keywords']
|
|
result['passed'] = not session.asr_error and (
|
|
all(word.casefold() in session.transcript.casefold() for word in expected)
|
|
if expected else session.transcript == ''
|
|
)
|
|
except Exception as exc:
|
|
result.update(error=f'{type(exc).__name__}: {exc}', passed=False)
|
|
finally:
|
|
if session.send_task:
|
|
session.send_task.cancel()
|
|
await asyncio.gather(session.send_task, return_exceptions=True)
|
|
await asr.close()
|
|
result['task_id'] = asr.task_id
|
|
result['resources_released'] = asr._client is None and asr._receiver is None
|
|
results.append(result)
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(json.dumps(results, ensure_ascii=False, indent=2))
|
|
print(json.dumps(result, ensure_ascii=False), flush=True)
|
|
return all(item['passed'] and item['resources_released'] for item in results)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('manifest', type=Path)
|
|
parser.add_argument('--output', type=Path, required=True)
|
|
parser.add_argument('--bytes-per-second', type=float, default=2000)
|
|
args = parser.parse_args()
|
|
sys.exit(0 if asyncio.run(run(args)) else 1)
|