Angular

Comment implémenter Speech-To-Text avec 4 APIs en Angular ? 2/2

Sep 27, 2022

Emmanuelle ABOAF

Introduction

Dans l’article précédent, j’ai présenté comment implémenter le Speech-To-Text (transformation de flux sonore en texte) avec les APIs de Deepgram et de Google en Angular.

Dans cette deuxième partie je vais aborder l’implémentation avec les APIs de Microsoft et de Mozilla, puis en conclusion, je vous ferai une synthèse sur l’ensemble de ces APIs.

Microsoft

Pour utiliser l’API de Microsoft, il faut avoir un abonnement Azure et créer un service « Azure Cognitive Services ». Le service est gratuit jusqu’à 5h d’audio par mois. Pour créer le service, il faut :

  • Créer la ressource « Speech » avec le service plan et tout ce qui va avec. Vous choisissez le service plan qui correspond à vos besoins et la localisation de votre service ;
  • Avoir la clé API et la localisation de votre ressource qui se trouvent dans les paramètres de votre ressource « Keys and Endpoint » ;
  • Utiliser le SDK microsoft-cognitiveservices-speech-sdk disponible en NodeJS.

Le SDK est disponible dans plusieurs langages de programmation : C#, C++, Go, Java, JavaScript, Objective-C, Python, Swift. Vous pouvez aussi implémenter le service avec l’interface CLI Speech en ligne de commande ou en REST.

Implémentation

Grâce au package microsoft-cognitiveservices-speech-sdk , je n’ai pas eu besoin de coder grand-chose. Il y a tout ce qu’il faut sans que j’ai besoin de générer de requêtes particulières. Pour mon application simple, le code fourni par Microsoft va faire le travail tout seul avec ma clé API et ma localisation. Il suffit de faire appel à deux méthodes :

  • startContinuousRecognitionAsync pour démarrer la transcription en temps réel
  • stopContinuousRecognitionAsync pour arrêter la transcription en temps réel.

D’abord, on va initialiser l’objet SpeechRecognizer avec ses paramètres. Je définis ma configuration avec ma clé API et la localisation de ma ressource dans l’objet SpeechConfig, ainsi que la langue à utiliser pour la reconnaissance vocale.

Puis j’instancie l’objet SpeechRecozigner avec ma configuration. L’événement recognizing est un événement déclencheur qui va permettre de transcrire automatiquement les propos. Pour cela, je lui affecte ma méthode « onRecognitionResult(event) » pour avoir les résultats de la transcription automatique.

const speechConfig = SpeechConfig.fromSubscription("API_KEY", "location");
speechConfig.speechRecognitionLanguage = "fr-FR";

this.recognizer = new SpeechRecognizer(speechConfig);
this.recognizer.recognizing = (sender: Recognizer, event: SpeechRecognitionEventArgs) => 
         this.onRecognitionResult(event);

Dans la méthode « onRecognitionResult », on va gérer à la fois les erreurs et les résultats.

public onRecognitionResult(event: SpeechRecognitionEventArgs): void {
    switch (event.result.reason) {
      case ResultReason.RecognizedSpeech:
        console.log(`RECOGNIZED: Text=${event.result.text}`);
        break;
      case ResultReason.NoMatch:
        console.log('NOMATCH: Speech could not be recognized.');
        return;
      case ResultReason.Canceled:
        const cancellation = CancellationDetails.fromResult(event.result);
        console.log(`CANCELED: Reason=${cancellation.reason}`);

        if (cancellation.reason == CancellationReason.Error) {
          console.log(`CANCELED: ErrorCode=${cancellation.ErrorCode}`);
          console.log(`CANCELED: ErrorDetails=${cancellation.errorDetails}`);
          console.log('CANCELED: Did you update the key and location/region info?');
        }
        return;
    }

    this.transcript = event.result.text;
  }

Mais cela ne suffit pas. Je dois jouer sur les offsets (ou décalages en français) pour avoir la phrase entière. Comme vous pouvez le constater sur l’image affichant les morceaux de texte et les différents offsets sur la console F12, on obtient la phrase complète sur trois tranches d’offset.

Affichage des différents offsets en temps-réel du Speech-To-Text

