import fs from 'fs/promises' import path from 'path' import { exec } from 'child_process' // WAVEFORM GENERATION // TODO@mx use node-js library async function generateWaveform(audioFile, outputDir) { const inputFile = path.join(outputDir, audioFile) audioFile = audioFile.replace(/\.[^/.]+$/, '.png') const outputPath = path.join(outputDir || '.', audioFile) return new Promise((resolve, reject) => { exec( `audiowaveform -i '${inputFile}' -o '${outputPath}' --output-format png -z auto -w 640 -h 180 --background-color 00000000 --waveform-color FFFFFF50 --waveform-style bars --bar-style rounded --border-color 00000000 --bar-width 4 --bar-gap 6 --no-axis-labels`, (error, stdOut) => { if (error) { console.error(error) reject(error) } else { resolve(audioFile) } }) }) } // MAIN FUNCTION async function main() { // variable for incoming variable let mediaDir = '' let outputDir = '' let recurse = false let imagesOnly = false let dryRun = false let formatted = null let waveform = false let update = [] // get command line arguments process.argv.forEach(function (val, index) { // console.log(index + ': ' + val) switch (val) { case '-dir': mediaDir = process.argv[index + 1] || '' break case '-o': outputDir = process.argv[index + 1] || '' break case '-u': update = process.argv[index + 1].match(/(title|details|media|image[s]*|tracks|)/i) ? process.argv[index + 1] .split(',') .filter(a => [ 'title', 'details', 'media', 'image', 'tracks', 'images' ].includes(a)) : true break case '-r': recurse = true break case '--images-only': imagesOnly = true break case '--dry-run': dryRun = true break case '--formatted': formatted = ['parent', 'recurse', 'both' ].includes(process.argv[index + 1]) ? process.argv[index + 1] : 'both' break case '--gen-waveform': waveform = true break default: break } }) // if no media dir passed jump out if (!mediaDir) { console.log('no directory passed to script') return 0 } // // read directory const dirList = await fs.readdir(mediaDir) // variable to hold media let media = [] let images = [] // If recursing we are making the "media" the directories where we will find the // acutal media to playback or see if (recurse) { media = dirList.filter(i => !i.match(/\.[^/.]+$/)) } else { media = dirList.filter(i => i.match(/.(mp\d|m\d\w)$/i)) } // create an array of images found in the directory as well images = dirList.filter(i => i.match(/.(jp\w*g|png)$/i)) // clean up passed directory for json let parent_dir = mediaDir .match(/(?<=\/)(\w|\d)+\/*$/i)[0] .replace(/\/$/i, '') // set an output directory if not set by flags if (!outputDir) { outputDir = '.' } // THE MAIN EVENT const obj = await Promise.all(media.map(async m => { // setup structure const _r = { ...genMetadata(m, formatted == 'parent' || formatted == 'both'), parent_dir, media: m, image: '' } // if we are scanning for images only, remove the 'image' property // as it will be saved in the media property if (imagesOnly) { delete _r.image } else { if (waveform) { // if waveform generate waveform _r.image = await generateWaveform(m, mediaDir) } else { // if not compare the media name with the image name // and if a match assign it to 'image' variable from earlier _r.image = linkImage(m.replace(/\.[^/.]+$/, ''), images) } } // if recurse (again) now we are looking for the media in the folders if (recurse) { // create full path from the passed dir, and the recuring dir const rDir = path.join(mediaDir, m) // read the dir let files = await fs.readdir(rDir) // filterout any rogue folders // generate the metadata for each file files = files.filter(f => f.match(/\.[^/.]+$/)) files = files.map(f => { const { title, details } = genMetadata(f, formatted == 'recurse' || formatted == 'both') return { title, details, media: f } }) // if images only if (imagesOnly) { // setup object per image in array _r.images = files } else { _r.album = true _r.tracks = files } } // return the result to the 'obj' variable return _r })) // turn the obj variable into JSON const json = JSON.stringify(obj, null, 2) // set the output filename let outputFile = `${parent_dir}.json` // check if file already exists at location // const dataFile = await fs.readFile(path.join(outputDir, outputFile), 'utf8') // console.log(JSON.parse(dataFile) || 'no data file exists') // update only new fields // if dryrun if (dryRun) { // just console it out console.log(json) } else { // write it to a file fs.writeFile(path.join(outputDir, outputFile), json) } } function genMetadata(dir, formatted) { let title, details // if formatted flag set if (formatted) { // split the incoming name from the map on the '_' // and set the first element to title, and the second to details let [ t, d ] = dir.split('_') // assign to return object title = t details = d ? d.replace(/\.+(mp\d|m\dv)$/i, '') : '' } else { // else just set title to filename without extensions title = dir.replace(/\.[^/.]+$/, '') } return { title, details } } function linkImage(name, images) { return images.find(i => { return i.replace(/\.[^/.]+$/, '') == name }) } main()