import { stat, readFile, readdir, copyFile } from "node:fs/promises"; import { join, extname, resolve } from "node:path"; import { parseDwi } from "./parseDwi"; import { parseSm } from "./parseSm"; type RawSimfile = Omit & { title: string; titletranslit: string | null; banner: string | null; bannerEncoded: string | null; displayBpm: string | undefined; }; type Parser = (simfileSource: string, titleDir: string) => Promise; const parsers: Record = { ".sm": parseSm, ".dwi": parseDwi, }; async function getSongFile(songDir: string): Promise { const files = await readdir(songDir); // TODO: support more than .sm const extensions = Object.keys(parsers); const songFile = files.find((f) => extensions.some((ext) => f.endsWith(ext))); if (!songFile) { throw new Error(`No song file found in ${songDir}`); } return songFile; } function toSafeName(name: string): string { let name2 = name; name2 = name2.replace(".png", ""); name2 = name2.replace(/\s/g, "-").replace(/[^\w]/g, "_"); return `${name}.png`; } function getBpms(sm: RawSimfile): number[] { const chart = Object.values(sm.charts)[0]; return chart.bpm.map((b) => b.bpm); } async function parseSimfile( rootDir: string, mixDir: string, titleDir: string, ): Promise> { const stepchartSongDirPath = join(rootDir, mixDir, titleDir); const songFile = await getSongFile(stepchartSongDirPath); const stepchartPath = join(stepchartSongDirPath, songFile); const extension = extname(stepchartPath); const parser = parsers[extension]; if (!parser) { throw new Error(`No parser registered for extension: ${extension}`); } const fileContents = await readFile(stepchartPath); const rawStepchart = await parser( fileContents.toString(), stepchartSongDirPath, ); const bannerPath = join(stepchartSongDirPath, rawStepchart.banner); try { const bannerData = await readFile(bannerPath); rawStepchart.bannerEncoded = bannerData.toString("base64"); } catch (e) {} // if (bannerMeta !== undefined) { // const publicName = toSafeName(`${mixDir}-${rawStepchart.banner}`); // const srcPath = resolve(stepchartSongDirPath, rawStepchart.banner); // const destPath = resolve("public/bannerImages", publicName); // await copyFile(srcPath, destPath); // await copyFile( // join(stepchartSongDirPath, rawStepchart.banner), // join("components/bannerImages", publicName), // ); // rawStepchart.banner = publicName; // } else { // rawStepchart.banner = null; // } const bpms = getBpms(rawStepchart); const minBpm = Math.round(Math.min(...bpms)); const maxBpm = Math.round(Math.max(...bpms)); const displayBpm = minBpm === maxBpm ? minBpm.toString() : `${minBpm}-${maxBpm}`; return { ...rawStepchart, title: { titleName: rawStepchart.title, translitTitleName: rawStepchart.titletranslit ?? null, titleDir, banner: rawStepchart.banner, }, minBpm, maxBpm, displayBpm, stopCount: Object.values(rawStepchart.charts)[0].stops.length, }; } export { parseSimfile }; export type { RawSimfile };