Puheen muuntaminen PDF-muotoon NextJS:n ja ExpressJS PlatoBlockchain Data Intelligencen avulla. Pystysuuntainen haku. Ai.

Puheen muuntaminen PDF-muotoon NextJS:n ja ExpressJS:n avulla

Puheliitäntöjen yleistyessä on syytä tutkia joitain asioita, joita voimme tehdä puhevuorovaikutuksilla. Entä jos voisimme sanoa jotain ja saada sen litteroimaan ja pumppaamaan ladattavana PDF-tiedostona?

No, spoilerivaroitus: me ehdottomasti voida tehdä! On olemassa kirjastoja ja kehyksiä, joita voimme rakentaa yhdessä toteuttaaksemme sen, ja sitä aiomme tehdä yhdessä tässä artikkelissa.

Nämä ovat työkaluja, joita käytämme

Ensinnäkin nämä ovat kaksi suurta pelaajaa: Next.js ja Express.js.

Next.js sisältää Reactin lisätoimintoja, mukaan lukien keskeiset ominaisuudet staattisten kohteiden rakentamiseen. Se sopii monille kehittäjille, koska se tarjoaa heti käyttövalmiina, kuten dynaamisen reitityksen, kuvan optimoinnin, sisäänrakennetun verkkotunnuksen ja aliverkkotunnuksen reitityksen, nopeat päivitykset, tiedostojärjestelmän reitityksen ja API-reitit… monia, monia muita asioita.

Meidän tapauksessamme tarvitsemme ehdottomasti Next.js:n siihen API-reitit asiakaspalvelimellamme. Haluamme reitin, joka vie tekstitiedoston, muuntaa sen PDF-muotoon, kirjoittaa sen tiedostojärjestelmäämme ja lähettää sitten vastauksen asiakkaalle.

Express.js antaa meille mahdollisuuden saada pieni Node.js-sovellus toimimaan reitityksen, HTTP-apuohjelmien ja mallien avulla. Se on palvelin omalle API:llemme, jota tarvitsemme siirtäessämme ja jäsentäessämme tietoja asioiden välillä.

Meillä on joitain muita riippuvuuksia, joita otamme käyttöön:

  1. reagoi-puheentunnistus: Kirjasto puheen muuntamiseksi tekstiksi, jolloin se on React-komponenttien käytettävissä.
  2. regeneraattorin käyttöaika: Kirjasto vianetsintää vartenregeneratorRuntime ei ole määritelty" -virhe, joka näkyy Next.js:ssä käytettäessä react-speech-recognition
  3. html-pdf-solmu: Kirjasto HTML-sivun tai julkisen URL-osoitteen muuntamiseen PDF-tiedostoksi
  4. Axios: Kirjasto HTTP-pyyntöjen tekemiseen sekä selaimessa että Node.js:ssä
  5. cors: Kirjasto, joka mahdollistaa lähteiden välisen resurssien jakamisen

Asettaa

Ensimmäinen asia, jonka haluamme tehdä, on luoda kaksi projektikansiota, yksi asiakkaalle ja toinen palvelimelle. Nimeä ne miten haluat. Nimeän omani audio-to-pdf-client ja audio-to-pdf-serverVastaavasti.

Nopein tapa aloittaa Next.js:n käyttö asiakaspuolella on käynnistää se luo-seuraava-sovellus. Joten avaa terminaali ja suorita seuraava komento asiakasprojektikansiostasi:

npx create-next-app client

Nyt tarvitsemme Express-palvelimemme. Voimme selvitä siitä cd-siirrä palvelimen projektikansioon ja suorita npm init komento. A package.json tiedosto luodaan palvelimen projektikansioon, kun se on valmis.

Meidän on vielä asennettava Express, joten tehdään se nyt npm install express. Nyt voimme luoda uuden index.js tiedosto palvelimen projektikansioon ja pudota tämä koodi sinne:

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

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

Oletko valmis käyttämään palvelinta?

node index.js

Tarvitsemme vielä pari kansiota ja toinen tiedosto, jotta pääsemme eteenpäin:

  • Luo components kansio asiakasprojektikansiossa.
  • Luo SpeechToText.jsx Tiedosto components alikansio.

Ennen kuin jatkamme pidemmälle, meillä on pieni siivous tehtävänä. Erityisesti meidän on korvattava oletuskoodi pages/index.js tiedosto tällä:

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="/fi/favicon.ico" />
      </Head>

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

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

Tuotuja SpeechToText komponentti viedään lopulta components/SpeechToText.jsx.

Asennamme muut riippuvuudet

