Konwersja mowy do formatu PDF za pomocą NextJS i ExpressJS PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.

Konwersja mowy do formatu PDF za pomocą NextJS i ExpressJS

Ponieważ interfejsy głosowe stają się coraz bardziej popularne, warto zbadać niektóre rzeczy, które możemy zrobić z interakcjami mowy. Na przykład, co by było, gdybyśmy mogli coś powiedzieć i przepisać to jako plik PDF do pobrania?

Cóż, uwaga spoilera: absolutnie mogą Zrób to! Istnieją biblioteki i frameworki, które możemy skleić, aby tak się stało, i to właśnie zamierzamy zrobić razem w tym artykule.

To są narzędzia, których używamy

Po pierwsze, są to dwaj wielcy gracze: Next.js i Express.js.

Next.js dodaje dodatkowe funkcje do Reacta, w tym kluczowe funkcje do budowania statycznych witryn. Jest to cel dla wielu programistów ze względu na to, co oferuje zaraz po wyjęciu z pudełka, takie jak routing dynamiczny, optymalizacja obrazu, wbudowany routing domen i subdomen, szybkie odświeżanie, routing systemu plików i trasy API… między innymi wiele, wiele innych rzeczy.

W naszym przypadku zdecydowanie potrzebujemy do tego Next.js Trasy API na naszym serwerze klienta. Chcemy trasy, która pobiera plik tekstowy, konwertuje go do formatu PDF, zapisuje go w naszym systemie plików, a następnie wysyła odpowiedź do klienta.

Express.js. pozwala nam uzyskać małą aplikację Node.js obsługującą routing, pomocniki HTTP i tworzenie szablonów. Jest to serwer dla naszego własnego API, którego będziemy potrzebować, gdy będziemy przekazywać i analizować dane między rzeczami.

Mamy kilka innych zależności, które wykorzystamy:

  1. Reaguj-rozpoznawanie-mowy: Biblioteka do konwersji mowy na tekst, udostępniająca ją komponentom React.
  2. czas pracy regeneratora: Biblioteka do rozwiązywania problemów „regeneratorRuntime nie jest zdefiniowany” błąd, który pojawia się w Next.js podczas używania rozpoznawania mowy
  3. węzeł html-pdf: Biblioteka do konwersji strony HTML lub publicznego adresu URL na plik PDF
  4. aksjos: Biblioteka do tworzenia żądań HTTP zarówno w przeglądarce, jak i Node.js
  5. cors: Biblioteka, która umożliwia współdzielenie zasobów między źródłami

Konfigurowanie

Pierwszą rzeczą, którą chcemy zrobić, to utworzyć dwa foldery projektu, jeden dla klienta i jeden dla serwera. Nazwij je jak chcesz. Nazywam moje audio-to-pdf-client i audio-to-pdf-server, Odpowiednio.

Najszybszym sposobem na rozpoczęcie pracy z Next.js po stronie klienta jest uruchomienie go za pomocą utwórz-następną-aplikację. Otwórz więc terminal i uruchom następujące polecenie z folderu projektu klienta:

npx create-next-app client

Teraz potrzebujemy naszego serwera Express. Możemy to zdobyć przez cd- wpisanie do folderu projektu na serwerze i uruchomienie npm init Komenda. A package.json plik zostanie utworzony w folderze projektu serwera po zakończeniu.

Nadal musimy zainstalować Express, więc zróbmy to teraz za pomocą npm install express. Teraz możemy stworzyć nowy index.js plik w folderze projektu serwera i upuść tam ten kod:

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

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

Gotowy do uruchomienia serwera?

node index.js

Będziemy potrzebować jeszcze kilku folderów i kolejnego pliku, aby przejść dalej:

  • Stwórz components folder w folderze projektu klienta.
  • Stwórz SpeechToText.jsx Plik w components podfolder.

Zanim przejdziemy dalej, musimy trochę posprzątać. W szczególności musimy zastąpić domyślny kod w pages/index.js plik z tym:

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

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

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

Importowane SpeechToText komponent zostanie ostatecznie wyeksportowany z components/SpeechToText.jsx.

Zainstalujmy pozostałe zależności

W porządku, mamy już początkową konfigurację naszej aplikacji. Teraz możemy zainstalować biblioteki, które obsługują przekazywane dane.

Zależności naszych klientów możemy zainstalować za pomocą:

