@sequencemedia/music-library-parser
Version:
Parse Apple Music Library XML to JSON, JS, ES, or M3U
130 lines (109 loc) • 3.12 kB
JavaScript
import {
exec
} from 'node:child_process'
import {
resolve,
join
} from 'node:path'
import debug from 'debug'
import {
clear
} from '#music-library-parser'
import hereIAm from '#where-am-i'
const log = debug('@sequencemedia/music-library-parser:to-m3u')
const error = debug('@sequencemedia/music-library-parser:to-m3u:error')
const xsl = join(hereIAm, 'src/xsl/library.xsl')
let immediate = null
const queue = []
export function parse (jar, xml, destination = './Music Library') {
const j = resolve(jar)
const x = resolve(xml)
const d = resolve(destination)
return (
new Promise((resolve, reject) => {
exec(`java -jar "${j}" -s:"${x}" -xsl:"${xsl}" destination="${d}"`, { cwd: hereIAm }, (e) => (!e) ? resolve() : reject(e))
})
)
}
export async function toM3U (jar, xml, destination) {
/**
* Ignore these values if they are duplicates of some in the queue
*/
if (!queue.some(({ jar: j, xml: x, destination: d }) => (
j === jar &&
x === xml &&
d === destination
))) {
/**
* These values are unique, so
*/
if (immediate) {
/**
* XML is being parsed. En-queue these values
*/
queue.push({ jar, xml, destination })
/**
* (... if the XML changes while it's being parsed
* it should be parsed again immediately after,
* so the second of two identical calls will be
* put into the queue while the first is executing
*
* In other words: the first call isn't put into the
* queue in order to allow that second call to be
* put into the queue!)
*/
} else {
/**
* XML is not being parsed
*/
immediate = setImmediate(async () => {
try {
/**
* Clear the destination directory
*/
await clear(destination)
log(`Parsing "${xml}" ...`)
/**
* Parse these values immediately
*/
await parse(jar, xml, destination)
log(`Succeeded parsing "${xml}"`)
immediate = null
if (queue.length) {
/**
* The queue is not empty. De-queue some values
*/
const {
jar: j,
xml: x,
destination: d
} = queue.shift()
/**
* Repeat
*/
return toM3U(j, x, d)
}
/**
* Return handle from `setImmediate` or null
*/
return immediate
} catch (e) {
const {
code = 'No error code defined'
} = e
if (code === 2) {
error('I/O error in Saxon', e)
} else {
const {
message = 'No error message defined'
} = e
error(code, message, e)
}
}
})
}
}
}
export * as tracks from '#music-library-parser/library/tracks'
export * as playlists from '#music-library-parser/library/playlists'
export * as transform from '#music-library-parser/library/transform'