From b5250125cd49c5199ea643cdffa9f01c82c65411 Mon Sep 17 00:00:00 2001 From: Eli White Date: Sat, 18 Apr 2026 22:38:05 -0700 Subject: [PATCH] Split scanChartFolder into parseChartAndIni + scanChart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the single `scanChartFolder(files, config?)` entry point with two functions so editor consumers can stop after parsing and skip the expensive validation/hashing/asset-scanning step. parseChartAndIni(files): ParseChartAndIniResult - file discovery + parseChartFile + scanIni - returns ParsedChart (with chartBytes/format/iniChartModifiers attached for downstream hashing) plus the ini scan results - no hashing, no asset I/O scanChart(parseResult, files, options?): ScannedChart - what scanChartFolder used to do, minus the parsing - calls scanParsedChart for chart hashing + notesData - then runs the same difficulty / playable / metadata-flatten / audio / image / video logic, returning the same ScannedChart shape as before - ScanChartOptions: { includeMd5?, includeBTrack? } scanParsedChart(parsedChart, includeBTrack?) - chart-only hashing + notesData. Same body as the previous module-level chart-scanner `scanChart`, just taking a parsed chart instead of (files, ini, btrack) so the parsing step isn't redone here. scanChartFolder is removed; ScanChartFolderConfig is removed (replaced by ScanChartOptions). All other helpers stay where they were — findChartIssues, getChartHash, legacyGetChartHash, etc. remain in chart-scanner.ts unchanged. Validation: - vitest 278/278 pass - 78,452-chart chart-edit roundtrip: 78,452/78,453 deeply equal - 78,046-chart hash baseline: same 3 pre-existing trackname-discovery diffs vs scan-chart@8.0.1 (independently fixed in #22) --- readme.md | 41 +++++- src/chart/chart-scanner.ts | 207 +++++++++++++------------------ src/chart/index.ts | 1 + src/chart/parse-chart-and-ini.ts | 123 ++++++++++++++++++ src/index.ts | 60 +++++---- src/test.ts | 4 +- 6 files changed, 289 insertions(+), 147 deletions(-) create mode 100644 src/chart/parse-chart-and-ini.ts diff --git a/readme.md b/readme.md index c9808f3..c4aea79 100644 --- a/readme.md +++ b/readme.md @@ -23,9 +23,24 @@ Note: running this will print usage information. Add command line arguments to t ```ts /** - * Scans `files` as a chart folder, and returns a `ScannedChart` object. + * Parses a chart folder's `notes.{mid,chart}` and `song.ini` into a `ParsedChart`. + * No hashing or audio/image scanning. + */ +function parseChartAndIni(files: { fileName: string; data: Uint8Array }[]): ParseChartAndIniResult + +/** + * Validates, hashes, and asset-scans the parsed chart folder. Pair with + * `parseChartAndIni()` to get the input. Returns the same `ScannedChart` + * shape as the previous `scanChartFolder()` did. + */ +function scanChart(files: { fileName: string; data: Uint8Array }[], parseResult: ParseChartAndIniResult, config?: ScanChartFolderConfig): ScannedChart + +/** + * @deprecated Back-compat shim equivalent to + * `scanChart(files, parseChartAndIni(files), config)`. Prefer the two-step form. */ function scanChartFolder(files: { fileName: string; data: Uint8Array }[], config?: ScanChartFolderConfig): ScannedChart + function parseChartFile(data: Uint8Array, format: 'chart' | 'mid', iniChartModifiers: IniChartModifiers): ParsedChart function calculateTrackHash(parsedChart: ParsedChart, instrument: Instrument, difficulty: Difficulty): { hash: string, btrack: Uint8Array } @@ -45,6 +60,21 @@ interface ScanChartFolderConfig { includeBTrack: boolean } +interface ParseChartAndIniResult { + /** The parsed chart, or `null` if a chart file could not be found or could not be parsed. Inspect `chartFolderIssues` for the reason. */ + parsedChart: ParsedChart | null + /** Folder-level issues from chart file discovery and parsing (`noChart`, `invalidChart`, `multipleChart`, `badChart`). */ + chartFolderIssues: { folderIssue: FolderIssueType; description: string }[] + /** The metadata parsed from `song.ini`, or `null` if no ini was present. */ + iniMetadata: { /* same shape as defaultMetadata */ } | null + /** Folder-level issues from ini scanning (`noMetadata`, `invalidIni`, `invalidMetadata`, `badIniLine`, `multipleIniFiles`). */ + iniFolderIssues: { folderIssue: FolderIssueType; description: string }[] + /** Validation issues with ini values. */ + iniMetadataIssues: { metadataIssue: MetadataIssueType; description: string }[] + /** ini key/value pairs not in scan-chart's known list. */ + iniUnknownValues: { [key: string]: string } +} + interface ScannedChart { /** An MD5 hash of the names and binary contents of every file in the chart. */ md5: string @@ -350,6 +380,15 @@ type NoteType = | 'greenTomOrCymbalMarker' interface ParsedChart { + /** + * The raw bytes of the source chart file. Needed by `scanParsedChart` to + * compute `chartHash` (which is `blake3(chartBytes ++ ini-modifier name/value pairs)`). + */ + chartBytes: Uint8Array + /** The format the chart was parsed from. */ + format: 'chart' | 'mid' + /** The fully-resolved ini modifiers that influenced parsing. */ + iniChartModifiers: IniChartModifiers resolution: number drumType: DrumType | null metadata: { diff --git a/src/chart/chart-scanner.ts b/src/chart/chart-scanner.ts index ba035cb..c679946 100644 --- a/src/chart/chart-scanner.ts +++ b/src/chart/chart-scanner.ts @@ -5,10 +5,10 @@ import * as _ from 'lodash' import { base64url } from 'rfc4648' import { defaultMetadata } from 'src/ini' -import { ChartIssueType, Difficulty, FolderIssueType, getInstrumentType, Instrument, instrumentTypes, NotesData } from '../interfaces' -import { getExtension, hasChartExtension, hasChartName, msToExactTime } from '../utils' +import { ChartIssueType, Difficulty, getInstrumentType, Instrument, instrumentTypes, NotesData } from '../interfaces' +import { msToExactTime } from '../utils' import { IniChartModifiers, NoteEvent, noteFlags, NoteType, noteTypes } from './note-parsing-interfaces' -import { parseChartFile, ParsedChart } from './notes-parser' +import { ParsedChart } from './parse-chart-and-ini' import { calculateTrackHash, pruneEmptyPhrases } from './track-hasher' const LEADING_SILENCE_THRESHOLD_MS = 1000 @@ -16,130 +16,95 @@ const MIN_SUSTAIN_GAP_MS = 40 const MIN_SUSTAIN_MS = 100 const NPS_GROUP_SIZE_MS = 1000 -export function scanChart(files: { fileName: string; data: Uint8Array }[], iniChartModifiers: IniChartModifiers, includeBTrack = false) { - const { chartData, format, folderIssues } = findChartData(files) - - if (chartData) { - try { - const result = parseChartFile(chartData, format, iniChartModifiers) - const trackHashes = result.trackData.map(t => { - const hash = calculateTrackHash(result, t.instrument, t.difficulty) - return { - instrument: t.instrument, - difficulty: t.difficulty, - hash: hash.hash, - btrack: includeBTrack ? hash.btrack : null, - } - }) +/** + * Compute the chart-derived hashes and `notesData` from an already-parsed + * chart. The output is the same chart-only subset of `ScannedChart` that + * the previous `scanChart(files, …)` returned; this version takes a + * `ParsedChart` so callers can reuse a parse result without redoing it. + * + * `trackHashes` is byte-stable across releases — see the spec in track-hasher.ts. + */ +export function scanParsedChart(parsedChart: ParsedChart, includeBTrack = false) { + const result = parsedChart + const iniChartModifiers = result.iniChartModifiers - let [hasTapNotes, hasOpenNotes, has2xKick] = [false, false, false] - for (const track of result.trackData) { - for (const noteGroup of track.noteEventGroups) { - for (const note of noteGroup) { - if (note.flags & noteFlags.tap) { - hasTapNotes = true - } - if (note.flags & noteFlags.doubleKick) { - has2xKick = true - } - if (note.type === noteTypes.open) { - hasOpenNotes = true - } - } + const trackHashes = result.trackData.map(t => { + const hash = calculateTrackHash(result, t.instrument, t.difficulty) + return { + instrument: t.instrument, + difficulty: t.difficulty, + hash: hash.hash, + btrack: includeBTrack ? hash.btrack : null, + } + }) + + let [hasTapNotes, hasOpenNotes, has2xKick] = [false, false, false] + for (const track of result.trackData) { + for (const noteGroup of track.noteEventGroups) { + for (const note of noteGroup) { + if (note.flags & noteFlags.tap) { + hasTapNotes = true + } + if (note.flags & noteFlags.doubleKick) { + has2xKick = true + } + if (note.type === noteTypes.open) { + hasOpenNotes = true } } - - return { - chartHash: getChartHash(chartData, iniChartModifiers), - notesData: { - instruments: _.chain(result.trackData) - .map(t => t.instrument) - .uniq() - .value(), - drumType: result.drumType, - hasSoloSections: - _.chain(result.trackData) - .map(t => t.soloSections.length) - .max() - .value() > 0, - hasLyrics: result.hasLyrics, - hasVocals: result.hasVocals, - hasForcedNotes: result.hasForcedNotes, - hasTapNotes, - hasOpenNotes, - has2xKick, - hasFlexLanes: - _.chain(result.trackData) - .map(t => t.flexLanes.length) - .max() - .value() > 0, - chartIssues: findChartIssues(result, iniChartModifiers.song_length, trackHashes), - noteCounts: result.trackData.map(t => ({ - instrument: t.instrument, - difficulty: t.difficulty, - count: t.instrument === 'drums' ? _.sumBy(t.noteEventGroups, 'length') : t.noteEventGroups.length, - })), - maxNps: result.trackData.map(t => ({ - instrument: t.instrument, - difficulty: t.difficulty, - ...findMaxNps(t.noteEventGroups), - })), - trackHashes, - tempoMapHash: md5 - .create() - .update(result.tempos.map(t => `${t.tick}_${t.beatsPerMinute * 1000}`).join(':')) - .update(result.timeSignatures.map(t => `${t.tick}_${t.numerator}_${t.denominator}`).join(':')) - .hex(), - tempoMarkerCount: result.tempos.length, - effectiveLength: _.chain(result.trackData) - .thru(tracks => ({ - min: _.min(tracks.map(track => _.first(track.noteEventGroups)?.[0]?.msTime)), - max: _.max(tracks.map(track => _.last(track.noteEventGroups)?.[0]?.msTime)), - })) - .thru(({ min, max }) => (min !== undefined && max !== undefined ? _.round(max - min, 3) : iniChartModifiers.song_length)) - .value(), - }, - metadata: result.metadata, - folderIssues, - } - } catch (err) { - folderIssues.push({ folderIssue: 'badChart', description: typeof err === 'string' ? err : (err?.message ?? JSON.stringify(err)) }) } } - return { chartHash: null, notesData: null, metadata: null, folderIssues } -} - -function findChartData(files: { fileName: string; data: Uint8Array }[]) { - const folderIssues: { folderIssue: FolderIssueType; description: string }[] = [] - - const chartFiles = _.chain(files) - .filter(f => hasChartExtension(f.fileName)) - .orderBy([f => hasChartName(f.fileName), f => getExtension(f.fileName).toLowerCase() === 'mid'], ['desc', 'desc']) - .value() - - for (const file of chartFiles) { - if (!hasChartName(file.fileName)) { - folderIssues.push({ - folderIssue: 'invalidChart', - description: `"${file.fileName}" is not named "notes.${getExtension(file.fileName).toLowerCase()}".`, - }) - } - } - - if (chartFiles.length > 1) { - folderIssues.push({ folderIssue: 'multipleChart', description: 'This chart has multiple .chart/.mid files.' }) - } - - if (chartFiles.length === 0) { - folderIssues.push({ folderIssue: 'noChart', description: 'This chart doesn\'t have "notes.chart"/"notes.mid".' }) - return { chartData: null, format: null, folderIssues } - } else { - return { - chartData: chartFiles[0].data, - format: (getExtension(chartFiles[0].fileName).toLowerCase() === 'mid' ? 'mid' : 'chart') as 'mid' | 'chart', - folderIssues, - } + return { + chartHash: getChartHash(result.chartBytes, iniChartModifiers), + notesData: { + instruments: _.chain(result.trackData) + .map(t => t.instrument) + .uniq() + .value(), + drumType: result.drumType, + hasSoloSections: + _.chain(result.trackData) + .map(t => t.soloSections.length) + .max() + .value() > 0, + hasLyrics: result.hasLyrics, + hasVocals: result.hasVocals, + hasForcedNotes: result.hasForcedNotes, + hasTapNotes, + hasOpenNotes, + has2xKick, + hasFlexLanes: + _.chain(result.trackData) + .map(t => t.flexLanes.length) + .max() + .value() > 0, + chartIssues: findChartIssues(result, iniChartModifiers.song_length, trackHashes), + noteCounts: result.trackData.map(t => ({ + instrument: t.instrument, + difficulty: t.difficulty, + count: t.instrument === 'drums' ? _.sumBy(t.noteEventGroups, 'length') : t.noteEventGroups.length, + })), + maxNps: result.trackData.map(t => ({ + instrument: t.instrument, + difficulty: t.difficulty, + ...findMaxNps(t.noteEventGroups), + })), + trackHashes, + tempoMapHash: md5 + .create() + .update(result.tempos.map(t => `${t.tick}_${t.beatsPerMinute * 1000}`).join(':')) + .update(result.timeSignatures.map(t => `${t.tick}_${t.numerator}_${t.denominator}`).join(':')) + .hex(), + tempoMarkerCount: result.tempos.length, + effectiveLength: _.chain(result.trackData) + .thru(tracks => ({ + min: _.min(tracks.map(track => _.first(track.noteEventGroups)?.[0]?.msTime)), + max: _.max(tracks.map(track => _.last(track.noteEventGroups)?.[0]?.msTime)), + })) + .thru(({ min, max }) => (min !== undefined && max !== undefined ? _.round(max - min, 3) : iniChartModifiers.song_length)) + .value(), + }, } } diff --git a/src/chart/index.ts b/src/chart/index.ts index e139bff..47eaa40 100644 --- a/src/chart/index.ts +++ b/src/chart/index.ts @@ -1 +1,2 @@ export * from './chart-scanner' +export * from './parse-chart-and-ini' diff --git a/src/chart/parse-chart-and-ini.ts b/src/chart/parse-chart-and-ini.ts new file mode 100644 index 0000000..388ca5b --- /dev/null +++ b/src/chart/parse-chart-and-ini.ts @@ -0,0 +1,123 @@ +import * as _ from 'lodash' + +import { defaultMetadata, scanIni } from '../ini' +import { FolderIssueType, MetadataIssueType } from '../interfaces' +import { getExtension, hasChartExtension, hasChartName } from '../utils' +import { defaultIniChartModifiers, IniChartModifiers } from './note-parsing-interfaces' +import { parseChartFile } from './notes-parser' + +/** + * The full parsed chart, including the source bytes and ini modifiers needed + * for downstream hashing. + */ +export type ParsedChart = ReturnType & { + /** + * The raw bytes of the source chart file. Needed by `scanChart` to compute + * `chartHash`, which is `blake3(chartBytes ++ ini-modifier name/value + * pairs)` — the file contents directly plus the few ini knobs that affect + * parsing. (Predates the SongHash spec, which scan-chart does not yet + * implement; once it does, this field can be dropped.) + */ + chartBytes: Uint8Array + /** The format the chart was parsed from. */ + format: 'chart' | 'mid' + /** The fully-resolved ini modifiers that influenced parsing. */ + iniChartModifiers: IniChartModifiers +} + +export interface ParseChartAndIniResult { + /** + * The parsed chart, or `null` if a chart file could not be found or could + * not be parsed. Inspect `chartFolderIssues` for the reason. + */ + parsedChart: ParsedChart | null + /** + * Folder-level issues from chart file discovery and parsing + * (`noChart`, `invalidChart`, `multipleChart`, `badChart`). + */ + chartFolderIssues: { folderIssue: FolderIssueType; description: string }[] + /** The metadata parsed from `song.ini`, or `null` if no ini was present. */ + iniMetadata: typeof defaultMetadata | null + /** + * Folder-level issues from ini scanning (`noMetadata`, `invalidIni`, + * `invalidMetadata`, `badIniLine`, `multipleIniFiles`). + */ + iniFolderIssues: { folderIssue: FolderIssueType; description: string }[] + /** Validation issues with ini values. */ + iniMetadataIssues: { metadataIssue: MetadataIssueType; description: string }[] + /** ini key/value pairs not in scan-chart's known list. */ + iniUnknownValues: { [key: string]: string } +} + +/** + * Parse a chart folder's `notes.{mid,chart}` and `song.ini` into a + * `ParsedChart`, with no hashing or audio/image scanning. Pair with + * `scanChart` if you need hashes, chart issues, or asset scanning. + */ +export function parseChartAndIni(files: { fileName: string; data: Uint8Array }[]): ParseChartAndIniResult { + const iniData = scanIni(files) + const iniChartModifiers: IniChartModifiers = iniData.metadata + ? { ...defaultIniChartModifiers, ...iniData.metadata } + : defaultIniChartModifiers + + const { chartData, format, folderIssues: chartFolderIssues } = findChartData(files) + + let parsedChart: ParsedChart | null = null + if (chartData) { + try { + const inner = parseChartFile(chartData, format!, iniChartModifiers) + parsedChart = Object.assign({}, inner, { + chartBytes: chartData, + format: format!, + iniChartModifiers, + }) as ParsedChart + } catch (err) { + chartFolderIssues.push({ + folderIssue: 'badChart', + description: typeof err === 'string' ? err : ((err as Error)?.message ?? JSON.stringify(err)), + }) + } + } + + return { + parsedChart, + chartFolderIssues, + iniMetadata: iniData.metadata, + iniFolderIssues: iniData.folderIssues, + iniMetadataIssues: iniData.metadataIssues, + iniUnknownValues: iniData.unknownIniValues, + } +} + +function findChartData(files: { fileName: string; data: Uint8Array }[]) { + const folderIssues: { folderIssue: FolderIssueType; description: string }[] = [] + + const chartFiles = _.chain(files) + .filter(f => hasChartExtension(f.fileName)) + .orderBy([f => hasChartName(f.fileName), f => getExtension(f.fileName).toLowerCase() === 'mid'], ['desc', 'desc']) + .value() + + for (const file of chartFiles) { + if (!hasChartName(file.fileName)) { + folderIssues.push({ + folderIssue: 'invalidChart', + description: `"${file.fileName}" is not named "notes.${getExtension(file.fileName).toLowerCase()}".`, + }) + } + } + + if (chartFiles.length > 1) { + folderIssues.push({ folderIssue: 'multipleChart', description: 'This chart has multiple .chart/.mid files.' }) + } + + if (chartFiles.length === 0) { + folderIssues.push({ folderIssue: 'noChart', description: 'This chart doesn\'t have "notes.chart"/"notes.mid".' }) + return { chartData: null, format: null, folderIssues } + } else { + return { + chartData: chartFiles[0].data, + format: (getExtension(chartFiles[0].fileName).toLowerCase() === 'mid' ? 'mid' : 'chart') as 'mid' | 'chart', + folderIssues, + } + } +} diff --git a/src/index.ts b/src/index.ts index ec758fb..2805679 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,9 @@ import { md5 } from 'js-md5' import * as _ from 'lodash' import { scanAudio } from './audio' -import { scanChart } from './chart' +import { parseChartAndIni, ParseChartAndIniResult, scanParsedChart } from './chart' import { scanImage } from './image' -import { defaultMetadata, scanIni } from './ini' +import { defaultMetadata } from './ini' import { Instrument, ScanChartFolderConfig, ScannedChart } from './interfaces' import { RequireMatchingProps, Subset } from './utils' import { scanVideo } from './video' @@ -12,13 +12,19 @@ import { scanVideo } from './video' export * from './interfaces' export * from './chart/note-parsing-interfaces' export { parseChartFile } from './chart/notes-parser' +export { parseChartAndIni } from './chart' +export type { ParsedChart, ParseChartAndIniResult } from './chart' export { scanIni } from './ini' export { calculateTrackHash } from './chart/track-hasher' /** - * Scans `files` as a chart folder, and returns a `ScannedChart` object. + * Validate, hash, and asset-scan a parsed chart folder. Pair with `parseChartAndIni()` to get the input. */ -export function scanChartFolder(files: { fileName: string; data: Uint8Array }[], config?: ScanChartFolderConfig): ScannedChart { +export function scanChart( + files: { fileName: string; data: Uint8Array }[], + parseResult: ParseChartAndIniResult, + config?: ScanChartFolderConfig, +): ScannedChart { config = { includeMd5: true, includeBTrack: false, @@ -33,20 +39,18 @@ export function scanChartFolder(files: { fileName: string; data: Uint8Array }[], chart.md5 = config.includeMd5 ? getChartMD5(files) : 'md5 calculation skipped' - const iniData = scanIni(files) - chart.folderIssues.push(...iniData.folderIssues) - chart.metadataIssues.push(...iniData.metadataIssues) - - const chartData = scanChart(files, iniData.metadata ?? defaultMetadata, config.includeBTrack) - chart.chartHash = chartData.chartHash ?? undefined - chart.folderIssues.push(...chartData.folderIssues) + chart.folderIssues.push(...parseResult.iniFolderIssues) + chart.metadataIssues.push(...parseResult.iniMetadataIssues) + chart.folderIssues.push(...parseResult.chartFolderIssues) - if (chartData.notesData) { + if (parseResult.parsedChart) { + const chartData = scanParsedChart(parseResult.parsedChart, config.includeBTrack) + chart.chartHash = chartData.chartHash chart.notesData = chartData.notesData const instruments = chartData.notesData.instruments - if (iniData.metadata) { + if (parseResult.iniMetadata) { const checkMissingDifficulty = (instrument: Instrument, diffKey: keyof typeof defaultMetadata) => { - if (instruments.includes(instrument) && iniData.metadata[diffKey] === defaultMetadata[diffKey]) { + if (instruments.includes(instrument) && parseResult.iniMetadata![diffKey] === defaultMetadata[diffKey]) { chart.metadataIssues.push({ metadataIssue: 'missingValue', description: `Metadata is missing a "${diffKey}" value.` }) } } @@ -60,12 +64,12 @@ export function scanChartFolder(files: { fileName: string; data: Uint8Array }[], checkMissingDifficulty('guitarcoopghl', 'diff_guitar_coop_ghl') checkMissingDifficulty('rhythmghl', 'diff_rhythm_ghl') checkMissingDifficulty('bassghl', 'diff_bassghl') - if (chartData.notesData.hasVocals && iniData.metadata.diff_vocals === defaultMetadata.diff_vocals) { + if (chartData.notesData.hasVocals && parseResult.iniMetadata.diff_vocals === defaultMetadata.diff_vocals) { chart.metadataIssues.push({ metadataIssue: 'missingValue', description: 'Metadata is missing a "diff_vocals" value.' }) } const checkExtraDifficulty = (instrument: Instrument, diffKey: keyof typeof defaultMetadata) => { - if (iniData.metadata[diffKey] !== defaultMetadata[diffKey] && !instruments.includes(instrument)) { + if (parseResult.iniMetadata![diffKey] !== defaultMetadata[diffKey] && !instruments.includes(instrument)) { chart.metadataIssues.push({ metadataIssue: 'extraValue', description: `Metadata contains "${diffKey}", but ${instrument} is not charted.`, @@ -82,7 +86,7 @@ export function scanChartFolder(files: { fileName: string; data: Uint8Array }[], checkExtraDifficulty('guitarcoopghl', 'diff_guitar_coop_ghl') checkExtraDifficulty('rhythmghl', 'diff_rhythm_ghl') checkExtraDifficulty('bassghl', 'diff_bassghl') - if (iniData.metadata.diff_vocals !== defaultMetadata.diff_vocals && !chartData.notesData.hasVocals) { + if (parseResult.iniMetadata.diff_vocals !== defaultMetadata.diff_vocals && !chartData.notesData.hasVocals) { chart.metadataIssues.push({ metadataIssue: 'extraValue', description: 'Metadata contains "diff_vocals", but vocals are not charted.', @@ -91,17 +95,17 @@ export function scanChartFolder(files: { fileName: string; data: Uint8Array }[], } } - if (iniData.metadata) { + if (parseResult.iniMetadata) { // Use metadata from .ini file if it exists (filled in with defaults for properties that are not included) - _.assign(chart, iniData.metadata) - } else if (chartData.metadata) { + _.assign(chart, parseResult.iniMetadata) + } else if (parseResult.parsedChart?.metadata) { // Use metadata from .chart file if it exists - _.assign(chart, chartData.metadata) + _.assign(chart, parseResult.parsedChart.metadata) } else { // No metadata available chart.playable = false } - chart.chart_offset = chartData.metadata?.delay ?? 0 + chart.chart_offset = parseResult.parsedChart?.metadata?.delay ?? 0 const imageData = scanImage(files) chart.folderIssues.push(...imageData.folderIssues) @@ -115,7 +119,7 @@ export function scanChartFolder(files: { fileName: string; data: Uint8Array }[], const audioData = scanAudio(files) chart.folderIssues.push(...audioData.folderIssues) - if (!chartData.notesData || chart.folderIssues.find(i => i!.folderIssue === 'noAudio') /* TODO: || !audioData.audioHash */) { + if (!parseResult.parsedChart || chart.folderIssues.find(i => i!.folderIssue === 'noAudio') /* TODO: || !audioData.audioHash */) { chart.playable = false } @@ -126,6 +130,16 @@ export function scanChartFolder(files: { fileName: string; data: Uint8Array }[], return chart as ScannedChart } +/** + * Scans `files` as a chart folder, and returns a `ScannedChart` object. + * + * @deprecated Call `parseChartAndIni()` + `scanChart()` directly. Preserved + * as a back-compat shim for existing callers. + */ +export function scanChartFolder(files: { fileName: string; data: Uint8Array }[], config?: ScanChartFolderConfig): ScannedChart { + return scanChart(files, parseChartAndIni(files), config) +} + function getChartMD5(files: { fileName: string; data: Uint8Array }[]) { const hash = md5.create() for (const file of _.orderBy(files, f => f.fileName)) { diff --git a/src/test.ts b/src/test.ts index eedd8be..52df803 100644 --- a/src/test.ts +++ b/src/test.ts @@ -3,7 +3,7 @@ import { readdir, readFile, writeFile } from 'fs/promises' import * as _ from 'lodash' import { join } from 'path' import sanitize from 'sanitize-filename' -import { scanChartFolder } from 'src' +import { parseChartAndIni, scanChart } from 'src' import yargs from 'yargs' import { hideBin } from 'yargs/helpers' @@ -78,7 +78,7 @@ async function main() { ) ).filter(f => f?.data !== undefined) as { fileName: string; data: Uint8Array }[] - const result = scanChartFolder(files, { includeBTrack: config.createBTrackFiles, includeMd5: false }) + const result = scanChart(files, parseChartAndIni(files), { includeBTrack: config.createBTrackFiles, includeMd5: false }) scanCount++ if (scanCount % 100 === 0 && !config.silent) { console.log(`${scanCount} scanned...`)