« get me outta code hell

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:
Diffstat (limited to 'src')
-rw-r--r--src/general-util.js286
-rwxr-xr-xsrc/play.js349
2 files changed, 349 insertions, 286 deletions
diff --git a/src/general-util.js b/src/general-util.js
index 825dd90..278eea6 100644
--- a/src/general-util.js
+++ b/src/general-util.js
@@ -1,8 +1,20 @@
 const { promisify } = require('util')
 const fs = require('fs')
 const fetch = require('node-fetch')
+const clone = require('clone')
+const processArgv = require('./process-argv')
+const { processSmartPlaylist } = require('./smart-playlist')
 
 const readFile = promisify(fs.readFile)
+const writeFile = promisify(fs.writeFile)
+
+// TODO: Check which of these are actually used. For now stolen from play.js,
+// along with the zillion functions that use at least some of these.
+const {
+  filterPlaylistByPathString, removeGroupByPathString, getPlaylistTreeString,
+  updatePlaylistFormat, collapseGrouplike, filterGrouplikeByProperty, isTrack,
+  flattenGrouplike
+} = require('./playlist-utils')
 
 module.exports.showTrackProcessStatus = function(
   total, doneCount, noLineBreak = false
@@ -29,7 +41,7 @@ function downloadPlaylistFromLocalPath(path) {
   return readFile(path).then(buf => buf.toString())
 }
 
-module.exports.downloadPlaylistFromOptionValue = function(arg) {
+function downloadPlaylistFromOptionValue (arg) {
   // TODO: Verify things!
   if (arg.startsWith('http://') || arg.startsWith('https://')) {
     return downloadPlaylistFromURL(arg)
@@ -37,3 +49,275 @@ module.exports.downloadPlaylistFromOptionValue = function(arg) {
     return downloadPlaylistFromLocalPath(arg)
   }
 }
+
+Object.assign(module.exports, {
+  downloadPlaylistFromOptionValue
+})
+
+module.exports.makePlaylistOptions = function() {
+  let sourcePlaylist = null
+  let activePlaylist = null
+
+  // Whether or not a playlist has been opened yet. This is just used to
+  // decide when exactly to load the default playlist. (We don't want to load
+  // it as soon as the process starts, since there might be an --open-playlist
+  // option that specifies opening a *different* playlist! But if we encounter
+  // an action that requires a playlist, and no playlist has yet been opened,
+  // we assume that the user probably wants to do something with the default
+  // playlist, and that's when we open it. See requiresOpenPlaylist for the
+  // implementation of this.)
+  let hasOpenedPlaylist = false
+
+  const openPlaylist = async function (arg, silent = false) {
+    // Takes a playlist download argument and loads it as the source and
+    // active playlist.
+
+    let playlistText
+
+    if (!silent) {
+      console.log("Opening playlist from: " + arg)
+    }
+
+    try {
+      playlistText = await downloadPlaylistFromOptionValue(arg)
+    } catch(err) {
+      if (!silent) {
+        console.error("Failed to open playlist file: " + arg)
+        console.error(err)
+      }
+
+      return false
+    }
+
+    const importedPlaylist = JSON.parse(playlistText)
+
+    hasOpenedPlaylist = true
+
+    await loadPlaylist(importedPlaylist)
+  }
+
+  const loadPlaylist = async function (importedPlaylist) {
+    // Takes an actual playlist object and sets it up as the source and active
+    // playlist.
+
+    const openedPlaylist = updatePlaylistFormat(importedPlaylist)
+
+    // We also want to de-smart-ify (stupidify? - simplify?) the playlist.
+    const processedPlaylist = await processSmartPlaylist(openedPlaylist)
+
+    // ..And finally, we have to update the playlist format again, since
+    // processSmartPlaylist might have added new (un-updated) items:
+    const finalPlaylist = updatePlaylistFormat(processedPlaylist, true)
+    // We also pass true so that the playlist-format-updater knows that this
+    // is the source playlist.
+
+    sourcePlaylist = finalPlaylist
+
+    // The active playlist is a clone of the source playlist; after all it's
+    // quite possible we'll be messing with the value of the active playlist,
+    // and we don't want to reflect those changes in the source playlist.
+    activePlaylist = clone(sourcePlaylist)
+
+    await processArgv(processedPlaylist.options, optionFunctions)
+  }
+
+  const requiresOpenPlaylist = async function() {
+    if (activePlaylist === null) {
+      if (hasOpenedPlaylist === false) {
+        await openDefaultPlaylist()
+      } else {
+        throw new Error(
+          "This action requires an open playlist - try --open (file)"
+        )
+      }
+    }
+  }
+
+  const openDefaultPlaylist = function() {
+    return openPlaylist('./playlist.json', true)
+  }
+
+  const optionFunctions = {
+    '-open-playlist': async function(util) {
+      // --open-playlist <file>  (alias: --open, -o)
+      // Opens a separate playlist file.
+      // This sets the source playlist.
+
+      await openPlaylist(util.nextArg())
+    },
+
+    '-open': util => util.alias('-open-playlist'),
+    'o': util => util.alias('-open-playlist'),
+
+    '-open-playlist-string': async function(util) {
+      // --open-playlist-string <string>
+      // Opens a playlist, using the given string as the JSON text of the
+      // playlist. This sets the source playlist.
+
+      await loadPlaylist(JSON.parse(util.nextArg()))
+    },
+
+    '-playlist-string': util => util.alias('-open-playlist-string'),
+
+    '-write-playlist': async function(util) {
+      // --write-playlist <file>  (alias: --write, -w, --save)
+      // Writes the active playlist to a file. This file can later be used
+      // with --open <file>; you won't need to stick in all the filtering
+      // options again.
+
+      await requiresOpenPlaylist()
+
+      const playlistString = JSON.stringify(activePlaylist, null, 2)
+      const file = util.nextArg()
+
+      console.log(`Saving playlist to file ${file}...`)
+
+      await writeFile(file, playlistString)
+
+      console.log("Saved.")
+    },
+
+    '-write': util => util.alias('-write-playlist'),
+    'w': util => util.alias('-write-playlist'),
+    '-save': util => util.alias('-write-playlist'),
+
+    '-print-playlist': async function(util) {
+      // --print-playlist  (alias: --log-playlist, --json)
+      // Prints out the JSON representation of the active playlist.
+
+      await requiresOpenPlaylist()
+
+      console.log(JSON.stringify(activePlaylist, null, 2))
+    },
+
+    '-log-playlist': util => util.alias('-print-playlist'),
+    '-json': util => util.alias('-print-playlist'),
+
+    '-clear': async function(util) {
+      // --clear  (alias: -c)
+      // Clears the active playlist. This does not affect the source
+      // playlist.
+
+      await requiresOpenPlaylist()
+
+      activePlaylist.items = []
+    },
+
+    'c': util => util.alias('-clear'),
+
+    '-keep': async function(util) {
+      // --keep <groupPath>  (alias: -k)
+      // Keeps a group by loading it from the source playlist into the
+      // active playlist. This is usually useful after clearing the
+      // active playlist; it can also be used to keep a subgroup when
+      // you've removed an entire parent group, e.g. `-r foo -k foo/baz`.
+
+      await requiresOpenPlaylist()
+
+      const pathString = util.nextArg()
+      const group = filterPlaylistByPathString(sourcePlaylist, pathString)
+
+      if (group) {
+        activePlaylist.items.push(group)
+      }
+    },
+
+    'k': util => util.alias('-keep'),
+
+    '-remove': async function(util) {
+      // --remove <groupPath>  (alias: -r, -x)
+      // Filters the playlist so that the given path is removed.
+
+      await requiresOpenPlaylist()
+
+      const pathString = util.nextArg()
+      console.log("Ignoring path: " + pathString)
+      removeGroupByPathString(activePlaylist, pathString)
+    },
+
+    'r': util => util.alias('-remove'),
+    'x': util => util.alias('-remove'),
+
+    '-filter': async function(util) {
+      // --filter <filterJSON>
+      // Filters the playlist so that only tracks that match the given filter
+      // are kept. FilterJSON should be a JSON object as described in the
+      // man page section "filters".
+
+      const filterJSON = util.nextArg()
+
+      let filterObj
+      try {
+        filterObj = JSON.parse(filterJSON)
+      } catch (error) {
+        console.error('Invalid JSON for filter:', filterJSON)
+        return
+      }
+
+      activePlaylist.filters = [filterObj]
+      activePlaylist = await processSmartPlaylist(activePlaylist)
+      activePlaylist = updatePlaylistFormat(activePlaylist)
+    },
+
+    'f': util => util.alias('-filter'),
+
+    '-collapse-groups': async function() {
+      // --collapse-groups  (alias: --collapse)
+      // Collapses groups in the active playlist so that there is only one
+      // level of sub-groups. Handy for shuffling the order groups play in;
+      // try `--collapse-groups --sort shuffle-groups`.
+
+      await requiresOpenPlaylist()
+
+      activePlaylist = updatePlaylistFormat(collapseGrouplike(activePlaylist))
+    },
+
+    '-collapse': util => util.alias('-collapse-groups'),
+
+    '-flatten-tracks': async function() {
+      // --flatten-tracks  (alias: --flatten)
+      // Flattens the entire active playlist, so that only tracks remain,
+      // and there are no groups.
+
+      await requiresOpenPlaylist()
+
+      activePlaylist = updatePlaylistFormat(flattenGrouplike(activePlaylist))
+    },
+
+    '-flatten': util => util.alias('-flatten-tracks'),
+
+    '-list-groups': async function(util) {
+      // --list-groups  (alias: -l, --list)
+      // Lists all groups in the playlist.
+
+      await requiresOpenPlaylist()
+
+      console.log(getPlaylistTreeString(activePlaylist))
+    },
+
+    '-list': util => util.alias('-list-groups'),
+    'l': util => util.alias('-list-groups'),
+
+    '-list-all': async function(util) {
+      // --list-all  (alias: --list-tracks, -L)
+      // Lists all groups and tracks in the playlist.
+
+      await requiresOpenPlaylist()
+
+      console.log(getPlaylistTreeString(activePlaylist, true))
+
+    },
+
+    '-list-tracks': util => util.alias('-list-all'),
+    'L': util => util.alias('-list-all'),
+  }
+
+  return {
+    optionFunctions,
+    openDefaultPlaylist,
+    getStuff: {
+      get hasOpenedPlaylist() { return hasOpenedPlaylist },
+      get activePlaylist() { return activePlaylist }
+    }
+  }
+}
diff --git a/src/play.js b/src/play.js
index 30a151e..dd49afe 100755
--- a/src/play.js
+++ b/src/play.js
@@ -4,7 +4,6 @@
 
 const { promisify } = require('util')
 const { spawn } = require('child_process')
-const clone = require('clone')
 const fs = require('fs')
 const fetch = require('node-fetch')
 const commandExists = require('./command-exists')
@@ -12,20 +11,9 @@ const startLoopPlay = require('./loop-play')
 const processArgv = require('./process-argv')
 const promisifyProcess = require('./promisify-process')
 const { processSmartPlaylist } = require('./smart-playlist')
-
-const {
-  filterPlaylistByPathString, removeGroupByPathString, getPlaylistTreeString,
-  updatePlaylistFormat, collapseGrouplike, filterGrouplikeByProperty, isTrack,
-  flattenGrouplike
-} = require('./playlist-utils')
-
-const {
-  downloadPlaylistFromOptionValue
-} = require('./general-util')
-
-const {
-  compileKeybindings, getComboForCommand, stringifyCombo
-} = require('./keybinder')
+const { filterPlaylistByPathString, isTrack, flattenGrouplike } = require('./playlist-utils')
+const { compileKeybindings, getComboForCommand, stringifyCombo } = require('./keybinder')
+const { makePlaylistOptions } = require('./general-util')
 
 const readFile = promisify(fs.readFile)
 const writeFile = promisify(fs.writeFile)
@@ -60,7 +48,6 @@ async function determineDefaultConverter() {
 
 async function main(args) {
   let sourcePlaylist = null
-  let activePlaylist = null
 
   let pickerSortMode = 'shuffle'
   let pickerLoopMode = 'loop-regenerate'
@@ -93,16 +80,6 @@ async function main(args) {
   // The file to output the playlist path to the current file.
   let trackDisplayFile
 
-  // Whether or not a playlist has been opened yet. This is just used to
-  // decide when exactly to load the default playlist. (We don't want to load
-  // it as soon as the process starts, since there might be an --open-playlist
-  // option that specifies opening a *different* playlist! But if we encounter
-  // an action that requires a playlist, and no playlist has yet been opened,
-  // we assume that the user probably wants to do something with the default
-  // playlist, and that's when we open it. See requiresOpenPlaylist for the
-  // implementation of this.)
-  let hasOpenedPlaylist = false
-
   const keybindings = [
     [['space'], 'togglePause'],
     [['left'], 'seek', -5],
@@ -119,59 +96,6 @@ async function main(args) {
     [['q'], 'quit'], [['Q'], 'quit']
   ]
 
-  async function openPlaylist(arg, silent = false) {
-    // Takes a playlist download argument and loads it as the source and
-    // active playlist.
-
-    let playlistText
-
-    if (!silent) {
-      console.log("Opening playlist from: " + arg)
-    }
-
-    try {
-      playlistText = await downloadPlaylistFromOptionValue(arg)
-    } catch(err) {
-      if (!silent) {
-        console.error("Failed to open playlist file: " + arg)
-        console.error(err)
-      }
-
-      return false
-    }
-
-    const importedPlaylist = JSON.parse(playlistText)
-
-    hasOpenedPlaylist = true
-
-    await loadPlaylist(importedPlaylist)
-  }
-
-  async function loadPlaylist(importedPlaylist) {
-    // Takes an actual playlist object and sets it up as the source and active
-    // playlist.
-
-    const openedPlaylist = updatePlaylistFormat(importedPlaylist)
-
-    // We also want to de-smart-ify (stupidify? - simplify?) the playlist.
-    const processedPlaylist = await processSmartPlaylist(openedPlaylist)
-
-    // ..And finally, we have to update the playlist format again, since
-    // processSmartPlaylist might have added new (un-updated) items:
-    const finalPlaylist = updatePlaylistFormat(processedPlaylist, true)
-    // We also pass true so that the playlist-format-updater knows that this
-    // is the source playlist.
-
-    sourcePlaylist = finalPlaylist
-
-    // The active playlist is a clone of the source playlist; after all it's
-    // quite possible we'll be messing with the value of the active playlist,
-    // and we don't want to reflect those changes in the source playlist.
-    activePlaylist = clone(sourcePlaylist)
-
-    await processArgv(processedPlaylist.options, optionFunctions)
-  }
-
   async function openKeybindings(arg, add = true) {
     console.log("Opening keybindings from: " + arg)
 
@@ -198,95 +122,36 @@ async function main(args) {
     keybindings.unshift(...openedKeybindings)
   }
 
-  async function requiresOpenPlaylist() {
-    if (activePlaylist === null) {
-      if (hasOpenedPlaylist === false) {
-        await openDefaultPlaylist()
-      } else {
-        throw new Error(
-          "This action requires an open playlist - try --open (file)"
-        )
-      }
-    }
-  }
+  const {
+    optionFunctions, getStuff,
+    openDefaultPlaylist
+  } = await makePlaylistOptions()
 
-  function openDefaultPlaylist() {
-    return openPlaylist('./playlist.json', true)
-  }
+  const {
+    '-write-playlist': originalWritePlaylist,
+    '-print-playlist': originalPrintPlaylist,
+    '-list-groups': originalListGroups,
+    '-list-all': originalListAll
+  } = optionFunctions
 
-  const optionFunctions = {
-    '-help': function(util) {
-      // --help  (alias: -h, -?)
-      // Presents a help message.
+  Object.assign(optionFunctions, {
 
-      console.log('http-music\nTry man http-music!')
-
-      if (util.index === util.argv.length - 1) {
-        shouldPlay = false
-      }
-    },
-
-    'h': util => util.alias('-help'),
-    '?': util => util.alias('-help'),
-
-    '-open-playlist': async function(util) {
-      // --open-playlist <file>  (alias: --open, -o)
-      // Opens a separate playlist file.
-      // This sets the source playlist.
-
-      await openPlaylist(util.nextArg())
-    },
-
-    '-open': util => util.alias('-open-playlist'),
-    'o': util => util.alias('-open-playlist'),
-
-    '-open-playlist-string': async function(util) {
-      // --open-playlist-string <string>
-      // Opens a playlist, using the given string as the JSON text of the
-      // playlist. This sets the source playlist.
-
-      await loadPlaylist(JSON.parse(util.nextArg()))
-    },
-
-    '-playlist-string': util => util.alias('-open-playlist-string'),
+    // Extra play-specific behavior -------------------------------------------
 
     '-write-playlist': async function(util) {
-      // --write-playlist <file>  (alias: --write, -w, --save)
-      // Writes the active playlist to a file. This file can later be used
-      // with --open <file>; you won't need to stick in all the filtering
-      // options again.
-
-      await requiresOpenPlaylist()
-
-      const playlistString = JSON.stringify(activePlaylist, null, 2)
-      const file = util.nextArg()
-
-      console.log(`Saving playlist to file ${file}...`)
+      await originalWritePlaylist(util)
 
-      return writeFile(file, playlistString).then(() => {
-        console.log("Saved.")
-
-        // If this is the last option, the user probably doesn't actually
-        // want to play the playlist. (We need to check if this is len - 2
-        // rather than len - 1, because of the <file> option that comes
-        // after --write-playlist.)
-        if (util.index === util.argv.length - 2) {
-          shouldPlay = false
-        }
-      })
+      // If this is the last option, the user probably doesn't actually
+      // want to play the playlist. (We need to check if this is len - 2
+      // rather than len - 1, because of the <file> option that comes
+      // after --write-playlist.)
+      if (util.index === util.argv.length - 2) {
+        shouldPlay = false
+      }
     },
 
-    '-write': util => util.alias('-write-playlist'),
-    'w': util => util.alias('-write-playlist'),
-    '-save': util => util.alias('-write-playlist'),
-
     '-print-playlist': async function(util) {
-      // --print-playlist  (alias: --log-playlist, --json)
-      // Prints out the JSON representation of the active playlist.
-
-      await requiresOpenPlaylist()
-
-      console.log(JSON.stringify(activePlaylist, null, 2))
+      await originalPrintPlaylist(util)
 
       // As with --write-playlist, the user probably doesn't want to actually
       // play anything if this is the last option.
@@ -295,152 +160,57 @@ async function main(args) {
       }
     },
 
-    '-log-playlist': util => util.alias('-print-playlist'),
-    '-json': util => util.alias('-print-playlist'),
-
-    // Add appends the keybindings to the existing keybindings; import replaces
-    // the current ones with the opened ones.
-
-    '-add-keybindings': async function(util) {
-      await openKeybindings(util.nextArg())
-    },
-
-    '-open-keybindings': util => util.alias('-add-keybindings'),
-
-    '-import-keybindings': async function(util) {
-      await openKeybindings(util.nextArg(), false)
-    },
-
-    '-clear': async function(util) {
-      // --clear  (alias: -c)
-      // Clears the active playlist. This does not affect the source
-      // playlist.
-
-      await requiresOpenPlaylist()
-
-      activePlaylist.items = []
-    },
-
-    'c': util => util.alias('-clear'),
-
-    '-keep': async function(util) {
-      // --keep <groupPath>  (alias: -k)
-      // Keeps a group by loading it from the source playlist into the
-      // active playlist. This is usually useful after clearing the
-      // active playlist; it can also be used to keep a subgroup when
-      // you've removed an entire parent group, e.g. `-r foo -k foo/baz`.
-
-      await requiresOpenPlaylist()
-
-      const pathString = util.nextArg()
-      const group = filterPlaylistByPathString(sourcePlaylist, pathString)
+    '-list-groups': async function(util) {
+      await originalListGroups(util)
 
-      if (group) {
-        activePlaylist.items.push(group)
+      // If this is the last item in the argument list, the user probably
+      // only wants to get the list, so we'll mark the 'should run' flag
+      // as false.
+      if (util.index === util.argv.length - 1) {
+        shouldPlay = false
       }
     },
 
-    'k': util => util.alias('-keep'),
-
-    '-remove': async function(util) {
-      // --remove <groupPath>  (alias: -r, -x)
-      // Filters the playlist so that the given path is removed.
-
-      await requiresOpenPlaylist()
-
-      const pathString = util.nextArg()
-      console.log("Ignoring path: " + pathString)
-      removeGroupByPathString(activePlaylist, pathString)
-    },
-
-    'r': util => util.alias('-remove'),
-    'x': util => util.alias('-remove'),
-
-    '-filter': async function(util) {
-      // --filter <filterJSON>
-      // Filters the playlist so that only tracks that match the given filter
-      // are kept. FilterJSON should be a JSON object as described in the
-      // man page section "filters".
 
-      const filterJSON = util.nextArg()
+    '-list-all': async function(util) {
+      await originalListAll(util)
 
-      let filterObj
-      try {
-        filterObj = JSON.parse(filterJSON)
-      } catch (error) {
-        console.error('Invalid JSON for filter:', filterJSON)
-        return
+      // As with -l, if this is the last item in the argument list, we
+      // won't actually be playing the playlist.
+      if (util.index === util.argv.length - 1) {
+        shouldPlay = false
       }
-
-      activePlaylist.filters = [filterObj]
-      activePlaylist = await processSmartPlaylist(activePlaylist)
-      activePlaylist = updatePlaylistFormat(activePlaylist)
-    },
-
-    'f': util => util.alias('-filter'),
-
-    '-collapse-groups': async function() {
-      // --collapse-groups  (alias: --collapse)
-      // Collapses groups in the active playlist so that there is only one
-      // level of sub-groups. Handy for shuffling the order groups play in;
-      // try `--collapse-groups --sort shuffle-groups`.
-
-      await requiresOpenPlaylist()
-
-      activePlaylist = updatePlaylistFormat(collapseGrouplike(activePlaylist))
     },
 
-    '-collapse': util => util.alias('-collapse-groups'),
+    // Other options, specific to play ----------------------------------------
 
-    '-flatten-tracks': async function() {
-      // --flatten-tracks  (alias: --flatten)
-      // Flattens the entire active playlist, so that only tracks remain,
-      // and there are no groups.
-
-      await requiresOpenPlaylist()
-
-      activePlaylist = updatePlaylistFormat(flattenGrouplike(activePlaylist))
-    },
-
-    '-flatten': util => util.alias('-flatten-tracks'),
-
-    '-list-groups': async function(util) {
-      // --list-groups  (alias: -l, --list)
-      // Lists all groups in the playlist.
-
-      await requiresOpenPlaylist()
+    '-help': function(util) {
+      // --help  (alias: -h, -?)
+      // Presents a help message.
 
-      console.log(getPlaylistTreeString(activePlaylist))
+      console.log('http-music\nTry man http-music!')
 
-      // If this is the last item in the argument list, the user probably
-      // only wants to get the list, so we'll mark the 'should run' flag
-      // as false.
       if (util.index === util.argv.length - 1) {
         shouldPlay = false
       }
     },
 
-    '-list': util => util.alias('-list-groups'),
-    'l': util => util.alias('-list-groups'),
+    'h': util => util.alias('-help'),
+    '?': util => util.alias('-help'),
 
-    '-list-all': async function(util) {
-      // --list-all  (alias: --list-tracks, -L)
-      // Lists all groups and tracks in the playlist.
+    // Add appends the keybindings to the existing keybindings; import replaces
+    // the current ones with the opened ones.
 
-      await requiresOpenPlaylist()
+    '-add-keybindings': async function(util) {
+      await openKeybindings(util.nextArg())
+    },
 
-      console.log(getPlaylistTreeString(activePlaylist, true))
+    '-open-keybindings': util => util.alias('-add-keybindings'),
 
-      // As with -l, if this is the last item in the argument list, we
-      // won't actually be playing the playlist.
-      if (util.index === util.argv.length - 1) {
-        shouldPlay = false
-      }
+    '-import-keybindings': async function(util) {
+      await openKeybindings(util.nextArg(), false)
     },
 
-    '-list-tracks': util => util.alias('-list-all'),
-    'L': util => util.alias('-list-all'),
-
     '-list-keybindings': function() {
       console.log('Keybindings:')
 
@@ -513,8 +283,13 @@ async function main(args) {
       // Sets the first track to be played.
       // This is especially useful when using an ordered sort; this option
       // could be used to start a long album part way through.
+
       const pathString = util.nextArg()
-      const track = filterPlaylistByPathString(activePlaylist, pathString)
+
+      const track = filterPlaylistByPathString(
+        getStuff.activePlaylist, pathString
+      )
+
       if (isTrack(track)) {
         startTrack = track
         console.log('Starting on track', pathString)
@@ -647,14 +422,18 @@ async function main(args) {
     },
 
     '-trust': util => util.alias('-trust-shell-commands')
-  }
+  })
 
   await processArgv(args, optionFunctions)
 
-  if (!hasOpenedPlaylist) {
+  if (!getStuff.hasOpenedPlaylist) {
     await openDefaultPlaylist()
   }
 
+  // All done processing: let's actually grab the active playlist, which
+  // we'll quickly validate and then play (if it contains tracks).
+  const activePlaylist = getStuff.activePlaylist
+
   if (activePlaylist === null) {
     console.error(
       "Cannot play - no open playlist. Try --open <playlist file>?"