@rel-packages/osu-beatmap-parser 0.1.9 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/build/osu-beatmap-parser.js +0 -0
- package/dist/index.d.ts +4 -10
- package/dist/index.js +12 -40
- package/dist/lib/bindings.d.ts +2 -0
- package/dist/lib/bindings.js +119 -0
- package/dist/types.d.ts +3 -6
- package/package.json +28 -10
- package/prebuilds/prebuilds-linux-x64/linux-x64/osu-beatmap-parser.node +0 -0
- package/prebuilds/prebuilds-win32-x64/win32-x64/osu-beatmap-parser.node +0 -0
- package/.prebuildrc +0 -6
- package/CMakeLists.txt +0 -68
- package/prebuilds/linux-x64/osu-beatmap-parser.node +0 -0
- package/prebuilds/win32-x64/osu-beatmap-parser.node +0 -0
- package/src/native/addon.cpp +0 -310
- package/src/native/addon.hpp +0 -26
- package/src/native/definitions.hpp +0 -56
- package/src/native/osu/audio.cpp +0 -30
- package/src/native/osu/audio.hpp +0 -34
- package/src/native/osu/parser.cpp +0 -294
- package/src/native/osu/parser.hpp +0 -14
- package/src/native/pool.hpp +0 -80
- package/src/types.ts +0 -48
package/README.md
CHANGED
|
@@ -13,4 +13,9 @@ npm install osu-beatmap-parser
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install
|
|
15
15
|
npm run compile
|
|
16
|
-
```
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## TODO
|
|
19
|
+
- [ ] native: handle "Storyboard" key
|
|
20
|
+
- [ ] native: handle beatmap file version
|
|
21
|
+
- [ ] native: handle lazer files (like, if we're trying to get AudioLocation from a lazer file, we will need to follow the hashed lazer path)
|
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { OsuKey, OsuInput } from "./types";
|
|
2
|
-
|
|
3
|
-
export declare
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
export declare function process_beatmaps(inputs: (string | OsuInput)[], keys: OsuKey[], update_fn?: (index: number) => void): Promise<(Record<OsuKey, string> & {
|
|
7
|
-
id?: string;
|
|
8
|
-
})[]>;
|
|
9
|
-
export declare function get_duration(location: string): number;
|
|
10
|
-
export declare function get_audio_duration(location: string): number;
|
|
11
|
-
export { OsuKey, OsuInput };
|
|
2
|
+
import { init_wasm } from "./lib/bindings";
|
|
3
|
+
export declare const get_property: (data: Uint8Array, key: OsuKey) => any;
|
|
4
|
+
export declare const get_properties: (input: Uint8Array | OsuInput, keys: OsuKey[]) => any;
|
|
5
|
+
export { OsuKey, OsuInput, init_wasm };
|
package/dist/index.js
CHANGED
|
@@ -1,46 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init_wasm = exports.get_properties = exports.get_property = void 0;
|
|
4
|
+
const bindings_1 = require("./lib/bindings");
|
|
5
|
+
Object.defineProperty(exports, "init_wasm", { enumerable: true, get: function () { return bindings_1.init_wasm; } });
|
|
6
|
+
const get_property = (data, key) => {
|
|
7
|
+
return bindings_1.native.get_property(data, key);
|
|
8
|
+
};
|
|
6
9
|
exports.get_property = get_property;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const node_gyp_build_1 = __importDefault(require("node-gyp-build"));
|
|
12
|
-
const path_1 = __importDefault(require("path"));
|
|
13
|
-
const native = (0, node_gyp_build_1.default)(path_1.default.join(__dirname, ".."));
|
|
14
|
-
function get_property(location, key) {
|
|
15
|
-
return native.get_property(location, key);
|
|
16
|
-
}
|
|
17
|
-
;
|
|
18
|
-
function get_properties(input, keys) {
|
|
19
|
-
const location = typeof input === "string" ? input : input.path;
|
|
20
|
-
const result = native.get_properties(location, keys);
|
|
21
|
-
if (typeof input !== "string" && input.id) {
|
|
10
|
+
const get_properties = (input, keys) => {
|
|
11
|
+
const data = input instanceof Uint8Array ? input : input.data;
|
|
12
|
+
const result = bindings_1.native.get_properties(data, keys);
|
|
13
|
+
if (!(input instanceof Uint8Array) && input.id) {
|
|
22
14
|
return { ...result, id: input.id };
|
|
23
15
|
}
|
|
24
16
|
return result;
|
|
25
|
-
}
|
|
26
|
-
;
|
|
27
|
-
async function process_beatmaps(inputs, keys, update_fn) {
|
|
28
|
-
const locations = inputs.map(i => typeof i === "string" ? i : i.path);
|
|
29
|
-
const results = await native.process_beatmaps(locations, keys, update_fn);
|
|
30
|
-
return results.map((res, i) => {
|
|
31
|
-
const input = inputs[i];
|
|
32
|
-
if (typeof input !== "string" && input.id) {
|
|
33
|
-
return { ...res, id: input.id };
|
|
34
|
-
}
|
|
35
|
-
return res;
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
;
|
|
39
|
-
function get_duration(location) {
|
|
40
|
-
return native.get_duration(location);
|
|
41
|
-
}
|
|
42
|
-
;
|
|
43
|
-
function get_audio_duration(location) {
|
|
44
|
-
return native.get_audio_duration(location);
|
|
45
|
-
}
|
|
46
|
-
;
|
|
17
|
+
};
|
|
18
|
+
exports.get_properties = get_properties;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.native = exports.init_wasm = void 0;
|
|
37
|
+
const IS_BROWSER = globalThis.window != undefined;
|
|
38
|
+
const wasm_proxy = {
|
|
39
|
+
instance: null,
|
|
40
|
+
init_promise: null,
|
|
41
|
+
get_property: (data, key) => {
|
|
42
|
+
if (wasm_proxy.instance == null)
|
|
43
|
+
throw Error("WASM not loaded yet...");
|
|
44
|
+
const wasm_inst = wasm_proxy.instance;
|
|
45
|
+
const buffer_ptr = wasm_inst._malloc(data.length);
|
|
46
|
+
wasm_inst.HEAPU8.set(data, buffer_ptr);
|
|
47
|
+
const result = wasm_inst.get_property(buffer_ptr, data.length, key);
|
|
48
|
+
wasm_inst._free(buffer_ptr);
|
|
49
|
+
return result;
|
|
50
|
+
},
|
|
51
|
+
get_properties: (data, keys) => {
|
|
52
|
+
if (wasm_proxy.instance == null)
|
|
53
|
+
throw Error("WASM not loaded yet...");
|
|
54
|
+
const wasm_inst = wasm_proxy.instance;
|
|
55
|
+
const buffer_ptr = wasm_inst._malloc(data.length);
|
|
56
|
+
wasm_inst.HEAPU8.set(data, buffer_ptr);
|
|
57
|
+
const result = wasm_inst.get_properties(buffer_ptr, data.length, keys);
|
|
58
|
+
wasm_inst._free(buffer_ptr);
|
|
59
|
+
return result;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
const init_wasm = async () => {
|
|
63
|
+
if (!IS_BROWSER)
|
|
64
|
+
return;
|
|
65
|
+
if (wasm_proxy.instance)
|
|
66
|
+
return;
|
|
67
|
+
if (wasm_proxy.init_promise)
|
|
68
|
+
return wasm_proxy.init_promise;
|
|
69
|
+
wasm_proxy.init_promise = (async () => {
|
|
70
|
+
// try dynamic import first, fallback to globalThis
|
|
71
|
+
let create_func;
|
|
72
|
+
try {
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
const mod = await Promise.resolve().then(() => __importStar(require("../../build/osu-beatmap-parser.js")));
|
|
75
|
+
create_func = mod.default || mod.create_osu_parser;
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
create_func = globalThis.create_osu_parser;
|
|
79
|
+
}
|
|
80
|
+
if (!create_func) {
|
|
81
|
+
throw Error("create_osu_parser not found");
|
|
82
|
+
}
|
|
83
|
+
wasm_proxy.instance = await create_func();
|
|
84
|
+
})();
|
|
85
|
+
return wasm_proxy.init_promise;
|
|
86
|
+
};
|
|
87
|
+
exports.init_wasm = init_wasm;
|
|
88
|
+
const load_native_module = () => {
|
|
89
|
+
if (IS_BROWSER) {
|
|
90
|
+
return wasm_proxy;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const fs = require("fs");
|
|
94
|
+
const path = require("path");
|
|
95
|
+
const platform = process.platform;
|
|
96
|
+
const arch = process.arch;
|
|
97
|
+
const paths = [
|
|
98
|
+
// prebuilt binaries
|
|
99
|
+
path.join(__dirname, "..", "..", "prebuilds", `${platform}-${arch}`, "osu-beatmap-parser.node"),
|
|
100
|
+
path.join(__dirname, "..", "prebuilds", `${platform}-${arch}`, "osu-beatmap-parser.node"),
|
|
101
|
+
// local builds
|
|
102
|
+
path.join(__dirname, "..", "..", "build", "osu-beatmap-parser.node"),
|
|
103
|
+
path.join(__dirname, "..", "..", "build", "Release", "osu-beatmap-parser.node"),
|
|
104
|
+
path.join(__dirname, "..", "build", "osu-beatmap-parser.node"),
|
|
105
|
+
path.join(__dirname, "build", "osu-beatmap-parser.node"),
|
|
106
|
+
];
|
|
107
|
+
for (const p of paths) {
|
|
108
|
+
if (fs.existsSync(p)) {
|
|
109
|
+
return require(p);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (e) { }
|
|
114
|
+
return false;
|
|
115
|
+
};
|
|
116
|
+
exports.native = load_native_module();
|
|
117
|
+
if (exports.native == false && !IS_BROWSER) {
|
|
118
|
+
throw new Error("failed to load native module...");
|
|
119
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
export type OsuKey = "AudioFilename" | "AudioLeadIn" | "PreviewTime" | "Countdown" | "SampleSet" | "StackLeniency" | "Mode" | "LetterboxInBreaks" | "WidescreenStoryboard" | "Bookmarks" | "DistanceSpacing" | "BeatDivisor" | "GridSize" | "TimelineZoom" | "Title" | "TitleUnicode" | "Artist" | "ArtistUnicode" | "Creator" | "Version" | "Source" | "Tags" | "BeatmapID" | "BeatmapSetID" | "HPDrainRate" | "CircleSize" | "OverallDifficulty" | "ApproachRate" | "SliderMultiplier" | "SliderTickRate" | "Background" | "Video" | "Storyboard" | "Duration";
|
|
2
2
|
export interface OsuInput {
|
|
3
|
-
|
|
3
|
+
data: Uint8Array;
|
|
4
4
|
id?: string;
|
|
5
5
|
}
|
|
6
6
|
export interface INativeExporter {
|
|
7
|
-
get_property(
|
|
8
|
-
get_properties(
|
|
9
|
-
process_beatmaps(locations: string[], keys: string[], callback?: (index: number) => void): Promise<Record<string, string>[]>;
|
|
10
|
-
get_duration(location: string): number;
|
|
11
|
-
get_audio_duration(location: string): number;
|
|
7
|
+
get_property(data: Uint8Array, key: string): string;
|
|
8
|
+
get_properties(data: Uint8Array, keys: string[]): Record<string, string>;
|
|
12
9
|
}
|
package/package.json
CHANGED
|
@@ -1,24 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rel-packages/osu-beatmap-parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": ".osu parser for nodejs",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"homepage": "https://github.com/mezleca/osu-beatmap-parser",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
9
|
+
"update:patch": "npm version patch && git push --follow-tags",
|
|
10
|
+
"update:minor": "npm version minor && git push --follow-tags",
|
|
11
|
+
"update:major": "npm version major && git push --follow-tags",
|
|
12
|
+
"prepublishOnly": "npm run compile:tsc && mkdir -p lib",
|
|
13
|
+
"compile:native": "tsx scripts/build.ts native",
|
|
14
|
+
"compile:wasm": "tsx scripts/build.ts wasm",
|
|
11
15
|
"compile:tsc": "tsc",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
16
|
+
"build": "tsx scripts/build.ts all && npm run compile:tsc",
|
|
17
|
+
"example:wasm": "bun run compile:wasm && bun run examples/wasm/server.ts",
|
|
18
|
+
"example:node": "bun run compile:native && tsx examples/node/index.ts"
|
|
14
19
|
},
|
|
15
|
-
"keywords": [
|
|
20
|
+
"keywords": [
|
|
21
|
+
"osu",
|
|
22
|
+
"beatmap",
|
|
23
|
+
"parser",
|
|
24
|
+
"native"
|
|
25
|
+
],
|
|
16
26
|
"author": "",
|
|
17
27
|
"license": "ISC",
|
|
18
28
|
"repository": {
|
|
19
29
|
"url": "https://github.com/mezleca/osu-beatmap-parser.git"
|
|
20
30
|
},
|
|
21
31
|
"type": "commonjs",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/",
|
|
34
|
+
"prebuilds/",
|
|
35
|
+
"build/*.node",
|
|
36
|
+
"build/*.wasm",
|
|
37
|
+
"build/*.js",
|
|
38
|
+
"lib/bindings.ts",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
22
41
|
"devDependencies": {
|
|
23
42
|
"@types/bindings": "^1.5.5",
|
|
24
43
|
"@types/node": "^24.10.1",
|
|
@@ -27,7 +46,6 @@
|
|
|
27
46
|
"tsx": "^4.21.0",
|
|
28
47
|
"typescript": "^5.9.3"
|
|
29
48
|
},
|
|
30
|
-
"dependencies": {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
49
|
+
"dependencies": {},
|
|
50
|
+
"optionalDependencies": {}
|
|
51
|
+
}
|
|
Binary file
|
|
Binary file
|
package/.prebuildrc
DELETED
package/CMakeLists.txt
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
cmake_minimum_required(VERSION 3.16)
|
|
2
|
-
|
|
3
|
-
if(WIN32)
|
|
4
|
-
if(NOT DEFINED CMAKE_TOOLCHAIN_FILE)
|
|
5
|
-
if(EXISTS "${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake")
|
|
6
|
-
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake")
|
|
7
|
-
elseif(EXISTS "C:/vcpkg/scripts/buildsystems/vcpkg.cmake")
|
|
8
|
-
set(CMAKE_TOOLCHAIN_FILE "C:/vcpkg/scripts/buildsystems/vcpkg.cmake")
|
|
9
|
-
endif()
|
|
10
|
-
endif()
|
|
11
|
-
set(VCPKG_TARGET_TRIPLET "x64-windows-static")
|
|
12
|
-
endif()
|
|
13
|
-
|
|
14
|
-
project(osu-beatmap-parser VERSION 0.0.1)
|
|
15
|
-
|
|
16
|
-
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
17
|
-
set(CMAKE_CXX_STANDARD 17)
|
|
18
|
-
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
19
|
-
set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE)
|
|
20
|
-
set(BUILD_SHARED_LIBS ON CACHE BOOL "Build shared libraries")
|
|
21
|
-
|
|
22
|
-
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/native/*.cpp")
|
|
23
|
-
file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS "src/native/*.hpp")
|
|
24
|
-
|
|
25
|
-
if(WIN32)
|
|
26
|
-
find_package(SndFile CONFIG REQUIRED)
|
|
27
|
-
set(SNDFILE_LIBRARIES SndFile::sndfile)
|
|
28
|
-
else()
|
|
29
|
-
find_package(PkgConfig REQUIRED)
|
|
30
|
-
pkg_check_modules(SNDFILE REQUIRED sndfile)
|
|
31
|
-
endif()
|
|
32
|
-
|
|
33
|
-
execute_process(
|
|
34
|
-
COMMAND node -p "require('node-addon-api').include"
|
|
35
|
-
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
36
|
-
OUTPUT_VARIABLE NODE_ADDON_API_DIR
|
|
37
|
-
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
|
|
41
|
-
|
|
42
|
-
add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS} ${CMAKE_JS_SRC})
|
|
43
|
-
|
|
44
|
-
set_target_properties(${PROJECT_NAME} PROPERTIES
|
|
45
|
-
PREFIX ""
|
|
46
|
-
SUFFIX ".node"
|
|
47
|
-
CXX_STANDARD 17
|
|
48
|
-
CXX_STANDARD_REQUIRED ON
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
if (MSVC)
|
|
52
|
-
set(MSVC_RUNTIME_LIBRARY OFF)
|
|
53
|
-
add_compile_definitions(_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
|
54
|
-
execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS})
|
|
55
|
-
target_compile_options(${PROJECT_NAME} PRIVATE /Zc:__cplusplus)
|
|
56
|
-
endif()
|
|
57
|
-
|
|
58
|
-
target_include_directories(${PROJECT_NAME} PRIVATE
|
|
59
|
-
${NODE_ADDON_API_DIR}
|
|
60
|
-
${CMAKE_JS_INC}
|
|
61
|
-
${SNDFILE_INCLUDE_DIRS}
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
target_link_libraries(
|
|
65
|
-
${PROJECT_NAME}
|
|
66
|
-
${CMAKE_JS_LIB}
|
|
67
|
-
${SNDFILE_LIBRARIES}
|
|
68
|
-
)
|
|
Binary file
|
|
Binary file
|
package/src/native/addon.cpp
DELETED
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
#include <filesystem>
|
|
2
|
-
#include <napi.h>
|
|
3
|
-
#include <iostream>
|
|
4
|
-
#include <sndfile.h>
|
|
5
|
-
#include <string>
|
|
6
|
-
#include <thread>
|
|
7
|
-
#include <fstream>
|
|
8
|
-
#include <vector>
|
|
9
|
-
#include <atomic>
|
|
10
|
-
#include "osu/parser.hpp"
|
|
11
|
-
#include "osu/audio.hpp"
|
|
12
|
-
#include "addon.hpp"
|
|
13
|
-
#include "pool.hpp"
|
|
14
|
-
|
|
15
|
-
#define NOOP_FUNC(env) Napi::Function::New(env, [](const Napi::CallbackInfo& info){})
|
|
16
|
-
|
|
17
|
-
AudioAnalyzer audio_analizer;
|
|
18
|
-
|
|
19
|
-
std::filesystem::path dirname(const std::string &path) {
|
|
20
|
-
std::filesystem::path p(path);
|
|
21
|
-
return p.parent_path();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
std::string read_file(const std::string& path) {
|
|
25
|
-
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
|
26
|
-
|
|
27
|
-
if (!file.is_open()) {
|
|
28
|
-
return "";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
std::streamsize size = file.tellg();
|
|
32
|
-
file.seekg(0, std::ios::beg);
|
|
33
|
-
|
|
34
|
-
std::string buffer(size, ' ');
|
|
35
|
-
|
|
36
|
-
if (file.read(buffer.data(), size)) {
|
|
37
|
-
return buffer;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return "";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
Napi::Value ParserAddon::get_property(const Napi::CallbackInfo& info) {
|
|
44
|
-
if (info.Length() < 2) {
|
|
45
|
-
return Napi::String::New(info.Env(), "");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!info[0].IsString() || !info[1].IsString()) {
|
|
49
|
-
return Napi::String::New(info.Env(), "");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
std::string location = info[0].As<Napi::String>().Utf8Value();
|
|
53
|
-
std::string key = info[1].As<Napi::String>().Utf8Value();
|
|
54
|
-
std::string content = read_file(location);
|
|
55
|
-
|
|
56
|
-
if (content.empty()) {
|
|
57
|
-
return Napi::String::New(info.Env(), "");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return Napi::String::From(info.Env(), osu_parser::get_property(content, key));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
Napi::Value ParserAddon::get_properties(const Napi::CallbackInfo& info) {
|
|
64
|
-
if (info.Length() < 2) {
|
|
65
|
-
return Napi::Object::New(info.Env());
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
std::string location = info[0].As<Napi::String>().Utf8Value();
|
|
69
|
-
Napi::Array keys_array = info[1].As<Napi::Array>();
|
|
70
|
-
|
|
71
|
-
std::vector<std::string> keys;
|
|
72
|
-
|
|
73
|
-
for (uint32_t i = 0; i < keys_array.Length(); i++) {
|
|
74
|
-
Napi::Value val = keys_array[i];
|
|
75
|
-
if (val.IsString()) {
|
|
76
|
-
keys.push_back(val.As<Napi::String>().Utf8Value());
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
std::string content = read_file(location);
|
|
81
|
-
|
|
82
|
-
if (content.empty()) {
|
|
83
|
-
return Napi::Object::New(info.Env());
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
auto results = osu_parser::get_properties(content, keys);
|
|
87
|
-
|
|
88
|
-
Napi::Object obj = Napi::Object::New(info.Env());
|
|
89
|
-
|
|
90
|
-
for (const auto& [k, v] : results) {
|
|
91
|
-
obj.Set(k, v);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return obj;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
struct CallbackContext {
|
|
98
|
-
Napi::ThreadSafeFunction tsfn;
|
|
99
|
-
std::atomic<size_t> current_index = 0;
|
|
100
|
-
|
|
101
|
-
CallbackContext() {}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
struct BatchContext {
|
|
105
|
-
std::vector<std::string> paths;
|
|
106
|
-
std::vector<std::string> keys;
|
|
107
|
-
std::vector<std::unordered_map<std::string, std::string>> results;
|
|
108
|
-
std::atomic<size_t> completed_count{0};
|
|
109
|
-
Napi::ThreadSafeFunction tsfn;
|
|
110
|
-
CallbackContext *callback_ctx;
|
|
111
|
-
Napi::Promise::Deferred deferred;
|
|
112
|
-
bool needs_duration = false;
|
|
113
|
-
|
|
114
|
-
BatchContext(Napi::Env env) : deferred(Napi::Promise::Deferred::New(env)) {}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
void process_chunk(BatchContext* context, size_t start, size_t end) {
|
|
118
|
-
auto callback_ctx = context->callback_ctx;
|
|
119
|
-
|
|
120
|
-
for (size_t i = start; i < end; i++) {
|
|
121
|
-
std::string content = read_file(context->paths[i]);
|
|
122
|
-
if (!content.empty()) {
|
|
123
|
-
context->results[i] = osu_parser::get_properties(content, context->keys);
|
|
124
|
-
|
|
125
|
-
if (context->needs_duration) {
|
|
126
|
-
std::string audio_file_name = osu_parser::get_property(content, "AudioFilename");
|
|
127
|
-
if (!audio_file_name.empty()) {
|
|
128
|
-
std::filesystem::path audio_path = dirname(context->paths[i]) / audio_file_name;
|
|
129
|
-
double duration = audio_analizer.get_audio_duration(audio_path.string());
|
|
130
|
-
context->results[i]["Duration"] = std::to_string(duration);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// only increment and call if file was actually processed
|
|
135
|
-
if (callback_ctx != nullptr) {
|
|
136
|
-
size_t current = callback_ctx->current_index.fetch_add(1) + 1;
|
|
137
|
-
callback_ctx->tsfn.NonBlockingCall([current](Napi::Env env, Napi::Function js_callback) {
|
|
138
|
-
js_callback.Call({
|
|
139
|
-
Napi::Number::New(env, static_cast<double>(current))
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
size_t completed = context->completed_count.fetch_add(end - start) + (end - start);
|
|
147
|
-
|
|
148
|
-
// last thread to finish resolves
|
|
149
|
-
if (completed == context->paths.size()) {
|
|
150
|
-
// release callback
|
|
151
|
-
if (context->callback_ctx != nullptr) {
|
|
152
|
-
context->callback_ctx->tsfn.Release();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// then resolve the promise
|
|
156
|
-
context->tsfn.BlockingCall([context](Napi::Env env, Napi::Function) {
|
|
157
|
-
Napi::Array result_array = Napi::Array::New(env, context->results.size());
|
|
158
|
-
|
|
159
|
-
for (size_t i = 0; i < context->results.size(); i++) {
|
|
160
|
-
Napi::Object obj = Napi::Object::New(env);
|
|
161
|
-
for (const auto& [k, v] : context->results[i]) {
|
|
162
|
-
obj.Set(k, v);
|
|
163
|
-
}
|
|
164
|
-
result_array[i] = obj;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
context->deferred.Resolve(result_array);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
context->tsfn.Release();
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
Napi::Value ParserAddon::process_beatmaps(const Napi::CallbackInfo& info) {
|
|
175
|
-
Napi::Env env = info.Env();
|
|
176
|
-
|
|
177
|
-
if (info.Length() < 2) {
|
|
178
|
-
auto deferred = Napi::Promise::Deferred::New(env);
|
|
179
|
-
deferred.Reject(Napi::String::New(env, "Invalid arguments"));
|
|
180
|
-
return deferred.Promise();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
Napi::Array paths_array = info[0].As<Napi::Array>();
|
|
184
|
-
Napi::Array keys_array = info[1].As<Napi::Array>();
|
|
185
|
-
|
|
186
|
-
auto context = new BatchContext(env);
|
|
187
|
-
|
|
188
|
-
for (uint32_t i = 0; i < paths_array.Length(); i++) {
|
|
189
|
-
context->paths.push_back(Napi::Value(paths_array[i]).As<Napi::String>().Utf8Value());
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
for (uint32_t i = 0; i < keys_array.Length(); i++) {
|
|
193
|
-
std::string key = Napi::Value(keys_array[i]).As<Napi::String>().Utf8Value();
|
|
194
|
-
context->keys.push_back(key);
|
|
195
|
-
if (key == "Duration") {
|
|
196
|
-
context->needs_duration = true;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (info[2].IsFunction()) {
|
|
201
|
-
Napi::Function callback_fn = info[2].As<Napi::Function>();
|
|
202
|
-
auto callback_ctx = new CallbackContext();
|
|
203
|
-
|
|
204
|
-
callback_ctx = new CallbackContext();
|
|
205
|
-
callback_ctx->tsfn = Napi::ThreadSafeFunction::New(
|
|
206
|
-
callback_fn.Env(),
|
|
207
|
-
callback_fn,
|
|
208
|
-
"ProcessBeatmapsUpdate",
|
|
209
|
-
0,
|
|
210
|
-
1,
|
|
211
|
-
[callback_ctx](Napi::Env) {
|
|
212
|
-
delete callback_ctx;
|
|
213
|
-
}
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
context->callback_ctx = callback_ctx;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
context->results.resize(context->paths.size());
|
|
220
|
-
|
|
221
|
-
context->tsfn = Napi::ThreadSafeFunction::New(
|
|
222
|
-
env,
|
|
223
|
-
NOOP_FUNC(env),
|
|
224
|
-
"ProcessBeatmaps",
|
|
225
|
-
0,
|
|
226
|
-
1,
|
|
227
|
-
[context](Napi::Env) {
|
|
228
|
-
delete context;
|
|
229
|
-
}
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
size_t total_files = context->paths.size();
|
|
233
|
-
size_t thread_count = std::thread::hardware_concurrency();
|
|
234
|
-
|
|
235
|
-
if (thread_count == 0) {
|
|
236
|
-
thread_count = 1;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
size_t chunk_size = (total_files + thread_count - 1) / thread_count;
|
|
240
|
-
|
|
241
|
-
for (size_t t = 0; t < thread_count; t++) {
|
|
242
|
-
size_t start = t * chunk_size;
|
|
243
|
-
size_t end = std::min(start + chunk_size, total_files);
|
|
244
|
-
|
|
245
|
-
if (start >= end) {
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
pool.enqueue([context, start, end]() {
|
|
250
|
-
process_chunk(context, start, end);
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return context->deferred.Promise();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
Napi::Value ParserAddon::get_duration(const Napi::CallbackInfo& info) {
|
|
258
|
-
if (info.Length() < 1) {
|
|
259
|
-
return Napi::Number::New(info.Env(), 0.0);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (!info[0].IsString()) {
|
|
263
|
-
return Napi::String::New(info.Env(), "");
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
std::string location = info[0].As<Napi::String>().Utf8Value();
|
|
267
|
-
std::string content = read_file(location);
|
|
268
|
-
|
|
269
|
-
if (content.empty()) {
|
|
270
|
-
return Napi::Number::New(info.Env(), 0.0);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
std::string audio_file_name = osu_parser::get_property(content, "AudioFilename");
|
|
274
|
-
std::filesystem::path audio_path = dirname(location) / audio_file_name;
|
|
275
|
-
|
|
276
|
-
return Napi::Number::From(info.Env(), audio_analizer.get_audio_duration(audio_path.string()));
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
Napi::Value ParserAddon::get_audio_duration(const Napi::CallbackInfo& info) {
|
|
280
|
-
if (info.Length() < 1) {
|
|
281
|
-
return Napi::Number::New(info.Env(), 0.0);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (!info[0].IsString()) {
|
|
285
|
-
return Napi::String::New(info.Env(), "");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
std::string location = info[0].As<Napi::String>().Utf8Value();
|
|
289
|
-
std::filesystem::path audio_path(location);
|
|
290
|
-
|
|
291
|
-
return Napi::Number::From(info.Env(), audio_analizer.get_audio_duration(audio_path.string()));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
Napi::Value ParserAddon::test_promise(const Napi::CallbackInfo& info) {
|
|
295
|
-
auto deffered = Napi::Promise::Deferred::New(info.Env());
|
|
296
|
-
auto tsfn = Napi::ThreadSafeFunction::New(info.Env(), NOOP_FUNC(info.Env()), "tsfn", 0, 1);
|
|
297
|
-
|
|
298
|
-
std::thread([tsfn, deffered]() {
|
|
299
|
-
tsfn.NonBlockingCall([deffered](Napi::Env env, Napi::Function) {
|
|
300
|
-
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
|
|
301
|
-
deffered.Resolve(Napi::Boolean::New(env, true));
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
tsfn.Release();
|
|
305
|
-
}).detach();
|
|
306
|
-
|
|
307
|
-
return deffered.Promise();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
NODE_API_ADDON(ParserAddon)
|
package/src/native/addon.hpp
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <napi.h>
|
|
4
|
-
#include "pool.hpp"
|
|
5
|
-
|
|
6
|
-
class ParserAddon : public Napi::Addon<ParserAddon> {
|
|
7
|
-
public:
|
|
8
|
-
ParserAddon(Napi::Env env, Napi::Object exports) {
|
|
9
|
-
pool.initialize(std::thread::hardware_concurrency());
|
|
10
|
-
DefineAddon(exports, {
|
|
11
|
-
InstanceMethod("get_property", &ParserAddon::get_property),
|
|
12
|
-
InstanceMethod("get_properties", &ParserAddon::get_properties),
|
|
13
|
-
InstanceMethod("process_beatmaps", &ParserAddon::process_beatmaps),
|
|
14
|
-
InstanceMethod("get_duration", &ParserAddon::get_duration),
|
|
15
|
-
InstanceMethod("get_audio_duration", &ParserAddon::get_audio_duration),
|
|
16
|
-
InstanceMethod("test_promise", &ParserAddon::test_promise)
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
Napi::Value get_property(const Napi::CallbackInfo& info);
|
|
21
|
-
Napi::Value get_properties(const Napi::CallbackInfo& info);
|
|
22
|
-
Napi::Value process_beatmaps(const Napi::CallbackInfo& info);
|
|
23
|
-
Napi::Value get_duration(const Napi::CallbackInfo& info);
|
|
24
|
-
Napi::Value get_audio_duration(const Napi::CallbackInfo& info);
|
|
25
|
-
Napi::Value test_promise(const Napi::CallbackInfo& info);
|
|
26
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
#include <string>
|
|
3
|
-
#include <unordered_set>
|
|
4
|
-
#include <unordered_map>
|
|
5
|
-
|
|
6
|
-
enum OSU_SECTIONS {
|
|
7
|
-
General = 0,
|
|
8
|
-
Editor,
|
|
9
|
-
Metadata,
|
|
10
|
-
Difficulty,
|
|
11
|
-
Events,
|
|
12
|
-
TimingPoints,
|
|
13
|
-
Colours,
|
|
14
|
-
HitObjects
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const std::unordered_map<std::string_view, std::string_view> KEY_TO_SECTION = {
|
|
18
|
-
{"AudioFilename", "[General]"},
|
|
19
|
-
{"AudioLeadIn", "[General]"},
|
|
20
|
-
{"PreviewTime", "[General]"},
|
|
21
|
-
{"Countdown", "[General]"},
|
|
22
|
-
{"SampleSet", "[General]"},
|
|
23
|
-
{"StackLeniency", "[General]"},
|
|
24
|
-
{"Mode", "[General]"},
|
|
25
|
-
{"LetterboxInBreaks", "[General]"},
|
|
26
|
-
{"WidescreenStoryboard", "[General]"},
|
|
27
|
-
{"Bookmarks", "[Editor]"},
|
|
28
|
-
{"DistanceSpacing", "[Editor]"},
|
|
29
|
-
{"BeatDivisor", "[Editor]"},
|
|
30
|
-
{"GridSize", "[Editor]"},
|
|
31
|
-
{"TimelineZoom", "[Editor]"},
|
|
32
|
-
{"Title", "[Metadata]"},
|
|
33
|
-
{"TitleUnicode", "[Metadata]"},
|
|
34
|
-
{"Artist", "[Metadata]"},
|
|
35
|
-
{"ArtistUnicode", "[Metadata]"},
|
|
36
|
-
{"Creator", "[Metadata]"},
|
|
37
|
-
{"Version", "[Metadata]"},
|
|
38
|
-
{"Source", "[Metadata]"},
|
|
39
|
-
{"Tags", "[Metadata]"},
|
|
40
|
-
{"BeatmapID", "[Metadata]"},
|
|
41
|
-
{"BeatmapSetID", "[Metadata]"},
|
|
42
|
-
{"HPDrainRate", "[Difficulty]"},
|
|
43
|
-
{"CircleSize", "[Difficulty]"},
|
|
44
|
-
{"OverallDifficulty", "[Difficulty]"},
|
|
45
|
-
{"ApproachRate", "[Difficulty]"},
|
|
46
|
-
{"SliderMultiplier", "[Difficulty]"},
|
|
47
|
-
{"SliderTickRate", "[Difficulty]"},
|
|
48
|
-
// special ones (doenst have keys)
|
|
49
|
-
{"Background", "[Events]"},
|
|
50
|
-
{"Video", "[Events]"},
|
|
51
|
-
{"Storyboard", "[Events]"},
|
|
52
|
-
// extra special ones (why not)
|
|
53
|
-
{"Duration", "[General]"}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const std::unordered_set<std::string_view> SPECIAL_KEYS {"Background", "Video", "Storyboard"};
|
package/src/native/osu/audio.cpp
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#include <iostream>
|
|
2
|
-
#include <filesystem>
|
|
3
|
-
#include <sndfile.h>
|
|
4
|
-
#include "audio.hpp"
|
|
5
|
-
|
|
6
|
-
double AudioAnalyzer::get_audio_duration(std::string location) {
|
|
7
|
-
double duration = 0.0;
|
|
8
|
-
|
|
9
|
-
if (get_cache(location, duration)) {
|
|
10
|
-
return duration;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (!std::filesystem::exists(location)) {
|
|
14
|
-
std::cout << location << " does not exists\n";
|
|
15
|
-
return 0.0;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
SF_INFO sfinfo{};
|
|
19
|
-
SNDFILE *file = sf_open(location.c_str(), SFM_READ, &sfinfo);
|
|
20
|
-
|
|
21
|
-
if (!file) {
|
|
22
|
-
return 0.0;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
duration = static_cast<double>(sfinfo.frames) / sfinfo.samplerate;
|
|
26
|
-
|
|
27
|
-
sf_close(file);
|
|
28
|
-
set_cache(location, duration);
|
|
29
|
-
return duration;
|
|
30
|
-
}
|
package/src/native/osu/audio.hpp
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
#include <mutex>
|
|
3
|
-
#include <string>
|
|
4
|
-
#include <unordered_map>
|
|
5
|
-
|
|
6
|
-
class AudioAnalyzer
|
|
7
|
-
{
|
|
8
|
-
private:
|
|
9
|
-
std::unordered_map<std::string, double> cache;
|
|
10
|
-
mutable std::mutex cache_mutex;
|
|
11
|
-
|
|
12
|
-
bool get_cache(const std::string &key, double &duration) const {
|
|
13
|
-
std::lock_guard<std::mutex> lock(cache_mutex);
|
|
14
|
-
auto it = cache.find(key);
|
|
15
|
-
if (it != cache.end())
|
|
16
|
-
{
|
|
17
|
-
duration = it->second;
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
void set_cache(const std::string &key, double duration) {
|
|
24
|
-
std::lock_guard<std::mutex> lock(cache_mutex);
|
|
25
|
-
cache[key] = duration;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
void clear_cache() {
|
|
29
|
-
std::lock_guard<std::mutex> lock(cache_mutex);
|
|
30
|
-
cache.clear();
|
|
31
|
-
}
|
|
32
|
-
public:
|
|
33
|
-
double get_audio_duration(std::string location);
|
|
34
|
-
};
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
#include <algorithm>
|
|
2
|
-
#include <cstdio>
|
|
3
|
-
#include <iostream>
|
|
4
|
-
#include <optional>
|
|
5
|
-
#include <string>
|
|
6
|
-
#include <string_view>
|
|
7
|
-
#include <unordered_set>
|
|
8
|
-
#include <unordered_map>
|
|
9
|
-
#include "./parser.hpp"
|
|
10
|
-
#include "../definitions.hpp"
|
|
11
|
-
|
|
12
|
-
const std::unordered_set<std::string_view> VIDEO_EXTENSIONS = {
|
|
13
|
-
".mp4", ".avi", ".flv", ".mov", ".wmv", ".m4v", ".mpg", ".mpeg"
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const std::unordered_set<std::string_view> IMAGE_EXTENSIONS = {
|
|
17
|
-
".jpg", ".jpeg", ".png", ".bmp", ".gif"
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
std::string_view trim_view(std::string_view s) {
|
|
21
|
-
size_t start = s.find_first_not_of(" \t\r\n");
|
|
22
|
-
|
|
23
|
-
if (start == std::string_view::npos) {
|
|
24
|
-
return "";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
size_t end = s.find_last_not_of(" \t\r\n");
|
|
28
|
-
return s.substr(start, end - start + 1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
std::string trim(const std::string &s) {
|
|
32
|
-
std::string_view result = trim_view(s);
|
|
33
|
-
return std::string(result);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
std::vector<std::string_view> split_view(std::string_view s, char delim) {
|
|
37
|
-
std::vector<std::string_view> result;
|
|
38
|
-
|
|
39
|
-
size_t start = 0;
|
|
40
|
-
size_t end = s.find(delim);
|
|
41
|
-
|
|
42
|
-
while (end != std::string_view::npos) {
|
|
43
|
-
result.push_back(s.substr(start, end - start));
|
|
44
|
-
start = end + 1;
|
|
45
|
-
end = s.find(delim, start);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
result.push_back(s.substr(start));
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
std::string normalize_path(const std::string &path) {
|
|
53
|
-
#ifdef _WIN32
|
|
54
|
-
std::string normalized = path;
|
|
55
|
-
std::replace(normalized.begin(), normalized.end(), '/', '\\');
|
|
56
|
-
return normalized;
|
|
57
|
-
#else
|
|
58
|
-
return path;
|
|
59
|
-
#endif
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
std::string_view get_extension(std::string_view filename) {
|
|
63
|
-
size_t dot_pos = filename.find_last_of('.');
|
|
64
|
-
|
|
65
|
-
if (dot_pos == std::string_view::npos) {
|
|
66
|
-
return "";
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return filename.substr(dot_pos);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
std::optional<std::string> get_special_key(std::string_view key, std::string_view line) {
|
|
73
|
-
if (key == "Background" || key == "Video") {
|
|
74
|
-
std::vector<std::string_view> parts = split_view(line, ',');
|
|
75
|
-
|
|
76
|
-
if (parts.size() < 3) {
|
|
77
|
-
return std::nullopt;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
std::string_view event_type = trim_view(parts[0]);
|
|
81
|
-
std::string_view start_time = trim_view(parts[1]);
|
|
82
|
-
|
|
83
|
-
if (event_type != "0" || start_time != "0") {
|
|
84
|
-
return std::nullopt;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
std::string_view filename = trim_view(parts[2]);
|
|
88
|
-
|
|
89
|
-
// remove quotes if present
|
|
90
|
-
if (filename.size() >= 2 && filename.front() == '"' && filename.back() == '"') {
|
|
91
|
-
filename = filename.substr(1, filename.size() - 2);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
std::string_view ext = get_extension(filename);
|
|
95
|
-
std::string ext_lower(ext);
|
|
96
|
-
std::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);
|
|
97
|
-
|
|
98
|
-
if (key == "Video") {
|
|
99
|
-
if (VIDEO_EXTENSIONS.count(ext_lower) == 0) {
|
|
100
|
-
return std::nullopt;
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
if (IMAGE_EXTENSIONS.count(ext_lower) == 0) {
|
|
104
|
-
return std::nullopt;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return normalize_path(std::string(filename));
|
|
109
|
-
} else if (key == "Storyboard") {
|
|
110
|
-
// TODO:
|
|
111
|
-
return std::nullopt;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return std::nullopt;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
std::string osu_parser::get_property(std::string_view content, std::string_view key) {
|
|
118
|
-
bool is_special_key = SPECIAL_KEYS.count(key) > 0;
|
|
119
|
-
std::string current_section;
|
|
120
|
-
std::string special_section;
|
|
121
|
-
|
|
122
|
-
if (is_special_key) {
|
|
123
|
-
if (KEY_TO_SECTION.find(key) == KEY_TO_SECTION.end()) {
|
|
124
|
-
std::cout << "failed to find special key: " << key << "\n";
|
|
125
|
-
return "";
|
|
126
|
-
}
|
|
127
|
-
special_section = KEY_TO_SECTION.at(key);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
size_t start = 0;
|
|
131
|
-
size_t end = content.find('\n');
|
|
132
|
-
|
|
133
|
-
while (end != std::string_view::npos) {
|
|
134
|
-
std::string_view line_view = trim_view(content.substr(start, end - start));
|
|
135
|
-
start = end + 1;
|
|
136
|
-
end = content.find('\n', start);
|
|
137
|
-
|
|
138
|
-
if (line_view.empty() || line_view[0] == '/') {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (line_view[0] == '[') {
|
|
143
|
-
current_section = std::string(line_view);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (is_special_key) {
|
|
148
|
-
if (special_section != current_section) {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
auto result = get_special_key(key, line_view);
|
|
153
|
-
|
|
154
|
-
if (result.has_value()) {
|
|
155
|
-
return result.value();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
size_t delimiter_i = line_view.find(':');
|
|
162
|
-
|
|
163
|
-
if (delimiter_i == std::string_view::npos) {
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
std::string_view current_key = trim_view(line_view.substr(0, delimiter_i));
|
|
168
|
-
|
|
169
|
-
if (current_key == key) {
|
|
170
|
-
std::string_view value = trim_view(line_view.substr(delimiter_i + 1));
|
|
171
|
-
return std::string(value);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// check last line if no newline at end
|
|
176
|
-
if (start < content.size()) {
|
|
177
|
-
std::string_view line_view = trim_view(content.substr(start));
|
|
178
|
-
if (line_view.empty() || line_view[0] == '/') {
|
|
179
|
-
return "";
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// section change at end
|
|
183
|
-
if (line_view[0] == '[') {
|
|
184
|
-
return "";
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (is_special_key) {
|
|
188
|
-
if (special_section == current_section) {
|
|
189
|
-
auto result = get_special_key(key, line_view);
|
|
190
|
-
if (result.has_value()) {
|
|
191
|
-
return result.value();
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return "";
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
size_t delimiter_i = line_view.find(':');
|
|
198
|
-
|
|
199
|
-
if (delimiter_i != std::string_view::npos) {
|
|
200
|
-
std::string_view current_key = trim_view(line_view.substr(0, delimiter_i));
|
|
201
|
-
if (current_key == key) {
|
|
202
|
-
std::string_view value = trim_view(line_view.substr(delimiter_i + 1));
|
|
203
|
-
return std::string(value);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return "";
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
std::unordered_map<std::string, std::string> osu_parser::get_properties(std::string_view content, const std::vector<std::string>& keys) {
|
|
212
|
-
std::unordered_map<std::string, std::string> results;
|
|
213
|
-
std::unordered_map<std::string_view, std::string_view> key_to_section_map;
|
|
214
|
-
std::unordered_set<std::string_view> keys_to_find;
|
|
215
|
-
|
|
216
|
-
for (const auto& key : keys) {
|
|
217
|
-
keys_to_find.insert(key);
|
|
218
|
-
if (KEY_TO_SECTION.count(key)) {
|
|
219
|
-
key_to_section_map[key] = KEY_TO_SECTION.at(key);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
std::string current_section;
|
|
224
|
-
size_t start = 0;
|
|
225
|
-
size_t end = content.find('\n');
|
|
226
|
-
|
|
227
|
-
auto process_line = [&](std::string_view line_view) {
|
|
228
|
-
if (line_view.empty() || line_view[0] == '/') {
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (line_view[0] == '[') {
|
|
233
|
-
current_section = std::string(line_view);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
size_t delimiter_i = line_view.find(':');
|
|
238
|
-
if (delimiter_i != std::string_view::npos) {
|
|
239
|
-
std::string_view current_key = trim_view(line_view.substr(0, delimiter_i));
|
|
240
|
-
|
|
241
|
-
if (keys_to_find.count(current_key)) {
|
|
242
|
-
bool correct_section = !key_to_section_map.count(current_key) ||
|
|
243
|
-
key_to_section_map.at(current_key) == current_section;
|
|
244
|
-
|
|
245
|
-
bool not_found_yet = results.find(std::string(current_key)) == results.end();
|
|
246
|
-
|
|
247
|
-
if (correct_section && not_found_yet) {
|
|
248
|
-
std::string_view value = trim_view(line_view.substr(delimiter_i + 1));
|
|
249
|
-
results[std::string(current_key)] = std::string(value);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
for (const auto& key : keys) {
|
|
255
|
-
if (!SPECIAL_KEYS.count(key)) {
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (results.find(key) != results.end()) {
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (key_to_section_map.count(key) && key_to_section_map.at(key) == current_section) {
|
|
264
|
-
auto result = get_special_key(key, line_view);
|
|
265
|
-
if (result.has_value()) {
|
|
266
|
-
results[key] = result.value();
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
while (end != std::string_view::npos) {
|
|
273
|
-
std::string_view line_view = trim_view(content.substr(start, end - start));
|
|
274
|
-
process_line(line_view);
|
|
275
|
-
start = end + 1;
|
|
276
|
-
end = content.find('\n', start);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (start < content.size()) {
|
|
280
|
-
std::string_view line_view = trim_view(content.substr(start));
|
|
281
|
-
process_line(line_view);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return results;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
std::vector<std::string> osu_parser::get_section() {
|
|
288
|
-
std::vector<std::string> result;
|
|
289
|
-
return result;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
std::map<std::string, std::string> osu_parser::parse(std::string_view content) {
|
|
293
|
-
return {};
|
|
294
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
#include <map>
|
|
3
|
-
#include <string>
|
|
4
|
-
#include <vector>
|
|
5
|
-
#include <unordered_map>
|
|
6
|
-
|
|
7
|
-
namespace osu_parser {
|
|
8
|
-
std::string get_property(std::string_view content, std::string_view key);
|
|
9
|
-
std::unordered_map<std::string, std::string> get_properties(std::string_view content, const std::vector<std::string>& keys);
|
|
10
|
-
|
|
11
|
-
// TODO:
|
|
12
|
-
std::vector<std::string> get_section();
|
|
13
|
-
std::map<std::string, std::string> parse(std::string_view content);
|
|
14
|
-
};
|
package/src/native/pool.hpp
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <condition_variable>
|
|
4
|
-
#include <functional>
|
|
5
|
-
#include <mutex>
|
|
6
|
-
#include <queue>
|
|
7
|
-
#include <thread>
|
|
8
|
-
#include <vector>
|
|
9
|
-
#include <atomic>
|
|
10
|
-
|
|
11
|
-
class ThreadPool {
|
|
12
|
-
public:
|
|
13
|
-
void initialize(int count) {
|
|
14
|
-
for (int i = 0; i < count; i++) {
|
|
15
|
-
// add new worker
|
|
16
|
-
workers.emplace_back([this]() {
|
|
17
|
-
// initialize task loop
|
|
18
|
-
while (true) {
|
|
19
|
-
std::function<void()> task;
|
|
20
|
-
// wait for new task
|
|
21
|
-
{
|
|
22
|
-
std::unique_lock<std::mutex> lock(queue_mutex);
|
|
23
|
-
|
|
24
|
-
// use condition variable to make threads go zzz until we actually have a task to do
|
|
25
|
-
cv.wait(lock, [this] {
|
|
26
|
-
return stop || !tasks.empty();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// ensure we have a task
|
|
30
|
-
if (stop && tasks.empty()) {
|
|
31
|
-
break;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// move task from queue to var
|
|
35
|
-
task = std::move(tasks.front());
|
|
36
|
-
|
|
37
|
-
// if i recall correctly "move" moves the pointer data from the queue
|
|
38
|
-
// but the pointer is still there pointing to nothing
|
|
39
|
-
// so remove it anyway
|
|
40
|
-
tasks.pop();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// execute it :)
|
|
44
|
-
task();
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
~ThreadPool() {
|
|
51
|
-
stop = true;
|
|
52
|
-
|
|
53
|
-
// wake up all lazy ass threads
|
|
54
|
-
cv.notify_all();
|
|
55
|
-
|
|
56
|
-
// free them
|
|
57
|
-
for (std::thread& t : workers) {
|
|
58
|
-
if (t.joinable()) {
|
|
59
|
-
t.join();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
template<class T>
|
|
65
|
-
void enqueue(T&& f) {
|
|
66
|
-
{
|
|
67
|
-
std::unique_lock<std::mutex> lock(queue_mutex);
|
|
68
|
-
tasks.emplace(std::forward<T>(f));
|
|
69
|
-
}
|
|
70
|
-
cv.notify_one();
|
|
71
|
-
}
|
|
72
|
-
private:
|
|
73
|
-
std::atomic<bool> stop{false};
|
|
74
|
-
std::vector<std::thread> workers;
|
|
75
|
-
std::queue<std::function<void()>> tasks;
|
|
76
|
-
std::mutex queue_mutex;
|
|
77
|
-
std::condition_variable cv;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
inline ThreadPool pool;
|
package/src/types.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export type OsuKey =
|
|
2
|
-
| "AudioFilename"
|
|
3
|
-
| "AudioLeadIn"
|
|
4
|
-
| "PreviewTime"
|
|
5
|
-
| "Countdown"
|
|
6
|
-
| "SampleSet"
|
|
7
|
-
| "StackLeniency"
|
|
8
|
-
| "Mode"
|
|
9
|
-
| "LetterboxInBreaks"
|
|
10
|
-
| "WidescreenStoryboard"
|
|
11
|
-
| "Bookmarks"
|
|
12
|
-
| "DistanceSpacing"
|
|
13
|
-
| "BeatDivisor"
|
|
14
|
-
| "GridSize"
|
|
15
|
-
| "TimelineZoom"
|
|
16
|
-
| "Title"
|
|
17
|
-
| "TitleUnicode"
|
|
18
|
-
| "Artist"
|
|
19
|
-
| "ArtistUnicode"
|
|
20
|
-
| "Creator"
|
|
21
|
-
| "Version"
|
|
22
|
-
| "Source"
|
|
23
|
-
| "Tags"
|
|
24
|
-
| "BeatmapID"
|
|
25
|
-
| "BeatmapSetID"
|
|
26
|
-
| "HPDrainRate"
|
|
27
|
-
| "CircleSize"
|
|
28
|
-
| "OverallDifficulty"
|
|
29
|
-
| "ApproachRate"
|
|
30
|
-
| "SliderMultiplier"
|
|
31
|
-
| "SliderTickRate"
|
|
32
|
-
| "Background"
|
|
33
|
-
| "Video"
|
|
34
|
-
| "Storyboard"
|
|
35
|
-
| "Duration";
|
|
36
|
-
|
|
37
|
-
export interface OsuInput {
|
|
38
|
-
path: string;
|
|
39
|
-
id?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface INativeExporter {
|
|
43
|
-
get_property(location: string, key: string): string;
|
|
44
|
-
get_properties(location: string, keys: string[]): Record<string, string>;
|
|
45
|
-
process_beatmaps(locations: string[], keys: string[], callback?: (index: number) => void): Promise<Record<string, string>[]>;
|
|
46
|
-
get_duration(location: string): number;
|
|
47
|
-
get_audio_duration(location: string): number;
|
|
48
|
-
}
|