|
@@ -0,0 +1,253 @@
|
|
|
+/*
|
|
|
+ Render each OSMD sample, grab the generated images, and
|
|
|
+ dump them into a local directory as PNG files.
|
|
|
+
|
|
|
+ inspired by Vexflow's generate_png_images and vexflow-tests.js
|
|
|
+
|
|
|
+ This can be used to generate PNGs from OSMD without a browser.
|
|
|
+ It's also used with the visual regression test system in
|
|
|
+ `tools/visual_regression.sh`.
|
|
|
+
|
|
|
+ Note: this script needs to "fake" quite a few browser elements, like window, document, and a Canvas HTMLElement.
|
|
|
+ For that it needs the canvas package installed.
|
|
|
+ There are also some hacks needed to set the container size (offsetWidth) correctly.
|
|
|
+*/
|
|
|
+
|
|
|
+function sleep (ms) {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ setTimeout(resolve, ms)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+async function init () {
|
|
|
+ console.log('[OSMD.generate] init')
|
|
|
+
|
|
|
+ let [osmdBuildDir, sampleDir, imageDir, pageWidth, pageHeight, filterRegex, debugFlag, debugSleepTimeString] = process.argv.slice(2, 10)
|
|
|
+ if (!osmdBuildDir || !sampleDir || !imageDir) {
|
|
|
+ console.log('usage: node test/Util/generateImages_browserless.js osmdBuildDir sampleDirectory imageDirectory [width|0] [height|0] [filterRegex|all] [--debug] [debugSleepTime]')
|
|
|
+ console.log(' (use "all" to skip filterRegex parameter)')
|
|
|
+ console.log('example: node test/Util/generateImages_browserless.js ../../build ./test/data/ ./export 210 297 all --debug 5000')
|
|
|
+ console.log('Error: need sampleDir and imageDir. Exiting.')
|
|
|
+ process.exit(1)
|
|
|
+ }
|
|
|
+ console.log('sampleDir: ' + sampleDir)
|
|
|
+ console.log('imageDir: ' + imageDir)
|
|
|
+
|
|
|
+ let pageFormat = 'Endless'
|
|
|
+ pageWidth = Number.parseInt(pageWidth)
|
|
|
+ pageHeight = Number.parseInt(pageHeight)
|
|
|
+ const endlessPage = !(pageHeight > 0 && pageWidth > 0)
|
|
|
+ if (!endlessPage) {
|
|
|
+ pageFormat = `${pageWidth}x${pageHeight}`
|
|
|
+ }
|
|
|
+
|
|
|
+ const DEBUG = debugFlag === '--debug'
|
|
|
+ // const debugSleepTime = Number.parseInt(process.env.GENERATE_DEBUG_SLEEP_TIME) || 0; // 5000 works for me [sschmidTU]
|
|
|
+ if (DEBUG) {
|
|
|
+ console.log('debug sleep time: ' + debugSleepTimeString)
|
|
|
+ const debugSleepTimeMs = Number.parseInt(debugSleepTimeString)
|
|
|
+ if (debugSleepTimeMs > 0) {
|
|
|
+ await sleep(Number.parseInt(debugSleepTimeMs))
|
|
|
+ // [VSCode] apparently this is necessary for the debugger to attach itself in time before the program closes.
|
|
|
+ // sometimes this is not enough, so you may have to try multiple times or increase the sleep timer. Unfortunately debugging nodejs isn't easy.
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ---- hacks to fake Browser elements OSMD and Vexflow need, like window, document, and a canvas HTMLElement ----
|
|
|
+ const { JSDOM } = require('jsdom')
|
|
|
+ const dom = new JSDOM('<!DOCTYPE html></html>')
|
|
|
+ // eslint-disable-next-line no-global-assign
|
|
|
+ window = dom.window
|
|
|
+ // eslint-disable-next-line no-global-assign
|
|
|
+ document = dom.window.document
|
|
|
+
|
|
|
+ // eslint-disable-next-line no-global-assign
|
|
|
+ global.window = dom.window
|
|
|
+ // eslint-disable-next-line no-global-assign
|
|
|
+ global.document = window.document
|
|
|
+ global.HTMLElement = window.HTMLElement
|
|
|
+ global.HTMLAnchorElement = window.HTMLAnchorElement
|
|
|
+ global.XMLHttpRequest = window.XMLHttpRequest
|
|
|
+ global.DOMParser = window.DOMParser
|
|
|
+ global.Node = window.Node
|
|
|
+ global.Canvas = window.Canvas
|
|
|
+
|
|
|
+ // fix Blob not found
|
|
|
+ const Blob = require('cross-blob')
|
|
|
+
|
|
|
+ // eslint-disable-next-line no-new
|
|
|
+ new Blob([])
|
|
|
+ // => Blob {size: 0, type: ''}
|
|
|
+
|
|
|
+ // Global patch (to support external modules like is-blob).
|
|
|
+ global.Blob = Blob
|
|
|
+
|
|
|
+ const div = document.createElement('div')
|
|
|
+ div.id = 'browserlessDiv'
|
|
|
+ document.body.appendChild(div)
|
|
|
+ // const canvas = document.createElement('canvas')
|
|
|
+ // div.canvas = document.createElement('canvas')
|
|
|
+
|
|
|
+ const zoom = 1.0
|
|
|
+ // somehow, witdh * 5 will preserve the aspect ratio (0.7070 repeating, *1 will be way too short, *10 too long)
|
|
|
+ // there's width * zoom * 10 in the OSMD code because Vexflow's pixels are OSMD's size units * 10, so i thought it should be * 10.
|
|
|
+ // not sure where the / 2 factor comes from.
|
|
|
+ let width = pageWidth * zoom * 5
|
|
|
+ if (endlessPage) {
|
|
|
+ width = 1440
|
|
|
+ }
|
|
|
+ let height = pageHeight
|
|
|
+ if (endlessPage) {
|
|
|
+ height = 32767
|
|
|
+ }
|
|
|
+ div.width = width
|
|
|
+ div.height = height
|
|
|
+ div.offsetWidth = width // doesn't work, offsetWidth is always 0 from this. see below
|
|
|
+ div.clientWidth = width
|
|
|
+ div.clientHeight = height
|
|
|
+ div.scrollHeight = height
|
|
|
+ div.scrollWidth = width
|
|
|
+ div.setAttribute('width', width)
|
|
|
+ div.setAttribute('height', height)
|
|
|
+ div.setAttribute('offsetWidth', width)
|
|
|
+ debug('div.offsetWidth: ' + div.offsetWidth, DEBUG)
|
|
|
+ debug('div.height: ' + div.height, DEBUG)
|
|
|
+
|
|
|
+ // hack: set offsetWidth reliably
|
|
|
+ Object.defineProperties(window.HTMLElement.prototype, {
|
|
|
+ offsetLeft: {
|
|
|
+ get: function () { return parseFloat(window.getComputedStyle(this).marginTop) || 0 }
|
|
|
+ },
|
|
|
+ offsetTop: {
|
|
|
+ get: function () { return parseFloat(window.getComputedStyle(this).marginTop) || 0 }
|
|
|
+ },
|
|
|
+ offsetHeight: {
|
|
|
+ get: function () { return height }
|
|
|
+ },
|
|
|
+ offsetWidth: {
|
|
|
+ get: function () { return width }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ debug('div.offsetWidth: ' + div.offsetWidth, DEBUG)
|
|
|
+ debug('div.height: ' + div.height, DEBUG)
|
|
|
+ // ---- end browser hacks (hopefully) ----
|
|
|
+
|
|
|
+ const OSMD = require(`${osmdBuildDir}/opensheetmusicdisplay.min.js`)
|
|
|
+
|
|
|
+ const fs = require('fs')
|
|
|
+ // Create the image directory if it doesn't exist.
|
|
|
+ fs.mkdirSync(imageDir, { recursive: true })
|
|
|
+
|
|
|
+ const sampleDirFilenames = fs.readdirSync(sampleDir)
|
|
|
+ let samplesToProcess = [] // samples we want to process/generate pngs of, excluding the filtered out files/filenames
|
|
|
+ for (const sampleFilename of sampleDirFilenames) {
|
|
|
+ if (DEBUG) {
|
|
|
+ if (sampleFilename.match('^(Actor)|(Gounod)')) {
|
|
|
+ console.log('DEBUG: filtering big file: ' + sampleFilename)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // eslint-disable-next-line no-useless-escape
|
|
|
+ if (sampleFilename.match('^.*(\.xml)|(\.musicxml)|(\.mxl)$')) {
|
|
|
+ // console.log('found musicxml/mxl: ' + sampleFilename)
|
|
|
+ samplesToProcess.push(sampleFilename)
|
|
|
+ } else {
|
|
|
+ console.log('discarded file/directory: ' + sampleFilename)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // filter samples to process by regex if given
|
|
|
+ if (filterRegex && filterRegex !== '' && filterRegex !== 'all') {
|
|
|
+ console.log('filtering samples for regex: ' + filterRegex)
|
|
|
+ samplesToProcess = samplesToProcess.filter((filename) => filename.match(filterRegex))
|
|
|
+ console.log(`found ${samplesToProcess.length} matches: `)
|
|
|
+ for (let i = 0; i < samplesToProcess.length; i++) {
|
|
|
+ console.log(samplesToProcess[i])
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const osmdInstance = new OSMD.OpenSheetMusicDisplay(div, {
|
|
|
+ autoResize: false,
|
|
|
+ backend: 'canvas',
|
|
|
+ pageBackgroundColor: '#FFFFFF',
|
|
|
+ pageFormat: pageFormat
|
|
|
+ })
|
|
|
+ // await sleep(5000)
|
|
|
+ if (DEBUG) {
|
|
|
+ osmdInstance.setLogLevel('debug')
|
|
|
+ // console.log(`osmd PageFormat: ${osmdInstance.EngravingRules.PageFormat.width}x${osmdInstance.EngravingRules.PageFormat.height}`)
|
|
|
+ console.log(`osmd PageFormat idString: ${osmdInstance.EngravingRules.PageFormat.idString}`)
|
|
|
+ console.log('PageHeight: ' + osmdInstance.EngravingRules.PageHeight)
|
|
|
+ }
|
|
|
+
|
|
|
+ debug('generateImages', DEBUG)
|
|
|
+ for (let i = 0; i < samplesToProcess.length; i++) {
|
|
|
+ var sampleFilename = samplesToProcess[i]
|
|
|
+ debug('sampleFilename: ' + sampleFilename, DEBUG)
|
|
|
+
|
|
|
+ let loadParameter = fs.readFileSync(sampleDir + '/' + sampleFilename)
|
|
|
+ if (sampleFilename.endsWith('.mxl')) {
|
|
|
+ loadParameter = await OSMD.MXLHelper.MXLtoXMLstring(loadParameter)
|
|
|
+ } else {
|
|
|
+ loadParameter = loadParameter.toString()
|
|
|
+ }
|
|
|
+ // console.log('loadParameter: ' + loadParameter)
|
|
|
+ // console.log('typeof loadParameter: ' + typeof loadParameter)
|
|
|
+
|
|
|
+ await osmdInstance.load(loadParameter).then(function () {
|
|
|
+ debug('xml loaded', DEBUG)
|
|
|
+ try {
|
|
|
+ osmdInstance.render()
|
|
|
+ } catch (ex) {
|
|
|
+ console.log('renderError: ' + ex)
|
|
|
+ }
|
|
|
+ debug('rendered', DEBUG)
|
|
|
+
|
|
|
+ const dataUrls = []
|
|
|
+ let canvasImage
|
|
|
+
|
|
|
+ for (let pageNumber = 1; pageNumber < 999; pageNumber++) {
|
|
|
+ canvasImage = document.getElementById('osmdCanvasVexFlowBackendCanvas' + pageNumber)
|
|
|
+ if (!canvasImage) {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if (!canvasImage.toDataURL) {
|
|
|
+ console.log(`error: could not get canvas image for page ${pageNumber} for file: ${sampleFilename}`)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ dataUrls.push(canvasImage.toDataURL())
|
|
|
+ }
|
|
|
+ for (let urlIndex = 0; urlIndex < dataUrls.length; urlIndex++) {
|
|
|
+ const pageNumberingString = `_${urlIndex + 1}`
|
|
|
+ // pageNumberingString = dataUrls.length > 0 ? pageNumberingString : '' // don't put '_1' at the end if only one page. though that may cause more work
|
|
|
+ var pageFilename = `${imageDir}/${sampleFilename}${pageNumberingString}.png`
|
|
|
+
|
|
|
+ const dataUrl = dataUrls[urlIndex]
|
|
|
+ if (!dataUrl || !dataUrl.split) {
|
|
|
+ console.log(`error: could not get dataUrl (imageData) for page ${urlIndex + 1} of sample: ${sampleFilename}`)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ const imageData = dataUrl.split(';base64,').pop()
|
|
|
+ const imageBuffer = Buffer.from(imageData, 'base64')
|
|
|
+
|
|
|
+ console.log('got image data, saving to: ' + pageFilename)
|
|
|
+ fs.writeFileSync(pageFilename, imageBuffer, { encoding: 'base64' })
|
|
|
+ }
|
|
|
+ }) // end render then
|
|
|
+ // },
|
|
|
+ // function (e) {
|
|
|
+ // console.log('error while rendering: ' + e)
|
|
|
+ // }) // end load then
|
|
|
+ // }) // end read file
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[OSMD.generate_browserless] exit')
|
|
|
+}
|
|
|
+
|
|
|
+function debug (msg, debugEnabled) {
|
|
|
+ if (debugEnabled) {
|
|
|
+ console.log(msg)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+init()
|