starry / backend /libs /util.ts
k-l-lambda's picture
feat: add Python ML services (CPU mode) with model download
2b7aae2
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<Buffer> => {
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<Buffer>((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<void> => {
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,
});
});
};