2023-10-09 17:43:50 +00:00
|
|
|
import fs from 'fs/promises'
|
|
|
|
import path from 'path'
|
2023-10-11 16:06:52 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2023-10-09 17:43:50 +00:00
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// MAIN FUNCTION
|
2023-10-09 17:43:50 +00:00
|
|
|
async function main() {
|
|
|
|
// variable for incoming variable
|
|
|
|
let mediaDir = ''
|
|
|
|
let outputDir = ''
|
|
|
|
let recurse = false
|
2023-10-10 11:04:35 +00:00
|
|
|
let imagesOnly = false
|
|
|
|
let dryRun = false
|
2023-10-11 16:06:52 +00:00
|
|
|
let formatted = null
|
|
|
|
let waveform = false
|
|
|
|
let update = []
|
2023-10-09 17:43:50 +00:00
|
|
|
|
|
|
|
// get command line arguments
|
|
|
|
process.argv.forEach(function (val, index) {
|
|
|
|
// console.log(index + ': ' + val)
|
2023-10-11 16:06:52 +00:00
|
|
|
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
|
2023-10-09 17:43:50 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// if no media dir passed jump out
|
2023-10-09 17:43:50 +00:00
|
|
|
if (!mediaDir) {
|
|
|
|
console.log('no directory passed to script')
|
|
|
|
return 0
|
|
|
|
}
|
2023-10-11 16:06:52 +00:00
|
|
|
//
|
|
|
|
// read directory
|
|
|
|
const dirList = await fs.readdir(mediaDir)
|
2023-10-09 17:43:50 +00:00
|
|
|
|
2023-10-10 11:04:35 +00:00
|
|
|
// variable to hold media
|
|
|
|
let media = []
|
|
|
|
let images = []
|
|
|
|
|
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// 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 {
|
2023-10-10 11:04:35 +00:00
|
|
|
media = dirList.filter(i => i.match(/.(mp\d|m\d\w)$/i))
|
|
|
|
}
|
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// create an array of images found in the directory as well
|
|
|
|
images = dirList.filter(i => i.match(/.(jp\w*g|png)$/i))
|
2023-10-09 17:43:50 +00:00
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// clean up passed directory for json
|
2023-10-09 17:43:50 +00:00
|
|
|
let parent_dir = mediaDir
|
|
|
|
.match(/(?<=\/)(\w|\d)+\/*$/i)[0]
|
|
|
|
.replace(/\/$/i, '')
|
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// set an output directory if not set by flags
|
2023-10-09 17:43:50 +00:00
|
|
|
if (!outputDir) {
|
|
|
|
outputDir = '.'
|
|
|
|
}
|
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// THE MAIN EVENT
|
|
|
|
const obj = await Promise.all(media.map(async m => {
|
2023-10-09 17:43:50 +00:00
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// setup structure
|
2023-10-09 17:43:50 +00:00
|
|
|
const _r = {
|
2023-10-11 16:06:52 +00:00
|
|
|
...genMetadata(m, formatted == 'parent' || formatted == 'both'),
|
2023-10-09 17:43:50 +00:00
|
|
|
parent_dir,
|
|
|
|
media: m,
|
2023-10-11 16:06:52 +00:00
|
|
|
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)
|
|
|
|
}
|
2023-10-09 17:43:50 +00:00
|
|
|
}
|
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// if recurse (again) now we are looking for the media in the folders
|
2023-10-09 17:43:50 +00:00
|
|
|
if (recurse) {
|
2023-10-11 16:06:52 +00:00
|
|
|
// create full path from the passed dir, and the recuring dir
|
2023-10-09 17:43:50 +00:00
|
|
|
const rDir = path.join(mediaDir, m)
|
2023-10-11 16:06:52 +00:00
|
|
|
// 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
|
|
|
|
}
|
2023-10-09 17:43:50 +00:00
|
|
|
}
|
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// return the result to the 'obj' variable
|
2023-10-09 17:43:50 +00:00
|
|
|
return _r
|
|
|
|
}))
|
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
|
|
|
|
// turn the obj variable into JSON
|
2023-10-09 17:43:50 +00:00
|
|
|
const json = JSON.stringify(obj, null, 2)
|
2023-10-11 16:06:52 +00:00
|
|
|
// 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
|
2023-10-10 11:04:35 +00:00
|
|
|
|
2023-10-11 16:06:52 +00:00
|
|
|
// if dryrun
|
2023-10-10 11:04:35 +00:00
|
|
|
if (dryRun) {
|
2023-10-11 16:06:52 +00:00
|
|
|
// just console it out
|
2023-10-10 11:04:35 +00:00
|
|
|
console.log(json)
|
|
|
|
} else {
|
2023-10-11 16:06:52 +00:00
|
|
|
// 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(/\.[^/.]+$/, '')
|
2023-10-10 11:04:35 +00:00
|
|
|
}
|
2023-10-11 16:06:52 +00:00
|
|
|
|
|
|
|
return { title, details }
|
|
|
|
}
|
|
|
|
|
|
|
|
function linkImage(name, images) {
|
|
|
|
return images.find(i => {
|
|
|
|
return i.replace(/\.[^/.]+$/, '') == name
|
|
|
|
})
|
2023-10-09 17:43:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main()
|