LMNT is a text-to-speech tool that can generate natural-sounding audio from text. It's an excellent tool for dubbing content, creating audiobooks, or even for accessibility.
Integrating LMNT into your Appwrite project is simple. This tutorial will guide you through setting up the LMNT API and incorporating it into your Appwrite project.
Prerequisites
An Appwrite Project
An Appwrite Bucket
An LMNT API Key
Create new function
Head to the Appwrite Console, click on Functions in the left sidebar and click the Create Function button.
In the Appwrite Console's sidebar, click Functions.
Click Create function.
Under Connect Git repository, select your provider.
After connecting to GitHub, under Quick start, select the Node.js starter template.
In the Variables step, add APPWRITE_BUCKET_ID, LMNT_API_KEY. Generate your LMNT Key here. For the APPWRITE_API_KEY, tick the box to Generate API key on completion.
Follow the step-by-step wizard and create the Function.
Add dependencies
Once the Function is created, please navigate to the freshly created repository and clone it to your local machine.
Install the lmnt-node package to make requests to the LMNT API and node-appwrite package to upload the generated audio files to Appwrite Storage.
npm install lmnt-node node-appwrite
Create utility functions
For this example, the Function can take both GET and POST requests.
For the GET request, return a static HTML page with a form to submit text to the API. Meanwhile, the POST request will send the text to the LMNT API and return the generated audio file.
To begin with, write the code to return the static HTML page. To do this, create a new src/utils.js file with the following code:
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const staticFolder = path.join(__dirname, '../static');
export function getStaticFile(fileName) {
return fs.readFileSync(path.join(staticFolder, fileName)).toString();
}
Handle GET request
Write the GET request handler in the src/main.js file. This handler will return a static HTML page, which will be created in the next section.
import { getStaticFile } from './utils.js';
export default async ({ req, res, error }) => {
throwIfMissing(process.env, [
"LMNT_API_KEY",
"APPWRITE_API_KEY",
"APPWRITE_BUCKET_ID",
"APPWRITE_FUNCTION_PROJECT_ID"
]);
if (req.method === 'GET') {
return res.text(getStaticFile('index.html'), 200, {
'Content-Type': 'text/html; charset=utf-8',
});
}
};
A check is also included to ensure that the LMNT_API_KEY, APPWRITE_API_KEY and APPWRITE_BUCKET_ID environment variables are set.
Create web page
Create an HTML web page that the Function will serve. Create a new file at static/index.html with some HTML boilerplate:
<!doctype html>
<html lang="en">
</html>
Within the <html> tag, Add a <head> tag that will define the style and scripts.
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LMNT Demo</title>
<script>
async function onSubmit(prompt) {
const response = await fetch('/', {
method: 'POST',
body: JSON.stringify({ text: prompt }),
headers: {
'Content-Type': 'application/json',
},
});
const json = await response.json();
if (!json.ok || json.error) {
alert(json.error);
}
return json.response;
}
</script>
<script src="//unpkg.com/alpinejs" defer></script>
<link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
<link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
And after the </head> tag add this <body> which will contain the actual form:
<body>
<main class="main-content">
<div class="top-cover u-padding-block-end-56">
<div class="container">
<div
class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
>
<h1 class="heading-level-1">LMNT Demo</h1>
<code class="u-un-break-text"></code>
</div>
<p
class="body-text-1 u-normal u-margin-block-start-8"
style="max-width: 50rem"
>
Use this page to test your implementation with LMNT. Enter
text and receive an audio response.
</p>
</div>
</div>
<div
class="container u-margin-block-start-negative-56"
x-data="{ prompt: '', response: '', loading: false }"
>
<div class="card u-flex u-gap-24 u-flex-vertical">
<div class="u-flex u-cross-center u-gap-8">
<div
class="input-text-wrapper is-with-end-button u-width-full-line"
>
<input x-model="prompt" type="search" placeholder="Enter text" />
<div class="icon-search" aria-hidden="true"></div>
</div>
<button
class="button"
x-bind:disabled="loading"
x-on:click="async () => { loading = true; response = ''; try { response = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }"
>
<span class="text">Generate</span>
</button>
</div>
<template x-if="response">
<div class="u-flex u-flex-vertical u-gap-12">
<div class="u-flex u-flex-vertical u-gap-12 card">
<div class="u-flex u-gap-12">
<h5 class="eyebrow-heading-2">Output:</h5>
</div>
<div>
<audio x-bind:src="response" controls></audio>
</div>
</div>
</div>
</template>
</div>
</div>
</main>
</body>
All of this together will render a form that will submit your text to the Appwrite Function through a POST request. The Appwrite function will then call LMNT's API, upload the audio to Appwrite Storage and return the URL, which will be displayed on your page.
Handle POST Request
Next, you'll add the methods necessary to integrate with the LMNT API.
Import the Speech class from lmnt-node, and the required features from the Appwrite Node.js SDK at the top of the main.js file.
import { Client, Storage, ID, InputFile, Permission, Role } from "node-appwrite";
import Speech from 'lmnt-node';
Next, add code to validate the body of the request and initialize the Appwrite SDK also within main.js following the previously added GET handler:
const endpoint = process.env.APPWRITE_ENDPOINT ?? "https://cloud.appwrite.io/v1";
const client = new Client()
.setEndpoint(endpoint)
.setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
.setKey(process.env.APPWRITE_API_KEY);
if (!req.body.text || typeof req.body.text !== "string") {
return res.json({ ok: false, error: "Missing required field `text`" }, 400);
}
Next, send a request to the LMNT API and store the response:
const lmnt = new Speech(process.env.LMNT_API_KEY);
const response = await lmnt.synthesize(req.body.text, 'lily', { format: 'mp3' });
This code will send the prompt to the LMNT API and return the audio as a blob. Additionally, any errors will be caught and reported for easy debugging.
Store Audio in Appwrite Storage
Store the audio file in Appwrite Storage for easy retrieval later:
const storage = new Storage(client);
const file = await storage.createFile(
process.env.APPWRITE_BUCKET_ID,
ID.unique(),
InputFile.fromBlob(new Blob([response.audio]), "audio.mp3"),
[Permission.read(Role.any())],
);
To show it to the user, parse the download URL from Appwrite and return it in the response:
const url = `${endpoint}/storage/buckets/${process.env.APPWRITE_BUCKET_ID}/files/${file.$id}/view?project=${process.env.APPWRITE_FUNCTION_PROJECT_ID}`;
return res.json({ ok: true, response: url });
This should finish up the Function. Deploy it to Appwrite by pushing it to the git repository created earlier.
Test the function
Now that the Function is deployed test it by visiting the function URL in your browser. This should show the UI created earlier. To test it, write a prompt and click the submit button. After a brief moment, you should see the audio below the input.