// TODO: Make this concurrent instead of serial import { join, extname } from "node:path"; import { pipeline } from "node:stream/promises"; import { Readable } from "node:stream"; import * as ndjson from "ndjson"; import { readdir, writeFile, stat } from "node:fs/promises"; import { createWriteStream } from "node:fs"; import { parseSm } from "../lib/stepcharts/parseSm"; import { parseSimfile } from "../lib/stepcharts/parseSimfile"; const rootDir = process.env.STEPCHARTS_DIR ?? "/tmp/stepcharts"; const stepchartsDir = join(rootDir, "prodStepcharts"); const outFile = process.env.OUT_FILE ?? "data/stepData.ndjson"; async function* mixToSongs( mixDirs: string[], ): AsyncGenerator<[string, string], void, unknown> { for (const mix of mixDirs) { const mixDir = join(stepchartsDir, mix); const files = await readdir(mixDir); for (const song of files) { yield [mix, song]; } } } async function* processSong(gen: AsyncGenerator<[string, string]>) { for await (const [mix, song] of gen) { const totalPath = join(stepchartsDir, mix, song); const meta = await stat(totalPath); if (!meta.isDirectory()) continue; try { const parsed = await parseSimfile(stepchartsDir, mix, song); for (const [name, chart] of Object.entries(parsed.charts)) { yield { difficulty: name, artist: parsed.artist, title: parsed.title.titleName, titleRomanized: parsed.title.translitTitleName, bannerFilename: parsed.bannerFilename, }; } // yield parsed; } catch (e) { console.error("failed on", mix, song, ":", e.message); } } } const mixDirs = await readdir(stepchartsDir); const songIter = mixToSongs(mixDirs); const outIter = processSong(songIter); const writer = createWriteStream(outFile); for await (const songObj of outIter) { writer.write(JSON.stringify(songObj)); writer.write("\n"); } console.log("Done.");