Conversion de la parole en PDF avec NextJS et ExpressJS PlatoBlockchain Data Intelligence. Recherche verticale. Aï.

Conversion de Speech en PDF avec NextJS et ExpressJS

Les interfaces vocales devenant de plus en plus courantes, il vaut la peine d'explorer certaines des choses que nous pouvons faire avec les interactions vocales. Comme, et si nous pouvions dire quelque chose et le faire transcrire et pomper sous forme de PDF téléchargeable ?

Eh bien, alerte spoiler : nous sommes absolument vous fais ça ! Il existe des bibliothèques et des frameworks que nous pouvons bricoler pour y arriver, et c'est ce que nous allons faire ensemble dans cet article.

Ce sont les outils que nous utilisons

Tout d'abord, voici les deux grands acteurs : Next.js et Express.js.

Next.js s'attaque aux fonctionnalités supplémentaires de React, y compris les fonctionnalités clés pour la création de sites statiques. C'est un incontournable pour de nombreux développeurs en raison de ce qu'il offre dès la sortie de la boîte, comme le routage dynamique, l'optimisation d'image, le routage de domaine et de sous-domaine intégré, les actualisations rapides, le routage du système de fichiers et les routes d'API... parmi beaucoup, beaucoup d'autres choses.

Dans notre cas, nous avons définitivement besoin de Next.js pour son Itinéraires d'API sur notre serveur client. Nous voulons une route qui prend un fichier texte, le convertit en PDF, l'écrit dans notre système de fichiers, puis envoie une réponse au client.

Express.js nous permet d'obtenir une petite application Node.js avec routage, aides HTTP et modèles. C'est un serveur pour notre propre API, ce dont nous aurons besoin lorsque nous transmettrons et analyserons des données entre les choses.

Nous avons d'autres dépendances que nous allons utiliser :

  1. réaction-reconnaissance vocale: Une bibliothèque pour convertir la parole en texte, la rendant disponible pour les composants React.
  2. runtime du régénérateur: Une bibliothèque pour dépanner le "regeneratorRuntime n'est pas défini" erreur qui apparaît dans Next.js lors de l'utilisation de la reconnaissance vocale de réaction
  3. nœud html-pdf: Une bibliothèque pour convertir une page HTML ou une URL publique en PDF
  4. axios: Une bibliothèque pour faire des requêtes HTTP à la fois dans le navigateur et Node.js
  5. cors: Une bibliothèque qui permet le partage de ressources cross-origin

Mise en place

La première chose que nous voulons faire est de créer deux dossiers de projet, un pour le client et un pour le serveur. Nommez-les comme vous voulez. je nomme le mien audio-to-pdf-client ainsi que le audio-to-pdf-server, Respectivement.

Le moyen le plus rapide de démarrer avec Next.js côté client est de le démarrer avec créer-prochaine-application. Alors, ouvrez votre terminal et exécutez la commande suivante depuis votre dossier de projet client :

npx create-next-app client

Nous avons maintenant besoin de notre serveur Express. Nous pouvons l'obtenir par cd-ing dans le dossier du projet du serveur et en exécutant le npm init commander. UNE package.json Le fichier sera créé dans le dossier du projet du serveur une fois terminé.

Nous devons encore installer Express, alors faisons-le maintenant avec npm install express. Nous pouvons maintenant créer un nouveau index.js fichier dans le dossier du projet du serveur et déposez-y ce code :

const express = require("express")
const app = express()

app.listen(4000, () => console.log("Server is running on port 4000"))

Prêt à exécuter le serveur ?

node index.js

Nous allons avoir besoin de quelques dossiers supplémentaires et d'un autre fichier pour avancer :

  • Créer un components dossier dans le dossier du projet client.
  • Créer un SpeechToText.jsx déposer dans le components sous-dossier.

Avant d'aller plus loin, nous avons un petit nettoyage à faire. Plus précisément, nous devons remplacer le code par défaut dans le pages/index.js fichier avec ceci :

import Head from "next/head";
import SpeechToText from "../components/SpeechToText";

export default function Home() {
  return (
    <div className="home">
      <Head>
        <title>Audio To PDF</title>
        <meta
          name="description"
          content="An app that converts audio to pdf in the browser"
        />
        <link rel="icon" href="/fr/favicon.ico" />
      </Head>

      <h1>Convert your speech to pdf</h1>

      <main>
        <SpeechToText />
      </main>
    </div>
  );
}

L'import SpeechToText le composant sera éventuellement exporté de components/SpeechToText.jsx.

Installons les autres dépendances

Très bien, nous avons la configuration initiale de notre application à l'écart. Nous pouvons maintenant installer les bibliothèques qui gèrent les données qui circulent.

Nous pouvons installer nos dépendances client avec :

npm install react-speech-recognition regenerator-runtime axios

