diff --git a/sea-config-tts.json b/sea-config-tts.json new file mode 100644 index 0000000..1f147f7 --- /dev/null +++ b/sea-config-tts.json @@ -0,0 +1,6 @@ +{ + "main": "dist/tts.js", + "output": "dist/ttsc", + "useCodeCache": true, + "disableExperimentalSEAWarning": true +} diff --git a/tts.ts b/tts.ts index af47e3e..1343c24 100644 --- a/tts.ts +++ b/tts.ts @@ -8,140 +8,147 @@ import { execa } from "execa"; import { Window } from "happy-dom"; import wrapAnsi from "wrap-ansi"; -const start = Date.now(); +async function main() { + const start = Date.now(); -const { values: args } = parseArgs({ - args: process.argv, - options: { - wait: { type: "boolean" }, - w: { type: "boolean" }, - url: { type: "string" }, - u: { type: "string" }, - }, - strict: true, - allowPositionals: true, -}); + const { values: args } = parseArgs({ + args: process.argv.slice(2), + options: { + wait: { type: "boolean" }, + w: { type: "boolean" }, + url: { type: "string" }, + u: { type: "string" }, + }, + strict: true, + allowPositionals: false, + }); -const wait = args.wait ?? args.w; -const url = args.url ?? args.u; + const wait = args.wait ?? args.w; + const url = args.url ?? args.u; -let input = url - ? url - : await new Promise((resolve) => { - let data = ""; - process.stdin.setEncoding("utf8"); - process.stdin.on("data", (chunk) => (data += chunk)); - process.stdin.on("end", () => resolve(data)); - }); + let input = url + ? url + : await new Promise((resolve) => { + let data = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", (chunk) => (data += chunk)); + process.stdin.on("end", () => resolve(data)); + }); -if (url !== undefined) { - const result = await fetch(input.trim()); - const window = new Window({ url: input.trim() }); - const doc = window.document; - doc.write(await result.text()); - await window.happyDOM.waitUntilComplete(); - const readable = new Readability(doc).parse(); - if (readable?.textContent) input = readable.textContent; - if (input.trim() === result.url) { - console.error("Unable to read web page."); + if (url !== undefined) { + const result = await fetch(input.trim()); + const window = new Window({ url: input.trim() }); + const doc = window.document; + doc.write(await result.text()); + await window.happyDOM.waitUntilComplete(); + const readable = new Readability(doc).parse(); + if (readable?.textContent) input = readable.textContent; + if (input.trim() === result.url) { + console.error("Unable to read web page."); + process.exit(1); + } + } + + if (!input) { + console.error("No input given."); process.exit(1); } -} -if (!input) { - console.error("No input given."); - process.exit(1); -} + const home = os.homedir(); + const cwd = `${home}/.ttsc/`; -const home = os.homedir(); -const cwd = `${home}/.ttsc/`; + await execa({ cwd, shell: true, reject: false })`rm *.wav`; -await execa({ cwd, shell: true, reject: false })`rm *.wav`; + console.log(chalk.blue("Welcome to TTSC!")); + console.log( + chalk.yellow( + "Beginning TTS processing... (This will take extra time the first time)", + ), + ); -console.log(chalk.blue("Welcome to TTSC!")); -console.log( - chalk.yellow( - "Beginning TTS processing... (This will take extra time the first time)", - ), -); - -const koko = execa({ - cwd, - input: Buffer.from(input), // execa v6+ uses 'input'. Use 'stdin' for v5 - stdout: "pipe", - stderr: "inherit", -})`uv run -- python -u main.py`; - -if (!wait) { - koko.catch(() => {}); - koko.then(() => { - const stop = Date.now(); - const duration = ((stop - start) / 1000).toFixed(2); - console.log(chalk.green(`\nProcessing finished: ${duration}s`)); - }); -} - -let first = true; -const rl = createInterface({ input: koko.stdout, crlfDelay: Infinity }); - -for await (const line of rl) { - const trimmed = line.trim(); - if (!trimmed) continue; - - let info: { file: number; text: string }; - try { - info = JSON.parse(trimmed); - } catch { - console.log(trimmed); - continue; - } - - if (first) { - first = false; - const stop = Date.now(); - const duration = ((stop - start) / 1000).toFixed(2); - console.log(chalk.green(`\nTime-to-audio: ${duration}s`)); - } - - const [columns] = process.stdout.getWindowSize(); - const text = wrapAnsi(info.text, columns); - const file = `${home}/.ttsc/${info.file}.wav`; - console.log(`\n${chalk.blue(file)}:\n${text}\n`); + const koko = execa({ + cwd, + input, + stdout: "pipe", + stderr: "inherit", + })`uv run -- python -u main.py`; if (!wait) { - await execa({ - cwd, - stdin: "ignore", - stdout: "inherit", - stderr: "inherit", - })`mpv ${file}`; + koko.catch(() => {}); + koko.then(() => { + const stop = Date.now(); + const duration = ((stop - start) / 1000).toFixed(2); + console.log(chalk.green(`\nProcessing finished: ${duration}s`)); + }); } + + let first = true; + const rl = createInterface({ input: koko.stdout, crlfDelay: Infinity }); + + for await (const line of rl) { + const trimmed = line.trim(); + if (!trimmed) continue; + + let info: { file: number; text: string }; + try { + info = JSON.parse(trimmed); + } catch { + console.log(trimmed); + continue; + } + + if (first) { + first = false; + const stop = Date.now(); + const duration = ((stop - start) / 1000).toFixed(2); + console.log(chalk.green(`\nTime-to-audio: ${duration}s`)); + } + + const [columns] = process.stdout.getWindowSize(); + const text = wrapAnsi(info.text, columns); + const file = `${home}/.ttsc/${info.file}.wav`; + console.log(`\n${chalk.blue(file)}:\n${text}\n`); + + if (!wait) { + await execa({ + cwd, + stdin: "ignore", + stdout: "inherit", + stderr: "inherit", + })`mpv ${file}`; + } + } + + await koko; + if (!wait) process.exit(); + + const stop = Date.now(); + const duration = ((stop - start) / 1000).toFixed(2); + console.log(chalk.green(`\nProcessing finished: ${duration}s`)); + + console.log(chalk.yellow(`Combining audio with sox...`)); + try { + await execa({ cwd, shell: true })`sox *.wav output.wav`; + console.log(chalk.green(`Playing audio from ${cwd}output.wav`)); + } catch (err) { + if (err instanceof Object && "code" in err && err.code === "ENOENT") { + console.error("Sox not found. Unable to combine outputs."); + } else { + throw err; + } + console.log(chalk.yellow(`Audio available in ${cwd}`)); + process.exit(); + } + + await execa({ + cwd, + stdin: "ignore", + stdout: "inherit", + stderr: "inherit", + })`mpv --pause output.wav`; } -await koko; -if (!wait) process.exit(); - -const stop = Date.now(); -const duration = ((stop - start) / 1000).toFixed(2); -console.log(chalk.green(`\nProcessing finished: ${duration}s`)); - -console.log(chalk.yellow(`Combining audio with sox...`)); -try { - await execa({ cwd, shell: true })`sox *.wav output.wav`; - console.log(chalk.green(`Playing audio from ${cwd}output.wav`)); -} catch (err) { - if (err instanceof Object && "code" in err && err.code === "ENOENT") { - console.error("Sox not found. Unable to combine outputs."); - } else { - throw err; - } - console.log(chalk.yellow(`Audio available in ${cwd}`)); - process.exit(); -} - -await execa({ - cwd, - stdin: "ignore", - stdout: "inherit", - stderr: "inherit", -})`mpv --pause output.wav`; +main().catch((err) => { + console.error(err); + process.exit(1); +});