|
const { resolve } = require('path'); |
|
const { readdir } = require('fs').promises; |
|
const { unlink } = require('fs').promises; |
|
const path = require('path'); |
|
const { exec } = require("child_process"); |
|
|
|
function fileConverterCommand(file) { |
|
return `ffmpeg -i "${file}" -acodec mp3 "${file}.mp4"` |
|
} |
|
function fileConcatCommand(files, outputFile) { |
|
const inputs = files.join("|") |
|
return `ffmpeg -i "concat:${inputs}" -c copy ${outputFile}` |
|
} |
|
|
|
const dir = process.argv[2] || "." |
|
|
|
main() |
|
|
|
async function main() { |
|
const files = await getFiles(dir); |
|
|
|
const wParents = files.map(f => {return { |
|
file: f, |
|
parentDir: path.basename(path.dirname(f)) |
|
}}) |
|
|
|
const onlyTSDirs = wParents.filter(d => splitDirNameToTs(d.parentDir)).map(d => {return { |
|
file: d.file, |
|
fname: path.basename(d.file), |
|
date: splitDirNameToTs(d.parentDir), |
|
}}) |
|
|
|
const grupped = groupBy(onlyTSDirs, "date") |
|
//console.log(Object.values(grupped)) |
|
|
|
await mapAllSettled(Object.values(grupped), arr => convertDir(arr), 2) |
|
} |
|
|
|
function splitDirNameToTs(dirName) { |
|
try{ |
|
return (new Date(dirName.split("_")[0] * 1000)) |
|
} catch { |
|
return null; |
|
} |
|
} |
|
|
|
async function convertDir(arr) { |
|
//console.log() |
|
//console.log("=========") |
|
//console.log(arr) |
|
const farr = arr.filter(e => { |
|
try{ |
|
//console.log(Number.isInteger(e.fname.split(".")[0])) |
|
return !Number.isInteger(e.fname.split(".")[0]) && e.fname!==".info" |
|
} catch (e) { |
|
return false |
|
} |
|
}) |
|
//console.log(farr) |
|
farr.sort(function(a, b) { |
|
return Number(a.fname.split(".")[0]) - Number(b.fname.split(".")[0]); |
|
}); |
|
|
|
//console.log(farr) |
|
try { |
|
await mapAllSettled(farr, async d => { |
|
const out = await execPromise(fileConverterCommand(d.file)) |
|
//console.log(out) |
|
console.log(`converted ${d.file}`) |
|
}, 10) |
|
} catch (e) { |
|
console.log(e) |
|
} |
|
|
|
const outputFile = dir+"/clip_" + arr[0].date.toISOString().replace("T", "_").replace(".000Z", "") + ".mp4" |
|
await execPromise(fileConcatCommand(farr.map(d => `${d.file}.mp4`), outputFile)) |
|
|
|
await Promise.all(farr.map(async d => await unlink(`${d.file}.mp4`))) |
|
console.log(`created ${outputFile}`) |
|
} |
|
|
|
//https://codereview.stackexchange.com/a/66752/178655 |
|
function groupBy(array, keyOrIterator) { |
|
var iterator, key; |
|
|
|
// use the function passed in, or create one |
|
if(typeof key !== 'function') { |
|
key = String(keyOrIterator); |
|
iterator = function (item) { return item[key]; }; |
|
} else { |
|
iterator = keyOrIterator; |
|
} |
|
|
|
return array.reduce(function (memo, item) { |
|
var key = iterator(item); |
|
memo[key] = memo[key] || []; |
|
memo[key].push(item); |
|
return memo; |
|
}, {}); |
|
} |
|
|
|
//https://stackoverflow.com/a/36960207/2118749 |
|
async function execPromise(cmd) { |
|
console.log(cmd) |
|
return new Promise(function(resolve, reject) { |
|
exec(cmd, function(err, stdout) { |
|
if (err) return reject(err); |
|
resolve(stdout); |
|
}); |
|
}); |
|
} |
|
|
|
|
|
//https://stackoverflow.com/a/45130990/2118749 |
|
async function getFiles(dir) { |
|
const dirents = await readdir(dir, { withFileTypes: true }); |
|
const files = await Promise.all(dirents.map((dirent) => { |
|
const res = resolve(dir, dirent.name); |
|
return dirent.isDirectory() ? getFiles(res) : res; |
|
})); |
|
return Array.prototype.concat(...files); |
|
} |
|
|
|
//https://codeburst.io/async-map-with-limited-parallelism-in-node-js-2b91bd47af70 |
|
const { promisify } = require('util') |
|
const { setImmediate } = require('timers') |
|
|
|
const setImmediateP = promisify(setImmediate) |
|
|
|
async function mapItem(mapFn, currentValue, index, array) { |
|
try { |
|
await setImmediateP() |
|
return { |
|
status: 'fulfilled', |
|
value: await mapFn(currentValue, index, array) |
|
} |
|
} catch (reason) { |
|
return { |
|
status: 'rejected', |
|
reason |
|
} |
|
} |
|
} |
|
|
|
async function worker(id, gen, mapFn, result) { |
|
//console.time(`Worker ${id}`) |
|
for (let [ currentValue, index, array ] of gen) { |
|
//console.time(`Worker ${id} --- index ${index} item ${currentValue}`) |
|
result[index] = await mapItem(mapFn, currentValue, index, array) |
|
//console.timeEnd(`Worker ${id} --- index ${index} item ${currentValue}`) |
|
} |
|
//console.timeEnd(`Worker ${id}`) |
|
} |
|
|
|
function* arrayGenerator(array) { |
|
for (let index = 0; index < array.length; index++) { |
|
const currentValue = array[index] |
|
yield [ currentValue, index, array ] |
|
} |
|
} |
|
|
|
async function mapAllSettled(arr, mapFn, limit = arr.length) { |
|
const result = [] |
|
|
|
if (arr.length === 0) { |
|
return result |
|
} |
|
|
|
const gen = arrayGenerator(arr) |
|
|
|
limit = Math.min(limit, arr.length) |
|
|
|
const workers = new Array(limit) |
|
for (let i = 0; i < limit; i++) { |
|
workers.push(worker(i, gen, mapFn, result)) |
|
} |
|
|
|
//console.log(`Initialized ${limit} workers`) |
|
|
|
await Promise.all(workers) |
|
|
|
return result |
|
} |