npm install react-speech-recognition regenerator-runtime axios

Nasze zależności serwera Express są następne, więc zacznijmy cd do folderu projektu serwera i zainstaluj te:

npm install html-pdf-node cors

To chyba dobry moment na przerwę i upewnienie się, że pliki w naszych folderach projektów są nienaruszone. Oto, co powinieneś mieć w tym momencie w folderze projektu klienta:

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

A oto, co powinieneś mieć w folderze projektu serwera:

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

Budowanie interfejsu użytkownika

Cóż, nasza zamiana mowy na plik PDF nie byłaby tak świetna, gdyby nie było możliwości interakcji z nią, więc stwórzmy dla niej komponent React, który możemy wywołać <SpeechToText>.

Możesz całkowicie użyć własnego znacznika. Oto, co muszę dać ci wyobrażenie o elementach, które składamy razem:

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;

Ten składnik zwraca a Fragment reakcji który zawiera HTML <``section``> element zawierający trzy divy:

  • .button-container zawiera dwa przyciski, które będą używane do uruchamiania i zatrzymywania rozpoznawania mowy.
  • .words ma contentEditable i suppressContentEditableWarning atrybuty, aby uczynić ten element edytowalnym i wyłączyć wszelkie ostrzeżenia z Reacta.
  • Inne .button-container posiada dwa dodatkowe przyciski, które będą używane odpowiednio do resetowania i konwertowania mowy do formatu PDF.

Stylizacja to zupełnie inna sprawa. Nie będę się w to tutaj zagłębiał, ale możesz użyć niektórych stylów, które napisałem, jako punkt wyjścia dla własnych styles/global.css plik.

Zobacz pełną wersję 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;
}

Znajdujące się tam zmienne CSS są używane do kontrolowania koloru tła przycisków.

Zobaczmy najnowsze zmiany! Biegać npm run dev w terminalu i sprawdź je.

Powinieneś to zobaczyć w przeglądarce podczas wizyty http://localhost:3000:

Konwersja mowy do formatu PDF za pomocą NextJS i ExpressJS

Nasza pierwsza konwersja mowy na tekst!

Pierwszą czynnością, jaką należy podjąć, jest zaimportowanie niezbędnych zależności do naszego <SpeechToText> składnik:

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

Następnie sprawdzamy, czy rozpoznawanie mowy jest obsługiwane przez przeglądarkę i wyświetlamy powiadomienie, jeśli nie jest obsługiwane:

const speechRecognitionSupported =
  SpeechRecognition.browserSupportsSpeechRecognition();

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

Następnie wydobądźmy transcript i resetTranscript z useSpeechRecognition() hak:

const { transcript, resetTranscript } = useSpeechRecognition();

To jest to, czego potrzebujemy dla państwa, które obsługuje listening:

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

Potrzebujemy również ref dla div z contentEditable atrybut, to musimy dodać ref przypisz do niego i przekaż transcript as children:

const textBodyRef = useRef(null);

…oraz:

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

Ostatnią rzeczą, jakiej potrzebujemy, jest funkcja, która uruchamia rozpoznawanie mowy i wiąże tę funkcję z onClick Odbiornik zdarzeń naszego przycisku. Przycisk ustawia słuchanie true i sprawia, że ​​działa w sposób ciągły. Wyłączymy przycisk, gdy jest w tym stanie, aby uniemożliwić nam uruchamianie dodatkowych zdarzeń.

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

…oraz:

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

Kliknięcie przycisku powinno teraz uruchomić transkrypcję.

Więcej funkcji

OK, więc mamy komponent, który może początek słuchający. Ale teraz potrzebujemy go również do zrobienia kilku innych rzeczy, takich jak stopListening, resetText i handleConversion. Zróbmy te funkcje.

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

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

const handleConversion = async () => {}

Każda z funkcji zostanie dodana do jednego onClick detektor zdarzeń na odpowiednich przyciskach:

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

Połączenia handleConversion funkcja jest asynchroniczna, ponieważ w końcu będziemy wysyłać żądanie API. Przycisk „Stop” ma wyłączony atrybut, który zostanie uruchomiony, gdy nasłuch jest fałszywy.

Jeśli ponownie uruchomimy serwer i odświeżymy przeglądarkę, możemy teraz uruchomić, zatrzymać i zresetować transkrypcję mowy w przeglądarce.

