-
Notifications
You must be signed in to change notification settings - Fork 19
/
build-catalog.js
129 lines (112 loc) · 4.12 KB
/
build-catalog.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
const fs = require('fs');
const glob = require('glob');
const directoryTree = require('directory-tree');
const { FORMATS } = require('../src/config');
const { toArabic } = require('roman-numerals');
// Paths are relative to project root.
//
// Point this to the place where you keep all the music.
// this location is untracked, so put a symlink here.
// For example:
// chip-player-js $ ln -s ~/Downloads/catalog catalog
const catalogPath = 'catalog/';
const outputPath = 'server/catalog.json';
const dirDictOutputPath = 'server/directories.json';
const formatsRegex = new RegExp(`\\.(${FORMATS.join('|')})$`);
const romanNumeralNineRegex = /\bix\b/i;
const romanNumeralRegex = /\b([IVXLC]+|[ivxlc]+)[-.,)]/; // All upper case or all lower case
const NUMERIC_COLLATOR = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
/*
Sample catalog.json output:
[
"Classical MIDI/Balakirev/Islamey – Fantaisie Orientale (G. Giulimondi).mid",
"Classical MIDI/Balakirev/Islamey – Fantaisie Orientale (W. Pepperdine).mid"
]
Sample directories.json output:
{
"/Classical MIDI/Balakirev": [
{
"path": "/Classical MIDI/Balakirev/Islamey – Fantaisie Orientale (G. Giulimondi).mid",
"size": 54602,
"type": "file",
"idx": 0
},
{
"path": "/Classical MIDI/Balakirev/Islamey – Fantaisie Orientale (W. Pepperdine).mid",
"size": 213866,
"type": "file",
"idx": 1
}
]
}
*/
function replaceRomanWithArabic(str) {
// Works up to 399 (CCCXCIX)
try {
return str.replace(romanNumeralRegex, (_, match) => String(toArabic(match)).padStart(4, '0'));
} catch (e) {
// Ignore false positives like 'mill.', 'did-', or 'mix,'
return str;
}
}
if (!fs.existsSync(catalogPath)) {
console.log('Couldn\'t find a music folder for indexing. Create a folder or symlink at \'%s\'.', catalogPath);
process.exit(1);
}
const files = glob.sync(`${catalogPath}**/*.{${FORMATS.join(',')}}`, { nocase: true },)
.map(file => file.replace(catalogPath, ''));
const data = JSON.stringify(files, null, 2);
fs.writeSync(fs.openSync(outputPath, 'w+'), data);
console.log('Wrote %d entries in %s (%d bytes).', files.length, outputPath, data.length);
const dirDict = {};
const dirOptions = {
extensions: formatsRegex,
attributes: [ 'mtimeMs' ],
};
directoryTree(catalogPath, dirOptions, null, item => {
if (item.children) {
item.path = item.path.replace(catalogPath, '/');
const children = item.children.map(child => {
child.path = child.path.replace(catalogPath, '/');
if (child.children) {
child.numChildren = child.children.length;
delete child.children;
}
child.mtime = Math.floor(child.mtimeMs / 1000);
delete child.name;
delete child.extension;
delete child.mtimeMs;
return child;
});
const arabicMap = {};
const needsRomanNumeralSort = children.some(item => {
// Only convert Roman numerals if the list sort could benefit from it.
// Roman numerals less than 9 would be sorted incidentally.
// This assumes that Roman numeral ranges don't have gaps.
return item.path.match(romanNumeralNineRegex);
});
if (needsRomanNumeralSort) {
console.log("Roman numeral sort is active for %s", item.path);
// Movement IV. Wow => Movement 0004. Wow
children.forEach(item => arabicMap[item.path] = replaceRomanWithArabic(item.path));
}
children
.sort((a, b) => {
const [strA, strB] = needsRomanNumeralSort ?
[arabicMap[a.path], arabicMap[b.path]] :
[a.path, b.path];
return NUMERIC_COLLATOR.compare(strA, strB);
})
.sort((a, b) => {
if (a.type < b.type) return -1;
if (a.type > b.type) return 1;
return 0;
});
// Add file idx property
children.filter(child => child.type === 'file').forEach((item, idx) => item.idx = idx);
dirDict[item.path] = children;
}
});
const dirDictData = JSON.stringify(dirDict, null, 2);
fs.writeSync(fs.openSync(dirDictOutputPath, 'w+'), dirDictData);
console.log('Wrote %d entries in %s (%d bytes).', Object.keys(dirDict).length, dirDictOutputPath, dirDictData.length);