require("dotenv").config() const path = require("node:path") const fs = require("node:fs") const axios = require("axios") const { aws4Interceptor } = require("aws4-axios") const express = require("express") const cors = require("cors") const getPhrases = require("../../node_lib") let app = null const { LISTENING_PORT } = process.env const PORT = LISTENING_PORT || 3000 const cachePath = path.join(process.cwd(), ".cache") const audiosPath = path.join(cachePath, "audio") const PollyBaseURL = "https://polly.us-east-1.amazonaws.com" const PollyDefaultConfig = { Engine: "generative", VoiceId: "Lucia", OutputFormat: "mp3", LanguageCode: "es-ES", } const interceptor = aws4Interceptor({ options: { region: "us-east-1", service: "polly", }, credentials: { accessKeyId: process.env.AWS_API_KEY, secretAccessKey: process.env.AWS_API_SECRET, }, }) axios.interceptors.request.use(interceptor) async function synthesizePollyVoice(phrase, phraseId) { if (!fs.existsSync(audiosPath)) { fs.mkdirSync(audiosPath, { recursive: true }) } // call to api and stream result to a file const voiceResultPath = path.resolve(audiosPath, phraseId + ".mp3") if (fs.existsSync(voiceResultPath)) { fs.unlinkSync(voiceResultPath) } const voiceResultFile = fs.createWriteStream(voiceResultPath) console.log(`Catching TTS file for id [${phraseId}]`) const { data: stream } = await axios({ url: `${PollyBaseURL}/v1/speech`, method: "POST", data: { ...PollyDefaultConfig, Text: phrase, }, responseType: "stream", }) stream.pipe(voiceResultFile) return new Promise((resolve, reject) => { stream.on("end", () => resolve(voiceResultPath)) stream.on("error", (error) => reject(error)) }) } async function fetchTTSAudioURL(req, phrase, phraseId) { const filePath = path.join(audiosPath, `${phraseId}.mp3`) if (!fs.existsSync(filePath)) { await synthesizePollyVoice(phrase, phraseId) } return `${NODE_ENV === "production" ? "https" : req.protocol}://${req.get("host")}${req.path}/audio/${phraseId}.mp3` } async function handleApiRequest(req, res) { let { random } = req.query // try to parse random, can be a number or a boolean if (random) { if (random === "true") { random = true } else if (Number(random)) { random = Number(random) } } const result = await getPhrases({ random }) if (random) { const phraseId = result .trim() .toLowerCase() .replace(/\s+/g, "_") .replace(/[^\w\s]/gi, "") return res.json({ id: phraseId, phrase: result.trim(), tts_file: await fetchTTSAudioURL(req, result, phraseId), }) } return res.json(result) } async function main() { app = express() app.use(cors()) app.use(express.json()) app.get("/api", handleApiRequest) app.use("/api/audio", express.static(audiosPath)) app.use(express.static(path.join(__dirname, "..", "web", "dist"))) // app.get("*", (req, res) => { // res.sendFile(path.join(__dirname, "..", "web", "dist", "index.html")) // }) app.listen(PORT) console.log(`Listening on port ${PORT}`) } main().catch(console.error)