Nos dépendances de serveur Express sont à venir, alors allons-y cd dans le dossier du projet du serveur et installez ceux :

npm install html-pdf-node cors

Probablement le bon moment pour faire une pause et s'assurer que les fichiers de nos dossiers de projet sont intacts. Voici ce que vous devriez avoir dans le dossier du projet client à ce stade :

/audio-to-pdf-web-client
├─ /components
|  └── SpeechToText.jsx
├─ /pages
|  ├─ _app.js
|  └── index.js
└── /styles
    ├─globals.css
    └── Home.module.css

Et voici ce que vous devriez avoir dans le dossier du projet du serveur :

/audio-to-pdf-server
└── index.js

Construire l'interface utilisateur

Eh bien, notre speech-to-PDF ne serait pas si génial s'il n'y avait aucun moyen d'interagir avec lui, alors créons un composant React pour cela que nous pouvons appeler <SpeechToText>.

Vous pouvez totalement utiliser votre propre balisage. Voici ce que j'ai pour vous donner une idée des pièces que nous allons assembler :

import React from "react";

const SpeechToText = () => {
  return (
    <>
      <section>
        <div className="button-container">
          <button type="button" style={{ "--bgColor": "blue" }}>
            Start
          </button>
          <button type="button" style={{ "--bgColor": "orange" }}>
            Stop
          </button>
        </div>
        <div
          className="words"
          contentEditable
          suppressContentEditableWarning={true}
        ></div>
        <div className="button-container">
          <button type="button" style={{ "--bgColor": "red" }}>
            Reset
          </button>
          <button type="button" style={{ "--bgColor": "green" }}>
            Convert to pdf
          </button>
        </div>
      </section>
    </>
  );
};

export default SpeechToText;

Ce composant renvoie un Fragment de réaction qui contient un HTML <``section``> élément qui contient trois divs :

  • .button-container contient deux boutons qui seront utilisés pour démarrer et arrêter la reconnaissance vocale.
  • .words a contentEditable ainsi que le suppressContentEditableWarning attributs pour rendre cet élément modifiable et supprimer tous les avertissements de React.
  • Une autre .button-container contient deux autres boutons qui seront utilisés pour réinitialiser et convertir la parole en PDF, respectivement.

Le style est une tout autre chose. Je n'entrerai pas dans les détails ici, mais vous pouvez utiliser certains styles que j'ai écrits soit comme point de départ pour le vôtre styles/global.css fichier.

Afficher le CSS complet
html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

a {
  color: inherit;
  text-decoration: none;
}

* {
  box-sizing: border-box;
}

.home {
  background-color: #333;
  min-height: 100%;
  padding: 0 1rem;
  padding-bottom: 3rem;
}

h1 {
  width: 100%;
  max-width: 400px;
  margin: auto;
  padding: 2rem 0;
  text-align: center;
  text-transform: capitalize;
  color: white;
  font-size: 1rem;
}

.button-container {
  text-align: center;
  display: flex;
  justify-content: center;
  gap: 3rem;
}

button {
  color: white;
  background-color: var(--bgColor);
  font-size: 1.2rem;
  padding: 0.5rem 1.5rem;
  border: none;
  border-radius: 20px;
  cursor: pointer;
}

button:hover {
  opacity: 0.9;
}

button:active {
  transform: scale(0.99);
}

