transcribe method

Future<CactusTranscriptionResult> transcribe({
  1. String? audioFilePath,
  2. Stream<Uint8List>? audioStream,
  3. dynamic onChunk(
    1. CactusTranscriptionResult
    )?,
  4. String prompt = whisperPrompt,
  5. CactusTranscriptionParams? params,
})

Implementation

Future<CactusTranscriptionResult> transcribe({
  String? audioFilePath,
  Stream<Uint8List>? audioStream,
  Function(CactusTranscriptionResult)? onChunk,
  String prompt = whisperPrompt,
  CactusTranscriptionParams? params,
}) async {
  if (audioFilePath == null && audioStream == null) {
    throw ArgumentError('Must provide either audioFilePath or audioStream');
  }

  if (audioFilePath != null && audioStream != null) {
    throw ArgumentError('Cannot provide both audioFilePath and audioStream');
  }

  // File transcription mode
  if (audioFilePath != null) {
    return await _handleLock.synchronized(() async {
      final transcriptionParams = params ?? defaultTranscriptionParams;
      final model = _lastInitializedModel ?? defaultInitParams.model;
      final currentHandle = await _getValidatedHandle(model: model);

      if (currentHandle != null) {
        try {
          final result = await CactusContext.transcribe(
            currentHandle,
            prompt,
            audioFilePath: audioFilePath,
            params: transcriptionParams,
          );
          _logTranscriptionTelemetry(result, model, success: result.success, message: result.errorMessage);
          return result;
        } catch (e) {
          debugPrint('Transcription failed: $e');
          _logTranscriptionTelemetry(null, model, success: false, message: e.toString());
          rethrow;
        }
      }

      throw Exception('Model $_lastInitializedModel is not downloaded. Please download it before transcribing.');
    });
  }

  final List<int> buffer = [];
  final completer = Completer<CactusTranscriptionResult>();

  final subscription = audioStream!.listen(
    (pcmChunk) {
      buffer.addAll(pcmChunk);
    },
    onError: (error) {
      debugPrint('Audio stream error: $error');
      if (!completer.isCompleted) {
        completer.completeError(error);
      }
    },
    onDone: () async {
      if (buffer.isEmpty) {
        debugPrint('No audio data received');
        if (!completer.isCompleted) {
          completer.complete(CactusTranscriptionResult(
            success: false,
            text: '',
            errorMessage: 'No audio data received',
          ));
        }
        return;
      }

      final pcmData = PCMUtils.validatePCMBuffer(buffer)
          ? buffer
          : PCMUtils.trimToValidSamples(buffer);

      if (pcmData.isEmpty) {
        debugPrint('No valid audio data after trimming');
        if (!completer.isCompleted) {
          completer.complete(CactusTranscriptionResult(
            success: false,
            text: '',
            errorMessage: 'No valid audio data received',
          ));
        }
        return;
      }

      try {
        final result = await _transcribePCMInternal(pcmData, prompt, params);
        if (!completer.isCompleted) {
          completer.complete(result);
        }
        onChunk?.call(result);
      } catch (e) {
        debugPrint('Failed to transcribe audio: $e');
        if (!completer.isCompleted) {
          completer.completeError(e);
        }
      }
    },
  );

  // Store subscription for potential cancellation
  completer.future.whenComplete(() => subscription.cancel());

  return completer.future;
}