feat: predict_tja script

This commit is contained in:
2026-04-25 16:19:30 +09:00
parent df82515a33
commit 4f6ce49704
5 changed files with 201 additions and 38 deletions

BIN
output/.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,74 @@
[
{
"songno": "XODUS",
"difficulty": "oni",
"note_count": 1007,
"density_avg": 8.14959935897436,
"density_peak": 17,
"bpm_avg": 202,
"bpm_change": 0,
"scroll_change": 14,
"rhythm_complexity": 868,
"color_complexity": 0.012565274045963932
},
{
"songno": "Destructive Little Sister",
"difficulty": "oni",
"note_count": 1119,
"density_avg": 7.917452830188679,
"density_peak": 21,
"bpm_avg": 247.5,
"bpm_change": 0,
"scroll_change": 17,
"rhythm_complexity": 1079,
"color_complexity": 0.016291673920775917
},
{
"songno": "7 Wonders",
"difficulty": "oni",
"note_count": 794,
"density_avg": 5.571929824561403,
"density_peak": 12,
"bpm_avg": 168,
"bpm_change": 0,
"scroll_change": 0,
"rhythm_complexity": 726,
"color_complexity": 0.006656416024691356
},
{
"songno": "7 Wonders",
"difficulty": "ura",
"note_count": 1207,
"density_avg": 8.47017543859649,
"density_peak": 15,
"bpm_avg": 168,
"bpm_change": 0,
"scroll_change": 0,
"rhythm_complexity": 965,
"color_complexity": 0.01463965596985236
},
{
"songno": "Destruction 3 2 1",
"difficulty": "oni",
"note_count": 1160,
"density_avg": 7.46659375,
"density_peak": 15,
"bpm_avg": 321.3209999999973,
"bpm_change": 0,
"scroll_change": 5,
"rhythm_complexity": 1101,
"color_complexity": 0.012682773678672012
},
{
"songno": "Destruction 3 2 1",
"difficulty": "ura",
"note_count": 1491,
"density_avg": 9.59714765625,
"density_peak": 20,
"bpm_avg": 321.32099999999707,
"bpm_change": 0,
"scroll_change": 8,
"rhythm_complexity": 1417,
"color_complexity": 0.028926671611070473
}
]

View File

@@ -159,5 +159,6 @@ if __name__ == "__main__":
predict( predict(
args.workingDir, args.workingDir,
args.songno args.songno,
args.feature
) )

View File

@@ -1,51 +1,45 @@
import tjaParser, { Bar, Branch, Course } from 'tja-parser' import tjaParser, { Bar, Branch, Course } from 'tja-parser'
export function parseTja(tja: string): Partial<Record<'oni' | 'edit', Course>> | null { export function parseTja(tja: string): Partial<Record<'oni' | 'edit', Course>> {
try { const song = tjaParser.Song.parse(tja);
const song = tjaParser.Song.parse(tja);
let oni: Course | undefined = undefined; let oni: Course | undefined = undefined;
let edit: Course | undefined = undefined; let edit: Course | undefined = undefined;
if (song.course?.oni) { if (song.course?.oni) {
const noteGroups = song.course.oni.noteGroups; const noteGroups = song.course.oni.noteGroups;
oni = song.course.oni; oni = song.course.oni;
oni.noteGroups = [] oni.noteGroups = []
for (const noteGroup of noteGroups) { for (const noteGroup of noteGroups) {
if (noteGroup instanceof Bar) { if (noteGroup instanceof Bar) {
oni.pushNoteGroups(noteGroup) oni.pushNoteGroups(noteGroup)
} }
else if (noteGroup instanceof Branch) { else if (noteGroup instanceof Branch) {
const bar = noteGroup.master || noteGroup.advanced || noteGroup.normal; const bar = noteGroup.master || noteGroup.advanced || noteGroup.normal;
if (bar) { if (bar) {
oni.pushNoteGroups(...bar) oni.pushNoteGroups(...bar)
}
} }
} }
} }
if (song.course?.edit) { }
const noteGroups = song.course.edit.noteGroups; if (song.course?.edit) {
edit = song.course.edit; const noteGroups = song.course.edit.noteGroups;
edit.noteGroups = [] edit = song.course.edit;
edit.noteGroups = []
for (const noteGroup of noteGroups) { for (const noteGroup of noteGroups) {
if (noteGroup instanceof Bar) { if (noteGroup instanceof Bar) {
edit.pushNoteGroups(noteGroup) edit.pushNoteGroups(noteGroup)
} }
else if (noteGroup instanceof Branch) { else if (noteGroup instanceof Branch) {
const bar = noteGroup.master || noteGroup.advanced || noteGroup.normal; const bar = noteGroup.master || noteGroup.advanced || noteGroup.normal;
if (bar) { if (bar) {
edit.pushNoteGroups(...bar) edit.pushNoteGroups(...bar)
}
} }
} }
} }
}
return { oni, edit } return { oni, edit }
}
catch (err){
console.error(err)
return null;
}
} }

94
script/predict_tja.ts Normal file
View File

@@ -0,0 +1,94 @@
import Bun from 'bun';
import path from 'node:path';
import { parseArgs } from 'node:util';
import fs from 'node:fs';
import { featurize } from '../preprocess/featurize';
import { parseTja } from '../preprocess/parse';
const { values } = parseArgs({
args: Bun.argv,
options: {
tja: {
type: "string"
},
workingDir: {
type: "string"
},
script: {
type: "string"
}
},
allowPositionals: true,
});
if (!values.tja || !values.workingDir || !values.script) {
console.error("Usage: bun script/predict_tja.ts --tja <path_to_tja> [--workingDir <dir>] [--script <python_script>]");
process.exit(1);
}
const tjaPath = values.tja;
if (!fs.existsSync(tjaPath)) {
console.error(`File not found: ${tjaPath}`);
process.exit(1);
}
const workingDir = values.workingDir!;
const pythonScript = values.script!;
if (!fs.existsSync(pythonScript)) {
console.error(`Python script not found: ${pythonScript}`);
process.exit(1);
}
// 1. TJA 파싱 및 피처 추출
const tjaContent = fs.readFileSync(tjaPath, 'utf-8');
const songno = path.basename(tjaPath, '.tja');
const parsed = parseTja(tjaContent);
const features: any[] = [];
if (parsed.oni) {
features.push({
songno,
difficulty: 'oni',
...featurize(parsed.oni)
});
}
if (parsed.edit) {
features.push({
songno,
difficulty: 'ura',
...featurize(parsed.edit)
});
}
if (features.length === 0) {
console.error("No Oni or Ura difficulty found in the TJA file.");
process.exit(1);
}
// 2. 임시 피처 파일 저장
const tempFeaturePath = path.join(workingDir, `temp_predict_${Date.now()}.json`);
fs.writeFileSync(tempFeaturePath, JSON.stringify(features, null, 2), 'utf-8');
try {
// 3. Python 예측 스크립트 실행
const proc = Bun.spawnSync([
"python3",
pythonScript,
"--workingDir", workingDir,
"--feature", tempFeaturePath,
"--songno", songno
]);
if (proc.success) {
console.log(proc.stdout.toString());
} else {
console.error("Prediction failed:");
console.error(proc.stderr.toString());
}
} finally {
// 4. 임시 파일 삭제
if (fs.existsSync(tempFeaturePath)) {
fs.unlinkSync(tempFeaturePath);
}
}