diff options
-rw-r--r-- | man/http-music-crawl-itunes.1 | 25 | ||||
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | src/crawl-itunes.js | 103 | ||||
-rw-r--r-- | todo.txt | 3 | ||||
-rw-r--r-- | yarn.lock | 10 |
5 files changed, 145 insertions, 2 deletions
diff --git a/man/http-music-crawl-itunes.1 b/man/http-music-crawl-itunes.1 new file mode 100644 index 0000000..9d21734 --- /dev/null +++ b/man/http-music-crawl-itunes.1 @@ -0,0 +1,25 @@ +.TH http-music-crawl-itunes 1 + +.SH NAME +http-music-crawl-itunes - create a playlist file using an iTunes library + +.SH SYNOPSIS +.B http-music-crawl-itunes +[libraryPath] + +.SH DESCRIPTION +\fBhttp-music-crawl-itunes\fR is a command line utility used to generate a +playlist file for usage with \fBhttp-music\fR using the Apple iTunes library +XML format. + +It requires the iTunes XML format; this is not the usual iTunes Library.itl format, but it is officially supported by Apple (though not used in recent Apple software). +This iTunes XML file is automatically generated by iTunes if the \fB"Share iTunes Library XML with other applications"\fR option is checked in the advanced preferences section of iTunes. +If this option is not checked (which is the case, by default), it must be enabled for crawl-itunes to function. + +The path for the iTunes XML library file may be passed as an argument; if not given, it is assumed to be \fB~/Music/iTunes/iTunes Music Library.xml\fR. + +The resulting playlist file is sorted as a hierarchial tree formed of a group for each artist and then a group for each of the artists' albums. +See the \fBEXAMPLES\fR section of \fBhttp-music\fR (1) for information on filtering to a specific artist or album. + +.SH SEE ALSO +For more information on the iTunes Library XML file: \fIhttps://support.apple.com/en-ca/HT201610\fR diff --git a/package.json b/package.json index f082d05..92f11ea 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "bin": { "http-music": "./src/http-music.js", "http-music-crawl-http": "./src/crawl-http.js", - "http-music-crawl-local": "./src/crawl-local.js" + "http-music-crawl-local": "./src/crawl-local.js", + "http-music-crawl-itunes": "./src/crawl-itunes.js" }, "man": [ "./man/http-music.1", @@ -23,6 +24,7 @@ "node-fetch": "^1.7.0", "node-natural-sort": "^0.8.6", "sanitize-filename": "^1.6.1", - "tempy": "^0.1.0" + "tempy": "^0.1.0", + "xmldoc": "^1.1.0" } } diff --git a/src/crawl-itunes.js b/src/crawl-itunes.js new file mode 100644 index 0000000..ec6c3ec --- /dev/null +++ b/src/crawl-itunes.js @@ -0,0 +1,103 @@ + +const fs = require('fs') +const path = require('path') +const xmldoc = require('xmldoc') + +const { promisify } = require('util') +const readFile = promisify(fs.readFile) + +function getDictValue(dict, key) { + if (dict.name !== 'dict') { + throw new Error("Not a dict: " + dict.name) + } + + for (let i = 0; i < dict.children.length; i++) { + const child = dict.children[i] + if (child.name === 'key') { + if (child.val === key) { + return dict.children.slice(i + 1).find(item => !item.text) + } + } + } + + return null +} + +async function crawl(libraryXML) { + const document = new xmldoc.XmlDocument(libraryXML) + + const libraryDict = document.children.find(child => child.name === 'dict') + + const tracksDict = getDictValue(libraryDict, 'Tracks') + + const trackDicts = tracksDict.children.filter(child => child.name === 'dict') + + const result = [] + + for (let trackDict of trackDicts) { + let kind = getDictValue(trackDict, 'Kind') + kind = kind && kind.val + kind = kind || '' + + if (!kind.includes('audio file')) { + continue + } + + let location = getDictValue(trackDict, 'Location') + location = location && location.val + location = location || '' + + if (!location) { + continue + } + + let name = getDictValue(trackDict, 'Name') + name = name && name.val + name = name || 'Unknown Name' + + let album = getDictValue(trackDict, 'Album') + album = album && album.val + album = album || 'Unknown Album' + + let artist = getDictValue(trackDict, 'Artist') + artist = artist && artist.val + artist = artist || 'Unknown Artist' + + // console.log(`${artist} - ${name} (${album})`) + + const group = (arr, title) => arr.find(g => g[0] === title) + + let artistGroup = group(result, artist) + + if (!artistGroup) { + artistGroup = [artist, []] + result.push(artistGroup) + } + + let albumGroup = group(artistGroup[1], album) + + if (!albumGroup) { + albumGroup = [album, []] + artistGroup[1].push(albumGroup) + } + + albumGroup[1].push([name, location]) + } + + return result +} + +async function main() { + const libraryPath = process.argv[2] || ( + `${process.env.HOME}/Music/iTunes/iTunes Music Library.xml` + ) + + const library = await readFile(libraryPath) + + const playlist = await crawl(library) + + console.log(JSON.stringify(playlist, null, 2)) +} + +main() + .catch(err => console.error(err)) diff --git a/todo.txt b/todo.txt index 9317d3e..e6c4c76 100644 --- a/todo.txt +++ b/todo.txt @@ -163,3 +163,6 @@ TODO: The results of pressing key commands aren't very clear currently. Useful TODO: Figure out a way to make the same mpv process be reused, so that options such as volume can be remembered. (At the moment volume is reset to the default whenever a new track is played!) + +TODO: Figure out how to stream audio data directly, or at least at a lower + level (and stupider, as in "man git" stupid). diff --git a/yarn.lock b/yarn.lock index 414cc7d..86bf07a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -180,6 +180,10 @@ sanitize-filename@^1.6.1: dependencies: truncate-utf8-bytes "^1.0.0" +sax@^1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + string_decoder@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.1.tgz#62e200f039955a6810d8df0a33ffc0f013662d98" @@ -218,3 +222,9 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" +xmldoc: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.1.0.tgz#25c92f08f263f344dac8d0b32370a701ee9d0e93" + dependencies: + sax "^1.2.1" + |