« get me outta code hell

Smart playlists - http-music - Command-line music player + utils (not a server!)
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorliam4 <towerofnix@gmail.com>2017-08-01 11:33:19 -0300
committerliam4 <towerofnix@gmail.com>2017-08-01 11:33:19 -0300
commit6f4ce94467db4e95cab117007e3724695b7d9533 (patch)
treec327223c9e8ad031ae31ccdca97141123e531ecd /src
parente02f90272a805f85f94adec8d790eaf45dcf9c3f (diff)
Smart playlists
Diffstat (limited to 'src')
-rwxr-xr-xsrc/cli.js29
-rwxr-xr-xsrc/crawl-http.js2
-rwxr-xr-xsrc/crawl-itunes.js32
-rwxr-xr-xsrc/crawl-local.js2
-rw-r--r--src/crawl-youtube.js2
-rw-r--r--src/crawlers.js13
-rw-r--r--src/smart-playlist.js49
7 files changed, 103 insertions, 26 deletions
diff --git a/src/cli.js b/src/cli.js
index 4bc64ab..9b21395 100755
--- a/src/cli.js
+++ b/src/cli.js
@@ -4,6 +4,8 @@
 // maxlistenersexceededwarning.
 process.on('warning', e => console.warn(e.stack))
 
+const { getCrawlerByName } = require('./crawlers')
+
 async function main(args) {
   let script
 
@@ -13,18 +15,21 @@ async function main(args) {
     return
   }
 
-  switch (args[0]) {
-    case 'play': script = require('./play'); break
-    case 'crawl-http': script = require('./crawl-http'); break
-    case 'crawl-local': script = require('./crawl-local'); break
-    case 'crawl-itunes': script = require('./crawl-itunes'); break
-    case 'crawl-youtube': script = require('./crawl-youtube'); break
-    case 'download-playlist': script = require('./download-playlist'); break
-
-    default:
-      console.error(`Invalid command "${args[0]}" provided.`)
-      console.error("Try 'man http-music'?")
-      return
+  const module = getCrawlerByName(args[0])
+
+  if (module) {
+    script = module.main
+  } else {
+    switch (args[0]) {
+      case 'play': script = require('./play'); break
+      case 'download-playlist': script = require('./download-playlist'); break
+      case 'smart-playlist': script = require('./smart-playlist'); break
+
+      default:
+        console.error(`Invalid command "${args[0]}" provided.`)
+        console.error("Try 'man http-music'?")
+        return
+    }
   }
 
   await script(args.slice(1))
diff --git a/src/crawl-http.js b/src/crawl-http.js
index e776b9c..7553e85 100755
--- a/src/crawl-http.js
+++ b/src/crawl-http.js
@@ -195,7 +195,7 @@ async function main(args) {
   console.log(JSON.stringify(downloadedPlaylist, null, 2))
 }
 
-module.exports = main
+module.exports = {main, crawl}
 
 if (require.main === module) {
   main(process.argv.slice(2))
diff --git a/src/crawl-itunes.js b/src/crawl-itunes.js
index 6060ffa..e6b63b3 100755
--- a/src/crawl-itunes.js
+++ b/src/crawl-itunes.js
@@ -30,7 +30,23 @@ function findChild(grouplike, name) {
   return grouplike.items.find(x => x.name === name)
 }
 
-async function crawl(libraryXML) {
+let NO_LIBRARY_SYMBOL = Symbol('No library')
+
+async function crawl(
+  libraryPath = `${process.env.HOME}/Music/iTunes/iTunes Music Library.xml`
+) {
+  let libraryXML
+
+  try {
+    libraryXML = await readFile(libraryPath)
+  } catch (err) {
+    if (err.code === 'ENOENT') {
+      throw NO_LIBRARY_SYMBOL
+    } else {
+      throw err
+    }
+  }
+
   const document = new xmldoc.XmlDocument(libraryXML)
 
   const libraryDict = document.children.find(child => child.name === 'dict')
@@ -94,16 +110,12 @@ async function crawl(libraryXML) {
 }
 
 async function main(args) {
-  const libraryPath = args[0] || (
-    `${process.env.HOME}/Music/iTunes/iTunes Music Library.xml`
-  )
-
-  let library
+  let playlist
 
   try {
-    library = await readFile(libraryPath)
+    playlist = await crawl(args[0])
   } catch(err) {
-    if (err.code === 'ENOENT') {
+    if (err === NO_LIBRARY_SYMBOL) {
       console.error(
         "It looks like you aren't sharing the iTunes Library XML file."
       )
@@ -125,12 +137,10 @@ async function main(args) {
     }
   }
 
-  const playlist = await crawl(library)
-
   console.log(JSON.stringify(playlist, null, 2))
 }
 
-module.exports = main
+module.exports = {main, crawl}
 
 if (require.main === module) {
   main(process.argv.slice(2))
diff --git a/src/crawl-local.js b/src/crawl-local.js
index 629e015..d4176ed 100755
--- a/src/crawl-local.js
+++ b/src/crawl-local.js
@@ -41,7 +41,7 @@ async function main(args) {
   }
 }
 
-module.exports = main
+module.exports = {main, crawl}
 
 if (require.main === module) {
   main(process.argv.slice(2))
diff --git a/src/crawl-youtube.js b/src/crawl-youtube.js
index 823fef7..4b4c66c 100644
--- a/src/crawl-youtube.js
+++ b/src/crawl-youtube.js
@@ -41,7 +41,7 @@ async function main(args) {
   }
 }
 
-module.exports = main
+module.exports = {main, crawl}
 
 if (require.main === module) {
   main(process.argv.slice(2))
diff --git a/src/crawlers.js b/src/crawlers.js
new file mode 100644
index 0000000..5ad7fb4
--- /dev/null
+++ b/src/crawlers.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = {
+  getCrawlerByName: function(name) {
+    switch (name) {
+      case 'crawl-http': return require('./crawl-http')
+      case 'crawl-local': return require('./crawl-local')
+      case 'crawl-itunes': return require('./crawl-itunes')
+      case 'crawl-youtube': return require('./crawl-youtube')
+      default: return null
+    }
+  }
+}
diff --git a/src/smart-playlist.js b/src/smart-playlist.js
new file mode 100644
index 0000000..e65ff1f
--- /dev/null
+++ b/src/smart-playlist.js
@@ -0,0 +1,49 @@
+'use strict'
+
+const fs = require('fs')
+const { getCrawlerByName } = require('./crawlers')
+
+const { promisify } = require('util')
+const readFile = promisify(fs.readFile)
+
+async function processItem(item) {
+  // Object.assign is used so that we keep original properties, e.g. "name"
+  // or "apply".
+
+  if ('items' in item) {
+    return Object.assign(item, {
+      items: await Promise.all(item.items.map(processItem))
+    })
+  } else if ('source' in item) {
+    const [ name, ...args ] = item.source
+
+    const crawlModule = getCrawlerByName(name)
+
+    if (crawlModule === null) {
+      console.error(`No crawler by name ${name} - skipped item:`, item)
+      return Object.assign(item, {failed: true})
+    }
+
+    const { crawl } = crawlModule
+
+    return Object.assign(item, await crawl(...args))
+  } else {
+    return item
+  }
+}
+
+async function main(opts) {
+  // TODO: Error when no file is given
+
+  if (opts.length === 0) {
+    console.log("Usage: smart-playlist /path/to/playlist")
+  } else {
+    const playlist = JSON.parse(await readFile(opts[0]))
+    console.log(JSON.stringify(await processItem(playlist), null, 2))
+  }
+}
+
+if (require.main === module) {
+  main(process.argv.slice(2))
+    .catch(err => console.error(err))
+}