diff options
-rw-r--r-- | man/http-music-crawl-http.1 | 2 | ||||
-rw-r--r-- | man/http-music-setup.1 | 15 | ||||
-rw-r--r-- | man/http-music.1 | 7 | ||||
-rwxr-xr-x | src/cli.js | 1 | ||||
-rwxr-xr-x | src/crawl-http.js | 9 | ||||
-rwxr-xr-x | src/crawl-itunes.js | 9 | ||||
-rwxr-xr-x | src/crawl-local.js | 12 | ||||
-rw-r--r-- | src/crawl-youtube.js | 11 | ||||
-rwxr-xr-x | src/play.js | 7 | ||||
-rw-r--r-- | src/setup.js | 242 | ||||
-rw-r--r-- | yarn.lock | 13 |
11 files changed, 311 insertions, 17 deletions
diff --git a/man/http-music-crawl-http.1 b/man/http-music-crawl-http.1 index 9202c65..1f96fc6 100644 --- a/man/http-music-crawl-http.1 +++ b/man/http-music-crawl-http.1 @@ -4,7 +4,7 @@ http-music-crawl-http - create a playlist file using an HTTP-based directory listing .SH SYNOPSIS -.B http-music-crawl-http +.B http-music crawl-http <download URL> [opts...] diff --git a/man/http-music-setup.1 b/man/http-music-setup.1 new file mode 100644 index 0000000..c65d3c7 --- /dev/null +++ b/man/http-music-setup.1 @@ -0,0 +1,15 @@ +.TH HTTP-MUSIC-SETUP 1 + +.SH NAME +http-music-setup - interactively create an http-music playlist + +.SH SYNOPSIS +.B http-music setup + +.SH DESCRIPTION +\fBhttp-music-setup\fR is a command line utility used to interactively generate a playlist.json file, which can be used with \fBhttp-music-play\fR. +Once customization finishes, the playlist is generated and saved. +Alternatively, the user can choose to see the crawler-based command used to generate the playlist file. + +.SH EXAMPLES +Simply use \fBhttp-music setup\fR to get started. diff --git a/man/http-music.1 b/man/http-music.1 index c2787c6..0735901 100644 --- a/man/http-music.1 +++ b/man/http-music.1 @@ -30,6 +30,10 @@ They are as listed below; each sub-command also has its own \fBman\fR page (for Plays a playlist (from the playlist.json file, by default). .TP +.BR setup +Presents a simple wizard to walk through customizing a playlist.json file, which is automatically generated and saved. + +.TP .BR crawl-http Creates a playlist from each music file linked to from an HTML page online. @@ -49,7 +53,6 @@ Creates a playlist from a YouTube playlist URL. .BR download-playlist Downloads each item in a playlist into a directory. - - .SH EXAMPLES See \fBhttp-music-play\fR(1). +It's recommended to use \fBhttp-music setup\fR to get started. diff --git a/src/cli.js b/src/cli.js index 9b21395..9021cc6 100755 --- a/src/cli.js +++ b/src/cli.js @@ -24,6 +24,7 @@ async function main(args) { case 'play': script = require('./play'); break case 'download-playlist': script = require('./download-playlist'); break case 'smart-playlist': script = require('./smart-playlist'); break + case 'setup': script = require('./setup'); break default: console.error(`Invalid command "${args[0]}" provided.`) diff --git a/src/crawl-http.js b/src/crawl-http.js index 4925f12..d3e1533 100755 --- a/src/crawl-http.js +++ b/src/crawl-http.js @@ -147,7 +147,7 @@ function getHTMLLinks(text) { }) } -async function main(args) { +async function main(args, shouldReturn = false) { if (args.length === 0) { console.log("Usage: crawl-http http://.../example/path/ [opts]") return @@ -202,7 +202,12 @@ async function main(args) { filterRegex: filterRegex }) - console.log(JSON.stringify(downloadedPlaylist, null, 2)) + const str = JSON.stringify(downloadedPlaylist, null, 2) + if (shouldReturn) { + return str + } else { + console.log(str) + } } module.exports = {main, crawl} diff --git a/src/crawl-itunes.js b/src/crawl-itunes.js index e6b63b3..ce9ebf9 100755 --- a/src/crawl-itunes.js +++ b/src/crawl-itunes.js @@ -109,7 +109,7 @@ async function crawl( return resultGroup } -async function main(args) { +async function main(args, shouldReturn = false) { let playlist try { @@ -137,7 +137,12 @@ async function main(args) { } } - console.log(JSON.stringify(playlist, null, 2)) + const str = JSON.stringify(playlist, null, 2) + if (shouldReturn) { + return str + } else { + console.log(str) + } } module.exports = {main, crawl} diff --git a/src/crawl-local.js b/src/crawl-local.js index 91554af..3134193 100755 --- a/src/crawl-local.js +++ b/src/crawl-local.js @@ -56,7 +56,7 @@ function crawl(dirPath, extensions = [ .then(filteredItems => ({items: filteredItems})) } -async function main(args) { +async function main(args, shouldReturn = false) { if (args.length === 0) { console.log("Usage: crawl-local /example/path [opts]") return @@ -86,8 +86,14 @@ async function main(args) { 'e': util => util.alias('-extensions') }) - const res = await crawl(path, extensions) - console.log(JSON.stringify(res, null, 2)) + const playlist = await crawl(path, extensions) + + const str = JSON.stringify(playlist, null, 2) + if (shouldReturn) { + return str + } else { + console.log(str) + } } module.exports = {main, crawl} diff --git a/src/crawl-youtube.js b/src/crawl-youtube.js index 4b4c66c..38a531a 100644 --- a/src/crawl-youtube.js +++ b/src/crawl-youtube.js @@ -31,13 +31,20 @@ async function crawl(url) { } } -async function main(args) { +async function main(args, shouldReturn = false) { // TODO: Error message if none is passed. if (args.length === 0) { console.error("Usage: crawl-youtube <playlist URL>") + return + } + + const playlist = await crawl(args[0]) + const str = JSON.stringify(playlist, null, 2) + if (shouldReturn) { + return str } else { - console.log(JSON.stringify(await crawl(args[0]), null, 2)) + console.log(str) } } diff --git a/src/play.js b/src/play.js index c754836..ac6a232 100755 --- a/src/play.js +++ b/src/play.js @@ -544,9 +544,14 @@ async function main(args) { await processArgv(args, optionFunctions) if (activePlaylist === null) { - throw new Error( + console.error( "Cannot play - no open playlist. Try --open <playlist file>?" ) + console.error( + "You could also try \x1b[1mhttp-music setup\x1b[0m to easily " + + "create a playlist file!" + ) + return false } if (willPlay || (willPlay === null && shouldPlay)) { diff --git a/src/setup.js b/src/setup.js new file mode 100644 index 0000000..15aa2f2 --- /dev/null +++ b/src/setup.js @@ -0,0 +1,242 @@ +'use strict' + +const readline = require('readline') +const path = require('path') +const util = require('util') +const fs = require('fs') +const { getCrawlerByName } = require('./crawlers') + +const writeFile = util.promisify(fs.writeFile) +const access = util.promisify(fs.access) + +async function exists(file) { + try { + await access(file) + return true + } catch(err) { + return false + } +} + +function prompt(rl, promptMessage = '', defaultChoice = null, options = {}) { + return new Promise((resolve, reject) => { + const hasOptions = (Object.keys(options).length > 0) + + console.log('') + + if (hasOptions) { + for (const [ option, message ] of Object.entries(options)) { + if (option === defaultChoice) { + console.log(` [${option.toUpperCase()} (default)]: ${message}`) + } else { + console.log(` [${option.toLowerCase()}]: ${message}`) + } + } + console.log('') + } + + let promptStr = '' + + if (promptMessage) { + promptStr += promptMessage + ' ' + } + + if (hasOptions) { + promptStr += '[' + promptStr += Object.keys(options).map(option => { + if (option === defaultChoice) { + return option.toUpperCase() + } else { + return option.toLowerCase() + } + }).join('/') + promptStr += ']' + } else if (defaultChoice) { + promptStr += `[default: ${defaultChoice}]` + } + + promptStr += '> ' + + rl.question(promptStr, choice => { + toRepeat: { + if (choice.length === 0 && defaultChoice) { + resolve(defaultChoice) + } else if ( + hasOptions && Object.keys(options).includes(choice.toLowerCase()) + ) { + resolve(choice.toLowerCase()) + } else if (choice.length > 0 && !hasOptions) { + resolve(choice) + } else { + break toRepeat + } + + console.log('') + return + } + + resolve(prompt(rl, promptMessage, defaultChoice, options)) + }) + }) +} + +async function setupTool() { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }) + + console.log('Which source would you like to play music from?') + + const wd = process.cwd() + + const crawlerCommand = { + l: 'crawl-local', + h: 'crawl-http' + }[await prompt(rl, 'Which source?', 'l', { + l: 'Files on this local computer.', + h: 'Downloadable files linked from a page on the web.' + })] + + const crawlerOptions = [] + + if (crawlerCommand === 'crawl-local') { + console.log('What directory would you like to download music from?') + console.log(`(Your current working directory is: ${wd})`) + + crawlerOptions.push(await prompt(rl, 'What directory path?', '.')) + } + + if (crawlerCommand === 'crawl-http') { + console.log('What URL would you like to download music from?') + console.log('(This only works if the actual song files are linked; you') + console.log("can't, for example, give a Bandcamp album link here.") + + crawlerOptions.push(await prompt(rl, 'What URL?')) + } + + console.log('Would you like http-music to automatically process your') + console.log('playlist to find out which music to play every time?') + console.log('This is handy if you expect new music to be added to your') + console.log('source often (e.g. a folder you frequently add new music') + console.log('to, or a webpage that often has new links added to it).') + console.log('') + console.log('If you choose this, http-music may take longer to run.') + console.log('(If you are loading music from a webpage, then the amount of') + console.log("time you'll have to wait depends on your internet connection;") + console.log('if you are loading files from your own computer, the delay') + console.log('will depend on your hard drive speed - not a big deal, on') + console.log('most computers.)') + + const useSmartPlaylist = { + y: true, + n: false + }[await prompt(rl, 'Process playlist every time?', 'y', { + y: 'Yes, process the playlist for new items every time.', + n: "No, don't automatically process the playlist." + })] + + console.log("Do you want to save your playlist to a file? If not, you'll") + console.log('just be given the command you can use to generate the file.') + + const smartPlaylistString = JSON.stringify({ + source: [crawlerCommand, ...crawlerOptions] + }, null, 2) + + const savePlaylist = { + y: true, + n: false + }[await prompt(rl, 'Save playlist?', 'y', { + y: 'Yes, save the playlist to a file.', + n: 'No, just show the command.' + })] + + if (savePlaylist) { + console.log('What would you like to name your playlist file?') + console.log('"playlist.json" will be automatically detected by http-music,') + console.log('but you can specify a different file or path if you want.') + + let defaultOutput = 'playlist.json' + + const playlistExists = await exists('playlist.json') + + if (playlistExists) { + console.log('') + console.log( + '\x1b[1mBeware!\x1b[0m There is already a file called playlist.json' + + ' in this' + ) + console.log(`directory. (Your current working directory is: ${wd})`) + console.log('You may want to write to another file.') + defaultOutput = null + } + + let outputFile = await prompt(rl, 'Playlist file name?', defaultOutput) + + if (path.extname(outputFile) !== '.json') { + console.log('(http-music playlist files are JSON files, so your file') + console.log('was changed to a .json file.)') + console.log('') + + outputFile = path.basename(outputFile, path.extname(outputFile)) + + if (playlistExists && path.relative(outputFile, 'playlist') === '') { + console.log('(Since that would overwrite the playlist.json that already') + console.log( + "exists in this directory, it'll instead be saved to playlist2.json.)" + ) + console.log('') + outputFile += '2' + } + + outputFile += '.json' + } + + if (useSmartPlaylist) { + await writeFile(outputFile, smartPlaylistString) + } else { + console.log('Generating your playlist file. This could take a little while..') + const { main: crawlerMain } = getCrawlerByName(crawlerCommand) + const out = await crawlerMain(crawlerOptions, true) + await writeFile(outputFile, out) + } + + console.log('Done setting up and saving your playlist file.') + console.log(`Try it out with \x1b[1mhttp-music play${ + (path.relative(outputFile, 'playlist.json') === '') + ? '' + : ` --open ${path.relative('.', outputFile)}` + }\x1b[0m!`) + } else { + if (useSmartPlaylist) { + console.log("You'll want to create a playlist JSON file containing") + console.log('the following:') + console.log('') + console.log(`\x1b[1m${smartPlaylistString}\x1b[0m`) + } else { + console.log( + `You'll want to use the \x1b[1m${crawlerCommand}\x1b[0m crawler command.` + ) + + if (crawlerOptions.length > 1) { + console.log( + 'You should give it these arguments:', + crawlerOptions.map(l => `\x1b[1m${l}\x1b[0m`).join(', ') + ) + } else if (crawlerOptions.length === 1) { + const opt = crawlerOptions[0] + console.log(`You should give it this argument: \x1b[1m${opt}\x1b[0m`) + } + } + console.log('') + } + + rl.close() +} + +module.exports = setupTool + +if (require.main === module) { + setupTool() + .catch(err => console.error(err)) +} diff --git a/yarn.lock b/yarn.lock index bbb0f25..c17dd9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,5 +1,7 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 + + "@types/node@^6.0.46": version "6.0.73" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.73.tgz#85dc4bb6f125377c75ddd2519a1eeb63f0a4ed70" @@ -52,14 +54,14 @@ css-what@2.1: version "2.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" -dom-serializer@~0.1.0, dom-serializer@0: +dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" dependencies: domelementtype "~1.1.1" entities "~1.1.1" -domelementtype@^1.3.0, domelementtype@1: +domelementtype@1, domelementtype@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" @@ -73,7 +75,7 @@ domhandler@^2.3.0: dependencies: domelementtype "1" -domutils@^1.5.1, domutils@1.5.1: +domutils@1.5.1, domutils@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" dependencies: @@ -220,6 +222,10 @@ sax@^1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" +seed-random@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" + string_decoder@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.1.tgz#62e200f039955a6810d8df0a33ffc0f013662d98" @@ -267,4 +273,3 @@ xmldoc: resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.1.0.tgz#25c92f08f263f344dac8d0b32370a701ee9d0e93" dependencies: sax "^1.2.1" - |