import SparkMD5 from 'spark-md5'; //import JSZip from 'jszip'; import * as starry from '../../src/starry'; //import { encodeFindResource } from '../../src/isomorphic/converter'; import sharp, { FormatEnum } from 'sharp'; import got from 'got'; //import { Logger } from './ZeroClient'; import type { SolutionStore, SaveIssueMeasure } from './store'; import { ScoreJSON } from '../../src/isomorphic/types'; const SYSTEM_MARGIN = 4; export const constructSystem = ({ page, backgroundImage, detection, imageSize, position }) => { const systemWidth = (detection.phi2 - detection.phi1) / detection.interval; const systemHeight = imageSize.height / detection.interval; const lastSystem = page.systems[page.systems.length - 1]; const top = position ? position.y : (lastSystem ? lastSystem.top + lastSystem.height : 0) + SYSTEM_MARGIN; const left = position ? position.x : SYSTEM_MARGIN; const stavesTops = [ 0, ...Array(detection.middleRhos.length - 1) .fill(0) .map((_, i) => (detection.middleRhos[i] + detection.middleRhos[i + 1]) / 2 / detection.interval), ]; const measureBars = [systemWidth]; const staves = stavesTops.map( (top, i) => new starry.Staff({ top, height: (stavesTops[i + 1] || systemHeight) - top, staffY: detection.middleRhos[i] / detection.interval - top, measureBars, }) ); //console.log("detection:", detection, options, stavesTops); const imagePosition = { x: -detection.phi1 / detection.interval, y: 0, width: imageSize.width / detection.interval, height: imageSize.height / detection.interval, }; return new starry.System({ staves, left, top, width: systemWidth, backgroundImage, imagePosition, measureBars, }); }; export interface ConvertOption { format?: keyof FormatEnum; quality?: number; maxHeight?: number; } const toBuffer = async (url: string | Buffer): Promise => { if (typeof url === 'string') { if (/^https?:\/\//.test(url)) { return (await got(url, { responseType: 'buffer', decompress: true, https: { rejectUnauthorized: false } })).body; } if (/^data:image\//.test(url)) { return Buffer.from(url.split(',')[1], 'base64'); } return Buffer.from(url); } return url; }; /** * 转换图片格式,默认webp、最大高度1080,高度小于1080自动不做尺寸变换 * @param url * @param format * @param maxHeight * @param quality */ export async function convertImage(url: string | Buffer, { format = 'webp', maxHeight = 1080, quality = 80 }: ConvertOption = {}) { let buf = await toBuffer(url); const webpBuffer = await new Promise((resolve) => { sharp(buf) .resize({ width: maxHeight, height: maxHeight, fit: 'inside', withoutEnlargement: true, }) .toFormat(format, { quality }) .toBuffer((err, buf) => { resolve(buf); }); }); const md5 = SparkMD5.ArrayBuffer.hash(webpBuffer); return { buffer: webpBuffer, filename: `${md5}.${format}`, }; } /** * 替换scoreJson图片地址 * @param scoreJson * @param onReplaceImage */ export const replaceScoreJsonImages = (scoreJson: ScoreJSON, onReplaceImage: (src: string) => string = (src) => src) => { const json = JSON.parse(JSON.stringify(scoreJson)); json.pages.forEach((page) => { page?.src && (page.src = onReplaceImage(page?.src)); }); json.lines.forEach((system) => { system.lineStaves.forEach((line) => { line.imgs.forEach((staff) => { staff?.src && (staff.src = onReplaceImage(staff.src)); }); }); }); return json; }; /** * 获取scoreJson图片资源列表 * @param scoreJson */ export const getScoreJsonImages = (scoreJson: ScoreJSON) => { return [ ...scoreJson.pages.map((page) => page?.src), ...scoreJson.lines .map((system) => system.lineStaves.map((staff) => staff.imgs)) .flat(2) .map((staff) => staff?.src) .filter(Boolean), ]; }; interface ScorePatchesUpdateOptions { solutionStore?: SolutionStore; } export const updateScorePatches = (score: starry.Score, measures: starry.SpartitoMeasure[], options: ScorePatchesUpdateOptions = {}): void => { console.assert( measures.every((measure) => measure.validRegulated), '[updateScorePatches] some measures not valid regulated:', measures.filter((measure) => !measure.validRegulated) ); score.patches = measures.map((measure) => measure.createPatch()); if (options?.solutionStore) { score.assemble(); const spartito = score.makeSpartito(); measures.forEach((measure) => { options.solutionStore!.set(measure.regulationHash, { ...measure.asSolution(), priority: 1 }); if (measure.regulationHash0 !== measure.regulationHash) { const originMeasure = spartito.measures.find((m) => m.measureIndex === measure.measureIndex); options.solutionStore!.set(measure.regulationHash0, { ...measure.asSolution(originMeasure), priority: 1 }); } }); } }; interface EditableMeasuresSaveOptions { status?: number; solutionStore?: SolutionStore; } export const saveEditableMeasures = async ( score: starry.Score, measureIndices: number[], saveMeasure: SaveIssueMeasure, { status = 2, solutionStore }: EditableMeasuresSaveOptions = {} ): Promise => { score.assemble(); const spartito = score.spartito || score.makeSpartito(); const measures = measureIndices .map((index) => spartito.measures.find((measure) => measure.measureIndex === index)) .filter(Boolean) as starry.SpartitoMeasure[]; if (solutionStore) { const solutions = await solutionStore.batchGet(measures.map((measure) => measure.regulationHash0)); measures.forEach((measure, i) => { const solution = solutions[i]; if (solution) measure.applySolution(solution); }); } measures.forEach((measure) => { saveMeasure({ measureIndex: measure.measureIndex, measure: new starry.EditableMeasure(measure), status, }); }); };