Le premier offset est de 920000. Tant que l’offset ne change pas, on voit la phrase qui se construit petit à petit :

  • bonjour
  • bonjour je
  • bonjour je m’appelle
  • bonjour je m’appelle emmanuel
  • bonjour je m’appelle emmanuelle

On voit en temps-réel qu’il y a une correction de mon prénom.

Puis, l’offset change et passe à 7970000 pour avoir la phrase suivante :

  • et je
  • et je vais bien

Et enfin, on passe au dernier offset en passant à 129200000 pour obtenir la dernière phrase :

  • je
  • je suis
  • je suis content
  • je suis
  • je suis contente

Cette dernière tranche est intéressante car on voit réellement ce qui se passe. L’IA réfléchit et se dit « le mot content n’est pas bon », donc « je vais l’effacer et proposer un autre mot ».

Avec la variable prevOffset, je peux savoir quand avoir la phrase complète et la stocker avant de passer à la tranche suivante. Je stocke le résultat du texte reçu dans la variable « transcript » et si je change d’offset, je mets la phrase finale dans la variable « finalTranscript » et je mets la nouvelle phrase dans la variable « transcript ». Et ainsi de suite.

   if (this.prevOffset === 0) {
      this.prevOffset = event.result.offset;
    }

    if (event.result.offset !== this.prevOffset) {
      this.prevOffset = event.result.offset;
      this.finalTranscript += `${this.transcript} `;
    }

    this.transcript = event.result.text;

Quand je démarre ou j’arrête le service automatique, je déclenche les événements associés.

  public onStartRecognitionClick(): void {
    this.transcript = '';
    this.recognizer.startContinuousRecognitionAsync();
  }

  public onStopRecognitionClick(): void {
    this.recognizer.stopContinuousRecognitionAsync();
  }

Assemblage

Si on assemble tout ça, voici ce que ça va donner :

<div class="microsoft-speech-to-text-component">
  <h1>Microsoft</h1>
  <div class="microsoft-speech-to-text-component-buttons">
    <button class="btn btn-primary" (click)="onStartRecognitionClick($event)" *ngIf="!isRecording">
      Démarrer
      <i class="fas fa-microphone"></i>
    </button>
    <button class="btn btn-secondary" (click)="onStopRecognitionClick($event)" *ngIf="isRecording">
      Arrêter<i class="fas fa-microphone-slash"></i>
    </button>
  </div>
  <p class="microsoft-speech-to-text-component-content">
    {{ textTranscripted }}
  </p>
</div>
.microsoft-speech-to-text-component {
  &-buttons {
    margin-top: 2em;
    text-align: center;
  }

  &-content {
    margin-top: 2em;
  }
}
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import {
  CancellationDetails,
  CancellationReason,
  Recognizer,
  ResultReason,
  SpeechConfig,
  SpeechRecognitionEventArgs,
  SpeechRecognizer,
} from 'microsoft-cognitiveservices-speech-sdk';

@Component({
  selector: 'app-microsoft',
  templateUrl: './microsoft.component.html',
  styleUrls: ['./microsoft.component.scss'],
})
export class MicrosoftComponent implements OnInit {
  private recognizer: SpeechRecognizer;
  private finalTranscript: string = '';
  private prevOffset = 0;
  private transcript: string = '';

  public isRecording: boolean = false;
  public textTranscripted: string = '';

  constructor(private titleService: Title) {
    titleService.setTitle('Microsoft Speech-To-Text');
  }

  //#region LIFE CYCLES
  public ngOnInit(): void {
    this.initRecognition();
  }
  //#endregion

  //#region EVENTS
  public onStartRecognitionClick(): void {
    this.textTranscripted = '';
    this.transcript = '';
    this.finalTranscript = '';

    this.prevOffset = 0;
    this.isRecording = true;

    this.recognizer.startContinuousRecognitionAsync();
  }