Selvä, sovelluksemme alkuasetukset ovat poissa tieltä. Nyt voimme asentaa kirjastot, jotka käsittelevät siirrettyä tietoa.

Voimme asentaa asiakasriippuvuutemme:

npm install react-speech-recognition regenerator-runtime axios

Express-palvelinriippuvuutemme ovat seuraavaksi, joten mennään cd palvelimen projektikansioon ja asenna ne:

npm install html-pdf-node cors

Luultavasti on hyvä hetki pysähtyä ja varmistaa, että projektikansioidemme tiedostot ovat tarkkoja. Tässä on mitä sinulla pitäisi olla asiakasprojektikansiossa tässä vaiheessa:

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

Ja tässä sinun pitäisi olla palvelinprojektikansiossa:

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

Käyttöliittymän rakentaminen

Puheemme PDF-muotoon ei olisi niin hienoa, jos sen kanssa ei ole mahdollista olla vuorovaikutuksessa, joten tehdään sille React-komponentti, jota voimme kutsua <SpeechToText>.

Voit käyttää täysin omia merkintöjäsi. Tässä on mitä haluan antaakseni sinulle käsityksen kokoamistamme osista:

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;

Tämä komponentti palauttaa a Reagoi fragmentti joka sisältää HTML:n <``section``> elementti, joka sisältää kolme div:tä:

  • .button-container sisältää kaksi painiketta, joita käytetään puheentunnistuksen käynnistämiseen ja lopettamiseen.
  • .words on contentEditable ja suppressContentEditableWarning määritteitä, jotta tämä elementti olisi muokattavissa ja estetään kaikki Reactin varoitukset.
  • Toinen .button-container sisältää kaksi muuta painiketta, joita käytetään nollaamaan ja muuttamaan puhe PDF-muotoon.

Muotoilu on kokonaan toinen asia. En mene siihen tässä, mutta voit käyttää joitain kirjoittamiani tyylejä joko lähtökohtana omallesi styles/global.css tiedosto.

Näytä koko CSS
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;
}

Siellä olevia CSS-muuttujia käytetään säätämään painikkeiden taustaväriä.

Katsotaan viimeisimmät muutokset! Juosta npm run dev terminaalissa ja tarkista ne.

Sinun pitäisi nähdä tämä selaimessa, kun vierailet http://localhost:3000:

Puheen muuntaminen PDF-muotoon NextJS:n ja ExpressJS:n avulla

Ensimmäinen puheen muunnos tekstiksi!

Ensimmäinen toimenpide on tuoda tarvittavat riippuvuudet järjestelmäämme <SpeechToText> komponentti:

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

Sitten tarkistamme, tukeeko selain puheentunnistusta ja annamme ilmoituksen, jos se ei tueta:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

Seuraavaksi poimitaan transcript ja resetTranscript mistä useSpeechRecognition() koukku:

const { transcript, resetTranscript } = useSpeechRecognition();

Tätä tarvitsemme valtiolle, joka hoitaa listening:

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

Tarvitsemme myös a ref varten div jossa contentEditable attribuutti, meidän on lisättävä ref omistaa sen ja siirtää transcript as children:

const textBodyRef = useRef(null);

…ja:

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

Viimeinen asia, jota tarvitsemme tässä, on toiminto, joka laukaisee puheentunnistuksen ja liittää sen funktioon onClick painikkeemme tapahtumakuuntelija. Painike asettaa kuuntelun true ja saa sen toimimaan jatkuvasti. Poistamme painikkeen käytöstä, kun se on tässä tilassa, jotta emme käynnistä lisätapahtumia.

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

…ja:

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

Napsauttamalla painiketta pitäisi nyt alkaa transkriptio.

Lisää toimintoja

OK, joten meillä on komponentti, joka voi Alkaa kuuntelemalla. Mutta nyt meidän on tehtävä myös muutamia muita asioita, kuten stopListening, resetText ja handleConversion. Tehdään ne funktiot.

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

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

const handleConversion = async () => {}

Jokainen toiminto lisätään onClick tapahtumakuuntelija asianmukaisilla painikkeilla:

<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>

- handleConversion toiminto on asynkroninen, koska teemme lopulta API-pyynnön. "Stop"-painikkeessa on pois käytöstä -attribuutti, joka laukeaisi, kun kuuntelu on epätosi.

Jos käynnistämme palvelimen uudelleen ja päivitämme selaimen, voimme nyt käynnistää, pysäyttää ja nollata puheen transkription selaimessa.

Nyt tarvitsemme sovelluksen litteroida joka tunnisti puheen muuntamalla sen PDF-tiedostoksi. Tätä varten tarvitsemme Express.js:n palvelinpuolen polun.

