-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscrapeSimfiles.js
142 lines (123 loc) · 3.68 KB
/
scrapeSimfiles.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { walk, ensureDir } from 'fs/mod.ts'
import { writeJson } from 'jsonfile/mod.ts'
import { outputPath, readSimfile, allArcadeAlbums } from './utils.js'
const scrapeSimfiles = async (release) => {
const directory = `${outputPath}/Simfiles/${release}`
const songs = []
for await (const entry of walk(directory, { exts: ['.sm', '.ssc'] })) {
const sim = readSimfile(entry.path)
let {
title,
subtitle,
artist,
titletranslit,
subtitletranslit,
artisttranslit,
displaybpm,
bpms,
stops
} = getSimfileData(sim)
const parseTimeChanges = (times) => times
.split(',')
.map(mod => {
const [timestamp, value] = mod.split('=')
.map(e => e.includes(';')
? Number(e.split(';')[0])
: Number(e))
return { timestamp, value }
})
if (bpms) bpms = parseTimeChanges(bpms)
if (stops) stops = parseTimeChanges(stops)
displaybpm = displaybpm
? displaybpm
.split('-')
.map(e => Math.round(e))
.join('-')
: bpms[0].value
const data = {
title,
subtitle,
artist,
titletranslit,
subtitletranslit,
artisttranslit,
stops
}
data.charts = getChartData(sim)
data.bpm = {
display: displaybpm,
values: bpms
}
songs.push(data)
}
return songs
}
const getSimfileData = (text) => text
.split('//')[0]
.split('\n')
.map(prop => {
const [propKey, propValue] = prop
.trim()
.replace(';', '') // strip trailing semicolon from prop value
.replace(':\\', ':') // strip leading escape character from prop value
.replace('#', '') // strip leading hash sign from prop name
.replace(' #', '!!!') // split at any trailing comments
.replace(':', '!!!') // split between prop name and value
// note: displaybpm also uses a colon rather than a hyphen,
// so split at the first colon only
.split('!!!')
.filter((e, i, a) =>
e && a.length > 1)
.map((e, i, a) => a[i - 1] === 'DISPLAYBPM'
? e.replace(':', '-')
: e)
return propKey && propValue
? { [propKey.toLowerCase()]: propValue }
: null
})
.filter(e => e)
.reduce((a, b) => ({ ...a, ...b }))
const getChartData = (simfile) => simfile
.replace(/;/g, '')
// conversion between DDR and stepmania chart naming conventions
.toLowerCase()
.replace(/medium/g, 'difficult')
.replace(/hard/g, 'expert')
.split('//')
.slice(1)
// defaults to 4-panel arcade charts. Remove the below line to edit this.
.filter(e => e.includes('dance')
&& !e.includes('solo') // 6 panel single
&& !e.includes('couple') // 6 panel double
&& !e.includes('chartstyle')) // filter out chart edits
.map(chart => {
const data = chart
.split('\n')
.filter(e => e.includes('stepstype') ||
e.includes('difficulty') ||
e.includes('meter'))
.map(e => e
.trim()
.split(':')[1]
)
.filter(e => e)
let [style, difficulty, level] = data
level = Number(level)
const numPanels = 4
const numPads = style.includes('double') // || style.includes('couple')
? 2
: 1
return { numPads, numPanels, difficulty, level }
})
for (const { title } of allArcadeAlbums) {
await ensureDir('./Output/JSON')
console.log(`parsing release: ${title}...`)
const data = await scrapeSimfiles(title)
const sorted = data.sort((a, b) => {
let [a_sort, b_sort] = [a, b]
.map(e => (e.titletranslit || e.title).toLowerCase())
return a_sort > b_sort ? 1 : -1
})
await writeJson(`./Output/JSON/${title}.json`, sorted)
console.log('success!')
}