  public onRecognitionResult(event: SpeechRecognitionEventArgs): void {
    switch (event.result.reason) {
      case ResultReason.RecognizedSpeech:
        console.log(`RECOGNIZED: Text=${event.result.text}`);
        break;
      case ResultReason.NoMatch:
        console.log('NOMATCH: Speech could not be recognized.');
        return;
      case ResultReason.Canceled:
        const cancellation = CancellationDetails.fromResult(event.result);
        console.log(`CANCELED: Reason=${cancellation.reason}`);

        if (cancellation.reason == CancellationReason.Error) {
          console.log(`CANCELED: ErrorCode=${cancellation.ErrorCode}`);
          console.log(`CANCELED: ErrorDetails=${cancellation.errorDetails}`);
          console.log('CANCELED: Did you update the key and location/region info?');
        }
        return;
    }

    if (this.prevOffset === 0) {
      this.prevOffset = event.result.offset;
    }

    if (event.result.offset !== this.prevOffset) {
      this.prevOffset = event.result.offset;
      this.finalTranscript += `${this.transcript} `;
    }

    this.transcript = event.result.text;
    this.textTranscripted = this.finalTranscript + this.transcript;
  }

  public onStopRecognitionClick(): void {
    this.isRecording = false;
    this.textTranscripted = this.finalTranscript + this.transcript;
    this.recognizer.stopContinuousRecognitionAsync();
  }
  //#endregion

  //#region FUNCTIONS
  public initRecognition(): void {
    const speechConfig = SpeechConfig.fromSubscription("API_KEY", "location");
    speechConfig.speechRecognitionLanguage = "fr-FR";
    this.recognizer = new SpeechRecognizer(speechConfig);
    this.recognizer.recognizing = (sender: Recognizer, event: SpeechRecognitionEventArgs) => this.onRecognitionResult(event);
  }

  //#endregion
}

Ce qui va donner visuellement :

Microsoft, bouton démarrer, texte transcrit : bonjour je m'appelle emmanuelle et je vais bien je suis contente
Capture d’écran du rendu du Speech-To-Text de Microsoft

Mozilla

Mozilla n’a pas de package proprement dit puisqu’il met à disposition son code en open-source. On peut facilement utiliser l’API Web Speech grâce à la documentation MDN. D’ailleurs, celle-ci contient des bonnes pratiques du développement Web en HTML, CSS et JavaScript avec des protocoles de sécurité.

Attention, cette API n’est pas compatible avec les navigateurs Firefox (sic) et Opera.

Implémentation

On va utiliser l’objet « webkitSpeechRecognition » et ses paramètres pour initialiser l’API Web Speech avec la valeur « continuous » à true. Cela permet d’avoir les résultats en continu c’est à dire que l’on affiche les résultats petit à petit plutôt que tout en un seul coup.

On associe la méthode onRecognitionResult à l’évènement JavaScript « result », un événement propre à l’API Web Speech.

public initRecognition(): void {
    this.recognition = new webkitSpeechRecognition();
    this.recognition.lang = "fr-FR";
    this.recognition.continuous = true;
    this.recognition.addEventListener('result', (e: any) => this.onRecognitionResult(e));
  }

L’implémentation du résultat est également facilitée. On transforme l’objet « event.results » en tableau et on prend la première occurrence du tableau avec la valeur « transcript ».

  public onRecognitionResult(event: any): void {
    this.transcript = Array.from(event.results)
      .map((result: any) => result[0])
      .map((result: any) => result.transcript)
      .join('');
  }

Comme avec Microsoft, il est facile d’implémenter l’API en faisant appel à ses deux méthodes « start » et « stop ».

 public onStartRecognitionClick(): void {
    this.transcript = '';
    this.isRecording = true;
    this.recognition.start();
}

public onStopRecognitionClick(): void {
    this.isRecording = false;
    this.recognition.stop();
}

Et c’est tout. Comme vous avez pu le constater, c’est très facile à implémenter.

Assemblage

Voyons ce que cela donne lorsque l’on assemble tout ça.

<div class="mozilla-speech-to-text-component">
  <h1>Mozilla</h1>
  <div class="mozilla-speech-to-text-component-buttons">
    <button class="btn btn-primary" (click)="onStartRecognitionClick($event)" *ngIf="!isRecording">
      Démarrer
      <i class="fas fa-microphone"></i>
    </button>
    <button class="btn btn-secondary" (click)="onStopRecognitionClick($event)" *ngIf="isRecording">
      Arrêter<i class="fas fa-microphone-slash"></i>
    </button>
  </div>
  <p class="mozilla-speech-to-text-component-content">
    {{ transcript }}
  </p>
