import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:mymuseum_visitapp/Services/Glasses/engines/stt_engine.dart'; /// STT via API Whisper — format OpenAI multipart/form-data. /// /// Compatible avec : /// - OpenAI Whisper API : endpoint = 'https://api.openai.com/v1/audio/transcriptions' /// - faster-whisper-server : endpoint = 'http://ton-serveur:8000/v1/audio/transcriptions' /// - openedai-speech : même format /// /// Coût : $0.006/min (OpenAI) ou gratuit si auto-hébergé. class WhisperSttEngine implements SttEngine { final String endpoint; final String apiKey; final String model; final FlutterSoundRecorder _recorder = FlutterSoundRecorder(); bool _initialized = false; String? _currentPath; WhisperSttEngine({ required this.apiKey, this.endpoint = 'https://api.openai.com/v1/audio/transcriptions', this.model = 'whisper-1', }); Future _ensureInitialized() async { if (_initialized) return; await Permission.microphone.request(); await _recorder.openRecorder(); _initialized = true; } @override Future transcribeOnce({ required String languageCode, Duration timeout = const Duration(seconds: 8), }) async { await _ensureInitialized(); final dir = await getTemporaryDirectory(); _currentPath = '${dir.path}/whisper_stt_${DateTime.now().millisecondsSinceEpoch}.wav'; try { await _recorder.startRecorder( toFile: _currentPath, codec: Codec.pcm16WAV, sampleRate: 16000, numChannels: 1, ); await Future.delayed(timeout); await _recorder.stopRecorder(); return await _transcribe(_currentPath!, languageCode); } catch (e) { debugPrint('[WhisperSttEngine] error: $e'); try { await _recorder.stopRecorder(); } catch (_) {} return ''; } finally { try { if (_currentPath != null) File(_currentPath!).deleteSync(); } catch (_) {} _currentPath = null; } } @override Future cancel() async { try { await _recorder.stopRecorder(); } catch (_) {} } Future _transcribe(String wavPath, String languageCode) async { final lang = languageCode.split('-').first; // "fr-FR" → "fr" final bytes = await File(wavPath).readAsBytes(); final request = http.MultipartRequest('POST', Uri.parse(endpoint)) ..headers['Authorization'] = 'Bearer $apiKey' ..fields['model'] = model ..fields['language'] = lang ..fields['response_format'] = 'json' ..files.add(http.MultipartFile.fromBytes( 'file', bytes, filename: 'audio.wav', )); final response = await request.send().timeout(const Duration(seconds: 15)); final body = await response.stream.bytesToString(); if (response.statusCode != 200) { debugPrint('[WhisperSttEngine] HTTP ${response.statusCode}: $body'); return ''; } final json = jsonDecode(body) as Map; final text = (json['text'] as String? ?? '').trim(); debugPrint('[WhisperSttEngine] "$text"'); return text; } }