"""Direct Apple SpeechTranscriber file execution for Reyna CLI.""" from __future__ import annotations import hashlib import json import os import shutil import subprocess import tempfile from pathlib import Path from typing import Any, Dict, Optional _SWIFT_SOURCE = r'''import Speech import AVFoundation import Foundation @main struct ReynaTranscriber { static func main() async { let args = CommandLine.arguments guard args.count >= 2 else { fail("audio path is required", code: 2) } let audioPath = args[1] let localeID = args.count >= 3 ? args[2] : "en-US" guard FileManager.default.fileExists(atPath: audioPath) else { fail("audio file not found", code: 3) } guard SpeechTranscriber.isAvailable else { fail("SpeechTranscriber is unavailable", code: 4) } let requested = Locale(identifier: localeID) guard let locale = await SpeechTranscriber.supportedLocale(equivalentTo: requested) else { fail("unsupported locale", code: 5) } let transcriber = SpeechTranscriber(locale: locale, preset: .transcription) do { if let request = try await AssetInventory.assetInstallationRequest(supporting: [transcriber]) { try await request.downloadAndInstall() } let file = try AVAudioFile(forReading: URL(fileURLWithPath: audioPath)) let analyzer = try await SpeechAnalyzer(inputAudioFile: file, modules: [transcriber], finishAfterFile: true) _ = analyzer var segments: [String] = [] for try await result in transcriber.results { let text = String(result.text.characters).trimmingCharacters(in: .whitespacesAndNewlines) if !text.isEmpty { segments.append(text) } } let payload: [String: Any] = [ "ok": true, "engine": "SpeechAnalyzer+SpeechTranscriber", "locale": locale.identifier, "requestedLocale": localeID, "transcript": segments.joined(separator: " "), "segments": segments, "audioPath": audioPath, "isAvailable": SpeechTranscriber.isAvailable ] let data = try JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]) FileHandle.standardOutput.write(data) } catch { fail(String(describing: error), code: 6) } } static func fail(_ message: String, code: Int32) -> Never { let payload: [String: Any] = ["ok": false, "error": message] if let data = try? JSONSerialization.data(withJSONObject: payload, options: []) { FileHandle.standardOutput.write(data) } exit(code) } } ''' class SpeechExecutionError(RuntimeError): pass def _cache_dir() -> Path: return Path.home() / "Library" / "Application Support" / "reyna-cli" / "speech" def _binary_path() -> Path: digest = hashlib.sha256(_SWIFT_SOURCE.encode()).hexdigest()[:16] return _cache_dir() / f"transcriber-{digest}" def _ensure_binary() -> Path: cached = _binary_path() if cached.exists() and os.access(cached, os.X_OK): return cached swiftc = shutil.which("swiftc") or "/usr/bin/swiftc" if not Path(swiftc).exists(): raise SpeechExecutionError("swiftc is required for SpeechTranscriber file execution") _cache_dir().mkdir(parents=True, exist_ok=True) with tempfile.TemporaryDirectory(prefix="reyna-speech-build-") as tmp: source = Path(tmp) / "Transcriber.swift" binary = Path(tmp) / "transcriber" source.write_text(_SWIFT_SOURCE, encoding="utf-8") result = subprocess.run([swiftc, "-O", "-parse-as-library", str(source), "-o", str(binary), "-framework", "AVFoundation", "-framework", "Speech"], capture_output=True, text=True, timeout=120, check=False) if result.returncode != 0: raise SpeechExecutionError(f"swiftc failed: {(result.stderr or result.stdout).strip()[:2000]}") shutil.copyfile(binary, cached) cached.chmod(0o700) return cached def transcribe_file(audio_path: Path, *, locale: str = "en-US", timeout: int = 300) -> Dict[str, Any]: audio = audio_path.expanduser().resolve() if not audio.is_file(): raise SpeechExecutionError(f"audio file not found: {audio}") binary = _ensure_binary() result = subprocess.run([str(binary), str(audio), locale], capture_output=True, text=True, timeout=timeout, check=False) raw = (result.stdout or "").strip() try: payload = json.loads(raw) if raw else {} except json.JSONDecodeError as exc: raise SpeechExecutionError(f"SpeechTranscriber returned invalid JSON: {raw[:500]}") from exc if result.returncode != 0 or not payload.get("ok"): raise SpeechExecutionError(str(payload.get("error") or result.stderr.strip() or "SpeechTranscriber failed")) return {**payload, "source": "reyna_cli_direct", "binary": str(binary)}