API-reitin määrittäminen

Tämän reitin tarkoituksena on ottaa tekstitiedosto, muuntaa se PDF-muotoon, kirjoittaa se tiedostojärjestelmäämme ja lähettää sitten vastaus asiakkaalle.

Asennusta varten avasimme server/index.js tiedosto ja tuo html-pdf-node ja fs riippuvuudet, joita käytetään tiedostojärjestelmämme kirjoittamiseen ja avaamiseen.

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

Seuraavaksi määritämme reittimme:

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

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

Jatkamme sen jälkeen määrittämään käyttöön tarvittavat vaihtoehdot html-pdf-node reitin sisällä:

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

- options objekti hyväksyy arvon, jolla asetetaan paperin koko ja tyyli. Paperikoot noudattavat paljon erilaista järjestelmää kuin verkossa tavallisesti käyttämämme kokoyksiköt. Esimerkiksi, A4 on tyypillinen kirjekoko.

- file objekti hyväksyy joko julkisen verkkosivuston URL-osoitteen tai HTML-merkinnän. HTML-sivumme luomiseksi käytämme html, body, pre HTML-tunnisteet ja teksti osoitteesta req.body.

Voit käyttää mitä tahansa haluamaasi tyyliä.

Seuraavaksi lisäämme a trycatch käsitelläksesi matkan varrella mahdollisesti ilmeneviä virheitä:

try {

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

Seuraavaksi käytämme generatePdf mistä html-pdf-node kirjasto luomaan a pdfBuffer (raaka PDF-tiedosto) tiedostostamme ja luo ainutlaatuinen pdfName:

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

  // Next code here
}

Sieltä käytämme tiedostojärjestelmämoduulia kirjoittaaksemme, lukeaksemme ja (kyllä, vihdoin!) lähettääksemme vastauksen asiakassovellukseen:

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." });
  });
});

Puretaan sitä hieman:

  • - writeFile tiedostojärjestelmämoduuli hyväksyy tiedostonimen, tiedot ja takaisinsoittotoiminnon, joka voi palauttaa virheilmoituksen, jos tiedostoon kirjoittamisessa on ongelmia. Jos työskentelet CDN:n kanssa, joka tarjoaa virhepäätepisteitä, voit käyttää niitä sen sijaan.
  • - readFile tiedostojärjestelmämoduuli hyväksyy tiedostonimen ja takaisinsoittotoiminnon, joka pystyy tai palauttaa lukuvirheen sekä lukutiedot. Kun meillä ei ole lukuvirhettä ja luetut tiedot ovat saatavilla, rakennamme ja lähetämme vastauksen asiakkaalle. Tämä voidaan jälleen korvata CDN-päätepisteillä, jos sinulla on niitä.
  • - res.setHeader("Content-Type", "application/pdf"); kertoo selaimelle, että lähetämme PDF-tiedoston.
  • - res.setHeader("Content-Disposition", "attachment"); käskee selainta tekemään vastaanotetut tiedot ladattavaksi.

Koska API-reitti on valmis, voimme käyttää sitä sovelluksessamme osoitteessa http://localhost:4000. Voimme siirtyä hakemuksemme asiakasosaan viimeistelemään handleConversion toiminto.

Muuntamisen käsittely

Ennen kuin voimme aloittaa työskentelyn a handleConversion -toimintoa, meidän on luotava tila, joka käsittelee API-pyyntömme lataus-, virhe-, onnistumis- ja muut viestit. Käytämme Reactia useState koukku sen määrittämiseksi:

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

In handleConversion -toimintoa, tarkistamme, milloin verkkosivu on ladattu ennen koodin suorittamista, ja varmistamme, että div jossa editable attribuutti ei ole tyhjä:

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

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

Jatkamme käärimällä mahdollisen API-pyyntömme a trycatch, käsittelee mahdollisia virheitä ja päivittää vastaustilan:

try {

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

Seuraavaksi asetamme joitain arvoja vastetilalle ja määritämme myös asetukset axios ja lähetä pyyntö palvelimelle:

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
);

Kun olemme saaneet onnistuneen vastauksen, asetamme vastauksen tilan sopivilla arvoilla ja ohjeistamme selainta lataamaan vastaanotetun PDF:n:

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();

Ja voimme käyttää seuraavaa sisältöä Editable-kohdan alla div viestien näyttämiseen:

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

Lopullinen koodi

Olen pakannut kaiken GitHubiin, jotta voit tarkistaa täydellisen lähdekoodin sekä palvelimelle että asiakkaalle.

Aikaleima:

Lisää aiheesta CSS-temppuja