Da Sprachschnittstellen immer mehr zu einer Sache werden, lohnt es sich, einige der Dinge zu untersuchen, die wir mit Sprachinteraktionen machen können. Was wäre, wenn wir etwas sagen könnten und es transkribieren und als herunterladbares PDF herausgeben könnten?
Nun, Spoiler-Alarm: Wir absolut kann TU das! Es gibt Bibliotheken und Frameworks, die wir zusammenschustern können, um dies zu erreichen, und das werden wir in diesem Artikel gemeinsam tun.
Dies sind die Tools, die wir verwenden
Zunächst einmal sind dies die beiden großen Player: Next.js und Express.js.
Next.js fügt zusätzliche Funktionalitäten zu React hinzu, einschließlich Schlüsselfunktionen zum Erstellen statischer Sites. Es ist eine Anlaufstelle für viele Entwickler, da es sofort einsatzbereit ist, wie dynamisches Routing, Bildoptimierung, integriertes Domänen- und Subdomänen-Routing, schnelle Aktualisierungen, Dateisystem-Routing und API-Routen … unter anderem viele, viele andere Dinge.
In unserem Fall brauchen wir definitiv Next.js dafür API-Routen auf unserem Client-Server. Wir wollen eine Route, die eine Textdatei nimmt, sie in PDF konvertiert, sie in unser Dateisystem schreibt und dann eine Antwort an den Client sendet.
Express.js ermöglicht es uns, eine kleine Node.js-App mit Routing, HTTP-Helfern und Templating zum Laufen zu bringen. Es ist ein Server für unsere eigene API, die wir brauchen, wenn wir Daten zwischen Dingen übergeben und parsen.
Wir haben einige andere Abhängigkeiten, die wir verwenden werden:
- Reagieren-Spracherkennung: Eine Bibliothek zum Konvertieren von Sprache in Text, die für React-Komponenten verfügbar ist.
- Regenerator-Laufzeit: Eine Bibliothek zur Fehlerbehebung bei „
regeneratorRuntime
ist nicht definiert“ Fehler, der in Next.js angezeigt wird, wenn React-Speech-Recognition verwendet wird - html-pdf-Knoten: Eine Bibliothek zum Konvertieren einer HTML-Seite oder öffentlichen URL in ein PDF
- Axios: Eine Bibliothek zum Erstellen von HTTP-Anforderungen sowohl im Browser als auch in Node.js
- Hörner: Eine Bibliothek, die eine ursprungsübergreifende Ressourcenfreigabe ermöglicht
Einrichten
Als erstes wollen wir zwei Projektordner erstellen, einen für den Client und einen für den Server. Nennen Sie sie, wie Sie möchten. Ich nenne meine audio-to-pdf-client
und audio-to-pdf-server
, Bzw.
Der schnellste Weg, um mit Next.js auf der Clientseite zu beginnen, besteht darin, es mit zu booten nächste App erstellen. Öffnen Sie also Ihr Terminal und führen Sie den folgenden Befehl aus Ihrem Client-Projektordner aus:
npx create-next-app client
Jetzt brauchen wir unseren Express-Server. Wir können es schaffen cd
-ing in den Serverprojektordner und Ausführen der npm init
Befehl. EIN package.json
Datei wird im Server-Projektordner erstellt, sobald sie fertig ist.
Wir müssen Express noch tatsächlich installieren, also machen wir das jetzt mit npm install express
. Jetzt können wir eine neue erstellen index.js
Datei im Serverprojektordner und legen Sie diesen Code dort ab:
const express = require("express")
const app = express()
app.listen(4000, () => console.log("Server is running on port 4000"))
Bereit, den Server zu betreiben?
node index.js
Wir brauchen noch ein paar Ordner und eine weitere Datei, um voranzukommen:
- Erstellen Sie
components
Ordner im Client-Projektordner. - Erstellen Sie
SpeechToText.jsx
Datei in dascomponents
Unterordner.
Bevor wir weitermachen, müssen wir noch ein wenig aufräumen. Insbesondere müssen wir den Standardcode in der ersetzen pages/index.js
Datei damit:
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="/de/favicon.ico" />
</Head>
<h1>Convert your speech to pdf</h1>
<main>
<SpeechToText />
</main>
</div>
);
}
Das importierte SpeechToText
Komponente wird schließlich aus exportiert components/SpeechToText.jsx
.
Lassen Sie uns die anderen Abhängigkeiten installieren
Okay, wir haben die Ersteinrichtung für unsere App aus dem Weg geräumt. Jetzt können wir die Bibliotheken installieren, die die herumgereichten Daten verarbeiten.
Wir können unsere Client-Abhängigkeiten installieren mit:
npm install react-speech-recognition regenerator-runtime axios
Unsere Express-Server-Abhängigkeiten sind als nächstes dran, also los cd
in den Serverprojektordner und installieren Sie diese:
npm install html-pdf-node cors
Wahrscheinlich ein guter Zeitpunkt, um innezuhalten und sicherzustellen, dass die Dateien in unseren Projektordnern intakt sind. Hier ist, was Sie zu diesem Zeitpunkt im Client-Projektordner haben sollten:
/audio-to-pdf-web-client
├─ /components
| └── SpeechToText.jsx
├─ /pages
| ├─ _app.js
| └── index.js
└── /styles
├─globals.css
└── Home.module.css
Und hier ist, was Sie im Serverprojektordner haben sollten:
/audio-to-pdf-server
└── index.js
Erstellen der Benutzeroberfläche
Nun, unser Speech-to-PDF wäre nicht so toll, wenn es keine Möglichkeit gäbe, damit zu interagieren, also erstellen wir dafür eine React-Komponente, die wir aufrufen können <SpeechToText>
.
Sie können Ihr eigenes Markup vollständig verwenden. Hier ist, was ich habe, um Ihnen eine Vorstellung von den Teilen zu geben, die wir zusammenstellen:
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;
Diese Komponente gibt a zurück Reaktionsfragment die ein HTML enthält <``section``>
Element, das drei divs enthält:
.button-container
enthält zwei Schaltflächen, die zum Starten und Stoppen der Spracherkennung verwendet werden..words
hatcontentEditable
undsuppressContentEditableWarning
Attribute, um dieses Element bearbeitbar zu machen und alle Warnungen von React zu unterdrücken.- Ein anderer
.button-container
enthält zwei weitere Schaltflächen, die zum Zurücksetzen bzw. Konvertieren von Sprache in PDF verwendet werden.
Styling ist eine ganz andere Sache. Ich werde hier nicht darauf eingehen, aber Sie können gerne einige Stile, die ich geschrieben habe, als Ausgangspunkt für Ihre eigenen verwenden styles/global.css
Datei.
Vollständiges CSS anzeigen
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;
}
Die darin enthaltenen CSS-Variablen werden verwendet, um die Hintergrundfarbe der Schaltflächen zu steuern.
Sehen wir uns die neuesten Änderungen an! Laufen npm run dev
im Terminal und check sie aus.
Sie sollten dies bei Ihrem Besuch im Browser sehen http://localhost:3000
:
Unsere erste Rede-zu-Text-Konvertierung!
Als Erstes müssen Sie die erforderlichen Abhängigkeiten in unsere importieren <SpeechToText>
Komponente:
import React, { useRef, useState } from "react";
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";
import axios from "axios";
Dann prüfen wir, ob die Spracherkennung vom Browser unterstützt wird und geben einen Hinweis aus, wenn sie nicht unterstützt wird:
const speechRecognitionSupported =
SpeechRecognition.browserSupportsSpeechRecognition();
if (!speechRecognitionSupported) {
return <div>Your browser does not support speech recognition.</div>;
}
Als nächstes extrahieren wir transcript
und resetTranscript
von dem useSpeechRecognition()
Haken:
const { transcript, resetTranscript } = useSpeechRecognition();
Das brauchen wir für den Staat, der damit umgeht listening
:
const [listening, setListening] = useState(false);
Wir brauchen auch eine ref
für die div
an. Nach der Installation können Sie HEIC-Dateien mit der contentEditable
Attribut, dann müssen wir das hinzufügen ref
ihm zuschreiben und weitergeben transcript
as children
:
const textBodyRef = useRef(null);
…und:
<div
className="words"
contentEditable
ref={textBodyRef}
suppressContentEditableWarning={true}
>
{transcript}
</div>
Das Letzte, was wir hier brauchen, ist eine Funktion, die die Spracherkennung auslöst und diese Funktion mit der verknüpft onClick
Event-Listener unserer Schaltfläche. Die Taste stellt das Hören ein true
und lässt es kontinuierlich laufen. Wir deaktivieren die Schaltfläche, während sie sich in diesem Zustand befindet, um zu verhindern, dass wir zusätzliche Ereignisse auslösen.
const startListening = () => {
setListening(true);
SpeechRecognition.startListening({
continuous: true,
});
};
…und:
<button
type="button"
onClick={startListening}
style={{ "--bgColor": "blue" }}
disabled={listening}
>
Start
</button>
Ein Klick auf die Schaltfläche sollte nun die Transkription starten.
Mehr Funktionen
OK, wir haben also eine Komponente, die das kann Anfang Hören. Aber jetzt brauchen wir es auch, um ein paar andere Dinge zu tun, wie z stopListening
, resetText
und handleConversion
. Lassen Sie uns diese Funktionen erstellen.
const stopListening = () => {
setListening(false);
SpeechRecognition.stopListening();
};
const resetText = () => {
stopListening();
resetTranscript();
textBodyRef.current.innerText = "";
};
const handleConversion = async () => {}
Jede der Funktionen wird zu einer hinzugefügt onClick
Ereignis-Listener auf den entsprechenden Schaltflächen:
<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>
Das handleConversion
Die Funktion ist asynchron, da wir schließlich eine API-Anfrage stellen werden. Die Schaltfläche „Stopp“ hat das deaktivierte Attribut, das ausgelöst würde, wenn das Zuhören falsch ist.
Wenn wir den Server neu starten und den Browser aktualisieren, können wir jetzt unsere Sprachtranskription im Browser starten, stoppen und zurücksetzen.
Was wir jetzt brauchen, ist die App transkribieren diese erkannte Sprache durch Konvertieren in eine PDF-Datei. Dafür benötigen wir den serverseitigen Pfad von Express.js.
Einrichten der API-Route
Der Zweck dieser Route besteht darin, eine Textdatei zu nehmen, sie in ein PDF zu konvertieren, dieses PDF in unser Dateisystem zu schreiben und dann eine Antwort an den Client zu senden.
Zur Einrichtung öffnen wir die server/index.js
Datei und importiere die html-pdf-node
und fs
Abhängigkeiten, die zum Schreiben und Öffnen unseres Dateisystems verwendet werden.
const HTMLToPDF = require("html-pdf-node");
const fs = require("fs");
const cors = require("cors)
Als nächstes werden wir unsere Route einrichten:
app.use(cors())
app.use(express.json())
app.post("/", (req, res) => {
// etc.
})
Wir fahren dann fort, unsere Optionen zu definieren, die für die Verwendung erforderlich sind html-pdf-node
innerhalb der Strecke:
let options = { format: "A4" };
let file = {
content: `<html><body><pre style='font-size: 1.2rem'>${req.body.text}</pre></body></html>`,
};
Das options
Objekt akzeptiert einen Wert, um die Papiergröße und den Stil festzulegen. Papiergrößen folgen einem ganz anderen System als die Größeneinheiten, die wir normalerweise im Internet verwenden. Zum Beispiel, A4 ist die typische Briefgröße.
Das file
-Objekt akzeptiert entweder die URL einer öffentlichen Website oder HTML-Markup. Um unsere HTML-Seite zu generieren, verwenden wir die html
, body
, pre
HTML-Tags und der Text aus der req.body
.
Sie können jedes Styling Ihrer Wahl anwenden.
Als nächstes fügen wir a hinzu trycatch
um alle Fehler zu behandeln, die auf dem Weg auftreten könnten:
try {
} catch(error){
console.log(error);
res.status(500).send(error);
}
Als nächstes verwenden wir die generatePdf
von dem html-pdf-node
Bibliothek zum Generieren einer pdfBuffer
(die rohe PDF-Datei) aus unserer Datei und erstellen Sie ein Unikat pdfName
:
HTMLToPDF.generatePdf(file, options).then((pdfBuffer) => {
// console.log("PDF Buffer:-", pdfBuffer);
const pdfName = "./data/speech" + Date.now() + ".pdf";
// Next code here
}
Von dort aus verwenden wir das Dateisystemmodul, um eine Antwort an die Client-App zu schreiben, zu lesen und (ja, endlich!) zu senden:
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." });
});
});
Lassen Sie uns das ein wenig aufschlüsseln:
- Das
writeFile
Das Dateisystemmodul akzeptiert einen Dateinamen, Daten und eine Rückruffunktion, die eine Fehlermeldung zurückgeben kann, wenn beim Schreiben in die Datei ein Problem auftritt. Wenn Sie mit einem CDN arbeiten, das Fehlerendpunkte bereitstellt, können Sie stattdessen diese verwenden. - Das
readFile
Das Dateisystemmodul akzeptiert einen Dateinamen und eine Callback-Funktion, die in der Lage ist, einen Lesefehler sowie die gelesenen Daten zurückzugeben. Sobald wir keinen Lesefehler haben und die gelesenen Daten vorhanden sind, werden wir eine Antwort erstellen und an den Client senden. Auch dies kann durch die Endpunkte Ihres CDN ersetzt werden, falls Sie diese haben. - Das
res.setHeader("Content-Type", "application/pdf");
teilt dem Browser mit, dass wir eine PDF-Datei senden. - Das
res.setHeader("Content-Disposition", "attachment");
weist den Browser an, die empfangenen Daten herunterladbar zu machen.
Da die API-Route bereit ist, können wir sie in unserer App verwenden http://localhost:4000
. Wir können mit dem Kundenteil unserer Bewerbung fortfahren, um den zu vervollständigen handleConversion
Funktion.
Umgang mit der Konvertierung
Bevor wir mit der Arbeit an a beginnen können handleConversion
Funktion müssen wir einen Zustand erstellen, der unsere API-Anforderungen für Lade-, Fehler-, Erfolgs- und andere Nachrichten verarbeitet. Wir werden React verwenden useState
Haken, um das einzurichten:
const [response, setResponse] = useState({
loading: false,
message: "",
error: false,
success: false,
});
Im handleConversion
Funktion prüfen wir, wann die Webseite geladen wurde, bevor wir unseren Code ausführen, und stellen sicher, dass die div
an. Nach der Installation können Sie HEIC-Dateien mit der editable
Attribut ist nicht leer:
if (typeof window !== "undefined") {
const userText = textBodyRef.current.innerText;
// console.log(textBodyRef.current.innerText);
if (!userText) {
alert("Please speak or write some text.");
return;
}
}
Wir fahren fort, indem wir unsere eventuelle API-Anfrage in a verpacken trycatch
, Behandlung eventuell auftretender Fehler und Aktualisierung des Antwortstatus:
try {
} catch(error){
setResponse({
...response,
loading: false,
error: true,
message:
"An unexpected error occurred. Text not converted. Please try again",
success: false,
});
}
Als nächstes setzen wir einige Werte für den Antwortstatus und setzen auch config for axios
und stellen Sie eine Post-Anfrage an den Server:
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
);
Sobald wir eine erfolgreiche Antwort erhalten haben, setzen wir den Antwortstatus mit den entsprechenden Werten und weisen den Browser an, das empfangene PDF herunterzuladen:
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();
Und wir können das Folgende unter contentEditable verwenden div
zum Anzeigen von Meldungen:
<div>
{response.success && <i className="success">{response.message}</i>}
{response.error && <i className="error">{response.message}</i>}
</div>
Endgültiger Code
Ich habe alles auf GitHub gepackt, sodass Sie den vollständigen Quellcode sowohl für den Server als auch für den Client einsehen können.