From 18435f58f82849dcc86ab2042491828b2873b39a Mon Sep 17 00:00:00 2001 From: Florrie Date: Fri, 5 Jan 2018 23:20:58 -0400 Subject: WIP(?) metadata processing tool --- src/cli.js | 1 + src/download-playlist.js | 9 ++-- src/general-util.js | 16 +++++++ src/process-metadata.js | 108 +++++++++++++++++++++++++++++++++++++++++++++++ todo.txt | 12 ++++++ 5 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 src/general-util.js create mode 100644 src/process-metadata.js diff --git a/src/cli.js b/src/cli.js index 9021cc6..095757f 100755 --- a/src/cli.js +++ b/src/cli.js @@ -23,6 +23,7 @@ async function main(args) { switch (args[0]) { case 'play': script = require('./play'); break case 'download-playlist': script = require('./download-playlist'); break + case 'process-metadata': script = require('./process-metadata'); break case 'smart-playlist': script = require('./smart-playlist'); break case 'setup': script = require('./setup'); break diff --git a/src/download-playlist.js b/src/download-playlist.js index b41c240..852cb64 100755 --- a/src/download-playlist.js +++ b/src/download-playlist.js @@ -12,6 +12,7 @@ const { } = require('./playlist-utils') const { getDownloaderFor, makePowerfulDownloader } = require('./downloaders') +const { showTrackProcessStatus } = require('./general-util') const { promisify } = require('util') const { spawn } = require('child_process') @@ -24,12 +25,8 @@ async function downloadCrawl(playlist, topOut = './out/') { const flat = flattenGrouplike(playlist) let doneCount = 0 - const showStatus = function() { - const total = flat.items.length - const percent = Math.trunc(doneCount / total * 10000) / 100 - console.log( - `\x1b[1mDownload crawler - ${percent}% completed ` + - `(${doneCount}/${total} tracks)\x1b[0m`) + const showStatus = () => { + showTrackProcessStatus(flat.items.length, doneCount) } // First off, we go through all tracks and see which are already downloaded. diff --git a/src/general-util.js b/src/general-util.js new file mode 100644 index 0000000..67f53e3 --- /dev/null +++ b/src/general-util.js @@ -0,0 +1,16 @@ +module.exports.showTrackProcessStatus = function( + total, doneCount, noLineBreak = false +) { + // Log a status line which tells how many tracks are processed and what + // percent is completed. (Uses non-specific language: it doesn't say + // "how many tracks downloaded" or "how many tracks processed", but + // rather, "how many tracks completed".) Pass noLineBreak = true to skip + // the \n character (you'll probably also want to log \r after). + + const percent = Math.trunc(doneCount / total * 10000) / 100 + process.stdout.write( + `\x1b[1m${percent}% completed ` + + `(${doneCount}/${total} tracks)\x1b[0m` + + (noLineBreak ? '' : '\n') + ) +} diff --git a/src/process-metadata.js b/src/process-metadata.js new file mode 100644 index 0000000..43ffe62 --- /dev/null +++ b/src/process-metadata.js @@ -0,0 +1,108 @@ +const fs = require('fs') +const processArgv = require('./process-argv') +const promisifyProcess = require('./promisify-process') +const { spawn } = require('child_process') +const { promisify } = require('util') +const { showTrackProcessStatus } = require('./general-util') +const { updatePlaylistFormat, flattenGrouplike } = require('./playlist-utils') + +const readFile = promisify(fs.readFile) +const writeFile = promisify(fs.writeFile) + +async function probe(filePath) { + const ffprobe = spawn('ffprobe', [ + '-print_format', 'json', + '-show_entries', 'stream=codec_name:format', + '-select_streams', 'a:0', + '-v', 'quiet', + filePath + ]) + + let probeDataString = '' + + ffprobe.stdout.on('data', data => { + probeDataString += data + }) + + await promisifyProcess(ffprobe, false) + + return JSON.parse(probeDataString) +} + +async function main(args) { + if (args.length < 2) { + console.error('Usage: http-music process-metadata (..args..)') + console.error('See \x1b[1mman http-music-process-metadata\x1b[0m!') + return false + } + + const inFile = args[0] + const outFile = args[1] + + // Whether or not to save actual audio tag data. (This includes things like + // genre, track #, and album, as well as any non-standard data set on the + // file.) + let saveTags = false + + // Whether or not to skip tracks which have already been processed. + let skipCompleted = true + + await processArgv(args.slice(1), { + '-save-tags': function() { + saveTags = true + }, + + '-tags': util => util.alias('-save-tags'), + 't': util => util.alias('-save-tags'), + + '-skip-completed': function() { + skipCompleted = true + }, + + '-skip-done': util => util.alias('-skip-completed'), + '-faster': util => util.alias('-skip-completed'), + + '-no-skip-completed': function() { + skipCompleted = false + }, + + '-no-skip-done': util => util.alias('-no-skip-completed'), + '-slower': util => util.alias('-no-skip-completed') + }) + + let doneCount = 0 + + const playlist = updatePlaylistFormat(JSON.parse(await readFile(args[0]))) + + const flattened = flattenGrouplike(playlist) + for (const item of flattened.items) { + if (!(skipCompleted && 'metadata' in item)) { + const probeData = await probe(item.downloaderArg) + + item.metadata = Object.assign(item.metadata || {}, { + duration: parseInt(probeData.format.duration), + size: parseInt(probeData.format.size), + bitrate: parseInt(probeData.format.bit_rate) + }) + + if (saveTags) { + item.metadata.tags = probeData.tags + } + } + + doneCount++ + showTrackProcessStatus(flattened.items.length, doneCount, true) + process.stdout.write(' \r') + } + + await writeFile(outFile, JSON.stringify(playlist, null, 2)) + + console.log(`\nDone! Processed ${flattened.items.length} tracks.`) +} + +module.exports = main + +if (require.main === module) { + main(process.argv.slice(2)) + .catch(err => console.error(err)) +} diff --git a/todo.txt b/todo.txt index 1c9d314..d2def51 100644 --- a/todo.txt +++ b/todo.txt @@ -408,3 +408,15 @@ TODO: Case-insensitive checking with command keybindings - I think this is TODO: Handle empty (active) playlists. Showing an error message and stopping is best, I think. (Done!) + +TODO: A way to switch between what information is displayed in the status bar. + I think using ">" and "<" as default keybindings would work. + Make one set be (track # in group) / (# of tracks in group); one be + (total track #) / (total # of tracks). + +TODO: Adding onto the last one, show the total amount of time in the group/all + groups together. Requires a track metadata tool, though... + +TODO: Make process-metadata work with non-local tracks, somehow... + +TODO: Make process-metadata work nicely with smart playlists, somehow... -- cgit 1.3.0-6-gf8a5