musescore-downloader/src/main.ts

183 lines
5.1 KiB
TypeScript

import "./meta"
import { ScorePlayerData } from "./types"
import { waitForDocumentLoaded } from "./utils"
import { PDFWorkerHelper } from "./worker-helper"
import FileSaver from "file-saver/dist/FileSaver.js"
const saveAs: typeof import("file-saver").saveAs = FileSaver.saveAs
let pdfBlob: Blob
const imgToBlob = async (imgURL: string) => {
const imageElement = document.createElement("img")
imageElement.style.display = "none"
document.body.appendChild(imageElement)
imageElement.src = imgURL
// wait until image loaded
await new Promise((resolve) => {
imageElement.onload = () => resolve()
})
const { naturalWidth: width, naturalHeight: height } = imageElement
const canvas = document.createElement("canvas")
const canvasContext = canvas.getContext("2d")
canvas.width = width
canvas.height = height
canvas.style.display = "none"
document.body.appendChild(canvas)
canvasContext.clearRect(0, 0, width, height)
canvasContext.drawImage(imageElement, 0, 0)
const data: Blob = await new Promise(resolve => canvas.toBlob(resolve, "image/png"))
canvas.remove()
imageElement.remove()
return data
}
const generatePDF = async (svgURLs: string[], name?: string) => {
if (pdfBlob) {
return saveAs(pdfBlob, `${name}.pdf`)
}
const cachedImg = document.querySelector("img[id^=score_]") as HTMLImageElement
const { naturalWidth: width, naturalHeight: height } = cachedImg
const imgDataBlobList = await Promise.all(svgURLs.map(imgToBlob))
const worker = new PDFWorkerHelper()
const pdfArrayBuffer = await worker.generatePDF(imgDataBlobList, width, height)
worker.terminate()
pdfBlob = new Blob([pdfArrayBuffer])
saveAs(pdfBlob, `${name}.pdf`)
}
const getPagesNumber = (scorePlayerData: ScorePlayerData) => {
try {
return scorePlayerData.json.metadata.pages
} catch (_) {
return document.querySelectorAll("img[id^=score_]").length
}
}
const getImgType = (): "svg" | "png" => {
try {
const imgE: HTMLImageElement = document.querySelector("img[id^=score_]")
const { pathname } = new URL(imgE.src)
const imgtype = pathname.match(/\.(\w+)$/)[1]
return imgtype as "svg" | "png"
} catch (_) {
return null
}
}
const getTitle = (scorePlayerData: ScorePlayerData) => {
try {
return scorePlayerData.json.metadata.title
} catch (_) {
return ""
}
}
const getScoreFileName = (scorePlayerData: ScorePlayerData) => {
return getTitle(scorePlayerData).replace(/[\s<>:"/\\|?*~\0\cA-\cZ]+/g, "_")
}
const main = () => {
// @ts-ignore
if (!window.UGAPP || !window.UGAPP.store || !window.UGAPP.store.jmuse_settings) { return }
// @ts-ignore
const scorePlayer: ScorePlayerData = window.UGAPP.store.jmuse_settings.score_player
const { id } = scorePlayer.json
const baseURL = scorePlayer.urls.image_path
// const msczURL = `https://musescore.com/static/musescore/scoredata/score/${getIndexPath(id)}/${id}/score_${vid}_${scoreHexId}.mscz`
// https://github.com/Xmader/cloudflare-worker-musescore-mscz
const msczURL = `https://musescore-mscz.99.workers.dev/${id}`
const mxlURL = baseURL + "score.mxl"
const { midi: midiURL, mp3: mp3URL } = scorePlayer.urls
const btnsDiv = document.querySelector(".score-right .buttons-wrapper") || document.querySelectorAll("aside section > div")[3]
const downloadBtn = btnsDiv.querySelector("button, .button") as HTMLElement
downloadBtn.onclick = null
const imgType = getImgType() || "svg"
const svgURLs = Array.from({ length: getPagesNumber(scorePlayer) }).fill(null).map((_, i) => {
return baseURL + `score_${i}.${imgType}`
})
const downloadURLs = {
"MSCZ": msczURL,
"PDF": null,
"MusicXML": mxlURL,
"MIDI": midiURL,
"MP3": mp3URL,
}
const createBtn = (name: string) => {
const btn = downloadBtn.cloneNode(true) as HTMLElement
if (btn.nodeName.toLowerCase() == "button") {
btn.setAttribute("style", "width: 205px !important")
} else {
btn.dataset.target = ""
}
const textNode = [...btn.childNodes].find((x) => {
return x.textContent.includes("Download")
})
textNode.textContent = `Download ${name}`
return {
btn,
textNode,
}
}
const newDownloadBtns = Object.keys(downloadURLs).map((name) => {
const url = downloadURLs[name]
const { btn, textNode } = createBtn(name)
if (name !== "PDF") {
btn.onclick = () => {
window.open(url)
}
} else {
btn.onclick = () => {
const text = textNode.textContent
const filename = getScoreFileName(scorePlayer)
textNode.textContent = "Processing…"
generatePDF(svgURLs, filename).then(() => {
textNode.textContent = text
})
}
}
return btn
})
downloadBtn.replaceWith(...newDownloadBtns)
}
waitForDocumentLoaded().then(main)