From ed9c81066107a36159053340a138a1403dc551c3 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 00:05:26 -0700 Subject: [PATCH 01/14] terminal upgrades --- package-lock.json | 35 ++++++++++--- package.json | 8 +-- src/index.ts | 74 ++++++++++++++++++++++------ src/logger.ts | 123 ++++++++++++++++++++++++++++++++++++++++++++++ src/thumbnail.ts | 37 +++++++------- 5 files changed, 234 insertions(+), 43 deletions(-) create mode 100644 src/logger.ts diff --git a/package-lock.json b/package-lock.json index bb2819f..ffc5a31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,23 @@ { - "name": "art-gen", + "name": "artgen", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "art-gen", + "name": "artgen", "dependencies": { + "chalk": "^5.3.0", "chrome-launcher": "^0.15.2", + "colors": "^1.4.0", "jsmediatags": "^3.9.7", "puppeteer-core": "^20.8.2" }, "bin": { - "art-gen": "src/index.ts" + "artgen": "src/index.ts" }, "devDependencies": { "@types/jsmediatags": "^3.9.3", - "@types/node": "^20.4.2", + "@types/node": "^20.4.5", "typescript": "^5.1.6" } }, @@ -78,9 +80,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.4.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", - "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==" + "version": "20.4.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz", + "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==" }, "node_modules/@types/yauzl": { "version": "2.10.0", @@ -200,6 +202,17 @@ "node": "*" } }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/chrome-launcher": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", @@ -257,6 +270,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", diff --git a/package.json b/package.json index f58c9ce..3367101 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "art-gen", + "name": "artgen", "private": true, "type": "module", "bin": "./src/index.ts", @@ -8,13 +8,15 @@ "dev": "tsc -w -p ./tsconfig.build.json" }, "dependencies": { + "chalk": "^5.3.0", "chrome-launcher": "^0.15.2", + "colors": "^1.4.0", "jsmediatags": "^3.9.7", "puppeteer-core": "^20.8.2" }, "devDependencies": { "@types/jsmediatags": "^3.9.3", - "@types/node": "^20.4.2", + "@types/node": "^20.4.5", "typescript": "^5.1.6" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index c81511d..5841450 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,41 +1,65 @@ #!/usr/bin/env npx tsx +import { createInterface } from "node:readline"; import { extname } from "node:path"; import { exec as execCallback } from "node:child_process"; import { promisify } from "node:util"; import { inputs, artworkOnly, overwrite } from "./args.js"; import { createRenderer } from "./thumbnail.js"; +import { rmSync } from "node:fs"; +import { defaultLoggerOpts, newLogger } from "./logger.js"; const exec = promisify(execCallback); -console.log("Art Gen"); -console.log("-- An app to generate thumbnails for YouTube Art Tracks! --\n"); +export const Logger = newLogger(defaultLoggerOpts()); -if (artworkOnly) console.log("[artwork only]"); -if (overwrite) console.log("[overwrite]"); +Logger.log("Art Gen"); +Logger.log("-- An app to generate thumbnails for YouTube Art Tracks! --\n"); -const outputs = inputs.map(item => extRename(item,artworkOnly ? ".png" : ".mp4")); +if (artworkOnly) Logger.debug("[artwork only]"); +if (overwrite) Logger.debug("[overwrite]"); + +const outputs = inputs.map(item => extRename(item, artworkOnly ? ".png" : ".mp4")); const renderer = await createRenderer(); -for (let i = 0; i < inputs.length; i++){ +for (let i = 0; i < inputs.length; i++) { const songPath = inputs[i]; - const thumbnailPath = artworkOnly ? outputs[i] : extRename(outputs[i],".png"); - await renderer.generateThumbnail(songPath,thumbnailPath); + const thumbnailPath = artworkOnly ? outputs[i] : extRename(outputs[i], ".png"); + await renderer.generateThumbnail(songPath, thumbnailPath); } await renderer.close(); if (artworkOnly) process.exit(0); -for (let i = 0; i < inputs.length; i++){ +const tempArtwork = await (async () => { + return new Promise(async (r) => { + var response = null; + while (response == null) { + var t = await prompt("Delete artwork after video is generated? (Y|N): "); + if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { + response = t; + } else { + Logger.warning('Invalid response! Answer with "Y" or "N"!\n'); + } + } + Logger.debug(`${response}`); + console.log(""); + r(response == "Y"); + }); +})(); + +Logger.debug(`${tempArtwork}`); + +for (let i = 0; i < inputs.length; i++) { const songPath = inputs[i]; - const thumbnailPath = extRename(outputs[i],".png"); + const thumbnailPath = extRename(outputs[i], ".png"); const videoPath = outputs[i]; - console.log("Generating video..."); - // console.log(songPath); - // console.log(thumbnailPath); - // console.log(videoPath); + Logger.log("Generating video..."); + Logger.debug(songPath); + Logger.debug(thumbnailPath); + Logger.debug(videoPath); await exec(`ffmpeg \ -loop 1 \ -framerate 1 \ @@ -51,9 +75,29 @@ for (let i = 0; i < inputs.length; i++){ -shortest \ "${videoPath}"\ ${overwrite ? "-y" : "-n"}`); + + if (tempArtwork) { + rmSync(thumbnailPath); + } } +Logger.log("Done!"); +Logger.critical("Exiting process..."); +process.exit(0); + function extRename(path: string, ext: string): string { const extension = extname(path); - return path.replace(extension,ext); + return path.replace(extension, ext); +} + +async function prompt(prompt: string): Promise { + const rl = createInterface({ + input: process.stdin, + output: process.stdout + }); + + return new Promise(resolve => rl.question(prompt, ans => { + rl.close(); + resolve(ans); + })); } \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..e529212 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,123 @@ +import chalk from "chalk"; + +interface LoggerOpts { + + format: string; + debugLevel: number + +} + +export const LevelColors = [ + "blue", + "gray", + "yellow", + "red", + "bgRed" +] +export const Levels = [ + 'INFO', + 'DEBUG', + 'WARNING', + 'ERROR', + 'CRITICAL' +] + +export function newLogger(opts: LoggerOpts): Logger { + return new Logger(opts); +} +export function defaultLoggerOpts(debug: boolean = false): LoggerOpts { + return { + format: "[%level%]%sp% %message%", + debugLevel: debug ? 1 : 0 + } +} + +class Logger { + + private format: string; + private debugLevel: number; + + constructor(opts: LoggerOpts) { + this.format = opts.format; + this.debugLevel = opts.debugLevel; + } + + public getFormat(): string { + return this.format; + } + public debugEnabled(): boolean { + return this.debugLevel > 0; + } + + public log(message: any) { + this.info(message); + } + public info(message: any) { + console.log(this.formatMessage(message, 0)); + } + public debug(message: any) { + if (this.debugEnabled()) console.log(this.formatMessage(message, 1)); + } + public warning(message: any) { + console.log(this.formatMessage(message, 2)); + } + public error(message: any) { + console.log(this.formatMessage(message, 3)); + } + public critical(message: any) { + console.log(this.formatMessage(message, 4)); + } + + + + private formatMessage(message: any, level: number): any { + if (!["string", "boolean", "number"].includes(typeof message)) { + var parts = ["[%level%]"] + switch (LevelColors[level]) { + case "blue": + parts[0] = chalk.blue.bgBlack(parts[0]); + break; + case "gray": + parts[0] = chalk.gray.bgBlack(parts[0]); + break; + case "yellow": + parts[0] = chalk.yellow.bgBlack(parts[0]); + break; + case "red": + parts[0] = chalk.redBright.bgBlack(parts[0]); + break; + case "bgRed": + parts[0] = chalk.red.bold(parts[0]); + break; + } + parts[0] = parts[0].split("%level%").join(Levels[level]); + console.log(parts[0]); + return message; + } + message = message.toString(); + var parts = this.format.split('%sp%'); + switch (LevelColors[level]) { + case "blue": + parts[0] = chalk.blue.bgBlack(parts[0]); + break; + case "gray": + parts[0] = chalk.gray.bgBlack(parts[0]); + break; + case "yellow": + parts[0] = chalk.yellow.bgBlack(parts[0]); + break; + case "red": + parts[0] = chalk.redBright.bgBlack(parts[0]); + break; + case "bgRed": + parts[0] = chalk.red.bold(parts[0]); + break; + } + + parts[0] = parts[0].split("%level%").join(Levels[level]); + parts[1] = parts[1].split("%message%").join(message); + + return parts.join(""); + } + +} \ No newline at end of file diff --git a/src/thumbnail.ts b/src/thumbnail.ts index 8f6d3f1..8dce021 100644 --- a/src/thumbnail.ts +++ b/src/thumbnail.ts @@ -9,44 +9,45 @@ import { readTags } from "./jsmediatags.js"; import type { Server } from "node:http"; import type { Browser } from "puppeteer-core"; import type { MediaTags } from "./jsmediatags.js"; +import { Logger } from "./index.js"; const SERVER_PATH = "http://localhost:3000"; export async function createRenderer(): Promise { const server = await startServer(); const browser = await launchBrowser(); - return new ThumbnailGenerator(server,browser); + return new ThumbnailGenerator(server, browser); } async function startServer(): Promise { - const server = createServer(async (request,response) => { + const server = createServer(async (request, response) => { const url = new URL(`${SERVER_PATH}${request.url}`); - // console.log(url.toString()); + Logger.debug(url.toString()); if (url.searchParams.size === 0) return new Promise(resolve => response.end(resolve)); const songPath = decodeURIComponent(url.searchParams.get("songPath")!); - console.log(`\n${songPath}`); + Logger.debug(`\n${songPath}\n`); const song = await readFile(songPath); const tags = await readTags(song); const source = await generateSource(tags); - response.writeHead(200,{ "Content-Type": "text/html" }); + response.writeHead(200, { "Content-Type": "text/html" }); response.write(source); await new Promise(resolve => response.end(resolve)); }); - await new Promise(resolve => server.listen(3000,resolve)); + await new Promise(resolve => server.listen(3000, resolve)); return server; } async function generateSource(tags: MediaTags): Promise { - const index = new URL("../index.html",import.meta.url); - const source = await readFile(index,{ encoding: "utf-8" }); + const index = new URL("../index.html", import.meta.url); + const source = await readFile(index, { encoding: "utf-8" }); const { title, artist, album, artwork } = tags; - console.log(`${title}: ${artist} - ${album}`); - console.log("Generating thumbnail..."); + Logger.log(`${title}: ${artist} - ${album}`); + Logger.log("Generating thumbnail...\n"); return source - .replaceAll("%TITLE%",title) - .replaceAll("%ARTIST%",artist) - .replaceAll("%ALBUM%",album) - .replaceAll("%ARTWORK%",artwork); + .replaceAll("%TITLE%", title) + .replaceAll("%ARTIST%", artist) + .replaceAll("%ALBUM%", album) + .replaceAll("%ARTWORK%", artwork); } async function launchBrowser(): Promise { @@ -66,14 +67,14 @@ class ThumbnailGenerator { async generateThumbnail(songPath: string, thumbnailPath: string): Promise { const page = await this.#browser.newPage(); const renderPath = new URL(SERVER_PATH); - renderPath.searchParams.set("songPath",encodeURIComponent(resolve(songPath))); + renderPath.searchParams.set("songPath", encodeURIComponent(resolve(songPath))); - await page.goto(renderPath.toString(),{ waitUntil: "networkidle0" }); + await page.goto(renderPath.toString(), { waitUntil: "networkidle0" }); await page.setViewport({ width: 1920, height: 1080 }); const thumbnail = await page.screenshot(); - // console.log(thumbnail); - await writeFile(thumbnailPath,thumbnail,{ flag: overwrite ? undefined : "wx" }); + Logger.debug(thumbnail); + await writeFile(thumbnailPath, thumbnail, { flag: overwrite ? undefined : "wx" }); } async close(): Promise { From 584a7f65d283bb999311e30743cfc808e1922854 Mon Sep 17 00:00:00 2001 From: Jay <78117567+xJustJqy@users.noreply.github.com> Date: Mon, 31 Jul 2023 00:07:36 -0700 Subject: [PATCH 02/14] oops removed hyphen --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3367101..7cc11e5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "artgen", + "name": "art-gen", "private": true, "type": "module", "bin": "./src/index.ts", From 77eb23d8dfc2ab029e28e44eb1fea6540e9dfe97 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 01:24:12 -0700 Subject: [PATCH 03/14] fancy stuff, failsafes for certain things --- package-lock.json | 116 ++++++++++++++++++++++++++++++++++++++++------ package.json | 3 +- src/aligner.ts | 92 ++++++++++++++++++++++++++++++++++++ src/index.ts | 39 ++++++++++------ src/logger.ts | 111 +++++++++++++++++++++++++++++++++++--------- src/thumbnail.ts | 53 ++++++++++++++++----- 6 files changed, 352 insertions(+), 62 deletions(-) create mode 100644 src/aligner.ts diff --git a/package-lock.json b/package-lock.json index ffc5a31..b085747 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,20 @@ { - "name": "artgen", + "name": "art-gen", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "artgen", + "name": "art-gen", "dependencies": { "chalk": "^5.3.0", "chrome-launcher": "^0.15.2", "colors": "^1.4.0", "jsmediatags": "^3.9.7", - "puppeteer-core": "^20.8.2" + "puppeteer-core": "^20.8.2", + "string-width": "^6.1.0" }, "bin": { - "artgen": "src/index.ts" + "art-gen": "src/index.ts" }, "devDependencies": { "@types/jsmediatags": "^3.9.3", @@ -254,6 +255,24 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -299,10 +318,15 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1135028.tgz", "integrity": "sha512-jEcNGrh6lOXNRJvZb9RjeevtZGrgugPKSMJZxfyxWQnhlKawMPhMtk/dfC+Z/6xNXExlzTKlY5LzIAK/fRpQIw==" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz", + "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==" }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -747,16 +771,44 @@ } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-ansi": { @@ -852,6 +904,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -918,6 +988,24 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index 7cc11e5..1cc0b6d 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "chrome-launcher": "^0.15.2", "colors": "^1.4.0", "jsmediatags": "^3.9.7", - "puppeteer-core": "^20.8.2" + "puppeteer-core": "^20.8.2", + "string-width": "^6.1.0" }, "devDependencies": { "@types/jsmediatags": "^3.9.3", diff --git a/src/aligner.ts b/src/aligner.ts new file mode 100644 index 0000000..2c9c62b --- /dev/null +++ b/src/aligner.ts @@ -0,0 +1,92 @@ +'use strict' + +import stringWidth from "string-width"; + +interface alignOpts { + + align: string + split: string + pad: string + +} + +export function defaultAlignOpts(): alignOpts { + return { + align: "center", + split: "\n", + pad: " " + } +} + +export class Aligner { + + private opts: alignOpts; + + constructor(opts: alignOpts) { + this.opts = opts; + } + + public align(text: string): string { + return ansiAlign(text, this.opts); + } + +} + +function ansiAlign(text: any, opts: alignOpts): string { + if (typeof text == "undefined") return "" + + opts = opts || {} + const align = opts.align || 'center' + + // short-circuit `align: 'left'` as no-op + if (align === 'left') return text + + const split = opts.split || '\n' + const pad = opts.pad || ' ' + const widthDiffFn = align !== 'right' ? halfDiff : fullDiff + var textArray: string[] = []; + + let returnString = false + if (!Array.isArray(text)) { + returnString = true + textArray = String(text).split(split) + } + + let width + let maxWidth = process.stdout.columns; + textArray = textArray.map(function (str) { + str = String(str) + width = stringWidth(str) + maxWidth = Math.max(width, maxWidth) + return { + str, + width + } + }).map(function (obj) { + return new Array(widthDiffFn(maxWidth, obj.width) + 1).join(pad) + obj.str + }) + + return returnString ? textArray.join(split) : text +} + +ansiAlign.left = function left(text: string): string { + return ansiAlign(text, { align: 'left', split: "\n", pad: " " }) +} + +ansiAlign.center = function center(text: string): string { + return ansiAlign(text, { align: 'center', split: "\n", pad: " " }) +} + +ansiAlign.right = function right(text: string): string { + return ansiAlign(text, { align: 'right', split: "\n", pad: " " }) +} + +export default ansiAlign; + +function halfDiff(maxWidth: number, curWidth: number): number { + return Math.floor((maxWidth - curWidth) / 2) +} + +function fullDiff(maxWidth: number, curWidth: number): number { + return maxWidth - curWidth +} diff --git a/src/index.ts b/src/index.ts index 5841450..f39a8e0 100755 --- a/src/index.ts +++ b/src/index.ts @@ -4,48 +4,53 @@ import { createInterface } from "node:readline"; import { extname } from "node:path"; import { exec as execCallback } from "node:child_process"; import { promisify } from "node:util"; -import { inputs, artworkOnly, overwrite } from "./args.js"; +import { inputs, artworkOnly, overwrite as _overwrite } from "./args.js"; import { createRenderer } from "./thumbnail.js"; -import { rmSync } from "node:fs"; +import { rm, rmSync } from "node:fs"; import { defaultLoggerOpts, newLogger } from "./logger.js"; +import chalk from "chalk"; const exec = promisify(execCallback); export const Logger = newLogger(defaultLoggerOpts()); -Logger.log("Art Gen"); +Logger.log(chalk.bold.bold("Art Gen")); Logger.log("-- An app to generate thumbnails for YouTube Art Tracks! --\n"); if (artworkOnly) Logger.debug("[artwork only]"); -if (overwrite) Logger.debug("[overwrite]"); +if (_overwrite) Logger.debug("[overwrite]"); const outputs = inputs.map(item => extRename(item, artworkOnly ? ".png" : ".mp4")); const renderer = await createRenderer(); +var overwrite_: boolean = false; for (let i = 0; i < inputs.length; i++) { const songPath = inputs[i]; const thumbnailPath = artworkOnly ? outputs[i] : extRename(outputs[i], ".png"); - await renderer.generateThumbnail(songPath, thumbnailPath); + overwrite_ = await renderer.generateThumbnail(songPath, thumbnailPath); } +const overwrite = _overwrite || overwrite_; await renderer.close(); -if (artworkOnly) process.exit(0); +if (artworkOnly) end(); + +Logger.debugLineBreak(); const tempArtwork = await (async () => { return new Promise(async (r) => { var response = null; while (response == null) { - var t = await prompt("Delete artwork after video is generated? (Y|N): "); + var t = await prompt("Delete artwork after video is generated? (Y/N): "); if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { response = t; } else { Logger.warning('Invalid response! Answer with "Y" or "N"!\n'); } } + Logger.lineBreak(); Logger.debug(`${response}`); - console.log(""); r(response == "Y"); }); })(); @@ -75,22 +80,20 @@ for (let i = 0; i < inputs.length; i++) { -shortest \ "${videoPath}"\ ${overwrite ? "-y" : "-n"}`); - if (tempArtwork) { + Logger.debug("Removing " + thumbnailPath); rmSync(thumbnailPath); } } -Logger.log("Done!"); -Logger.critical("Exiting process..."); -process.exit(0); +end(); function extRename(path: string, ext: string): string { const extension = extname(path); return path.replace(extension, ext); } -async function prompt(prompt: string): Promise { +export async function prompt(prompt: string): Promise { const rl = createInterface({ input: process.stdin, output: process.stdout @@ -100,4 +103,14 @@ async function prompt(prompt: string): Promise { rl.close(); resolve(ans); })); +} + +export function term() { + Logger.critical("Ending process..."); + process.exit(0); +} +export function end() { + Logger.log("Done!"); + Logger.critical("Exiting process..."); + process.exit(0); } \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts index e529212..06444c3 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,9 +1,11 @@ import chalk from "chalk"; +import { Aligner, defaultAlignOpts } from "./aligner"; interface LoggerOpts { format: string; debugLevel: number + center: boolean } @@ -28,7 +30,8 @@ export function newLogger(opts: LoggerOpts): Logger { export function defaultLoggerOpts(debug: boolean = false): LoggerOpts { return { format: "[%level%]%sp% %message%", - debugLevel: debug ? 1 : 0 + debugLevel: debug ? 1 : 0, + center: false } } @@ -36,10 +39,15 @@ class Logger { private format: string; private debugLevel: number; + private center: boolean; + private aligner: Aligner constructor(opts: LoggerOpts) { + console.clear(); this.format = opts.format; this.debugLevel = opts.debugLevel; + this.center = opts.center; + this.aligner = new Aligner(defaultAlignOpts()) } public getFormat(): string { @@ -68,11 +76,85 @@ class Logger { console.log(this.formatMessage(message, 4)); } + public debugLineBreak(count: number = 1) { + if (this.debugEnabled()) for (var i = 0; i < count; i++) console.log(); + } + public lineBreak(count: number = 1) { + for (var i = 0; i < count; i++) console.log(); + } + private formatMessage(message: any, level: number): any { if (!["string", "boolean", "number"].includes(typeof message)) { + console.log(typeof message); var parts = ["[%level%]"] + if (this.center) { + switch (LevelColors[level]) { + case "blue": + parts[0] = chalk.blue.dim.bgBlack(parts[0]); + break; + case "gray": + parts[0] = chalk.gray.dim.bgBlack(parts[0]); + break; + case "yellow": + parts[0] = chalk.yellow.dim.bgBlack(parts[0]); + break; + case "red": + parts[0] = chalk.redBright.dim.bgBlack(parts[0]); + break; + case "bgRed": + parts[0] = chalk.red.dim.bold(parts[0]); + break; + } + } else { + switch (LevelColors[level]) { + case "blue": + parts[0] = chalk.blue.bgBlack(parts[0]); + break; + case "gray": + parts[0] = chalk.gray.bgBlack(parts[0]); + break; + case "yellow": + parts[0] = chalk.yellow.bgBlack(parts[0]); + break; + case "red": + parts[0] = chalk.redBright.bgBlack(parts[0]); + break; + case "bgRed": + parts[0] = chalk.red.bold(parts[0]); + break; + } + } + parts[0] = parts[0].split("%level%").join(Levels[level]); + if (this.center) { + console.log(this.aligner.align(parts[0])); + return this.aligner.align(message); + } + console.log(parts[0]); + return message; + } + message = message.toString(); + var parts = this.format.split('%sp%'); + if (this.center) { + switch (LevelColors[level]) { + case "blue": + parts[0] = chalk.blue.dim.bgBlack(parts[0]); + break; + case "gray": + parts[0] = chalk.gray.dim.bgBlack(parts[0]); + break; + case "yellow": + parts[0] = chalk.yellow.dim.bgBlack(parts[0]); + break; + case "red": + parts[0] = chalk.redBright.dim.bgBlack(parts[0]); + break; + case "bgRed": + parts[0] = chalk.red.dim.bold(parts[0]); + break; + } + } else { switch (LevelColors[level]) { case "blue": parts[0] = chalk.blue.bgBlack(parts[0]); @@ -90,33 +172,16 @@ class Logger { parts[0] = chalk.red.bold(parts[0]); break; } - parts[0] = parts[0].split("%level%").join(Levels[level]); - console.log(parts[0]); - return message; - } - message = message.toString(); - var parts = this.format.split('%sp%'); - switch (LevelColors[level]) { - case "blue": - parts[0] = chalk.blue.bgBlack(parts[0]); - break; - case "gray": - parts[0] = chalk.gray.bgBlack(parts[0]); - break; - case "yellow": - parts[0] = chalk.yellow.bgBlack(parts[0]); - break; - case "red": - parts[0] = chalk.redBright.bgBlack(parts[0]); - break; - case "bgRed": - parts[0] = chalk.red.bold(parts[0]); - break; } parts[0] = parts[0].split("%level%").join(Levels[level]); parts[1] = parts[1].split("%message%").join(message); + if (this.center) { + console.log(this.aligner.align(parts[0])); + return this.aligner.align(parts[1]); + } + return parts.join(""); } diff --git a/src/thumbnail.ts b/src/thumbnail.ts index 8dce021..9504e2a 100644 --- a/src/thumbnail.ts +++ b/src/thumbnail.ts @@ -9,7 +9,8 @@ import { readTags } from "./jsmediatags.js"; import type { Server } from "node:http"; import type { Browser } from "puppeteer-core"; import type { MediaTags } from "./jsmediatags.js"; -import { Logger } from "./index.js"; +import { Logger, prompt, term } from "./index.js"; +import { existsSync } from "node:fs"; const SERVER_PATH = "http://localhost:3000"; @@ -25,7 +26,7 @@ async function startServer(): Promise { Logger.debug(url.toString()); if (url.searchParams.size === 0) return new Promise(resolve => response.end(resolve)); const songPath = decodeURIComponent(url.searchParams.get("songPath")!); - Logger.debug(`\n${songPath}\n`); + Logger.debug(`${songPath}\n`); const song = await readFile(songPath); const tags = await readTags(song); const source = await generateSource(tags); @@ -64,17 +65,47 @@ class ThumbnailGenerator { this.#browser = browser; } - async generateThumbnail(songPath: string, thumbnailPath: string): Promise { - const page = await this.#browser.newPage(); - const renderPath = new URL(SERVER_PATH); - renderPath.searchParams.set("songPath", encodeURIComponent(resolve(songPath))); + async generateThumbnail(songPath: string, thumbnailPath: string): Promise { + var overwrite: boolean = false; + if (existsSync(thumbnailPath) || existsSync(songPath)) { + overwrite = await (async () => { + return new Promise(async (r) => { + var response = null; + while (response == null) { + var t = await prompt("Video file already exists! Would you like to overwrite? (Y/N): "); + if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { + response = t; + } else { + Logger.warning('Invalid response! Answer with "Y" or "N"!\n'); + } + } + Logger.lineBreak(); + Logger.debug(`${response}`); + r(response == "Y"); + }); + })(); + } else { + overwrite = true; + } + return new Promise(async (_resolve) => { + const page = await this.#browser.newPage(); + const renderPath = new URL(SERVER_PATH); + renderPath.searchParams.set("songPath", encodeURIComponent(resolve(songPath))); - await page.goto(renderPath.toString(), { waitUntil: "networkidle0" }); - await page.setViewport({ width: 1920, height: 1080 }); + await page.goto(renderPath.toString(), { waitUntil: "networkidle0" }); + await page.setViewport({ width: 1920, height: 1080 }); - const thumbnail = await page.screenshot(); - Logger.debug(thumbnail); - await writeFile(thumbnailPath, thumbnail, { flag: overwrite ? undefined : "wx" }); + const thumbnail = await page.screenshot(); + Logger.debug(thumbnail); + + if (!overwrite) { + term(); + _resolve(overwrite); + } else { + await writeFile(thumbnailPath, thumbnail); + _resolve(overwrite); + } + }); } async close(): Promise { From 7d43a6af9ca571f707df9d9b69591b858206c290 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 01:31:29 -0700 Subject: [PATCH 04/14] change from while loop to recursive function --- src/index.ts | 4 +++- src/thumbnail.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index f39a8e0..741d4ec 100755 --- a/src/index.ts +++ b/src/index.ts @@ -41,14 +41,16 @@ Logger.debugLineBreak(); const tempArtwork = await (async () => { return new Promise(async (r) => { var response = null; - while (response == null) { + var _prompt_ = async () => { var t = await prompt("Delete artwork after video is generated? (Y/N): "); if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { response = t; } else { Logger.warning('Invalid response! Answer with "Y" or "N"!\n'); + await _prompt_(); } } + await _prompt_(); Logger.lineBreak(); Logger.debug(`${response}`); r(response == "Y"); diff --git a/src/thumbnail.ts b/src/thumbnail.ts index 9504e2a..2a0e57a 100644 --- a/src/thumbnail.ts +++ b/src/thumbnail.ts @@ -71,14 +71,16 @@ class ThumbnailGenerator { overwrite = await (async () => { return new Promise(async (r) => { var response = null; - while (response == null) { + var _prompt_ = async () => { var t = await prompt("Video file already exists! Would you like to overwrite? (Y/N): "); if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { response = t; } else { Logger.warning('Invalid response! Answer with "Y" or "N"!\n'); + await _prompt_(); } } + await _prompt_(); Logger.lineBreak(); Logger.debug(`${response}`); r(response == "Y"); From 2ee43e9e3cc35c856bb1481648dc2212a35f3d34 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 01:32:55 -0700 Subject: [PATCH 05/14] remove instance of ".bold.bold" --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 741d4ec..3ec0b14 100755 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ const exec = promisify(execCallback); export const Logger = newLogger(defaultLoggerOpts()); -Logger.log(chalk.bold.bold("Art Gen")); +Logger.log(chalk.bold("Art Gen")); Logger.log("-- An app to generate thumbnails for YouTube Art Tracks! --\n"); if (artworkOnly) Logger.debug("[artwork only]"); From 0d23fbb2cbb9ffd354930348f87c5119311b2c0d Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 04:23:23 -0700 Subject: [PATCH 06/14] double check overwrite flag so it doesn't prompt --- src/index.ts | 2 +- src/thumbnail.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 3ec0b14..7502740 100755 --- a/src/index.ts +++ b/src/index.ts @@ -28,7 +28,7 @@ var overwrite_: boolean = false; for (let i = 0; i < inputs.length; i++) { const songPath = inputs[i]; const thumbnailPath = artworkOnly ? outputs[i] : extRename(outputs[i], ".png"); - overwrite_ = await renderer.generateThumbnail(songPath, thumbnailPath); + overwrite_ = await renderer.generateThumbnail(songPath, thumbnailPath, _overwrite); } const overwrite = _overwrite || overwrite_; diff --git a/src/thumbnail.ts b/src/thumbnail.ts index 2a0e57a..8beae0b 100644 --- a/src/thumbnail.ts +++ b/src/thumbnail.ts @@ -65,9 +65,9 @@ class ThumbnailGenerator { this.#browser = browser; } - async generateThumbnail(songPath: string, thumbnailPath: string): Promise { + async generateThumbnail(songPath: string, thumbnailPath: string, _overwrite: boolean): Promise { var overwrite: boolean = false; - if (existsSync(thumbnailPath) || existsSync(songPath)) { + if ((existsSync(thumbnailPath) || existsSync(songPath)) && !_overwrite) { overwrite = await (async () => { return new Promise(async (r) => { var response = null; From 9fb1bd3730ad69bad1bfbbd87c98dbed23bb83ef Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 04:26:12 -0700 Subject: [PATCH 07/14] check artworkOnly so prompt doesn't false --- src/index.ts | 2 +- src/thumbnail.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7502740..c098b08 100755 --- a/src/index.ts +++ b/src/index.ts @@ -28,7 +28,7 @@ var overwrite_: boolean = false; for (let i = 0; i < inputs.length; i++) { const songPath = inputs[i]; const thumbnailPath = artworkOnly ? outputs[i] : extRename(outputs[i], ".png"); - overwrite_ = await renderer.generateThumbnail(songPath, thumbnailPath, _overwrite); + overwrite_ = await renderer.generateThumbnail(songPath, thumbnailPath, _overwrite, artworkOnly); } const overwrite = _overwrite || overwrite_; diff --git a/src/thumbnail.ts b/src/thumbnail.ts index 8beae0b..7d376da 100644 --- a/src/thumbnail.ts +++ b/src/thumbnail.ts @@ -65,9 +65,9 @@ class ThumbnailGenerator { this.#browser = browser; } - async generateThumbnail(songPath: string, thumbnailPath: string, _overwrite: boolean): Promise { + async generateThumbnail(songPath: string, thumbnailPath: string, _overwrite: boolean, artworkOnly: boolean): Promise { var overwrite: boolean = false; - if ((existsSync(thumbnailPath) || existsSync(songPath)) && !_overwrite) { + if ((existsSync(thumbnailPath) || (existsSync(songPath) && !artworkOnly)) && !_overwrite) { overwrite = await (async () => { return new Promise(async (r) => { var response = null; From 4e8dbfee0c52d528433910a0eaba228a39a4a654 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 04:29:35 -0700 Subject: [PATCH 08/14] added flag "-d" for logger debug mode --- src/args.ts | 14 ++++++++++---- src/index.ts | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/args.ts b/src/args.ts index a44fa32..eff8a49 100644 --- a/src/args.ts +++ b/src/args.ts @@ -1,19 +1,24 @@ const COMMAND_PATTERN = /^--|-/; const ARTWORK_ONLY_PATTERN = /^--artwork-only|-a$/; const OVERWRITE_PATTERN = /^--overwrite|-w$/; +const DEBUG_PATTERN = /^--debug|-d$/; const args = process.argv.slice(2); const commands: string[] = []; export const inputs: string[] = []; -for (const item of args){ - switch (true){ +for (const item of args) { + switch (true) { case ARTWORK_ONLY_PATTERN.test(item): case OVERWRITE_PATTERN.test(item): { commands.push(item); break; } + case DEBUG_PATTERN.test(item): { + commands.push(item); + break; + } case COMMAND_PATTERN.test(item): { throw new Error(`Unexpected command '${item}'`); } @@ -23,9 +28,10 @@ for (const item of args){ } } -if (inputs.length === 0){ +if (inputs.length === 0) { throw new Error("Must provide song file path inputs"); } export const artworkOnly: boolean = commands.some(item => ARTWORK_ONLY_PATTERN.test(item)); -export const overwrite: boolean = commands.some(item => OVERWRITE_PATTERN.test(item)); \ No newline at end of file +export const overwrite: boolean = commands.some(item => OVERWRITE_PATTERN.test(item)); +export const debugMode: boolean = commands.some(item => DEBUG_PATTERN.test(item)); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c098b08..ad95c64 100755 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import { createInterface } from "node:readline"; import { extname } from "node:path"; import { exec as execCallback } from "node:child_process"; import { promisify } from "node:util"; -import { inputs, artworkOnly, overwrite as _overwrite } from "./args.js"; +import { inputs, artworkOnly, overwrite as _overwrite, debugMode } from "./args.js"; import { createRenderer } from "./thumbnail.js"; import { rm, rmSync } from "node:fs"; import { defaultLoggerOpts, newLogger } from "./logger.js"; @@ -12,7 +12,7 @@ import chalk from "chalk"; const exec = promisify(execCallback); -export const Logger = newLogger(defaultLoggerOpts()); +export const Logger = newLogger(defaultLoggerOpts(debugMode)); Logger.log(chalk.bold("Art Gen")); Logger.log("-- An app to generate thumbnails for YouTube Art Tracks! --\n"); From 29a88e57263c99f9eaf9f78e47f8469fa6af7e22 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 05:13:37 -0700 Subject: [PATCH 09/14] alter overwrite stuff and reorder Logger funcs --- src/index.ts | 43 ++++++++++++++++++++++++++++--------------- src/logger.ts | 39 +++++++++++++++++++++++++++------------ src/thumbnail.ts | 23 +++++++++++------------ 3 files changed, 66 insertions(+), 39 deletions(-) diff --git a/src/index.ts b/src/index.ts index ad95c64..29b3435 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,36 +1,43 @@ #!/usr/bin/env npx tsx import { createInterface } from "node:readline"; -import { extname } from "node:path"; +import { extname, basename } from "node:path"; import { exec as execCallback } from "node:child_process"; import { promisify } from "node:util"; -import { inputs, artworkOnly, overwrite as _overwrite, debugMode } from "./args.js"; +import { inputs, artworkOnly, overwrite, debugMode } from "./args.js"; import { createRenderer } from "./thumbnail.js"; -import { rm, rmSync } from "node:fs"; +import { rmSync } from "node:fs"; +import { readFile } from "node:fs/promises"; import { defaultLoggerOpts, newLogger } from "./logger.js"; import chalk from "chalk"; +import { readTags } from "./jsmediatags.js"; const exec = promisify(execCallback); export const Logger = newLogger(defaultLoggerOpts(debugMode)); +const termed: string[] = []; +const allowed: string[] = []; + Logger.log(chalk.bold("Art Gen")); Logger.log("-- An app to generate thumbnails for YouTube Art Tracks! --\n"); if (artworkOnly) Logger.debug("[artwork only]"); -if (_overwrite) Logger.debug("[overwrite]"); +if (overwrite) Logger.debug("[overwrite]"); const outputs = inputs.map(item => extRename(item, artworkOnly ? ".png" : ".mp4")); const renderer = await createRenderer(); -var overwrite_: boolean = false; for (let i = 0; i < inputs.length; i++) { const songPath = inputs[i]; const thumbnailPath = artworkOnly ? outputs[i] : extRename(outputs[i], ".png"); - overwrite_ = await renderer.generateThumbnail(songPath, thumbnailPath, _overwrite, artworkOnly); + const song = await readFile(songPath); + const tags = await readTags(song); + const { title, artist, album } = tags; + Logger.log(`${title}: ${artist} - ${album}`); + await renderer.generateThumbnail(songPath, thumbnailPath, overwrite, artworkOnly); } -const overwrite = _overwrite || overwrite_; await renderer.close(); @@ -42,7 +49,7 @@ const tempArtwork = await (async () => { return new Promise(async (r) => { var response = null; var _prompt_ = async () => { - var t = await prompt("Delete artwork after video is generated? (Y/N): "); + var t = await prompt(`Delete artwork after video${inputs.length > 1 ? "s" : ""} ${inputs.length > 1 ? "are" : "is"} generated? (Y/N): `); if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { response = t; } else { @@ -59,11 +66,15 @@ const tempArtwork = await (async () => { Logger.debug(`${tempArtwork}`); -for (let i = 0; i < inputs.length; i++) { - const songPath = inputs[i]; +for (let i: number = 0; i < inputs.length; i++) { + const songPath: string = inputs[i]; + if (termed.indexOf(songPath) >= 0) { + Logger.log(basename(songPath) + " skipped! Overwrite denied."); + continue; + } const thumbnailPath = extRename(outputs[i], ".png"); const videoPath = outputs[i]; - Logger.log("Generating video..."); + Logger.log(basename(songPath) + " | Generating video..."); Logger.debug(songPath); Logger.debug(thumbnailPath); Logger.debug(videoPath); @@ -81,7 +92,7 @@ for (let i = 0; i < inputs.length; i++) { -c:a aac \ -shortest \ "${videoPath}"\ - ${overwrite ? "-y" : "-n"}`); + ${allowed.indexOf(songPath) >= 0 ? "-y" : "-n"}`); if (tempArtwork) { Logger.debug("Removing " + thumbnailPath); rmSync(thumbnailPath); @@ -107,9 +118,11 @@ export async function prompt(prompt: string): Promise { })); } -export function term() { - Logger.critical("Ending process..."); - process.exit(0); +export function term(songPath: string) { + termed.push(songPath); +} +export function allow(songPath: string) { + allowed.push(songPath); } export function end() { Logger.log("Done!"); diff --git a/src/logger.ts b/src/logger.ts index 06444c3..6fba6cb 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -57,23 +57,38 @@ class Logger { return this.debugLevel > 0; } - public log(message: any) { - this.info(message); + public log(...messages: any) { + this.info(...messages); } - public info(message: any) { - console.log(this.formatMessage(message, 0)); + public info(...messages: any) { + for (var i in messages) { + var message = messages[i]; + console.log(this.formatMessage(message, 0)); + } } - public debug(message: any) { - if (this.debugEnabled()) console.log(this.formatMessage(message, 1)); + public debug(...messages: any) { + for (var i in messages) { + var message = messages[i]; + if (this.debugEnabled()) console.log(this.formatMessage(message, 1)); + } } - public warning(message: any) { - console.log(this.formatMessage(message, 2)); + public warning(...messages: any) { + for (var i in messages) { + var message = messages[i]; + console.log(this.formatMessage(message, 2)); + } } - public error(message: any) { - console.log(this.formatMessage(message, 3)); + public error(...messages: any) { + for (var i in messages) { + var message = messages[i]; + console.log(this.formatMessage(message, 3)); + } } - public critical(message: any) { - console.log(this.formatMessage(message, 4)); + public critical(...messages: any) { + for (var i in messages) { + var message = messages[i]; + console.log(this.formatMessage(message, 4)); + } } public debugLineBreak(count: number = 1) { diff --git a/src/thumbnail.ts b/src/thumbnail.ts index 7d376da..4968331 100644 --- a/src/thumbnail.ts +++ b/src/thumbnail.ts @@ -2,14 +2,14 @@ import { createServer } from "node:http"; import { launch } from "puppeteer-core"; import { getChromePath } from "chrome-launcher"; import { readFile, writeFile } from "node:fs/promises"; -import { resolve } from "node:path"; +import { basename, resolve } from "node:path"; import { overwrite } from "./args.js"; import { readTags } from "./jsmediatags.js"; import type { Server } from "node:http"; import type { Browser } from "puppeteer-core"; import type { MediaTags } from "./jsmediatags.js"; -import { Logger, prompt, term } from "./index.js"; +import { Logger, allow, prompt, term } from "./index.js"; import { existsSync } from "node:fs"; const SERVER_PATH = "http://localhost:3000"; @@ -42,7 +42,6 @@ async function generateSource(tags: MediaTags): Promise { const index = new URL("../index.html", import.meta.url); const source = await readFile(index, { encoding: "utf-8" }); const { title, artist, album, artwork } = tags; - Logger.log(`${title}: ${artist} - ${album}`); Logger.log("Generating thumbnail...\n"); return source .replaceAll("%TITLE%", title) @@ -72,16 +71,16 @@ class ThumbnailGenerator { return new Promise(async (r) => { var response = null; var _prompt_ = async () => { - var t = await prompt("Video file already exists! Would you like to overwrite? (Y/N): "); + var t = await prompt("File already exists! Would you like to overwrite? (Y/N): "); if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { response = t; + if (t.toUpperCase() == "N") Logger.lineBreak(); } else { Logger.warning('Invalid response! Answer with "Y" or "N"!\n'); await _prompt_(); } } await _prompt_(); - Logger.lineBreak(); Logger.debug(`${response}`); r(response == "Y"); }); @@ -90,6 +89,10 @@ class ThumbnailGenerator { overwrite = true; } return new Promise(async (_resolve) => { + if (!overwrite) { + term(songPath); + return _resolve(overwrite); + } const page = await this.#browser.newPage(); const renderPath = new URL(SERVER_PATH); renderPath.searchParams.set("songPath", encodeURIComponent(resolve(songPath))); @@ -100,13 +103,9 @@ class ThumbnailGenerator { const thumbnail = await page.screenshot(); Logger.debug(thumbnail); - if (!overwrite) { - term(); - _resolve(overwrite); - } else { - await writeFile(thumbnailPath, thumbnail); - _resolve(overwrite); - } + await writeFile(thumbnailPath, thumbnail); + allow(songPath); + _resolve(overwrite); }); } From 32180829c2388f9dec62a63a57661c94f5d937f8 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 05:59:08 -0700 Subject: [PATCH 10/14] fix puppeteer focus-loss & reform loops --- src/index.ts | 40 +++++++++++++++++++++++++++++++++++----- src/thumbnail.ts | 31 ++----------------------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/index.ts b/src/index.ts index 29b3435..abd4262 100755 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { exec as execCallback } from "node:child_process"; import { promisify } from "node:util"; import { inputs, artworkOnly, overwrite, debugMode } from "./args.js"; import { createRenderer } from "./thumbnail.js"; -import { rmSync } from "node:fs"; +import { existsSync, rmSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { defaultLoggerOpts, newLogger } from "./logger.js"; import chalk from "chalk"; @@ -29,15 +29,45 @@ const outputs = inputs.map(item => extRename(item, artworkOnly ? ".png" : ".mp4" const renderer = await createRenderer(); -for (let i = 0; i < inputs.length; i++) { +var cycleSongs = async function (i: number = 0) { const songPath = inputs[i]; const thumbnailPath = artworkOnly ? outputs[i] : extRename(outputs[i], ".png"); const song = await readFile(songPath); const tags = await readTags(song); const { title, artist, album } = tags; Logger.log(`${title}: ${artist} - ${album}`); - await renderer.generateThumbnail(songPath, thumbnailPath, overwrite, artworkOnly); + var _overwrite: boolean = false; + if ((existsSync(thumbnailPath) || (existsSync(songPath) && !artworkOnly)) && !overwrite) { + _overwrite = await (async () => { + return new Promise(async (r) => { + var response = null; + var _prompt_ = async () => { + var t = await prompt("File already exists! Would you like to overwrite? (Y/N): "); + if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { + response = t.toUpperCase(); + if (t.toUpperCase() == "N") { + Logger.log("Skipping file..."); + Logger.lineBreak(); + term(songPath); + } + } else { + Logger.warning('Invalid response! Answer with "Y" or "N"!\n'); + await _prompt_(); + } + } + await _prompt_(); + Logger.debug(`${response}`); + r(response == "Y"); + }); + })(); + } else { + _overwrite = true; + } + if (_overwrite || overwrite) await renderer.generateThumbnail(songPath, thumbnailPath, overwrite || _overwrite); + if (!!inputs[i + 1]) await cycleSongs(i + 1); } +await cycleSongs(); + await renderer.close(); @@ -112,8 +142,8 @@ export async function prompt(prompt: string): Promise { output: process.stdout }); - return new Promise(resolve => rl.question(prompt, ans => { - rl.close(); + return new Promise(resolve => rl.question(chalk.greenBright(prompt), ans => { + setTimeout(() => rl.close(), 10); //prevent rl hanging lines & freezing (Windows) resolve(ans); })); } diff --git a/src/thumbnail.ts b/src/thumbnail.ts index 4968331..9edcca6 100644 --- a/src/thumbnail.ts +++ b/src/thumbnail.ts @@ -52,7 +52,7 @@ async function generateSource(tags: MediaTags): Promise { async function launchBrowser(): Promise { const executablePath = getChromePath(); - return launch({ headless: "new", executablePath }); + return launch({ headless: true, executablePath }); } class ThumbnailGenerator { @@ -64,35 +64,8 @@ class ThumbnailGenerator { this.#browser = browser; } - async generateThumbnail(songPath: string, thumbnailPath: string, _overwrite: boolean, artworkOnly: boolean): Promise { - var overwrite: boolean = false; - if ((existsSync(thumbnailPath) || (existsSync(songPath) && !artworkOnly)) && !_overwrite) { - overwrite = await (async () => { - return new Promise(async (r) => { - var response = null; - var _prompt_ = async () => { - var t = await prompt("File already exists! Would you like to overwrite? (Y/N): "); - if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { - response = t; - if (t.toUpperCase() == "N") Logger.lineBreak(); - } else { - Logger.warning('Invalid response! Answer with "Y" or "N"!\n'); - await _prompt_(); - } - } - await _prompt_(); - Logger.debug(`${response}`); - r(response == "Y"); - }); - })(); - } else { - overwrite = true; - } + async generateThumbnail(songPath: string, thumbnailPath: string, overwrite: boolean): Promise { return new Promise(async (_resolve) => { - if (!overwrite) { - term(songPath); - return _resolve(overwrite); - } const page = await this.#browser.newPage(); const renderPath = new URL(SERVER_PATH); renderPath.searchParams.set("songPath", encodeURIComponent(resolve(songPath))); From ad895e0a81bb64b3e82c3c50d96a9b9f05a94438 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 08:36:37 -0700 Subject: [PATCH 11/14] autoDirLoading+recursiveArg, threading --- src/args.ts | 14 +++- src/index.ts | 188 +++++++++++++++++++++++++++++++++++++++-------- src/thumbnail.ts | 14 ++-- src/timing.ts | 31 ++++++++ 4 files changed, 211 insertions(+), 36 deletions(-) create mode 100644 src/timing.ts diff --git a/src/args.ts b/src/args.ts index eff8a49..0e9caac 100644 --- a/src/args.ts +++ b/src/args.ts @@ -2,6 +2,8 @@ const COMMAND_PATTERN = /^--|-/; const ARTWORK_ONLY_PATTERN = /^--artwork-only|-a$/; const OVERWRITE_PATTERN = /^--overwrite|-w$/; const DEBUG_PATTERN = /^--debug|-d$/; +const THREADED_PATTERN = /^--thread|t$/; +const RECURSIVE_PATTERN = /^--recursive|r$/; const args = process.argv.slice(2); const commands: string[] = []; @@ -19,6 +21,14 @@ for (const item of args) { commands.push(item); break; } + case THREADED_PATTERN.test(item): { + commands.push(item); + break; + } + case RECURSIVE_PATTERN.test(item): { + commands.push(item); + break; + } case COMMAND_PATTERN.test(item): { throw new Error(`Unexpected command '${item}'`); } @@ -34,4 +44,6 @@ if (inputs.length === 0) { export const artworkOnly: boolean = commands.some(item => ARTWORK_ONLY_PATTERN.test(item)); export const overwrite: boolean = commands.some(item => OVERWRITE_PATTERN.test(item)); -export const debugMode: boolean = commands.some(item => DEBUG_PATTERN.test(item)); \ No newline at end of file +export const debugMode: boolean = commands.some(item => DEBUG_PATTERN.test(item)); +export const threaded: boolean = commands.some(item => THREADED_PATTERN.test(item)); +export const recursive: boolean = commands.some(item => RECURSIVE_PATTERN.test(item)); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index abd4262..7ec9f84 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,18 @@ #!/usr/bin/env npx tsx import { createInterface } from "node:readline"; -import { extname, basename } from "node:path"; +import { extname, basename, join, dirname } from "node:path"; import { exec as execCallback } from "node:child_process"; import { promisify } from "node:util"; -import { inputs, artworkOnly, overwrite, debugMode } from "./args.js"; +import { inputs, artworkOnly, overwrite, recursive, debugMode, threaded } from "./args.js"; import { createRenderer } from "./thumbnail.js"; -import { existsSync, rmSync } from "node:fs"; -import { readFile } from "node:fs/promises"; +import { existsSync, readdirSync, rmSync, statSync } from "node:fs"; +import { readFile, rm } from "node:fs/promises"; import { defaultLoggerOpts, newLogger } from "./logger.js"; import chalk from "chalk"; import { readTags } from "./jsmediatags.js"; +import { cpus, totalmem } from "node:os"; +import { Timer as _T } from "./timing.js"; const exec = promisify(execCallback); @@ -19,28 +21,81 @@ export const Logger = newLogger(defaultLoggerOpts(debugMode)); const termed: string[] = []; const allowed: string[] = []; +const threadQueue: string[] = []; +const queueAddList: string[] = []; + +const maxThreads = Math.max(Math.floor((cpus().length - 2) * ((totalmem() / 1e+9) / 24)), 1); +if (threaded) Logger.debug("[threaded] MAX=" + maxThreads); + +const Timer = new _T(); +Timer.start(); + +let queueCallback: Function | null; +type DynamicFuncObj = { + [key: string]: Function +} +type DynamicArrayObj = { + [key: string]: Array +} +const queueCallbackList: DynamicFuncObj = {} + Logger.log(chalk.bold("Art Gen")); Logger.log("-- An app to generate thumbnails for YouTube Art Tracks! --\n"); if (artworkOnly) Logger.debug("[artwork only]"); if (overwrite) Logger.debug("[overwrite]"); +const loadedFolders: DynamicArrayObj = {} +var cycleFolders = async function (i: number = 0) { + try { + var recurs = async (dir: string = inputs[i], splice: boolean = true) => { + if (statSync(dir).isDirectory()) { + Logger.info(`${splice ? "F" : "Subf"}older detected ${dir}`); + loadedFolders[dir] = []; + if (splice) inputs.splice(i, 1); + var rdir = readdirSync(dir, { recursive }); + rdir.forEach((file) => { + var fname = file.toString(); + if (statSync(join(dir, fname)).isDirectory() && recursive) recurs(join(dir, fname), false); + if (extname(fname) == '.mp3' && inputs.indexOf(join(dir, fname)) < 0) { + inputs.push(join(dir, fname)); + Logger.debug(`${fname} found in ${dir}`); + loadedFolders[dir].push(fname); + } + }); + } + } + recurs(); + } catch (e) { } + if (!!inputs[i + 1]) await cycleFolders(i + 1); +} +await cycleFolders(); + +for (const key in loadedFolders) { + Logger.info(`Found: ${loadedFolders[key].join(", ")} in ${key}`); +} + +Logger.debug(inputs); + const outputs = inputs.map(item => extRename(item, artworkOnly ? ".png" : ".mp4")); const renderer = await createRenderer(); +if (threaded) Logger.log(`Generating thumbnails...\n`); var cycleSongs = async function (i: number = 0) { const songPath = inputs[i]; const thumbnailPath = artworkOnly ? outputs[i] : extRename(outputs[i], ".png"); const song = await readFile(songPath); const tags = await readTags(song); const { title, artist, album } = tags; - Logger.log(`${title}: ${artist} - ${album}`); + if (!threaded) Logger.log(`${title}: ${artist} - ${album}`); var _overwrite: boolean = false; - if ((existsSync(thumbnailPath) || (existsSync(songPath) && !artworkOnly)) && !overwrite) { + if ((existsSync(thumbnailPath) || (existsSync(outputs[i]) && !artworkOnly)) && !overwrite) { _overwrite = await (async () => { + Timer.stop(); return new Promise(async (r) => { var response = null; + if (threaded) Logger.log(`${title}: ${artist} - ${album}`); var _prompt_ = async () => { var t = await prompt("File already exists! Would you like to overwrite? (Y/N): "); if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { @@ -56,6 +111,7 @@ var cycleSongs = async function (i: number = 0) { } } await _prompt_(); + if (threaded) Logger.lineBreak(); Logger.debug(`${response}`); r(response == "Y"); }); @@ -63,25 +119,32 @@ var cycleSongs = async function (i: number = 0) { } else { _overwrite = true; } - if (_overwrite || overwrite) await renderer.generateThumbnail(songPath, thumbnailPath, overwrite || _overwrite); + Timer.start(); + if (!threaded) { + if (_overwrite || overwrite) await renderer.generateThumbnail(songPath, thumbnailPath, overwrite || _overwrite, threaded); + } else { + await addToQueue(songPath); + if (_overwrite || overwrite) renderer.generateThumbnail(songPath, thumbnailPath, overwrite || _overwrite, threaded); + } if (!!inputs[i + 1]) await cycleSongs(i + 1); } await cycleSongs(); +await waitForQueue(); await renderer.close(); if (artworkOnly) end(); - Logger.debugLineBreak(); -const tempArtwork = await (async () => { +const tempArtwork: boolean = await (async () => { + Timer.stop(); return new Promise(async (r) => { var response = null; var _prompt_ = async () => { var t = await prompt(`Delete artwork after video${inputs.length > 1 ? "s" : ""} ${inputs.length > 1 ? "are" : "is"} generated? (Y/N): `); if (t.toUpperCase() == "Y" || t.toUpperCase() == "N") { - response = t; + response = t.toUpperCase(); } else { Logger.warning('Invalid response! Answer with "Y" or "N"!\n'); await _prompt_(); @@ -93,6 +156,7 @@ const tempArtwork = await (async () => { r(response == "Y"); }); })(); +Timer.start(); Logger.debug(`${tempArtwork}`); @@ -104,31 +168,64 @@ for (let i: number = 0; i < inputs.length; i++) { } const thumbnailPath = extRename(outputs[i], ".png"); const videoPath = outputs[i]; - Logger.log(basename(songPath) + " | Generating video..."); Logger.debug(songPath); Logger.debug(thumbnailPath); Logger.debug(videoPath); - await exec(`ffmpeg \ - -loop 1 \ - -framerate 1 \ - -i "${thumbnailPath}" \ - -i "${songPath}" \ - -map 0 \ - -map 1:a \ - -c:v libx264 \ - -preset ultrafast \ - -tune stillimage \ - -vf "scale=out_color_matrix=bt709,fps=10,format=yuv420p" \ - -c:a aac \ - -shortest \ - "${videoPath}"\ + if (!threaded) { + Logger.log(basename(songPath) + " | Generating video..."); + await exec(`ffmpeg \ + -loop 1 \ + -framerate 1 \ + -i "${thumbnailPath}" \ + -i "${songPath}" \ + -map 0 \ + -map 1:a \ + -preset ultrafast \ + -c:v libx264 \ + -vf "scale=out_color_matrix=bt709,fps=10,format=yuv420p" \ + -tune stillimage \ + -shortest \ + -c:a aac \ + "${videoPath}"\ ${allowed.indexOf(songPath) >= 0 ? "-y" : "-n"}`); - if (tempArtwork) { - Logger.debug("Removing " + thumbnailPath); - rmSync(thumbnailPath); + if (tempArtwork) { + Logger.debug("Removing " + thumbnailPath); + rmSync(thumbnailPath); + } + } else { + await addToQueue(songPath); + (async (): Promise => { + Logger.log(basename(songPath) + " | Generating video..."); + return new Promise(async (r) => { + await exec(`ffmpeg \ + -loop 1 \ + -framerate 1 \ + -i "${thumbnailPath}" \ + -i "${songPath}" \ + -map 0 \ + -map 1:a \ + -c:v libx264 \ + -preset ultrafast \ + -tune stillimage \ + -vf "scale=out_color_matrix=bt709,fps=10,format=yuv420p" \ + -c:a aac \ + -shortest \ + "${videoPath}"\ + ${allowed.indexOf(songPath) >= 0 ? "-y" : "-n"}`); + Logger.debug(tempArtwork); + if (tempArtwork) { + Logger.debug("Removing " + thumbnailPath); + await rm(thumbnailPath); + } + queueFinish(songPath); + r(); + }); + })(); } } +await waitForQueue(); + end(); function extRename(path: string, ext: string): string { @@ -143,7 +240,7 @@ export async function prompt(prompt: string): Promise { }); return new Promise(resolve => rl.question(chalk.greenBright(prompt), ans => { - setTimeout(() => rl.close(), 10); //prevent rl hanging lines & freezing (Windows) + rl.close(); resolve(ans); })); } @@ -155,7 +252,38 @@ export function allow(songPath: string) { allowed.push(songPath); } export function end() { - Logger.log("Done!"); + Timer.stop(); + Logger.log(`Done! ${(Timer.getTime() / 1000).toLocaleString(undefined, { minimumFractionDigits: 3 })}s elapsed`); Logger.critical("Exiting process..."); + Timer.reset(); process.exit(0); +} +export async function waitForQueue(): Promise { + if (threadQueue.length < 1) return new Promise(r => r()); + return new Promise(r => { + queueCallback = r; + }); +} +export function queueFinish(songPath: string) { + threadQueue.splice(threadQueue.indexOf(songPath), 1); + if (queueAddList.length > 0 && threadQueue.length < maxThreads) { + var add = queueAddList.shift()!; + threadQueue.push(add); + queueCallbackList[add](); + delete queueCallbackList[add]; + } + if (threadQueue.length < 1 && !!queueCallback) { + queueCallback(); + queueCallback = null; + } +} +export async function addToQueue(songPath: string): Promise { + if (threadQueue.length < maxThreads) { + threadQueue.push(songPath); + return new Promise(r => r()); + } + queueAddList.push(songPath); + return new Promise(r => { + queueCallbackList[songPath] = r; + }); } \ No newline at end of file diff --git a/src/thumbnail.ts b/src/thumbnail.ts index 9edcca6..ee8426f 100644 --- a/src/thumbnail.ts +++ b/src/thumbnail.ts @@ -9,7 +9,7 @@ import { readTags } from "./jsmediatags.js"; import type { Server } from "node:http"; import type { Browser } from "puppeteer-core"; import type { MediaTags } from "./jsmediatags.js"; -import { Logger, allow, prompt, term } from "./index.js"; +import { Logger, allow, prompt, queueFinish, term } from "./index.js"; import { existsSync } from "node:fs"; const SERVER_PATH = "http://localhost:3000"; @@ -26,10 +26,11 @@ async function startServer(): Promise { Logger.debug(url.toString()); if (url.searchParams.size === 0) return new Promise(resolve => response.end(resolve)); const songPath = decodeURIComponent(url.searchParams.get("songPath")!); + const threaded = decodeURIComponent(url.searchParams.get("threaded")!) == "true"; Logger.debug(`${songPath}\n`); const song = await readFile(songPath); const tags = await readTags(song); - const source = await generateSource(tags); + const source = await generateSource(tags, threaded); response.writeHead(200, { "Content-Type": "text/html" }); response.write(source); await new Promise(resolve => response.end(resolve)); @@ -38,11 +39,11 @@ async function startServer(): Promise { return server; } -async function generateSource(tags: MediaTags): Promise { +async function generateSource(tags: MediaTags, threaded: boolean): Promise { const index = new URL("../index.html", import.meta.url); const source = await readFile(index, { encoding: "utf-8" }); const { title, artist, album, artwork } = tags; - Logger.log("Generating thumbnail...\n"); + if (!threaded) Logger.log(`${threaded ? title + " | " : ""}Generating thumbnail...\n`); return source .replaceAll("%TITLE%", title) .replaceAll("%ARTIST%", artist) @@ -64,11 +65,13 @@ class ThumbnailGenerator { this.#browser = browser; } - async generateThumbnail(songPath: string, thumbnailPath: string, overwrite: boolean): Promise { + async generateThumbnail(songPath: string, thumbnailPath: string, overwrite: boolean, threaded: boolean): Promise { + Logger.debug(songPath); return new Promise(async (_resolve) => { const page = await this.#browser.newPage(); const renderPath = new URL(SERVER_PATH); renderPath.searchParams.set("songPath", encodeURIComponent(resolve(songPath))); + renderPath.searchParams.set("threaded", encodeURIComponent(threaded)); await page.goto(renderPath.toString(), { waitUntil: "networkidle0" }); await page.setViewport({ width: 1920, height: 1080 }); @@ -78,6 +81,7 @@ class ThumbnailGenerator { await writeFile(thumbnailPath, thumbnail); allow(songPath); + queueFinish(songPath); _resolve(overwrite); }); } diff --git a/src/timing.ts b/src/timing.ts new file mode 100644 index 0000000..d32998f --- /dev/null +++ b/src/timing.ts @@ -0,0 +1,31 @@ +import { Logger } from "."; + +export class Timer { + + private addedTime: number = 0; + private _start: number = 0; + + public start() { + if (this._start > 0) return; + this._start = Date.now(); + } + + public stop() { + this.addedTime += Date.now() - this._start; + this._start = 0; + } + + public resume() { + this.start(); + } + + public reset() { + this.stop() + this.addedTime = 0; + } + + public getTime(): number { + return this.addedTime; + } + +} \ No newline at end of file From 69beb1277dfbb9e6e320bb05aa3958d14234b260 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 08:38:37 -0700 Subject: [PATCH 12/14] alter logging level to prevent filling terminal --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 7ec9f84..3bb8f1b 100755 --- a/src/index.ts +++ b/src/index.ts @@ -50,7 +50,7 @@ var cycleFolders = async function (i: number = 0) { try { var recurs = async (dir: string = inputs[i], splice: boolean = true) => { if (statSync(dir).isDirectory()) { - Logger.info(`${splice ? "F" : "Subf"}older detected ${dir}`); + Logger.debug(`${splice ? "F" : "Subf"}older detected ${dir}`); loadedFolders[dir] = []; if (splice) inputs.splice(i, 1); var rdir = readdirSync(dir, { recursive }); From 02be7684bf8b931f44093cbf87c0d921b75260f8 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 08:40:44 -0700 Subject: [PATCH 13/14] fix for found blank in folder log --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 3bb8f1b..7d2fea9 100755 --- a/src/index.ts +++ b/src/index.ts @@ -72,7 +72,7 @@ var cycleFolders = async function (i: number = 0) { await cycleFolders(); for (const key in loadedFolders) { - Logger.info(`Found: ${loadedFolders[key].join(", ")} in ${key}`); + if (loadedFolders[key].length > 0) Logger.info(`Found: ${loadedFolders[key].join(", ")} in ${key}`); } Logger.debug(inputs); From 678a232c9725ac6d6ba80dc1f669a00890c8f5b4 Mon Sep 17 00:00:00 2001 From: xJustJqy Date: Mon, 31 Jul 2023 08:51:36 -0700 Subject: [PATCH 14/14] added color to folder scanning --- src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 7d2fea9..2aa28d4 100755 --- a/src/index.ts +++ b/src/index.ts @@ -72,7 +72,11 @@ var cycleFolders = async function (i: number = 0) { await cycleFolders(); for (const key in loadedFolders) { - if (loadedFolders[key].length > 0) Logger.info(`Found: ${loadedFolders[key].join(", ")} in ${key}`); + var coloredNames = loadedFolders[key]; + coloredNames.forEach((n, i) => { + coloredNames[i] = chalk.greenBright(n); + }); + if (loadedFolders[key].length > 0) Logger.info(`Found: ${coloredNames.join(", ")} in ${chalk.hex("#F30BE5")(key)}`); } Logger.debug(inputs);