UNPKG

@sequencemedia/music-library-parser

Version:

Parse Apple Music Library XML to JSON, JS, ES, or M3U

130 lines (109 loc) 3.12 kB
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'