From acd46b3d1d1438b0f39cc1322aad82803c84739c Mon Sep 17 00:00:00 2001 From: betty Date: Tue, 20 Feb 2024 16:07:19 +0800 Subject: [PATCH] ready clean --- README.md | 3 + utils/generateWaveform.js | 259 ++++---------------------------------- 2 files changed, 25 insertions(+), 237 deletions(-) diff --git a/README.md b/README.md index be6b942fd..194674e06 100644 --- a/README.md +++ b/README.md @@ -115,3 +115,6 @@ export NODE_OPTIONS="--max-old-space-size=8192" pnpm install node initDB.js jsons/exportDataEmpty.json ``` +### Waveform generation +We use [audiowaveform](https://github.com/bbc/audiowaveform/tree/master?fbclid=IwAR19jDCDp5DCzdLK7Z8EgE7W5NSjm8B-wdFABsrT62D2b80bVtCuydkMgnM) to generate waveform. Refer to [Installation](https://github.com/bbc/audiowaveform/blob/master/README.md#installation) section to install the package first. + diff --git a/utils/generateWaveform.js b/utils/generateWaveform.js index 0797f1559..81a8d9f0f 100644 --- a/utils/generateWaveform.js +++ b/utils/generateWaveform.js @@ -1,60 +1,15 @@ -const fs = require("fs"); -const process = require("process"); const path = require("path"); -const axios = require("axios"); const child_process = require("child_process"); - -const sharp = require("sharp"); -const jm = require("join-images"); const readline = require("readline"); -const { getAudioDurationInSeconds } = require("get-audio-duration"); - -// the annotation part can be used when the file is a module - -// import { createWriteStream, existsSync, unlink } from "fs"; -// import { platform } from "process"; -// import { join, dirname, basename } from "path"; -// import path from 'path'; -// import axios from "axios"; -// import { fileURLToPath } from 'url'; -// import { execSync } from 'child_process'; -// const url = require("url"); -// const __filename = url.fileURLToPath(import.meta.url); -// const __dirname = path.dirname(__filename); -// const __dirname = path.dirname(__filename); -// procedure: check existance->download->construct command->generate waveform - -// const img_res = '4000x1000'; -let img_milisec_per_pin = 4; -let img_res_width = 40000; -let img_res_height = 400; -let img_res = String(img_res_width) + "x" + String(img_res_height); -let music_duration = 420000; -// console.log(`img_res = ${img_res}`); -const hex_colo = "3D82B1"; -const sname = "../files/music/waveform.png"; -const sname_tmp = "../files/music/waveform_tmp.png"; +const sname = "../files/data/wavform.json"; const musicName = "../files/music/2023.mp3"; // you can alter the music name here let ifp = path.join(String(__dirname), sname); // name (path included) to the waveform image -let ifp_temp = path.join(String(__dirname), sname_tmp); // name (path included) to the waveform image let sfp = path.join(String(__dirname), musicName); // name (path included) to the music -const leftName = "../files/music/leftWaveform.png"; -const rightName = "../files/music/rightWaveform.png"; -const lcp = path.join(String(__dirname), leftName); -const rcp = path.join(String(__dirname), rightName); -let releaseUrl = ""; // url for downloading ffmpeg -let ffbin = ""; // the path name(file name included) of the ffmpeg binary let cmd = []; // the command that will later be used to generate waveform - -const readLength = async () => { - await getAudioDurationInSeconds(sfp).then((duration) => { - music_duration = duration * 1000; - console.log(`duration = ${music_duration} miliseconds`); - }); -}; +let pixelsPerSecond = 1000; const rl = readline.createInterface({ @@ -65,13 +20,17 @@ const rl = readline.createInterface({ const readIntegerInput = (prompt) => { return new Promise((resolve, reject) => { rl.question(prompt, (input) => { + const parsedInput = parseInt(input, 10); if (!isNaN(parsedInput)) { resolve(parsedInput); } else { - console.log("Invalid input, try again..."); - resolve(readIntegerInput(prompt)); - // reject(new Error('Invalid input. Please enter a valid integer.')); + if (input == "") { + resolve(1000); + } else { + console.log("Invalid input, try again..."); + resolve(readIntegerInput(prompt)); + } } }); }); @@ -80,12 +39,10 @@ const readIntegerInput = (prompt) => { const readInput = async () => { try { - img_milisec_per_pin = await readIntegerInput("Enter time duration(miliseconds) in one pixel: "); - img_res_width = Math.round(music_duration / img_milisec_per_pin); - // img_res_width = await readIntegerInput('Enter the width of the waveform: '); - // img_res_height = await readIntegerInput('Enter the height of the waveform: '); - img_res = img_res_width + "x" + img_res_height; - console.log(`Waveform resolution: ${img_res}`); + pixelsPerSecond = await readIntegerInput("Enter pixels-per-second(1000): "); + if (!(typeof pixelsPerSecond === 'number' && isFinite(pixelsPerSecond))) { + pixelsPerSecond = 1000; + } } catch (error) { console.log("something wrong in the input..."); console.error(error.message); @@ -94,16 +51,13 @@ const readInput = async () => { } }; +const generateWav = () => { -const callFFMPEG = () => { - cmd = ["sudo", "ffmpeg"]; - cmd = [...cmd, "-i", String(sfp), - "-hide_banner", "-loglevel", "error", "-filter_complex", - `showwavespic=s=${img_res}:split_channels=1:colors=${hex_colo}`, - "-frames:v", "1", "-y", String(ifp_temp)]; + cmd = ["sudo", "audiowaveform"]; + cmd = [...cmd, "-i", String(sfp), "-o", String(ifp), "--pixels-per-second", String(pixelsPerSecond)]; let cmdString = cmd.join(" "); - console.log(`cmd = ${cmd.join(" ")}`); + console.log(`cmd = ${cmdString}`); let startGenerationTime = Date.now(); console.log("start generating"); try { @@ -116,178 +70,9 @@ const callFFMPEG = () => { console.log(`Time taken for generating waveform: ${elapsedTime} ms`); }; -// download from url and save as dest, then generate waveform -const dl_url = async (url, dest, ffmpegExistance) => { - await readInput(img_res_width, img_res_height); - if (!ffmpegExistance) { - let start_time = Date.now(); - console.log("downloading ffmpeg..."); - await axios({ - method: "get", - url: url, - responseType: "stream", - // httpsAgent: { - // rejectUnauthorized: false, // Disable SSL verification - // }, - }).then((response) => { - const writer = fs.createWriteStream(dest); - response.data.pipe(writer); - // console.log("here1"); - return new Promise((resolve, reject) => { - let error = null; - writer.on("error", err => { - error = err; - writer.close(); - reject(err); - }); - // or writer.on('finish')? - writer.on("close", () => { - if (!error) { - resolve(true); - } - //no need to call the reject here, as it will have been called in the - //'error' stream; - }); - - }); - - }).then(() => { - const downloadTime = (Date.now() - start_time) / 1000; // Convert milliseconds to seconds - console.log(`Download time ${downloadTime.toFixed(2)}s`); - callFFMPEG(); - }).catch(function(error) { - console.error("Error downloading file:", error.message); - }); - } else { - callFFMPEG(); - } -}; - -// unzip file (not used in this file but it exists in the original blender addon) -const unzip = (zipPath, extractDirPath) => { - const zip = new AdmZip(zipPath); - - try { - zip.extractAllTo(extractDirPath, /*overwrite*/ true); - console.log(`Successfully extracted: ${zipPath} to ${extractDirPath}`); - } catch (error) { - console.error("Error extracting zip file:", error.message); - } -}; - -// check if ffmpeg exists -const ffmpegExist = async () => { - // decide releaseUrl based on operating system - if (process.platform.startsWith("win")) { - releaseUrl = "https://github.com/Pullusb/static_bin/raw/main/ffmpeg/windows/ffmpeg.exe"; - } else if (process.platform.startsWith("linux") || process.platform === "freebsd") { - // console.log("linux"); - releaseUrl = "https://github.com/Pullusb/static_bin/raw/main/ffmpeg/linux/ffmpeg"; - } else { // Mac - releaseUrl = "https://github.com/Pullusb/static_bin/raw/main/ffmpeg/mac/ffmpeg"; - } - - // Check if ffmpeg is already in the current path - ffbin = path.join(String(__dirname), String(path.basename(releaseUrl))); - // console.log(ffbin); - // console.log(fs.existsSync(ffbin)); - return fs.existsSync(ffbin); -}; - -const cutImages = async () => { - await sharp(String(ifp_temp)) - .extract({ left: 0, top: 0, width: img_res_width, height: img_res_height / 4 }) - .toFile(String(lcp)) - .then(() => { - // console.log("here2.5") - }) - .catch((err) => { - // console.log("here2.5"); - if (err) { - console.log("Failed extracting upper half of the waveform..."); - console.log(err); - } - }); - // console.log("here3"); - await sharp(String(ifp_temp)) - .extract({ left: 0, top: 3 * img_res_height / 4, width: img_res_width, height: img_res_height / 4 }) - .toFile(String(rcp)) - .then(() => { - // console.log("here3.5") - }) - .catch((err) => { - // console.log("here3.5"); - if (err) { - console.log("Failed extracting lower half of the waveform..."); - console.log(err); - } - }); - // console.log("here4"); -}; - -const AsyncJoinImages = async () => { - console.log("joining waveforms..."); - await jm.joinImages([String(lcp), String(rcp)], { direction: "vertical" }) - .then((img) => { - // Save image as file - img.toFile(String(ifp)) - .then(() => { console.log("image saved"); }) - .catch((err) => { - console.log("failed to save image as file"); - console.log(err); - }); - - }).catch((err) => { - console.log("Failed joining waveforms..."); - console.log(err); - }); -}; - -const AsyncImageManipulation = async () => { - await cutImages(); - await AsyncJoinImages(); -}; - -const errReport = (err) => { - if (err) throw err; -}; - -const rmRedundantWaveform = () => { - let rmCmd = ["rm", String(lcp), String(rcp), String(ifp_temp)]; - rmCmdString = rmCmd.join(" "); - // console.log(`rmCmdString = ${rmCmdString}`); - console.log("removing redundant waveforms..."); - try { - fs.unlink(String(lcp), errReport); - fs.unlink(String(rcp), errReport); - fs.unlink(String(ifp_temp), errReport); - } catch (error) { - console.error("Error deleting existing file:", error.message); - } -}; - -const mainGenerate = async (releaseUrl, ffbin) => { - let ffmpegExistance = await ffmpegExist(); // ffbin, releaseUrl is set in ffmpegExist - await readLength(); - await dl_url(releaseUrl, String(ffbin), ffmpegExistance); - await AsyncImageManipulation(); - rmRedundantWaveform(); -}; - - -mainGenerate(releaseUrl, ffbin); - -// download if ffmpeg not in path - -// delete ffmpeg if exists -// if (ffmpegExistance) { -// try { -// fs.unlink(ffbin, (err) => { -// if (err) throw err; -// console.log(`successfully deleted ${ffbin}`); -// }); +const mainGenerate = async () => { + await readInput(); + generateWav(); +} -// } catch (error) { -// console.error('Error deleting existing file:', error.message); -// } -// } +mainGenerate(); \ No newline at end of file