</div>
.mozilla-speech-to-text-component {
  &-buttons {
    margin-top: 2em;
    text-align: center;
  }

  &-content {
    margin-top: 2em;
  }
}
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { AppConfig } from '@core/app-config';

declare var webkitSpeechRecognition: any;

@Component({
  selector: 'app-mozilla',
  templateUrl: './mozilla.component.html',
  styleUrls: ['./mozilla.component.scss'],
})
export class MozillaComponent implements OnInit {
  private recognition: any;

  public transcript: string = '';
  public isRecording = false;

  constructor(titleService: Title) {
    titleService.setTitle('Mozilla Speech-To-Text');
  }
  //#region LIFE CYCLES
  public ngOnInit(): void {
    this.initRecognition();
  }

  //#endregion

  //#region EVENTS
  public onStartRecognitionClick(): void {
    this.transcript = '';
    this.isRecording = true;
    this.recognition.start();
  }

  public onRecognitionResult(event: any): void {
    this.transcript = Array.from(event.results)
      .map((result: any) => result[0])
      .map((result: any) => result.transcript)
      .join('');
  }

  public onStopRecognitionClick(): void {
    this.isRecording = false;
    this.recognition.stop();
  }
  //#endregion

  //#region FUNCTIONS
  public initRecognition(): void {
    this.recognition = new webkitSpeechRecognition();
    this.recognition.lang = "fr-FR";
    this.recognition.continuous = true;

    this.recognition.addEventListener('result', (e: any) => this.onRecognitionResult(e));
  }
  //#endregion
}

Ce qui va donner visuellement :

Capture d’écran du rendu du Speech-To-Text de Mozilla

Conclusion

Comme vous avez pu le constater, les codes HTML et CSS sont identiques pour chacun des composants ainsi que certains appels en TypeScript. C’est pourquoi j’ai refactorisé ; pour éviter d’utiliser à chaque fois le même code. Vous pouvez trouver mes sources avec refactorisation sur https://github.com/emma11y/speech-to-text ainsi que les exemples de code de Microsoft et Mozilla dans le dossier example-article.

En refactorisant, j’ai créé un petit site avec les 4 APIs. Évidemment, avec mon accent de personne sourde, la transcription automatique n’est pas efficace à 100%. D’ailleurs, j’en parlais lors ma conférence API Speech-To-Text : quelles sont ses avantages et ses limites ? que vous pouvez visionner en replay et voir les démonstrations entre les 4 différents APIs.

Site Speech-To-Text avec les 4 APIs

Avant de me plonger sur le sujet, je me suis demandé comment fonctionnait le Speech-To-Text et pourquoi il y avait une telle différence entre ces services. En créant ce site, j’ai pu comprendre les subtilités des implémentations des différentes APIs sur mon projet Angular.

J’ai eu beaucoup de mal à utiliser le service Google car malgré la documentation et le package Node.JS existant, j’avais des erreurs de compilation, ce qui était fort dommage. Finalement, j’ai utilisé le service en HTTP Request.

Celui de Mozilla étant open-source et tournant complètement dans le navigateur, il ne nécessitait aucune création de compte ni de création de service ou installation de package. Il était donc le plus facile à mettre en place.

Avec Microsoft je n’ai eu aucun mal à implémenter le SDK ni à utiliser Azure, qui est la plateforme avec laquelle j’ai l’habitude de travailler car étant le cœur de mon métier. Comme Mozilla, cela a nécessité très peu de code supplémentaire.

Deepgram m’a fait découvrir l’univers des WebSockets, que je maîtrisais très peu et cela m’a permis d’en apprendre davantage.

Ce sont 4 APIs tout à fait différentes avec leurs avantages et leurs inconvénients (compatibilité navigateurs, dépendances à des services cloud, coût). À vous de choisir ce qui vous convient le mieux et la technologie avec laquelle vous vous sentez le plus à l’aise.

0 commentaires

Soumettre un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Découvrez nos autres articles

Aller au contenu principal