.words {
  max-width: 700px;
  margin: 50px auto;
  height: 50vh;
  border-radius: 5px;
  padding: 1rem 2rem 1rem 5rem;
  background-image: -webkit-gradient(
    linear,
    0 0,
    0 100%,
    from(#d9eaf3),
    color-stop(4%, #fff)
  ) 0 4px;
  background-size: 100% 3rem;
  background-attachment: scroll;
  position: relative;
  line-height: 3rem;
  overflow-y: auto;
}

.success,
.error {
  background-color: #fff;
  margin: 1rem auto;
  padding: 0.5rem 1rem;
  border-radius: 5px;
  width: max-content;
  text-align: center;
  display: block;
}

.success {
  color: green;
}

.error {
  color: red;
}

Les variables CSS qu'il contient sont utilisées pour contrôler la couleur d'arrière-plan des boutons.

Voyons les derniers changements ! Courir npm run dev dans le terminal et vérifiez-les.

Vous devriez voir cela dans le navigateur lorsque vous visitez http://localhost:3000:

Conversion de Speech en PDF avec NextJS et ExpressJS

Notre première conversion parole en texte !

La première action à effectuer est d'importer les dépendances nécessaires dans notre <SpeechToText> composant:

import React, { useRef, useState } from "react";
import SpeechRecognition, {
  useSpeechRecognition,
} from "react-speech-recognition";
import axios from "axios";

Ensuite, nous vérifions si la reconnaissance vocale est prise en charge par le navigateur et affichons une notification si elle n'est pas prise en charge :

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

if (!speechRecognitionSupported) {
  return <div>Your browser does not support speech recognition.</div>;
}

Ensuite, extrayons transcript ainsi que le resetTranscript du useSpeechRecognition() accrocher:

const { transcript, resetTranscript } = useSpeechRecognition();

C'est ce dont nous avons besoin pour l'État qui gère listening:

const [listening, setListening] = useState(false);

Nous avons également besoin d'un ref pour le div des contentEditable attribut, alors nous devons ajouter l'attribut ref lui attribuer et passer transcript as children:

const textBodyRef = useRef(null);

…et:

<div
  className="words"
  contentEditable
  ref={textBodyRef}
  suppressContentEditableWarning={true}
  >
  {transcript}
</div>

La dernière chose dont nous avons besoin ici est une fonction qui déclenche la reconnaissance vocale et de lier cette fonction à la onClick écouteur d'événement de notre bouton. Le bouton permet d'écouter true et le fait fonctionner en continu. Nous désactiverons le bouton pendant qu'il est dans cet état pour nous empêcher de déclencher des événements supplémentaires.

const startListening = () => {
  setListening(true);
  SpeechRecognition.startListening({
    continuous: true,
  });
};

…et:

<button
  type="button"
  onClick={startListening}
  style={{ "--bgColor": "blue" }}
  disabled={listening}
>
  Start
</button>

Cliquer sur le bouton devrait maintenant démarrer la transcription.

Plus de fonctions

OK, nous avons donc un composant qui peut Commencer écoute. Mais maintenant, nous en avons besoin pour faire quelques autres choses aussi, comme stopListening, resetText ainsi que le handleConversion. Créons ces fonctions.

const stopListening = () => {
  setListening(false);
  SpeechRecognition.stopListening();
};

const resetText = () => {
  stopListening();
  resetTranscript();
  textBodyRef.current.innerText = "";
};

const handleConversion = async () => {}

Chacune des fonctions sera ajoutée à un onClick écouteur d'événement sur les boutons appropriés :

<button
  type="button"
  onClick={stopListening}
  style={{ "--bgColor": "orange" }}
  disabled={listening === false}
>
  Stop
</button>

<div className="button-container">
  <button
    type="button"
    onClick={resetText}
    style={{ "--bgColor": "red" }}
  >
    Reset
  </button>
  <button
    type="button"
    style={{ "--bgColor": "green" }}
    onClick={handleConversion}
  >
    Convert to pdf
  </button>
</div>

La handleConversion La fonction est asynchrone car nous allons éventuellement faire une requête API. Le bouton "Stop" a l'attribut désactivé qui serait déclenché lorsque l'écoute est fausse.

Si nous redémarrons le serveur et actualisons le navigateur, nous pouvons maintenant démarrer, arrêter et réinitialiser notre transcription vocale dans le navigateur.

Maintenant, ce dont nous avons besoin, c'est que l'application transcrire ce discours reconnu en le convertissant en un fichier PDF. Pour cela, nous avons besoin du chemin côté serveur d'Express.js.

Configuration de la route API

Le but de cette route est de prendre un fichier texte, de le convertir en PDF, d'écrire ce PDF sur notre système de fichiers, puis d'envoyer une réponse au client.

Pour configurer, nous ouvririons le server/index.js fichier et importer le html-pdf-node ainsi que le fs dépendances qui seront utilisées pour écrire et ouvrir notre système de fichiers.

const HTMLToPDF = require("html-pdf-node");
const fs = require("fs");
const cors = require("cors)

Ensuite, nous allons configurer notre route :

app.use(cors())
app.use(express.json())

app.post("/", (req, res) => {
  // etc.
})

Nous procédons ensuite à la définition de nos options requises afin d'utiliser html-pdf-node à l'intérieur du parcours :

let options = { format: "A4" };
let file = {
  content: `<html><body><pre style='font-size: 1.2rem'>${req.body.text}</pre></body></html>`,
};

La options L'objet accepte une valeur pour définir la taille et le style du papier. Les tailles de papier suivent un système très différent de celui des unités de dimensionnement que nous utilisons généralement sur le Web. Par exemple, A4 est le format de lettre typique.

La file L'objet accepte soit l'URL d'un site Web public, soit le balisage HTML. Afin de générer notre page HTML, nous utiliserons le html, body, pre Les balises HTML et le texte du req.body.

Vous pouvez appliquer n'importe quel style de votre choix.

Ensuite, nous ajouterons un trycatch pour gérer les erreurs qui pourraient apparaître en cours de route :

try {

} catch(error){
  console.log(error);
  res.status(500).send(error);
}

Ensuite, nous utiliserons le generatePdf du html-pdf-node bibliothèque pour générer un pdfBuffer (le fichier PDF brut) à partir de notre fichier et créez un pdfName:

HTMLToPDF.generatePdf(file, options).then((pdfBuffer) => {
  // console.log("PDF Buffer:-", pdfBuffer);
  const pdfName = "./data/speech" + Date.now() + ".pdf";

  // Next code here
}

À partir de là, nous utilisons le module de système de fichiers pour écrire, lire et (oui, enfin !) Envoyer une réponse à l'application client :

fs.writeFile(pdfName, pdfBuffer, function (writeError) {
  if (writeError) {
    return res
      .status(500)
      .json({ message: "Unable to write file. Try again." });
  }

  fs.readFile(pdfName, function (readError, readData) {
    if (!readError && readData) {
      // console.log({ readData });
      res.setHeader("Content-Type", "application/pdf");
      res.setHeader("Content-Disposition", "attachment");
      res.send(readData);
      return;
    }

    return res
      .status(500)
      .json({ message: "Unable to write file. Try again." });
  });
});

Décomposons un peu cela :

  • La writeFile Le module de système de fichiers accepte un nom de fichier, des données et une fonction de rappel qui peut renvoyer un message d'erreur en cas de problème d'écriture dans le fichier. Si vous travaillez avec un CDN qui fournit des points de terminaison d'erreur, vous pouvez les utiliser à la place.
  • La readFile Le module de système de fichiers accepte un nom de fichier et une fonction de rappel capable de renvoyer une erreur de lecture ainsi que les données lues. Une fois que nous n'avons pas d'erreur de lecture et que les données lues sont présentes, nous construisons et envoyons une réponse au client. Encore une fois, cela peut être remplacé par les points de terminaison de votre CDN si vous en avez.
  • La res.setHeader("Content-Type", "application/pdf"); indique au navigateur que nous envoyons un fichier PDF.
  • La res.setHeader("Content-Disposition", "attachment"); indique au navigateur de rendre les données reçues téléchargeables.

Puisque la route API est prête, nous pouvons l'utiliser dans notre application à http://localhost:4000. Nous pouvons ensuite passer à la partie client de notre application pour compléter le handleConversion la fonction.

Gérer la conversion

Avant de commencer à travailler sur un handleConversion fonction, nous devons créer un état qui gère nos demandes d'API pour le chargement, l'erreur, le succès et d'autres messages. Nous allons utiliser React useState crochet pour configurer cela:

const [response, setResponse] = useState({
  loading: false,
  message: "",
  error: false,
  success: false,
});

Dans le handleConversion fonction, nous vérifierons quand la page Web a été chargée avant d'exécuter notre code et nous nous assurerons que le div des editable l'attribut n'est pas vide :

if (typeof window !== "undefined") {
const userText = textBodyRef.current.innerText;
  // console.log(textBodyRef.current.innerText);

  if (!userText) {
    alert("Please speak or write some text.");
    return;
  }
}

Nous procédons en enveloppant notre éventuelle requête API dans un trycatch, en gérant toute erreur pouvant survenir et en mettant à jour l'état de la réponse :

try {

} catch(error){
  setResponse({
    ...response,
    loading: false,
    error: true,
    message:
      "An unexpected error occurred. Text not converted. Please try again",
    success: false,
  });
}

Ensuite, nous définissons certaines valeurs pour l'état de réponse et définissons également la configuration pour axios et faites une demande de publication au serveur :

setResponse({
  ...response,
  loading: true,
  message: "",
  error: false,
  success: false,
});
const config = {
  headers: {
    "Content-Type": "application/json",
  },
  responseType: "blob",
};

const res = await axios.post(
  "http://localhost:4000",
  {
    text: textBodyRef.current.innerText,
  },
  config
);

Une fois que nous avons obtenu une réponse positive, nous définissons l'état de la réponse avec les valeurs appropriées et demandons au navigateur de télécharger le PDF reçu :

setResponse({
  ...response,
  loading: false,
  error: false,
  message:
    "Conversion was successful. Your download will start soon...",
  success: true,
});

// convert the received data to a file
const url = window.URL.createObjectURL(new Blob([res.data]));
// create an anchor element
const link = document.createElement("a");
// set the href of the created anchor element
link.href = url;
// add the download attribute, give the downloaded file a name
link.setAttribute("download", "yourfile.pdf");
// add the created anchor tag to the DOM
document.body.appendChild(link);
// force a click on the link to start a simulated download
link.click();

Et nous pouvons utiliser ce qui suit sous le contenuEditable div pour afficher les messages :

<div>
  {response.success && <i className="success">{response.message}</i>}
  {response.error && <i className="error">{response.message}</i>}
</div>

Code final

J'ai tout emballé sur GitHub afin que vous puissiez consulter le code source complet pour le serveur et le client.

Horodatage:

Plus de Astuces CSS