« get me outta code hell

crawl-local.js « src - http-music - Command-line music player + utils (not a server!)
about summary refs log tree commit diff
path: root/src/crawl-local.js
blob: bd83552ed26efa09bc7faac3711c0327d7705e89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/usr/bin/env node

'use strict'

const fs = require('fs')
const path = require('path')
const naturalSort = require('node-natural-sort')
const processArgv = require('./process-argv')

const { promisify } = require('util')
const readDir = promisify(fs.readdir)
const stat = promisify(fs.stat)

function sortIgnoreCase(sortFunction) {
  return function(a, b) {
    return sortFunction(a.toLowerCase(), b.toLowerCase())
  }
}

function crawl(dirPath, extensions = [
  // This list isn't very extensive, and can be customized via the
  // --extensions (or --exts, -e) option.
  'ogg', 'oga',
  'wav', 'mp3', 'mp4', 'm4a', 'aac',
  'mod'
]) {
  return readDir(dirPath).then(items => {
    items.sort(sortIgnoreCase(naturalSort()))

    return Promise.all(items.map(item => {
      const itemPath = path.join(dirPath, item)

      return stat(itemPath).then(stats => {
        if (stats.isDirectory()) {
          return crawl(itemPath, extensions)
            .then(group => Object.assign({name: item}, group))
        } else if (stats.isFile()) {
          // Extname returns a string starting with a dot; we don't want the
          // dot, so we slice it off of the front.
          const ext = path.extname(item).slice(1)

          if (extensions.includes(ext)) {
            // The name of the track doesn't include the file extension; a user
            // probably wouldn't add the file extensions to a hand-written
            // playlist, or want them in an auto-generated one.
            const basename = path.basename(item, path.extname(item))

            const track = {name: basename, downloaderArg: itemPath}
            return track
          } else {
            return null
          }
        }
      })
    }))
  }).then(items => items.filter(Boolean))
    .then(filteredItems => ({items: filteredItems}))
}

async function main(args, shouldReturn = false) {
  if (args.length === 0) {
    console.log("Usage: crawl-local /example/path [opts]")
    return
  }

  const path = args[0]

  let extensions

  await processArgv(args.slice(1), {
    '-extensions': function(util) {
      // --extensions <extensions>  (alias: --exts, -e)
      // Sets file extensions that are considered music tracks to be added to
      // the result playlist.
      // <extensions> is a comma-separated list of extensions, e.g. 'mp3,wav'.
      // A default list of extensions exists but is not *extremely* extensive.
      // (Use --extensions when needed!)

      extensions = util.nextArg().split(',')

      // *Somebody*'s going to start the extensions with dots; may as well be
      // careful for that!
      extensions = extensions.map(e => e.startsWith('.') ? e.slice(1) : e)
    },

    '-exts': util => util.alias('-extensions'),
    'e': util => util.alias('-extensions')
  })

  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}

if (require.main === module) {
  main(process.argv.slice(2))
    .catch(err => console.error(err))
}