Teraz potrzebujemy, aby aplikacja rozpisać rozpoznał mowę, konwertując ją na plik PDF. W tym celu potrzebujemy ścieżki po stronie serwera z Express.js.

Konfiguracja trasy API

Celem tej trasy jest pobranie pliku tekstowego, przekonwertowanie go na plik PDF, zapisanie tego pliku PDF w naszym systemie plików, a następnie wysłanie odpowiedzi do klienta.

Aby skonfigurować, otworzymy server/index.js plik i zaimportuj html-pdf-node i fs zależności, które będą używane do pisania i otwierania naszego systemu plików.

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

Następnie ustawimy naszą trasę:

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

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

Następnie przystępujemy do zdefiniowania naszych opcji wymaganych do użycia html-pdf-node wewnątrz trasy:

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

Połączenia options obiekt przyjmuje wartość określającą rozmiar i styl papieru. Rozmiary papieru mają znacznie inny system niż jednostki rozmiaru, których zwykle używamy w Internecie. Na przykład, A4 to typowy rozmiar liter.

Połączenia file obiekt akceptuje adres URL publicznej witryny internetowej lub znacznik HTML. W celu wygenerowania naszej strony HTML użyjemy html, body, pre Tagi HTML i tekst z req.body.

Możesz zastosować dowolną stylizację.

Następnie dodamy trycatch do obsługi wszelkich błędów, które mogą pojawić się po drodze:

try {

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

Następnie użyjemy generatePdf z html-pdf-node biblioteka do generowania pdfBuffer (surowy plik PDF) z naszego pliku i stwórz unikalny pdfName:

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

  // Next code here
}

Stamtąd używamy modułu systemu plików do pisania, czytania i (tak, w końcu!) wysyłania odpowiedzi do aplikacji klienckiej:

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

Rozłóżmy to trochę:

  • Połączenia writeFile Moduł systemu plików akceptuje nazwę pliku, dane i funkcję zwrotną, która może zwrócić komunikat o błędzie, jeśli wystąpi problem z zapisem do pliku. Jeśli pracujesz z siecią CDN, która udostępnia punkty końcowe błędów, możesz ich użyć.
  • Połączenia readFile Moduł systemu plików akceptuje nazwę pliku i funkcję zwrotną, która jest zdolna lub zwraca błąd odczytu, jak również odczytane dane. Gdy nie wystąpi błąd odczytu, a odczytane dane są obecne, skonstruujemy i wyślemy odpowiedź do klienta. Ponownie, można to zastąpić punktami końcowymi CDN, jeśli je masz.
  • Połączenia res.setHeader("Content-Type", "application/pdf"); informuje przeglądarkę, że wysyłamy plik PDF.
  • Połączenia res.setHeader("Content-Disposition", "attachment"); nakazuje przeglądarce, aby odebrane dane można było pobrać.

Ponieważ trasa API jest gotowa, możemy jej użyć w naszej aplikacji pod adresem http://localhost:4000. Możemy przejść do części klienckiej naszej aplikacji, aby zakończyć handleConversion funkcja.

Obsługa konwersji

Zanim zaczniemy pracować nad handleConversion funkcji, musimy utworzyć stan, który obsługuje nasze żądania API dotyczące ładowania, błędu, sukcesu i innych komunikatów. Będziemy korzystać z React useState zaczep, aby to ustawić:

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

W handleConversion funkcja, sprawdzimy, kiedy strona internetowa została załadowana przed uruchomieniem naszego kodu i upewnimy się, że div z editable atrybut nie jest pusty:

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

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

Kontynuujemy, pakując nasze ewentualne żądanie API w a trycatch, obsługa wszelkich błędów, które mogą wystąpić, i aktualizowanie stanu odpowiedzi:

try {

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

Następnie ustawiamy kilka wartości dla stanu odpowiedzi, a także ustawiamy konfigurację dla axios i wyślij zapytanie do serwera:

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

Gdy otrzymamy pomyślną odpowiedź, ustawiamy stan odpowiedzi z odpowiednimi wartościami i instruujemy przeglądarkę, aby pobrała otrzymany plik PDF:

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

I możemy użyć następującego poniżej contentEditable div do wyświetlania wiadomości:

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

Kod końcowy

Wszystko spakowałem na GitHub, więc możesz sprawdzić pełny kod źródłowy zarówno dla serwera, jak i klienta.

Znak czasu:

Więcej z Sztuczki CSS