a3216 commited on
Commit
d731d8e
·
1 Parent(s): 96d34f2

feat: 集成 XTC 听歌识曲模块(/xtc/* 路由,无侵入)

Browse files

新增 src/router/xtc_recognize/ 模块:
- POST /xtc/recognize 上传录音(base64) -> 返回识别结果列表
- GET /xtc/song/url 获取试听 URL(30s 试听片段)
- GET /xtc/song/detail 获取歌曲详情
- GET /xtc/health 健康检查

实现:
- Python subprocess 调用 Node.js 运行 afp.wasm 生成音频指纹
- ffmpeg 转 8kHz 单声道 PCM
- 直连网易云 interface.music.163.com 识曲接口

Dockerfile 加装 nodejs + ffmpeg
web.py 用 try/except 包裹注册,失败不影响主服务

Dockerfile CHANGED
@@ -9,9 +9,13 @@ ENV PYTHONUNBUFFERED=1 \
9
  TZ=Asia/Shanghai
10
 
11
  # Install tzdata and set timezone
 
12
  RUN apt-get update && \
13
- apt-get install -y --no-install-recommends tzdata && \
14
- ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
 
 
 
15
  echo "Asia/Shanghai" > /etc/timezone && \
16
  apt-get clean && \
17
  rm -rf /var/lib/apt/lists/*
 
9
  TZ=Asia/Shanghai
10
 
11
  # Install tzdata and set timezone
12
+ # Added: nodejs (for afp.wasm 音频指纹生成), ffmpeg (音频转码)
13
  RUN apt-get update && \
14
+ apt-get install -y --no-install-recommends \
15
+ tzdata \
16
+ nodejs \
17
+ ffmpeg \
18
+ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
19
  echo "Asia/Shanghai" > /etc/timezone && \
20
  apt-get clean && \
21
  rm -rf /var/lib/apt/lists/*
src/router/xtc_recognize/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """XTC 听歌识曲模块 - 无侵入集成到 gcli2api。
2
+
3
+ 提供路由:/xtc/recognize, /xtc/song/url, /xtc/song/detail, /xtc/health
4
+ """
5
+ from .router import router
6
+
7
+ __all__ = ["router"]
src/router/xtc_recognize/afp/afp.js ADDED
@@ -0,0 +1,1626 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict'
2
+ const WASM_BINARY_PLACEHOLDER = 'WASM_BINARY_PLACEHOLDER';
3
+ // See https://github.com/Distributive-Network/PythonMonkey/issues/266
4
+ if (typeof globalThis.setInterval != 'function'){
5
+ globalThis.setInterval = function pm$$setInterval(fn, timeout) {
6
+ const timerHnd = { cancel: false };
7
+ function fnWrapper()
8
+ {
9
+ if (timerHnd.cancel)
10
+ return;
11
+ setTimeout(fnWrapper, timeout);
12
+ fn();
13
+ }
14
+ timerHnd.id = setTimeout(fnWrapper, timeout);
15
+ return timerHnd;
16
+ }
17
+ globalThis.clearInterval = function pm$$clearInterval(timerHnd) {
18
+ timerHnd.clear = true;
19
+ clearTimeout(timerHnd.id);
20
+ }
21
+ }
22
+ globalThis.b64decode = function b64decode(data) {
23
+ return Uint8Array.from(atob(data), c => c.charCodeAt(0));
24
+ }
25
+ globalThis.b64encode = function b64encode(data) {
26
+ return btoa(String.fromCharCode(...data));
27
+ }
28
+ // https://fn.music.163.com/g/chrome-extension-home-page-beta/
29
+ let AudioFingerprintRuntime = (() => {
30
+ var n, o = void 0 !== o ? o : {},i = {};
31
+ for (n in o)
32
+ o.hasOwnProperty(n) && (i[n] = o[n]);
33
+ var read_, readSync, readBinary, c, f, l = [],
34
+ p = "./this.program",
35
+ readSync = function(t, e, r) {
36
+ switch (t) {
37
+ case WASM_BINARY_PLACEHOLDER:
38
+ if (typeof WASM_BINARY == 'undefined') {
39
+ const { WASM_BINARY } = require('./afp.wasm.js');
40
+ e(globalThis.b64decode(WASM_BINARY));
41
+ } else {
42
+ e(globalThis.b64decode(WASM_BINARY));
43
+ }
44
+ default:
45
+ throw "Reading " + t + " is not supported";
46
+ break;
47
+ }
48
+ }
49
+ var v = o.print || console.log.bind(console),
50
+ g = o.printErr || console.warn.bind(console);
51
+ for (n in i)
52
+ i.hasOwnProperty(n) && (o[n] = i[n]);
53
+ i = null,
54
+ o.arguments && (l = o.arguments),
55
+ o.thisProgram && (p = o.thisProgram),
56
+ o.quit && o.quit;
57
+ var w;
58
+ o.wasmBinary && (w = o.wasmBinary);
59
+ var b, _ = o.noExitRuntime || !0;
60
+ "object" != typeof WebAssembly && abort("no native wasm support detected");
61
+ var C = !1;
62
+
63
+ function T(t, e) {
64
+ t || abort("Assertion failed: " + e)
65
+ }
66
+ var UTF8Decoder = "undefined" != typeof TextDecoder ? new TextDecoder("utf8") : void 0;
67
+
68
+ function UTF8ArrayToString(heap, idx, maxBytesToRead) {
69
+ var endIdx = idx + maxBytesToRead;
70
+ var endPtr = idx;
71
+ while (heap[endPtr] && !(endPtr >= endIdx)) ++endPtr;
72
+ if (endPtr - idx > 16 && heap.subarray && UTF8Decoder) {
73
+ return UTF8Decoder.decode(heap.subarray(idx, endPtr))
74
+ } else {
75
+ var str = "";
76
+ while (idx < endPtr) {
77
+ var u0 = heap[idx++];
78
+ if (!(u0 & 128)) {
79
+ str += String.fromCharCode(u0);
80
+ continue
81
+ }
82
+ var u1 = heap[idx++] & 63;
83
+ if ((u0 & 224) == 192) {
84
+ str += String.fromCharCode((u0 & 31) << 6 | u1);
85
+ continue
86
+ }
87
+ var u2 = heap[idx++] & 63;
88
+ if ((u0 & 240) == 224) {
89
+ u0 = (u0 & 15) << 12 | u1 << 6 | u2
90
+ } else {
91
+ u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | heap[idx++] & 63
92
+ }
93
+ if (u0 < 65536) {
94
+ str += String.fromCharCode(u0)
95
+ } else {
96
+ var ch = u0 - 65536;
97
+ str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023)
98
+ }
99
+ }
100
+ }
101
+ return str
102
+ }
103
+
104
+ function UTF8ToString(ptr, maxBytesToRead) {
105
+ return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ""
106
+ }
107
+ function UTF8ToString(t, e) {
108
+ return t ? UTF8ArrayToString(O, t, e) : ""
109
+ }
110
+
111
+
112
+ function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) {
113
+ if (!(maxBytesToWrite > 0)) return 0;
114
+ var startIdx = outIdx;
115
+ var endIdx = outIdx + maxBytesToWrite - 1;
116
+ for (var i = 0; i < str.length; ++i) {
117
+ var u = str.charCodeAt(i);
118
+ if (u >= 55296 && u <= 57343) {
119
+ var u1 = str.charCodeAt(++i);
120
+ u = 65536 + ((u & 1023) << 10) | u1 & 1023
121
+ }
122
+ if (u <= 127) {
123
+ if (outIdx >= endIdx) break;
124
+ heap[outIdx++] = u
125
+ } else if (u <= 2047) {
126
+ if (outIdx + 1 >= endIdx) break;
127
+ heap[outIdx++] = 192 | u >> 6;
128
+ heap[outIdx++] = 128 | u & 63
129
+ } else if (u <= 65535) {
130
+ if (outIdx + 2 >= endIdx) break;
131
+ heap[outIdx++] = 224 | u >> 12;
132
+ heap[outIdx++] = 128 | u >> 6 & 63;
133
+ heap[outIdx++] = 128 | u & 63
134
+ } else {
135
+ if (outIdx + 3 >= endIdx) break;
136
+ heap[outIdx++] = 240 | u >> 18;
137
+ heap[outIdx++] = 128 | u >> 12 & 63;
138
+ heap[outIdx++] = 128 | u >> 6 & 63;
139
+ heap[outIdx++] = 128 | u & 63
140
+ }
141
+ }
142
+ heap[outIdx] = 0;
143
+ return outIdx - startIdx
144
+ }
145
+
146
+
147
+ function UTF8CharCount(t) {
148
+ for (var e = 0, r = 0; r < t.length; ++r) {
149
+ var n = t.charCodeAt(r);
150
+ n >= 55296 && n <= 57343 && (n = 65536 + ((1023 & n) << 10) | 1023 & t.charCodeAt(++r)),
151
+ n <= 127 ? ++e : e += n <= 2047 ? 2 : n <= 65535 ? 3 : 4
152
+ }
153
+ return e
154
+ }
155
+ var E, S, O, k, W, j, R, M, I, UTF16Decoder = "undefined" != typeof TextDecoder ? new TextDecoder("utf-16le") : void 0;
156
+
157
+ function UTF16ArrayToString(t, e) {
158
+ for (var r = t, n = r >> 1, o = n + e / 2; !(n >= o) && W[n];)
159
+ ++n;
160
+ if ((r = n << 1) - t > 32 && UTF16Decoder)
161
+ return UTF16Decoder.decode(O.subarray(t, r));
162
+ for (var i = "", a = 0; !(a >= e / 2); ++a) {
163
+ var u = k[t + 2 * a >> 1];
164
+ if (0 == u)
165
+ break;
166
+ i += String.fromCharCode(u)
167
+ }
168
+ return i
169
+ }
170
+
171
+ function H(t, e, r) {
172
+ if (void 0 === r && (r = 2147483647),
173
+ r < 2)
174
+ return 0;
175
+ for (var n = e, o = (r -= 2) < 2 * t.length ? r / 2 : t.length, i = 0; i < o; ++i) {
176
+ var a = t.charCodeAt(i);
177
+ k[e >> 1] = a,
178
+ e += 2
179
+ }
180
+ return k[e >> 1] = 0,
181
+ e - n
182
+ }
183
+
184
+ function Y(t) {
185
+ return 2 * t.length
186
+ }
187
+
188
+ function V(t, e) {
189
+ for (var r = 0, n = ""; !(r >= e / 4);) {
190
+ var o = j[t + 4 * r >> 2];
191
+ if (0 == o)
192
+ break;
193
+ if (++r,
194
+ o >= 65536) {
195
+ var i = o - 65536;
196
+ n += String.fromCharCode(55296 | i >> 10, 56320 | 1023 & i)
197
+ } else
198
+ n += String.fromCharCode(o)
199
+ }
200
+ return n
201
+ }
202
+
203
+ function z(t, e, r) {
204
+ if (void 0 === r && (r = 2147483647),
205
+ r < 4)
206
+ return 0;
207
+ for (var n = e, o = n + r - 4, i = 0; i < t.length; ++i) {
208
+ var a = t.charCodeAt(i);
209
+ if (a >= 55296 && a <= 57343)
210
+ a = 65536 + ((1023 & a) << 10) | 1023 & t.charCodeAt(++i);
211
+ if (j[e >> 2] = a,
212
+ (e += 4) + 4 > o)
213
+ break
214
+ }
215
+ return j[e >> 2] = 0,
216
+ e - n
217
+ }
218
+
219
+ function B(t) {
220
+ for (var e = 0, r = 0; r < t.length; ++r) {
221
+ var n = t.charCodeAt(r);
222
+ n >= 55296 && n <= 57343 && ++r,
223
+ e += 4
224
+ }
225
+ return e
226
+ }
227
+ o.INITIAL_MEMORY;
228
+ var L, G = [],
229
+ N = [],
230
+ q = [];
231
+ var J = 0,
232
+ X = null,
233
+ Z = null;
234
+
235
+ function abort(what) {
236
+ throw o.onAbort && o.onAbort(what),
237
+ g(what = "Aborted(" + what + ")"),
238
+ C = !0,
239
+ 1,
240
+ what += ". Build with -s ASSERTIONS=1 for more info.",
241
+ new WebAssembly.RuntimeError(what)
242
+ }
243
+ o.preloadedImages = {},
244
+ o.preloadedAudios = {};
245
+ var Q;
246
+
247
+ function isDataURI(t) {
248
+ return t.startsWith("data:application/octet-stream;base64,")
249
+ }
250
+
251
+ function isFileURI(t) {
252
+ return t.startsWith("file://")
253
+ }
254
+
255
+ function getBinary(file) {
256
+ try {
257
+ if (file == Q && w)
258
+ return new Uint8Array(w);
259
+ if (readBinary)
260
+ return readBinary(file);
261
+ throw "both async and sync fetching of the wasm failed"
262
+ } catch (t) {
263
+ abort(t)
264
+ }
265
+ }
266
+
267
+ function callRuntimeCallbacks(cb) {
268
+ for (; cb.length > 0;) {
269
+ var func = cb.shift();
270
+ if ("function" != typeof func) {
271
+ var r = func.func;
272
+ "number" == typeof r ? void 0 === func.arg ? it(r)() : it(r)(func.arg) : r(void 0 === func.arg ? null : func.arg)
273
+ } else
274
+ func(o)
275
+ }
276
+ }
277
+ var wasmBinaryFile = WASM_BINARY_PLACEHOLDER;
278
+ isDataURI(wasmBinaryFile) || (Q = function(t) {
279
+ return wasmBinaryFile
280
+ }(Q));
281
+ var ot = [];
282
+
283
+ function it(t) {
284
+ var e = ot[t];
285
+ return e || (t >= ot.length && (ot.length = t + 1),
286
+ ot[t] = e = L.get(t)),
287
+ e
288
+ }
289
+
290
+ function ExceptionInfo(excPtr) {
291
+ this.excPtr = excPtr,
292
+ this.ptr = excPtr - 16,
293
+ this.set_type = function(t) {
294
+ j[this.ptr + 4 >> 2] = t
295
+ },
296
+ this.get_type = function() {
297
+ return j[this.ptr + 4 >> 2]
298
+ },
299
+ this.set_destructor = function(t) {
300
+ j[this.ptr + 8 >> 2] = t
301
+ },
302
+ this.get_destructor = function() {
303
+ return j[this.ptr + 8 >> 2]
304
+ },
305
+ this.set_refcount = function(t) {
306
+ j[this.ptr >> 2] = t
307
+ },
308
+ this.set_caught = function(t) {
309
+ t = t ? 1 : 0,
310
+ S[this.ptr + 12 >> 0] = t
311
+ },
312
+ this.get_caught = function() {
313
+ return 0 != S[this.ptr + 12 >> 0]
314
+ },
315
+ this.set_rethrown = function(t) {
316
+ t = t ? 1 : 0,
317
+ S[this.ptr + 13 >> 0] = t
318
+ },
319
+ this.get_rethrown = function() {
320
+ return 0 != S[this.ptr + 13 >> 0]
321
+ },
322
+ this.init = function(t, e) {
323
+ this.set_type(t),
324
+ this.set_destructor(e),
325
+ this.set_refcount(0),
326
+ this.set_caught(!1),
327
+ this.set_rethrown(!1)
328
+ },
329
+ this.add_ref = function() {
330
+ var t = j[this.ptr >> 2];
331
+ j[this.ptr >> 2] = t + 1
332
+ },
333
+ this.release_ref = function() {
334
+ var t = j[this.ptr >> 2];
335
+ return j[this.ptr >> 2] = t - 1,
336
+ 1 === t
337
+ }
338
+ }
339
+
340
+ function ut(t) {
341
+ switch (t) {
342
+ case 1:
343
+ return 0;
344
+ case 2:
345
+ return 1;
346
+ case 4:
347
+ return 2;
348
+ case 8:
349
+ return 3;
350
+ default:
351
+ throw new TypeError("Unknown type size: " + t)
352
+ }
353
+ }
354
+ var st = void 0;
355
+
356
+ function ct(t) {
357
+ for (var e = "", r = t; O[r];)
358
+ e += st[O[r++]];
359
+ return e
360
+ }
361
+ var ft = {},
362
+ lt = {},
363
+ pt = {};
364
+
365
+ function dt(t) {
366
+ if (void 0 === t)
367
+ return "_unknown";
368
+ var e = (t = t.replace(/[^a-zA-Z0-9_]/g, "$")).charCodeAt(0);
369
+ return e >= 48 && e <= 57 ? "_" + t : t
370
+ }
371
+
372
+ function ht(t, e) {
373
+ return t = dt(t),
374
+ new Function("body", "return function " + t + '() {\n "use strict"; return body.apply(this, arguments);\n};\n')(e)
375
+ }
376
+
377
+ function yt(t, e) {
378
+ var r = ht(e, (function(t) {
379
+ this.name = e,
380
+ this.message = t;
381
+ var r = new Error(t).stack;
382
+ void 0 !== r && (this.stack = this.toString() + "\n" + r.replace(/^Error(:[^\n]*)?\n/, ""))
383
+ }));
384
+ return r.prototype = Object.create(t.prototype),
385
+ r.prototype.constructor = r,
386
+ r.prototype.toString = function() {
387
+ return void 0 === this.message ? this.name : this.name + ": " + this.message
388
+ },
389
+ r
390
+ }
391
+ var mt = void 0;
392
+
393
+ function vt(t) {
394
+ throw new mt(t)
395
+ }
396
+ var gt = void 0;
397
+
398
+ function wt(t) {
399
+ throw new gt(t)
400
+ }
401
+
402
+ function bt(t, e, r) {
403
+ function n(e) {
404
+ var n = r(e);
405
+ n.length !== t.length && wt("Mismatched type converter count");
406
+ for (var o = 0; o < t.length; ++o)
407
+ _t(t[o], n[o])
408
+ }
409
+ t.forEach((function(t) {
410
+ pt[t] = e
411
+ }));
412
+ var o = new Array(e.length),
413
+ i = [],
414
+ a = 0;
415
+ e.forEach((function(t, e) {
416
+ lt.hasOwnProperty(t) ? o[e] = lt[t] : (i.push(t),
417
+ ft.hasOwnProperty(t) || (ft[t] = []),
418
+ ft[t].push((function() {
419
+ o[e] = lt[t],
420
+ ++a === i.length && n(o)
421
+ })))
422
+ })),
423
+ 0 === i.length && n(o)
424
+ }
425
+
426
+ function _t(t, e, r) {
427
+ if (r = r || {},
428
+ !("argPackAdvance" in e))
429
+ throw new TypeError("registerType registeredInstance requires argPackAdvance");
430
+ var n = e.name;
431
+ if (t || vt('type "' + n + '" must have a positive integer typeid pointer'),
432
+ lt.hasOwnProperty(t)) {
433
+ if (r.ignoreDuplicateRegistrations)
434
+ return;
435
+ vt("Cannot register type '" + n + "' twice")
436
+ }
437
+ if (lt[t] = e,
438
+ delete pt[t],
439
+ ft.hasOwnProperty(t)) {
440
+ var o = ft[t];
441
+ delete ft[t],
442
+ o.forEach((function(t) {
443
+ t()
444
+ }))
445
+ }
446
+ }
447
+
448
+ function Ct(t) {
449
+ if (!(this instanceof Rt))
450
+ return !1;
451
+ if (!(t instanceof Rt))
452
+ return !1;
453
+ for (var e = this.$$.ptrType.registeredClass, r = this.$$.ptr, n = t.$$.ptrType.registeredClass, o = t.$$.ptr; e.baseClass;)
454
+ r = e.upcast(r),
455
+ e = e.baseClass;
456
+ for (; n.baseClass;)
457
+ o = n.upcast(o),
458
+ n = n.baseClass;
459
+ return e === n && r === o
460
+ }
461
+
462
+ function Tt(t) {
463
+ vt(t.$$.ptrType.registeredClass.name + " instance already deleted")
464
+ }
465
+ var $t = !1;
466
+
467
+ function Pt(t) {}
468
+
469
+ function At(t) {
470
+ t.count.value -= 1,
471
+ 0 === t.count.value && function(t) {
472
+ t.smartPtr ? t.smartPtrType.rawDestructor(t.smartPtr) : t.ptrType.registeredClass.rawDestructor(t.ptr)
473
+ }(t)
474
+ }
475
+
476
+ function Dt(t) {
477
+ return "undefined" == typeof FinalizationRegistry ? (Dt = function(t) {
478
+ return t
479
+ },
480
+ t) : ($t = new FinalizationRegistry((function(t) {
481
+ At(t.$$)
482
+ })),
483
+ Dt = function(t) {
484
+ var e = {
485
+ $$: t.$$
486
+ };
487
+ return $t.register(t, e, t),
488
+ t
489
+ },
490
+ Pt = function(t) {
491
+ $t.unregister(t)
492
+ },
493
+ Dt(t))
494
+ }
495
+
496
+ function Ft() {
497
+ if (this.$$.ptr || Tt(this),
498
+ this.$$.preservePointerOnDelete)
499
+ return this.$$.count.value += 1,
500
+ this;
501
+ var t, e = Dt(Object.create(Object.getPrototypeOf(this), {
502
+ $$: {
503
+ value: (t = this.$$, {
504
+ count: t.count,
505
+ deleteScheduled: t.deleteScheduled,
506
+ preservePointerOnDelete: t.preservePointerOnDelete,
507
+ ptr: t.ptr,
508
+ ptrType: t.ptrType,
509
+ smartPtr: t.smartPtr,
510
+ smartPtrType: t.smartPtrType
511
+ })
512
+ }
513
+ }));
514
+ return e.$$.count.value += 1,
515
+ e.$$.deleteScheduled = !1,
516
+ e
517
+ }
518
+
519
+ function Et() {
520
+ this.$$.ptr || Tt(this),
521
+ this.$$.deleteScheduled && !this.$$.preservePointerOnDelete && vt("Object already scheduled for deletion"),
522
+ Pt(this),
523
+ At(this.$$),
524
+ this.$$.preservePointerOnDelete || (this.$$.smartPtr = void 0,
525
+ this.$$.ptr = void 0)
526
+ }
527
+
528
+ function St() {
529
+ return !this.$$.ptr
530
+ }
531
+ var Ot = void 0,
532
+ kt = [];
533
+
534
+ function Wt() {
535
+ for (; kt.length;) {
536
+ var t = kt.pop();
537
+ t.$$.deleteScheduled = !1,
538
+ t.delete()
539
+ }
540
+ }
541
+
542
+ function jt() {
543
+ return this.$$.ptr || Tt(this),
544
+ this.$$.deleteScheduled && !this.$$.preservePointerOnDelete && vt("Object already scheduled for deletion"),
545
+ kt.push(this),
546
+ 1 === kt.length && Ot && Ot(Wt),
547
+ this.$$.deleteScheduled = !0,
548
+ this
549
+ }
550
+
551
+ function Rt() {}
552
+ var Mt = {};
553
+
554
+ function It(t, e, r) {
555
+ if (void 0 === t[e].overloadTable) {
556
+ var n = t[e];
557
+ t[e] = function() {
558
+ return t[e].overloadTable.hasOwnProperty(arguments.length) || vt("Function '" + r + "' called with an invalid number of arguments (" + arguments.length + ") - expects one of (" + t[e].overloadTable + ")!"),
559
+ t[e].overloadTable[arguments.length].apply(this, arguments)
560
+ },
561
+ t[e].overloadTable = [],
562
+ t[e].overloadTable[n.argCount] = n
563
+ }
564
+ }
565
+
566
+ function xt(t, e, r) {
567
+ o.hasOwnProperty(t) ? ((void 0 === r || void 0 !== o[t].overloadTable && void 0 !== o[t].overloadTable[r]) && vt("Cannot register public name '" + t + "' twice"),
568
+ It(o, t, t),
569
+ o.hasOwnProperty(r) && vt("Cannot register multiple overloads of a function with the same number of arguments (" + r + ")!"),
570
+ o[t].overloadTable[r] = e) : (o[t] = e,
571
+ void 0 !== r && (o[t].numArguments = r))
572
+ }
573
+
574
+ function Ut(t, e, r, n, o, i, a, u) {
575
+ this.name = t,
576
+ this.constructor = e,
577
+ this.instancePrototype = r,
578
+ this.rawDestructor = n,
579
+ this.baseClass = o,
580
+ this.getActualType = i,
581
+ this.upcast = a,
582
+ this.downcast = u,
583
+ this.pureVirtualFunctions = []
584
+ }
585
+
586
+ function Ht(t, e, r) {
587
+ for (; e !== r;)
588
+ e.upcast || vt("Expected null or instance of " + r.name + ", got an instance of " + e.name),
589
+ t = e.upcast(t),
590
+ e = e.baseClass;
591
+ return t
592
+ }
593
+
594
+ function Yt(t, e) {
595
+ if (null === e)
596
+ return this.isReference && vt("null is not a valid " + this.name),
597
+ 0;
598
+ e.$$ || vt('Cannot pass "' + ge(e) + '" as a ' + this.name),
599
+ e.$$.ptr || vt("Cannot pass deleted object as a pointer of type " + this.name);
600
+ var r = e.$$.ptrType.registeredClass;
601
+ return Ht(e.$$.ptr, r, this.registeredClass)
602
+ }
603
+
604
+ function Vt(t, e) {
605
+ var r;
606
+ if (null === e)
607
+ return this.isReference && vt("null is not a valid " + this.name),
608
+ this.isSmartPointer ? (r = this.rawConstructor(),
609
+ null !== t && t.push(this.rawDestructor, r),
610
+ r) : 0;
611
+ e.$$ || vt('Cannot pass "' + ge(e) + '" as a ' + this.name),
612
+ e.$$.ptr || vt("Cannot pass deleted object as a pointer of type " + this.name),
613
+ !this.isConst && e.$$.ptrType.isConst && vt("Cannot convert argument of type " + (e.$$.smartPtrType ? e.$$.smartPtrType.name : e.$$.ptrType.name) + " to parameter type " + this.name);
614
+ var n = e.$$.ptrType.registeredClass;
615
+ if (r = Ht(e.$$.ptr, n, this.registeredClass),
616
+ this.isSmartPointer)
617
+ switch (void 0 === e.$$.smartPtr && vt("Passing raw pointer to smart pointer is illegal"),
618
+ this.sharingPolicy) {
619
+ case 0:
620
+ e.$$.smartPtrType === this ? r = e.$$.smartPtr : vt("Cannot convert argument of type " + (e.$$.smartPtrType ? e.$$.smartPtrType.name : e.$$.ptrType.name) + " to parameter type " + this.name);
621
+ break;
622
+ case 1:
623
+ r = e.$$.smartPtr;
624
+ break;
625
+ case 2:
626
+ if (e.$$.smartPtrType === this)
627
+ r = e.$$.smartPtr;
628
+ else {
629
+ var o = e.clone();
630
+ r = this.rawShare(r, ve.toHandle((function() {
631
+ o.delete()
632
+ }))),
633
+ null !== t && t.push(this.rawDestructor, r)
634
+ }
635
+ break;
636
+ default:
637
+ vt("Unsupporting sharing policy")
638
+ }
639
+ return r
640
+ }
641
+
642
+ function zt(t, e) {
643
+ if (null === e)
644
+ return this.isReference && vt("null is not a valid " + this.name),
645
+ 0;
646
+ e.$$ || vt('Cannot pass "' + ge(e) + '" as a ' + this.name),
647
+ e.$$.ptr || vt("Cannot pass deleted object as a pointer of type " + this.name),
648
+ e.$$.ptrType.isConst && vt("Cannot convert argument of type " + e.$$.ptrType.name + " to parameter type " + this.name);
649
+ var r = e.$$.ptrType.registeredClass;
650
+ return Ht(e.$$.ptr, r, this.registeredClass)
651
+ }
652
+
653
+ function Bt(t) {
654
+ return this.fromWireType(R[t >> 2])
655
+ }
656
+
657
+ function Lt(t) {
658
+ return this.rawGetPointee && (t = this.rawGetPointee(t)),
659
+ t
660
+ }
661
+
662
+ function Gt(t) {
663
+ this.rawDestructor && this.rawDestructor(t)
664
+ }
665
+
666
+ function Nt(t) {
667
+ null !== t && t.delete()
668
+ }
669
+
670
+ function qt(t, e, r) {
671
+ if (e === r)
672
+ return t;
673
+ if (void 0 === r.baseClass)
674
+ return null;
675
+ var n = qt(t, e, r.baseClass);
676
+ return null === n ? null : r.downcast(n)
677
+ }
678
+
679
+ function Jt() {
680
+ return Object.keys(Kt).length
681
+ }
682
+
683
+ function Xt() {
684
+ var t = [];
685
+ for (var e in Kt)
686
+ Kt.hasOwnProperty(e) && t.push(Kt[e]);
687
+ return t
688
+ }
689
+
690
+ function Zt(t) {
691
+ Ot = t,
692
+ kt.length && Ot && Ot(Wt)
693
+ }
694
+ var Kt = {};
695
+
696
+ function Qt(t, e) {
697
+ return e = function(t, e) {
698
+ for (void 0 === e && vt("ptr should not be undefined"); t.baseClass;)
699
+ e = t.upcast(e),
700
+ t = t.baseClass;
701
+ return e
702
+ }(t, e),
703
+ Kt[e]
704
+ }
705
+
706
+ function te(t, e) {
707
+ return e.ptrType && e.ptr || wt("makeClassHandle requires ptr and ptrType"),
708
+ !!e.smartPtrType !== !!e.smartPtr && wt("Both smartPtrType and smartPtr must be specified"),
709
+ e.count = {
710
+ value: 1
711
+ },
712
+ Dt(Object.create(t, {
713
+ $$: {
714
+ value: e
715
+ }
716
+ }))
717
+ }
718
+
719
+ function ee(t) {
720
+ var e = this.getPointee(t);
721
+ if (!e)
722
+ return this.destructor(t),
723
+ null;
724
+ var r = Qt(this.registeredClass, e);
725
+ if (void 0 !== r) {
726
+ if (0 === r.$$.count.value)
727
+ return r.$$.ptr = e,
728
+ r.$$.smartPtr = t,
729
+ r.clone();
730
+ var n = r.clone();
731
+ return this.destructor(t),
732
+ n
733
+ }
734
+
735
+ function o() {
736
+ return this.isSmartPointer ? te(this.registeredClass.instancePrototype, {
737
+ ptrType: this.pointeeType,
738
+ ptr: e,
739
+ smartPtrType: this,
740
+ smartPtr: t
741
+ }) : te(this.registeredClass.instancePrototype, {
742
+ ptrType: this,
743
+ ptr: t
744
+ })
745
+ }
746
+ var i, a = this.registeredClass.getActualType(e),
747
+ u = Mt[a];
748
+ if (!u)
749
+ return o.call(this);
750
+ i = this.isConst ? u.constPointerType : u.pointerType;
751
+ var s = qt(e, this.registeredClass, i.registeredClass);
752
+ return null === s ? o.call(this) : this.isSmartPointer ? te(i.registeredClass.instancePrototype, {
753
+ ptrType: i,
754
+ ptr: s,
755
+ smartPtrType: this,
756
+ smartPtr: t
757
+ }) : te(i.registeredClass.instancePrototype, {
758
+ ptrType: i,
759
+ ptr: s
760
+ })
761
+ }
762
+
763
+ function re(t, e, r, n, o, i, a, u, s, c, f) {
764
+ this.name = t,
765
+ this.registeredClass = e,
766
+ this.isReference = r,
767
+ this.isConst = n,
768
+ this.isSmartPointer = o,
769
+ this.pointeeType = i,
770
+ this.sharingPolicy = a,
771
+ this.rawGetPointee = u,
772
+ this.rawConstructor = s,
773
+ this.rawShare = c,
774
+ this.rawDestructor = f,
775
+ o || void 0 !== e.baseClass ? this.toWireType = Vt : n ? (this.toWireType = Yt,
776
+ this.destructorFunction = null) : (this.toWireType = zt,
777
+ this.destructorFunction = null)
778
+ }
779
+
780
+ function ne(t, e, r) {
781
+ o.hasOwnProperty(t) || wt("Replacing nonexistant public symbol"),
782
+ void 0 !== o[t].overloadTable && void 0 !== r ? o[t].overloadTable[r] = e : (o[t] = e,
783
+ o[t].argCount = r)
784
+ }
785
+
786
+ function oe(t, e, r) {
787
+ return t.includes("j") ? function(t, e, r) {
788
+ var n = o["dynCall_" + t];
789
+ return r && r.length ? n.apply(null, [e].concat(r)) : n.call(null, e)
790
+ }(t, e, r) : it(e).apply(null, r)
791
+ }
792
+
793
+ function ie(t, e) {
794
+ var r, n, o, i = (t = ct(t)).includes("j") ? (r = t,
795
+ n = e,
796
+ o = [],
797
+ function() {
798
+ o.length = arguments.length;
799
+ for (var t = 0; t < arguments.length; t++)
800
+ o[t] = arguments[t];
801
+ return oe(r, n, o)
802
+ }
803
+ ) : it(e);
804
+ return "function" != typeof i && vt("unknown function pointer with signature " + t + ": " + e),
805
+ i
806
+ }
807
+ var ae = void 0;
808
+
809
+ function ue(t) {
810
+ var e = je(t),
811
+ r = ct(e);
812
+ return We(e),
813
+ r
814
+ }
815
+
816
+ function se(t, e) {
817
+ var r = [],
818
+ n = {};
819
+ throw e.forEach((function t(e) {
820
+ n[e] || lt[e] || (pt[e] ? pt[e].forEach(t) : (r.push(e),
821
+ n[e] = !0))
822
+ })),
823
+ new ae(t + ": " + r.map(ue).join([", "]))
824
+ }
825
+
826
+ function ce(t, e) {
827
+ for (var r = [], n = 0; n < t; n++)
828
+ r.push(j[(e >> 2) + n]);
829
+ return r
830
+ }
831
+
832
+ function fe(t) {
833
+ for (; t.length;) {
834
+ var e = t.pop();
835
+ t.pop()(e)
836
+ }
837
+ }
838
+
839
+ function le(t, e, r, n, o) {
840
+ var i = e.length;
841
+ i < 2 && vt("argTypes array size mismatch! Must at least get return value and 'this' types!");
842
+ for (var a = null !== e[1] && null !== r, u = !1, s = 1; s < e.length; ++s)
843
+ if (null !== e[s] && void 0 === e[s].destructorFunction) {
844
+ u = !0;
845
+ break
846
+ }
847
+ var c = "void" !== e[0].name,
848
+ f = "",
849
+ l = "";
850
+ for (s = 0; s < i - 2; ++s)
851
+ f += (0 !== s ? ", " : "") + "arg" + s,
852
+ l += (0 !== s ? ", " : "") + "arg" + s + "Wired";
853
+ var p = "return function " + dt(t) + "(" + f + ") {\nif (arguments.length !== " + (i - 2) + ") {\nthrowBindingError('function " + t + " called with ' + arguments.length + ' arguments, expected " + (i - 2) + " args!');\n}\n";
854
+ u && (p += "var destructors = [];\n");
855
+ var d = u ? "destructors" : "null",
856
+ h = ["throwBindingError", "invoker", "fn", "runDestructors", "retType", "classParam"],
857
+ y = [vt, n, o, fe, e[0], e[1]];
858
+ a && (p += "var thisWired = classParam.toWireType(" + d + ", this);\n");
859
+ for (s = 0; s < i - 2; ++s)
860
+ p += "var arg" + s + "Wired = argType" + s + ".toWireType(" + d + ", arg" + s + "); // " + e[s + 2].name + "\n",
861
+ h.push("argType" + s),
862
+ y.push(e[s + 2]);
863
+ if (a && (l = "thisWired" + (l.length > 0 ? ", " : "") + l),
864
+ p += (c ? "var rv = " : "") + "invoker(fn" + (l.length > 0 ? ", " : "") + l + ");\n",
865
+ u)
866
+ p += "runDestructors(destructors);\n";
867
+ else
868
+ for (s = a ? 1 : 2; s < e.length; ++s) {
869
+ var m = 1 === s ? "thisWired" : "arg" + (s - 2) + "Wired";
870
+ null !== e[s].destructorFunction && (p += m + "_dtor(" + m + "); // " + e[s].name + "\n",
871
+ h.push(m + "_dtor"),
872
+ y.push(e[s].destructorFunction))
873
+ }
874
+ return c && (p += "var ret = retType.fromWireType(rv);\nreturn ret;\n"),
875
+ p += "}\n",
876
+ h.push(p),
877
+ function(t, e) {
878
+ if (!(t instanceof Function))
879
+ throw new TypeError("new_ called with constructor type " + typeof t + " which is not a function");
880
+ var r = ht(t.name || "unknownFunctionName", (function() {}));
881
+ r.prototype = t.prototype;
882
+ var n = new r,
883
+ o = t.apply(n, e);
884
+ return o instanceof Object ? o : n
885
+ }(Function, h).apply(null, y)
886
+ }
887
+ var pe = [],
888
+ de = [{}, {
889
+ value: void 0
890
+ }, {
891
+ value: null
892
+ }, {
893
+ value: !0
894
+ }, {
895
+ value: !1
896
+ }];
897
+
898
+ function he(t) {
899
+ t > 4 && 0 == --de[t].refcount && (de[t] = void 0,
900
+ pe.push(t))
901
+ }
902
+
903
+ function ye() {
904
+ for (var t = 0, e = 5; e < de.length; ++e)
905
+ void 0 !== de[e] && ++t;
906
+ return t
907
+ }
908
+
909
+ function me() {
910
+ for (var t = 5; t < de.length; ++t)
911
+ if (void 0 !== de[t])
912
+ return de[t];
913
+ return null
914
+ }
915
+ var ve = {
916
+ toValue: function(t) {
917
+ return t || vt("Cannot use deleted val. handle = " + t),
918
+ de[t].value
919
+ },
920
+ toHandle: function(t) {
921
+ switch (t) {
922
+ case void 0:
923
+ return 1;
924
+ case null:
925
+ return 2;
926
+ case !0:
927
+ return 3;
928
+ case !1:
929
+ return 4;
930
+ default:
931
+ var e = pe.length ? pe.pop() : de.length;
932
+ return de[e] = {
933
+ refcount: 1,
934
+ value: t
935
+ },
936
+ e
937
+ }
938
+ }
939
+ };
940
+
941
+ function ge(t) {
942
+ if (null === t)
943
+ return "null";
944
+ var e = typeof t;
945
+ return "object" === e || "array" === e || "function" === e ? t.toString() : "" + t
946
+ }
947
+
948
+ function we(t, e) {
949
+ switch (e) {
950
+ case 2:
951
+ return function(t) {
952
+ return this.fromWireType(M[t >> 2])
953
+ };
954
+ case 3:
955
+ return function(t) {
956
+ return this.fromWireType(I[t >> 3])
957
+ };
958
+ default:
959
+ throw new TypeError("Unknown float type: " + t)
960
+ }
961
+ }
962
+
963
+ function be(t, e, r) {
964
+ switch (e) {
965
+ case 0:
966
+ return r ? function(t) {
967
+ return S[t]
968
+ } :
969
+ function(t) {
970
+ return O[t]
971
+ };
972
+ case 1:
973
+ return r ? function(t) {
974
+ return k[t >> 1]
975
+ } :
976
+ function(t) {
977
+ return W[t >> 1]
978
+ };
979
+ case 2:
980
+ return r ? function(t) {
981
+ return j[t >> 2]
982
+ } :
983
+ function(t) {
984
+ return R[t >> 2]
985
+ };
986
+ default:
987
+ throw new TypeError("Unknown integer type: " + t)
988
+ }
989
+ }
990
+ var Te = {
991
+ mappings: {},
992
+ buffers: [null, [],
993
+ []
994
+ ],
995
+ printChar: function(t, e) {
996
+ var r = Te.buffers[t];
997
+ 0 === e || 10 === e ? ((1 === t ? v : g)(UTF8ArrayToString(r, 0)),
998
+ r.length = 0) : r.push(e)
999
+ },
1000
+ varargs: void 0,
1001
+ get: function() {
1002
+ return Te.varargs += 4,
1003
+ j[Te.varargs - 4 >> 2]
1004
+ },
1005
+ getStr: function(t) {
1006
+ return UTF8ToString(t)
1007
+ },
1008
+ get64: function(t, e) {
1009
+ return t
1010
+ }
1011
+ };
1012
+
1013
+ function $e(t) {
1014
+ return t % 4 == 0 && (t % 100 != 0 || t % 400 == 0)
1015
+ }
1016
+
1017
+ function Pe(t, e) {
1018
+ for (var r = 0, n = 0; n <= e; r += t[n++])
1019
+ ;
1020
+ return r
1021
+ }
1022
+ var Ae = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
1023
+ De = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1024
+
1025
+ function Fe(t, e) {
1026
+ for (var r = new Date(t.getTime()); e > 0;) {
1027
+ var n = $e(r.getFullYear()),
1028
+ o = r.getMonth(),
1029
+ i = (n ? Ae : De)[o];
1030
+ if (!(e > i - r.getDate()))
1031
+ return r.setDate(r.getDate() + e),
1032
+ r;
1033
+ e -= i - r.getDate() + 1,
1034
+ r.setDate(1),
1035
+ o < 11 ? r.setMonth(o + 1) : (r.setMonth(0),
1036
+ r.setFullYear(r.getFullYear() + 1))
1037
+ }
1038
+ return r
1039
+ }
1040
+
1041
+ for (var t = new Array(256), e = 0; e < 256; ++e)
1042
+ t[e] = String.fromCharCode(e);
1043
+ st = t
1044
+
1045
+ mt = o.BindingError = yt(Error, "BindingError"),
1046
+ gt = o.InternalError = yt(Error, "InternalError"),
1047
+ Rt.prototype.isAliasOf = Ct,
1048
+ Rt.prototype.clone = Ft,
1049
+ Rt.prototype.delete = Et,
1050
+ Rt.prototype.isDeleted = St,
1051
+ Rt.prototype.deleteLater = jt,
1052
+ re.prototype.getPointee = Lt,
1053
+ re.prototype.destructor = Gt,
1054
+ re.prototype.argPackAdvance = 8,
1055
+ re.prototype.readValueFromPointer = Bt,
1056
+ re.prototype.deleteObject = Nt,
1057
+ re.prototype.fromWireType = ee,
1058
+ o.getInheritedInstanceCount = Jt,
1059
+ o.getLiveInheritedInstances = Xt,
1060
+ o.flushPendingDeletes = Wt,
1061
+ o.setDelayFunction = Zt,
1062
+ ae = o.UnboundTypeError = yt(Error, "UnboundTypeError"),
1063
+ o.count_emval_handles = ye,
1064
+ o.get_first_emval = me;
1065
+ var Se, import_table_impl = {
1066
+ d: function(t, e, r, n) {
1067
+ abort("Assertion failed: " + UTF8ToString(t) + ", at: " + [e ? UTF8ToString(e) : "unknown filename", r, n ? UTF8ToString(n) : "unknown function"])
1068
+ },
1069
+ g: function(t) {
1070
+ return ke(t + 16) + 16
1071
+ },
1072
+ f: function(t, e, r) {
1073
+ throw new ExceptionInfo(t).init(e, r),t,t
1074
+ },
1075
+ p: function(t, e, r, n, o) {},
1076
+ y: function(t, e, r, n, o) {
1077
+ var i = ut(r);
1078
+ _t(t, {
1079
+ name: e = ct(e),
1080
+ fromWireType: function(t) {
1081
+ return !!t
1082
+ },
1083
+ toWireType: function(t, e) {
1084
+ return e ? n : o
1085
+ },
1086
+ argPackAdvance: 8,
1087
+ readValueFromPointer: function(t) {
1088
+ var n;
1089
+ if (1 === r)
1090
+ n = S;
1091
+ else if (2 === r)
1092
+ n = k;
1093
+ else {
1094
+ if (4 !== r)
1095
+ throw new TypeError("Unknown boolean type size: " + e);
1096
+ n = j
1097
+ }
1098
+ return this.fromWireType(n[t >> i])
1099
+ },
1100
+ destructorFunction: null
1101
+ })
1102
+ },
1103
+ A: function(t, e, r, n, o, i, a, u, s, c, f, l, p) {
1104
+ f = ct(f),
1105
+ i = ie(o, i),
1106
+ u && (u = ie(a, u)),
1107
+ c && (c = ie(s, c)),
1108
+ p = ie(l, p);
1109
+ var d = dt(f);
1110
+ xt(d, (function() {
1111
+ se("Cannot construct " + f + " due to unbound types", [n])
1112
+ })),
1113
+ bt([t, e, r], n ? [n] : [], (function(e) {
1114
+ var r, o;
1115
+ e = e[0],
1116
+ o = n ? (r = e.registeredClass).instancePrototype : Rt.prototype;
1117
+ var a = ht(d, (function() {
1118
+ if (Object.getPrototypeOf(this) !== s)
1119
+ throw new mt("Use 'new' to construct " + f);
1120
+ if (void 0 === l.constructor_body)
1121
+ throw new mt(f + " has no accessible constructor");
1122
+ var t = l.constructor_body[arguments.length];
1123
+ if (void 0 === t)
1124
+ throw new mt("Tried to invoke ctor of " + f + " with invalid number of parameters (" + arguments.length + ") - expected (" + Object.keys(l.constructor_body).toString() + ") parameters instead!");
1125
+ return t.apply(this, arguments)
1126
+ })),
1127
+ s = Object.create(o, {
1128
+ constructor: {
1129
+ value: a
1130
+ }
1131
+ });
1132
+ a.prototype = s;
1133
+ var l = new Ut(f, a, s, p, r, i, u, c),
1134
+ h = new re(f, l, !0, !1, !1),
1135
+ y = new re(f + "*", l, !1, !1, !1),
1136
+ m = new re(f + " const*", l, !1, !0, !1);
1137
+ return Mt[t] = {
1138
+ pointerType: y,
1139
+ constPointerType: m
1140
+ },
1141
+ ne(d, a),
1142
+ [h, y, m]
1143
+ }))
1144
+ },
1145
+ w: function(t, e, r, n, o, i) {
1146
+ T(e > 0);
1147
+ var a = ce(e, r);
1148
+ o = ie(n, o),
1149
+ bt([], [t], (function(t) {
1150
+ var r = "constructor " + (t = t[0]).name;
1151
+ if (void 0 === t.registeredClass.constructor_body && (t.registeredClass.constructor_body = []),
1152
+ void 0 !== t.registeredClass.constructor_body[e - 1])
1153
+ throw new mt("Cannot register multiple constructors with identical number of parameters (" + (e - 1) + ") for class '" + t.name + "'! Overload resolution is currently only performed using the parameter count, not actual type info!");
1154
+ return t.registeredClass.constructor_body[e - 1] = function() {
1155
+ se("Cannot construct " + t.name + " due to unbound types", a)
1156
+ },
1157
+ bt([], a, (function(n) {
1158
+ return n.splice(1, 0, null),
1159
+ t.registeredClass.constructor_body[e - 1] = le(r, n, null, o, i),
1160
+ []
1161
+ })),
1162
+ []
1163
+ }))
1164
+ },
1165
+ c: function(t, e, r, n, o, i, a, u) {
1166
+ var s = ce(r, n);
1167
+ e = ct(e),
1168
+ i = ie(o, i),
1169
+ bt([], [t], (function(t) {
1170
+ var n = (t = t[0]).name + "." + e;
1171
+
1172
+ function o() {
1173
+ se("Cannot call " + n + " due to unbound types", s)
1174
+ }
1175
+ e.startsWith("@@") && (e = Symbol[e.substring(2)]),
1176
+ u && t.registeredClass.pureVirtualFunctions.push(e);
1177
+ var c = t.registeredClass.instancePrototype,
1178
+ f = c[e];
1179
+ return void 0 === f || void 0 === f.overloadTable && f.className !== t.name && f.argCount === r - 2 ? (o.argCount = r - 2,
1180
+ o.className = t.name,
1181
+ c[e] = o) : (It(c, e, n),
1182
+ c[e].overloadTable[r - 2] = o),
1183
+ bt([], s, (function(o) {
1184
+ var u = le(n, o, t, i, a);
1185
+ return void 0 === c[e].overloadTable ? (u.argCount = r - 2,
1186
+ c[e] = u) : c[e].overloadTable[r - 2] = u,
1187
+ []
1188
+ })),
1189
+ []
1190
+ }))
1191
+ },
1192
+ x: function(t, e) {
1193
+ _t(t, {
1194
+ name: e = ct(e),
1195
+ fromWireType: function(t) {
1196
+ var e = ve.toValue(t);
1197
+ return he(t),
1198
+ e
1199
+ },
1200
+ toWireType: function(t, e) {
1201
+ return ve.toHandle(e)
1202
+ },
1203
+ argPackAdvance: 8,
1204
+ readValueFromPointer: Bt,
1205
+ destructorFunction: null
1206
+ })
1207
+ },
1208
+ j: function(t, e, r) {
1209
+ var n = ut(r);
1210
+ _t(t, {
1211
+ name: e = ct(e),
1212
+ fromWireType: function(t) {
1213
+ return t
1214
+ },
1215
+ toWireType: function(t, e) {
1216
+ if ("number" != typeof e && "boolean" != typeof e)
1217
+ throw new TypeError('Cannot convert "' + ge(e) + '" to ' + this.name);
1218
+ return e
1219
+ },
1220
+ argPackAdvance: 8,
1221
+ readValueFromPointer: we(e, n),
1222
+ destructorFunction: null
1223
+ })
1224
+ },
1225
+ l: function(t, e, r, n, o, i) {
1226
+ // Registering functions from constructor (;204;)
1227
+ var a = ce(e, r);
1228
+ t = ct(t),
1229
+ o = ie(n, o),
1230
+ xt(t, (function() {
1231
+ se("Cannot call " + t + " due to unbound types", a)
1232
+ }), e - 1),
1233
+ bt([], a, (function(r) {
1234
+ var n = [r[0], null].concat(r.slice(1));
1235
+ return ne(t, le(t, n, null, o, i), e - 1),
1236
+ []
1237
+ }))
1238
+ },
1239
+ b: function(t, e, r, n, o) {
1240
+ e = ct(e),
1241
+ -1 === o && (o = 4294967295);
1242
+ var i = ut(r),
1243
+ a = function(t) {
1244
+ return t
1245
+ };
1246
+ if (0 === n) {
1247
+ var u = 32 - 8 * r;
1248
+ a = function(t) {
1249
+ return t << u >>> u
1250
+ }
1251
+ }
1252
+ var s = e.includes("unsigned");
1253
+ _t(t, {
1254
+ name: e,
1255
+ fromWireType: a,
1256
+ toWireType: function(t, r) {
1257
+ if ("number" != typeof r && "boolean" != typeof r)
1258
+ throw new TypeError('Cannot convert "' + ge(r) + '" to ' + this.name);
1259
+ if (r < n || r > o)
1260
+ throw new TypeError('Passing a number "' + ge(r) + '" from JS side to C/C++ side to an argument of type "' + e + '", which is outside the valid range [' + n + ", " + o + "]!");
1261
+ return s ? r >>> 0 : 0 | r
1262
+ },
1263
+ argPackAdvance: 8,
1264
+ readValueFromPointer: be(e, i, 0 !== n),
1265
+ destructorFunction: null
1266
+ })
1267
+ },
1268
+ a: function(t, e, r) {
1269
+ var n = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array][e];
1270
+
1271
+ function o(t) {
1272
+ var e = R,
1273
+ r = e[t >>= 2],
1274
+ o = e[t + 1];
1275
+ return new n(E, o, r)
1276
+ }
1277
+ _t(t, {
1278
+ name: r = ct(r),
1279
+ fromWireType: o,
1280
+ argPackAdvance: 8,
1281
+ readValueFromPointer: o
1282
+ }, {
1283
+ ignoreDuplicateRegistrations: !0
1284
+ })
1285
+ },
1286
+ k: function(t, e) {
1287
+ var r = "std::string" === (e = ct(e));
1288
+ _t(t, {
1289
+ name: e,
1290
+ fromWireType: function(t) {
1291
+ var e, n = R[t >> 2];
1292
+ if (r)
1293
+ for (var o = t + 4, i = 0; i <= n; ++i) {
1294
+ var a = t + 4 + i;
1295
+ if (i == n || 0 == O[a]) {
1296
+ var u = UTF8ToString(o, a - o);
1297
+ void 0 === e ? e = u : (e += String.fromCharCode(0),
1298
+ e += u),
1299
+ o = a + 1
1300
+ }
1301
+ }
1302
+ else {
1303
+ var s = new Array(n);
1304
+ for (i = 0; i < n; ++i)
1305
+ s[i] = String.fromCharCode(O[t + 4 + i]);
1306
+ e = s.join("")
1307
+ }
1308
+ return We(t),
1309
+ e
1310
+ },
1311
+ toWireType: function(t, e) {
1312
+ e instanceof ArrayBuffer && (e = new Uint8Array(e));
1313
+ var n = "string" == typeof e;
1314
+ n || e instanceof Uint8Array || e instanceof Uint8ClampedArray || e instanceof Int8Array || vt("Cannot pass non-string to std::string");
1315
+ var o = (r && n ? function() {
1316
+ return UTF8CharCount(e)
1317
+ } :
1318
+ function() {
1319
+ return e.length
1320
+ }
1321
+ )(),
1322
+ i = ke(4 + o + 1);
1323
+ if (R[i >> 2] = o,
1324
+ r && n)
1325
+ stringToUTF8Array(e, O, i + 4, o + 1);
1326
+ else if (n)
1327
+ for (var a = 0; a < o; ++a) {
1328
+ var u = e.charCodeAt(a);
1329
+ u > 255 && (We(i),
1330
+ vt("String has UTF-16 code units that do not fit in 8 bits")),
1331
+ O[i + 4 + a] = u
1332
+ }
1333
+ else
1334
+ for (a = 0; a < o; ++a)
1335
+ O[i + 4 + a] = e[a];
1336
+ return null !== t && t.push(We, i),
1337
+ i
1338
+ },
1339
+ argPackAdvance: 8,
1340
+ readValueFromPointer: Bt,
1341
+ destructorFunction: function(t) {
1342
+ We(t)
1343
+ }
1344
+ })
1345
+ },
1346
+ e: function(t, e, r) {
1347
+ var n, o, i, a, u;
1348
+ r = ct(r),
1349
+ 2 === e ? (n = UTF16ArrayToString,
1350
+ o = H,
1351
+ a = Y,
1352
+ i = function() {
1353
+ return W
1354
+ },
1355
+ u = 1) : 4 === e && (n = V,
1356
+ o = z,
1357
+ a = B,
1358
+ i = function() {
1359
+ return R
1360
+ },
1361
+ u = 2),
1362
+ _t(t, {
1363
+ name: r,
1364
+ fromWireType: function(t) {
1365
+ for (var r, o = R[t >> 2], a = i(), s = t + 4, c = 0; c <= o; ++c) {
1366
+ var f = t + 4 + c * e;
1367
+ if (c == o || 0 == a[f >> u]) {
1368
+ var l = n(s, f - s);
1369
+ void 0 === r ? r = l : (r += String.fromCharCode(0),
1370
+ r += l),
1371
+ s = f + e
1372
+ }
1373
+ }
1374
+ return We(t),
1375
+ r
1376
+ },
1377
+ toWireType: function(t, n) {
1378
+ "string" != typeof n && vt("Cannot pass non-string to C++ string type " + r);
1379
+ var i = a(n),
1380
+ s = ke(4 + i + e);
1381
+ return R[s >> 2] = i >> u,
1382
+ o(n, s + 4, i + e),
1383
+ null !== t && t.push(We, s),
1384
+ s
1385
+ },
1386
+ argPackAdvance: 8,
1387
+ readValueFromPointer: Bt,
1388
+ destructorFunction: function(t) {
1389
+ We(t)
1390
+ }
1391
+ })
1392
+ },
1393
+ z: function(t, e) {
1394
+ _t(t, {
1395
+ isVoid: !0,
1396
+ name: e = ct(e),
1397
+ argPackAdvance: 0,
1398
+ fromWireType: function() {},
1399
+ toWireType: function(t, e) {}
1400
+ })
1401
+ },
1402
+ m: he,
1403
+ n: function(t) {
1404
+ t > 4 && (de[t].refcount += 1)
1405
+ },
1406
+ o: function(t, e) {
1407
+ var r, n, o;
1408
+ n = "_emval_take_value",
1409
+ void 0 === (o = lt[r = t]) && vt(n + " has unknown type " + ue(r));
1410
+ var i = (t = o).readValueFromPointer(e);
1411
+ return ve.toHandle(i)
1412
+ },
1413
+ h: function() {
1414
+ abort("")
1415
+ },
1416
+ r: function(t, e, r) {
1417
+ O.copyWithin(t, e, e + r)
1418
+ },
1419
+ s: function(t) {
1420
+ O.length,
1421
+ abort("OOM")
1422
+ },
1423
+ u: function(t, e) {},
1424
+ v: function(t, e) {},
1425
+ i: function(t, e, r, n) {
1426
+ for (var o = 0, i = 0; i < r; i++) {
1427
+ var a = j[e >> 2],
1428
+ u = j[e + 4 >> 2];
1429
+ e += 8;
1430
+ for (var s = 0; s < u; s++)
1431
+ Te.printChar(t, O[a + s]);
1432
+ o += u
1433
+ }
1434
+ return j[n >> 2] = o,
1435
+ 0
1436
+ },
1437
+ q: function(t) {
1438
+ t
1439
+ },
1440
+ t: function(t, e, r, n) {
1441
+ return Ee(t, e, r, n)
1442
+ }
1443
+ },
1444
+ ke = (function() {
1445
+ var import_table = {
1446
+ a: import_table_impl
1447
+ };
1448
+
1449
+ function updateGlobalBufferAndViews(t, e) {
1450
+ var r, n, exports = t.exports;
1451
+ o.asm = exports;
1452
+ b = o.asm.B; // Mem
1453
+ r = b.buffer
1454
+ E = r
1455
+ o.HEAP8 = S = new Int8Array(r),
1456
+ o.HEAP16 = k = new Int16Array(r),
1457
+ o.HEAP32 = j = new Int32Array(r),
1458
+ o.HEAPU8 = O = new Uint8Array(r),
1459
+ o.HEAPU16 = W = new Uint16Array(r),
1460
+ o.HEAPU32 = R = new Uint32Array(r),
1461
+ o.HEAPF32 = M = new Float32Array(r),
1462
+ o.HEAPF64 = I = new Float64Array(r),
1463
+ L = o.asm.D // Table
1464
+ n = o.asm.C // ctor
1465
+ N.unshift(n),
1466
+ function(t) {
1467
+ if (J--,
1468
+ o.monitorRunDependencies && o.monitorRunDependencies(J),
1469
+ 0 == J && (null !== X && (clearInterval(X),
1470
+ X = null),
1471
+ Z)) {
1472
+ var e = Z;
1473
+ Z = null,
1474
+ e()
1475
+ }
1476
+ }()
1477
+ }
1478
+
1479
+ function load_wasm(t) {
1480
+ updateGlobalBufferAndViews(t.instance)
1481
+ }
1482
+
1483
+ function getBinaryPromise(e) {
1484
+ return function() {
1485
+ if (!w) {
1486
+ if (readSync)
1487
+ return new Promise((function(t, e) {
1488
+ readSync(Q, (function(e) {
1489
+ t(new Uint8Array(e))
1490
+ }), e)
1491
+ }))
1492
+ }
1493
+ return Promise.resolve().then((function() {
1494
+ return getBinary(Q)
1495
+ }))
1496
+ }().then((function(e) {
1497
+ return WebAssembly.instantiate(e, import_table)
1498
+ })).then((function(t) {
1499
+ return t
1500
+ })).then(e, (function(t) {
1501
+ g("failed to asynchronously prepare wasm: " + t,Q),
1502
+ abort(t)
1503
+ }))
1504
+ }
1505
+ if (J++,
1506
+ o.monitorRunDependencies && o.monitorRunDependencies(J),
1507
+ o.instantiateWasm)
1508
+ try {
1509
+ return o.instantiateWasm(import_table, updateGlobalBufferAndViews)
1510
+ } catch (t) {
1511
+ return g("Module.instantiateWasm callback failed with error: " + t),
1512
+ !1
1513
+ }
1514
+ w || "function" != typeof WebAssembly.instantiate || isDataURI(Q) || isFileURI(Q) || getBinaryPromise(load_wasm)
1515
+ }(),
1516
+ o.___wasm_call_ctors = function() {
1517
+ return (o.___wasm_call_ctors = o.asm.C).apply(null, arguments)
1518
+ },
1519
+ o._malloc = function() {
1520
+ return (ke = o._malloc = o.asm.E).apply(null, arguments)
1521
+ }
1522
+ ),
1523
+ We = o._free = function() {
1524
+ return (We = o._free = o.asm.F).apply(null, arguments)
1525
+ },
1526
+ je = o.___getTypeName = function() {
1527
+ return (je = o.___getTypeName = o.asm.G).apply(null, arguments)
1528
+ };
1529
+ o.___embind_register_native_and_builtin_types = function() {
1530
+ return (o.___embind_register_native_and_builtin_types = o.asm.H).apply(null, arguments)
1531
+ },
1532
+ o.dynCall_jiji = function() {
1533
+ return (o.dynCall_jiji = o.asm.I).apply(null, arguments)
1534
+ },
1535
+ o.dynCall_iiiiij = function() {
1536
+ return (o.dynCall_iiiiij = o.asm.J).apply(null, arguments)
1537
+ },
1538
+ o.dynCall_iiiiijj = function() {
1539
+ return (o.dynCall_iiiiijj = o.asm.K).apply(null, arguments)
1540
+ },
1541
+ o.dynCall_iiiiiijj = function() {
1542
+ return (o.dynCall_iiiiiijj = o.asm.L).apply(null, arguments)
1543
+ },
1544
+ o.dynCall_viijii = function() {
1545
+ return (o.dynCall_viijii = o.asm.M).apply(null, arguments)
1546
+ };
1547
+
1548
+ function ExitStatus(t) {
1549
+ this.name = "ExitStatus",
1550
+ this.message = "Program terminated with exit(" + t + ")",
1551
+ this.status = t
1552
+ }
1553
+
1554
+ function doRun(t) {
1555
+ function postRun() {
1556
+ Se || (Se = !0,o.calledRun = !0,C || (!0,
1557
+ callRuntimeCallbacks(N),
1558
+ o.onRuntimeInitialized && o.onRuntimeInitialized(),
1559
+ function() {
1560
+ if (o.postRun)
1561
+ for ("function" == typeof o.postRun && (o.postRun = [o.postRun]); o.postRun.length;)
1562
+ t = o.postRun.shift(),
1563
+ q.unshift(t);
1564
+ var t;
1565
+ callRuntimeCallbacks(q)
1566
+ }()))
1567
+ }
1568
+ t = t || l
1569
+ J > 0 || (! function preRun() {
1570
+ if (o.preRun)
1571
+ for ("function" == typeof o.preRun && (o.preRun = [o.preRun]); o.preRun.length;)
1572
+ t = o.preRun.shift(),
1573
+ G.unshift(t);
1574
+ var t;
1575
+ callRuntimeCallbacks(G)
1576
+ }(),
1577
+ J > 0 || (o.setStatus ? (o.setStatus("Running..."),
1578
+ setTimeout((function() {
1579
+ setTimeout((function() {
1580
+ o.setStatus("")
1581
+ }), 1),
1582
+ postRun()
1583
+ }), 1)) : postRun()))
1584
+ }
1585
+ if (Z = function t() {
1586
+ Se || doRun(),
1587
+ Se || (Z = t)
1588
+ },
1589
+ o.run = doRun,
1590
+ o.preInit)
1591
+ for ("function" == typeof o.preInit && (o.preInit = [o.preInit]); o.preInit.length > 0;)
1592
+ o.preInit.pop()();
1593
+ doRun();
1594
+ return o;
1595
+ })
1596
+
1597
+ // XXX: With PythonMonkey, the required module
1598
+ // is destructed(?) once the function is called
1599
+ // This is probably not what actaully happened, but
1600
+ // for now, everytime an FP is generated, the entire
1601
+ // WASM module is reloaded as a workaround
1602
+ function instantiateRuntime(){
1603
+ return new Promise((resolve, reject) => {
1604
+ var fpRuntime = AudioFingerprintRuntime()
1605
+ var monitor = setInterval(() => {
1606
+ if (typeof fpRuntime.ExtractQueryFP == "function")
1607
+ clearInterval(monitor) || resolve(fpRuntime)
1608
+ })
1609
+ })
1610
+ }
1611
+
1612
+ function GenerateFP(floatArray) {
1613
+ let PCMBuffer = Float32Array.from(floatArray)
1614
+ console.info('[afp] input samples n=', PCMBuffer.length)
1615
+ return instantiateRuntime().then((fpRuntime) => {
1616
+ console.info('[afp] begin fingerprinting')
1617
+ let fp_vector = fpRuntime.ExtractQueryFP(PCMBuffer.buffer)
1618
+ let result_buf = new Uint8Array(fp_vector.size());
1619
+ for (let t = 0; t < fp_vector.size(); t++)
1620
+ result_buf[t] = fp_vector.get(t);
1621
+ return globalThis.b64encode(result_buf)
1622
+ });
1623
+ }
1624
+
1625
+ if (typeof exports != 'undefined') /* Node, PythonMonkey */
1626
+ exports.GenerateFP = GenerateFP;
src/router/xtc_recognize/afp/afp.wasm.js ADDED
The diff for this file is too large to render. See raw diff
 
src/router/xtc_recognize/afp/afp_runner.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * 指纹生成子进程脚本(被 Python 通过 subprocess 调用)
3
+ *
4
+ * 协议:
5
+ * stdin: base64 编码的 16-bit PCM s16le 数据(8kHz 单声道,3秒)
6
+ * stdout: base64 编码的指纹 FP 字符串
7
+ * stderr: 日志/错误
8
+ *
9
+ * 退出码:
10
+ * 0 成功;1 失败
11
+ */
12
+
13
+ const path = require('path')
14
+ const { GenerateFP } = require(path.join(__dirname, 'afp.js'))
15
+
16
+ async function main() {
17
+ const chunks = []
18
+ process.stdin.on('data', (c) => chunks.push(c))
19
+ process.stdin.on('end', async () => {
20
+ try {
21
+ const b64 = Buffer.concat(chunks).toString('utf8').trim()
22
+ const pcmBytes = Buffer.from(b64, 'base64')
23
+ // s16le -> Float32Array
24
+ const samples = pcmBytes.length / 2
25
+ const pcm = new Float32Array(samples)
26
+ const dv = new DataView(pcmBytes.buffer, pcmBytes.byteOffset, pcmBytes.byteLength)
27
+ for (let i = 0; i < samples; i++) {
28
+ pcm[i] = dv.getInt16(i * 2, true) / 32768
29
+ }
30
+ const t0 = Date.now()
31
+ const fp = await GenerateFP(pcm)
32
+ const t1 = Date.now()
33
+ process.stderr.write(`[afp-runner] samples=${samples} fp_len=${fp.length} took=${t1 - t0}ms\n`)
34
+ process.stdout.write(fp)
35
+ process.exit(0)
36
+ } catch (e) {
37
+ process.stderr.write('[afp-runner] ERROR: ' + (e.stack || e.message) + '\n')
38
+ process.exit(1)
39
+ }
40
+ })
41
+ process.stdin.on('error', (e) => {
42
+ process.stderr.write('[afp-runner] stdin error: ' + e.message + '\n')
43
+ process.exit(1)
44
+ })
45
+ }
46
+
47
+ main()
src/router/xtc_recognize/amr_decoder.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AMR/任意音频 -> 8kHz 单声道 PCM s16le 转码。
2
+
3
+ 使用系统 ffmpeg(Dockerfile 已 apt-get install ffmpeg)。
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ import logging
9
+ from typing import Optional
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ async def decode_to_pcm_s16le(
15
+ audio_bytes: bytes,
16
+ *,
17
+ duration_sec: int = 3,
18
+ sample_rate: int = 8000,
19
+ channels: int = 1,
20
+ ) -> bytes:
21
+ """把任意音频(amr/mp3/wav/m4a 等)转成 8kHz 单声道 PCM s16le。
22
+
23
+ Args:
24
+ audio_bytes: 输入音频字节
25
+ duration_sec: 截取时长(秒),默认 3
26
+ sample_rate: 采样率,默认 8000(网易云 shazam_v2 要求)
27
+ channels: 声道数,默认 1
28
+
29
+ Returns:
30
+ PCM s16le 字节流
31
+
32
+ Raises:
33
+ RuntimeError: ffmpeg 失败
34
+ """
35
+ args = [
36
+ "ffmpeg",
37
+ "-i", "pipe:0",
38
+ "-f", "s16le",
39
+ "-acodec", "pcm_s16le",
40
+ "-ar", str(sample_rate),
41
+ "-ac", str(channels),
42
+ "-t", str(duration_sec),
43
+ "pipe:1",
44
+ ]
45
+ logger.info("[amr-decoder] ffmpeg start, input=%d bytes", len(audio_bytes))
46
+
47
+ proc = await asyncio.create_subprocess_exec(
48
+ *args,
49
+ stdin=asyncio.subprocess.PIPE,
50
+ stdout=asyncio.subprocess.PIPE,
51
+ stderr=asyncio.subprocess.PIPE,
52
+ )
53
+ stdout, stderr = await proc.communicate(input=audio_bytes)
54
+ if proc.returncode != 0:
55
+ err = stderr.decode("utf-8", errors="replace")[-500:]
56
+ raise RuntimeError(f"ffmpeg exit {proc.returncode}: {err}")
57
+ logger.info("[amr-decoder] ffmpeg ok, pcm=%d bytes", len(stdout))
58
+ return stdout
src/router/xtc_recognize/fingerprint.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """调用 Node.js 子进程运行 afp.js 生成音频指纹。
2
+
3
+ 通过 stdin/stdout 通信,避免 Node 进程频繁启动开销(每次仍会重启 wasm,
4
+ 但实测 93ms 足够快)。
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import base64
10
+ import logging
11
+ import os
12
+ from typing import Optional
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # afp_runner.js 路径(相对于本文件)
17
+ _RUNNER_PATH = os.path.join(os.path.dirname(__file__), "afp", "afp_runner.js")
18
+
19
+
20
+ async def generate_fp(pcm_s16le: bytes) -> str:
21
+ """生成音频指纹。
22
+
23
+ Args:
24
+ pcm_s16le: 8kHz 单声道 PCM s16le 字节
25
+
26
+ Returns:
27
+ base64 编码的指纹字符串
28
+ """
29
+ b64_input = base64.b64encode(pcm_s16le).decode("ascii")
30
+
31
+ logger.info("[fp] spawn node, input b64=%d chars", len(b64_input))
32
+ proc = await asyncio.create_subprocess_exec(
33
+ "node", _RUNNER_PATH,
34
+ stdin=asyncio.subprocess.PIPE,
35
+ stdout=asyncio.subprocess.PIPE,
36
+ stderr=asyncio.subprocess.PIPE,
37
+ )
38
+ stdout, stderr = await proc.communicate(input=b64_input.encode("ascii"))
39
+ if proc.returncode != 0:
40
+ err = stderr.decode("utf-8", errors="replace")[-500:]
41
+ raise RuntimeError(f"afp_runner exit {proc.returncode}: {err}")
42
+ # 把 stderr 日志输出到 logger
43
+ if stderr:
44
+ for line in stderr.decode("utf-8", errors="replace").splitlines():
45
+ logger.info("[fp] %s", line)
46
+ fp = stdout.decode("ascii").strip()
47
+ logger.info("[fp] generated, len=%d", len(fp))
48
+ return fp
src/router/xtc_recognize/ncm_proxy.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """网易云 API 代理:识曲 / 歌曲 URL / 歌曲详情。
2
+
3
+ 所有调用直连网易云公开接口,不依赖 api-enhanced 子服务。
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ from typing import Any, Optional
9
+
10
+ import httpx
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ _NCM_HEADERS = {
15
+ "User-Agent": "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36",
16
+ "Referer": "https://music.163.com",
17
+ }
18
+ _TIMEOUT = 10.0
19
+
20
+
21
+ async def audio_match(audio_fp: str, duration: int = 3) -> dict:
22
+ """调用网易云听歌识曲接口。
23
+
24
+ Args:
25
+ audio_fp: base64 编码的音频指纹
26
+ duration: 录音时长(秒)
27
+
28
+ Returns:
29
+ 网易云原始响应 dict,结构 {data: {result: [{song, startTime, score}]}}
30
+ """
31
+ url = "https://interface.music.163.com/api/music/audio/match"
32
+ params = {
33
+ "sessionId": "0123456789abcdef",
34
+ "algorithmCode": "shazam_v2",
35
+ "duration": str(duration),
36
+ "rawdata": audio_fp,
37
+ "times": "1",
38
+ "decrypt": "1",
39
+ }
40
+ async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
41
+ resp = await client.get(url, params=params, headers=_NCM_HEADERS)
42
+ resp.raise_for_status()
43
+ return resp.json()
44
+
45
+
46
+ async def get_song_url(song_id: str, *, unblock: bool = False, level: str = "standard") -> dict:
47
+ """获取试听 URL。
48
+
49
+ 优先调用网易云公开接口(无 cookie,仅试听片段)。
50
+ 若需要完整播放,由前端开关 unblock=true 触发,但本代理不实现 unblock
51
+ (需要 api-enhanced 的 unblockmusic-utils,这里仅返回公开接口结果)。
52
+
53
+ Returns:
54
+ {id, url, type, freeTrialInfo, fee}
55
+ """
56
+ url = "https://music.163.com/api/song/enhance/player/url"
57
+ params = {"ids": f"[{song_id}]", "br": "128000"}
58
+ async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
59
+ resp = await client.get(url, params=params, headers=_NCM_HEADERS)
60
+ resp.raise_for_status()
61
+ data = resp.json()
62
+ item = (data.get("data") or [{}])[0]
63
+ return {
64
+ "id": item.get("id"),
65
+ "url": item.get("url"),
66
+ "type": item.get("type"),
67
+ "freeTrialInfo": None,
68
+ "fee": item.get("fee"),
69
+ "unblock_supported": False, # 标记给前端:本后端不支持 unblock
70
+ }
71
+
72
+
73
+ async def get_song_detail(song_id: str) -> Optional[dict]:
74
+ """获取歌曲详情(封面、歌手等)。"""
75
+ url = "https://music.163.com/api/v1/song/detail"
76
+ params = {"ids": f"[{song_id}]"}
77
+ async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
78
+ resp = await client.get(url, params=params, headers=_NCM_HEADERS)
79
+ resp.raise_for_status()
80
+ data = resp.json()
81
+ songs = data.get("songs") or []
82
+ if not songs:
83
+ return None
84
+ s = songs[0]
85
+ return {
86
+ "id": s.get("id"),
87
+ "name": s.get("name"),
88
+ "artists": "/".join(a.get("name", "") for a in s.get("artists", [])),
89
+ "album": (s.get("album") or {}).get("name"),
90
+ "cover": (s.get("album") or {}).get("picUrl"),
91
+ "duration": s.get("duration"),
92
+ }
93
+
94
+
95
+ async def get_song_detail_batch(song_ids: list[str]) -> list[Optional[dict]]:
96
+ """批量获取歌曲详情。公开接口不支持批量,逐个调用。"""
97
+ # 网易云公开接口 ids 参数实际是 JSON 数组字符串,可以传多个
98
+ # 但为稳定性考虑,单个查询更可靠
99
+ url = "https://music.163.com/api/v1/song/detail"
100
+ ids_json = "[" + ",".join(str(i) for i in song_ids) + "]"
101
+ params = {"ids": ids_json}
102
+ async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
103
+ resp = await client.get(url, params=params, headers=_NCM_HEADERS)
104
+ resp.raise_for_status()
105
+ data = resp.json()
106
+ songs = data.get("songs") or []
107
+ by_id = {}
108
+ for s in songs:
109
+ by_id[str(s.get("id"))] = {
110
+ "id": s.get("id"),
111
+ "name": s.get("name"),
112
+ "artists": "/".join(a.get("name", "") for a in s.get("artists", [])),
113
+ "album": (s.get("album") or {}).get("name"),
114
+ "cover": (s.get("album") or {}).get("picUrl"),
115
+ "duration": s.get("duration"),
116
+ }
117
+ return [by_id.get(str(sid)) for sid in song_ids]
src/router/xtc_recognize/recognize.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """识曲主流程:buffer -> PCM -> FP -> 网易云识曲 -> 批量补详情。"""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+ from typing import Any
6
+
7
+ from .amr_decoder import decode_to_pcm_s16le
8
+ from .fingerprint import generate_fp
9
+ from .ncm_proxy import audio_match, get_song_detail_batch
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ async def recognize_from_audio(
15
+ audio_bytes: bytes,
16
+ *,
17
+ duration: int = 3,
18
+ mime: str = "",
19
+ ) -> dict:
20
+ """主入口。
21
+
22
+ Args:
23
+ audio_bytes: 原始音频字节(amr/wav/mp3/m4a 等 ffmpeg 支持的格式)
24
+ duration: 采样时长(秒)
25
+ mime: MIME 类型(仅用于日志)
26
+
27
+ Returns:
28
+ {fp: str, list: [...], raw_match: dict}
29
+ """
30
+ logger.info(
31
+ "[recognize] start, input=%d bytes mime=%s duration=%d",
32
+ len(audio_bytes), mime, duration,
33
+ )
34
+
35
+ # 1) 转码
36
+ pcm = await decode_to_pcm_s16le(audio_bytes, duration_sec=duration)
37
+
38
+ # 2) 指纹
39
+ fp = await generate_fp(pcm)
40
+
41
+ # 3) 识曲
42
+ match_res = await audio_match(fp, duration)
43
+ candidates = ((match_res.get("data") or {}).get("result")) or []
44
+ logger.info("[recognize] match candidates=%d", len(candidates))
45
+
46
+ if not candidates:
47
+ return {"fp": fp, "list": [], "raw_match": match_res}
48
+
49
+ # 4) 批量补全详情
50
+ ids = [str(c.get("song", {}).get("id")) for c in candidates if c.get("song", {}).get("id")]
51
+ details = await get_song_detail_batch(ids) if ids else []
52
+ detail_map = {str(d["id"]): d for d in details if d}
53
+
54
+ # 5) 组装结果,按 score 排序
55
+ out_list = []
56
+ for c in candidates:
57
+ song = c.get("song") or {}
58
+ sid = str(song.get("id")) if song.get("id") else ""
59
+ d = detail_map.get(sid, {})
60
+ out_list.append({
61
+ "id": song.get("id"),
62
+ "name": d.get("name") or song.get("name"),
63
+ "artists": d.get("artists") or "/".join(
64
+ a.get("name", "") for a in song.get("artists", [])
65
+ ),
66
+ "album": d.get("album") or (song.get("album") or {}).get("name"),
67
+ "cover": d.get("cover") or (song.get("album") or {}).get("picUrl"),
68
+ "duration": d.get("duration"),
69
+ "matchScore": c.get("score"),
70
+ "startTime": c.get("startTime"),
71
+ })
72
+ out_list.sort(key=lambda x: (x.get("matchScore") or 0), reverse=True)
73
+
74
+ return {"fp": fp, "list": out_list, "raw_match": match_res}
src/router/xtc_recognize/router.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """XTC 听歌识曲 FastAPI 路由。
2
+
3
+ 无侵入集成到 gcli2api:所有路径前缀 /xtc,不与现有路由冲突。
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import base64
8
+ import logging
9
+ from typing import Any, Optional
10
+
11
+ from fastapi import APIRouter, Query, Request
12
+ from fastapi.responses import JSONResponse
13
+
14
+ from .ncm_proxy import get_song_detail, get_song_url
15
+ from .recognize import recognize_from_audio
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ router = APIRouter(prefix="/xtc", tags=["XTC Recognize"])
20
+
21
+ # 可选鉴权:环境变量 XTC_API_KEY 设置后,请求头 X-API-Key 需匹配
22
+ import os
23
+ _API_KEY = os.environ.get("XTC_API_KEY", "")
24
+
25
+
26
+ def _check_key(request: Request) -> Optional[JSONResponse]:
27
+ """校验 X-API-Key。未配置则放行。"""
28
+ if not _API_KEY:
29
+ return None
30
+ provided = request.headers.get("X-API-Key", "")
31
+ if provided == _API_KEY:
32
+ return None
33
+ return JSONResponse(
34
+ status_code=401,
35
+ content={"code": 401, "msg": "unauthorized"},
36
+ )
37
+
38
+
39
+ def _ok(data: Any) -> JSONResponse:
40
+ return JSONResponse(
41
+ status_code=200,
42
+ content={"code": 200, "data": data},
43
+ headers={
44
+ "Access-Control-Allow-Origin": "*",
45
+ "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
46
+ "Access-Control-Allow-Headers": "Content-Type, X-API-Key",
47
+ },
48
+ )
49
+
50
+
51
+ def _err(msg: str, status: int = 500) -> JSONResponse:
52
+ return JSONResponse(
53
+ status_code=status,
54
+ content={"code": status, "msg": msg},
55
+ headers={
56
+ "Access-Control-Allow-Origin": "*",
57
+ "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
58
+ "Access-Control-Allow-Headers": "Content-Type, X-API-Key",
59
+ },
60
+ )
61
+
62
+
63
+ @router.options("/{path:path}")
64
+ async def cors_preflight(path: str) -> JSONResponse:
65
+ return JSONResponse(
66
+ status_code=204,
67
+ content=None,
68
+ headers={
69
+ "Access-Control-Allow-Origin": "*",
70
+ "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
71
+ "Access-Control-Allow-Headers": "Content-Type, X-API-Key",
72
+ "Access-Control-Max-Age": "86400",
73
+ },
74
+ )
75
+
76
+
77
+ @router.get("/health")
78
+ async def health() -> JSONResponse:
79
+ """健康检查。"""
80
+ import shutil
81
+ node_ok = shutil.which("node") is not None
82
+ ffmpeg_ok = shutil.which("ffmpeg") is not None
83
+ return _ok({
84
+ "service": "xtc-recognize",
85
+ "node": node_ok,
86
+ "ffmpeg": ffmpeg_ok,
87
+ "auth": bool(_API_KEY),
88
+ })
89
+
90
+
91
+ @router.post("/recognize")
92
+ async def recognize(request: Request) -> JSONResponse:
93
+ """识曲接口。
94
+
95
+ Body: JSON
96
+ {
97
+ "audio": "<base64 编码的音频字节>",
98
+ "mime": "audio/amr", # 可选
99
+ "name": "record.amr", # 可选
100
+ "duration": 3 # 可选,默认 3
101
+ }
102
+
103
+ Returns:
104
+ {code:200, data:{fp, list:[{id,name,artists,album,cover,duration,matchScore,startTime}], raw_match}}
105
+ """
106
+ auth_err = _check_key(request)
107
+ if auth_err:
108
+ return auth_err
109
+
110
+ try:
111
+ body = await request.json()
112
+ if not isinstance(body, dict):
113
+ return _err("invalid body", 400)
114
+ audio_b64 = body.get("audio")
115
+ if not audio_b64:
116
+ return _err("audio required", 400)
117
+ duration = int(body.get("duration") or 3)
118
+ if duration < 1 or duration > 10:
119
+ duration = 3
120
+ mime = str(body.get("mime") or "")
121
+ name = str(body.get("name") or "")
122
+
123
+ audio_bytes = base64.b64decode(audio_b64)
124
+ if len(audio_bytes) > 2 * 1024 * 1024:
125
+ return _err("audio too large (max 2MB)", 400)
126
+
127
+ result = await recognize_from_audio(
128
+ audio_bytes,
129
+ duration=duration,
130
+ mime=mime,
131
+ )
132
+ # raw_match 体积大且含敏感字段,对外只返回精简后的 list
133
+ return _ok({
134
+ "fp": result["fp"],
135
+ "list": result["list"],
136
+ "count": len(result["list"]),
137
+ })
138
+ except Exception as e:
139
+ logger.exception("[recognize] error")
140
+ return _err(str(e) or "recognize failed")
141
+
142
+
143
+ @router.get("/song/url")
144
+ async def song_url(
145
+ request: Request,
146
+ id: str = Query(...),
147
+ unblock: str = Query("false"),
148
+ level: str = Query("standard"),
149
+ ) -> JSONResponse:
150
+ """获取试听 URL。
151
+
152
+ 公开接口仅返回 30s 试听片段(VIP 歌曲)。unblock 暂不支持。
153
+ """
154
+ auth_err = _check_key(request)
155
+ if auth_err:
156
+ return auth_err
157
+ try:
158
+ data = await get_song_url(id, unblock=(unblock == "true"), level=level)
159
+ return _ok(data)
160
+ except Exception as e:
161
+ logger.exception("[song/url] error")
162
+ return _err(str(e) or "get url failed")
163
+
164
+
165
+ @router.get("/song/detail")
166
+ async def song_detail(
167
+ request: Request,
168
+ id: str = Query(...),
169
+ ) -> JSONResponse:
170
+ """获取歌曲详情。"""
171
+ auth_err = _check_key(request)
172
+ if auth_err:
173
+ return auth_err
174
+ try:
175
+ data = await get_song_detail(id)
176
+ if not data:
177
+ return _err("song not found", 404)
178
+ return _ok(data)
179
+ except Exception as e:
180
+ logger.exception("[song/detail] error")
181
+ return _err(str(e) or "get detail failed")
web.py CHANGED
@@ -141,6 +141,14 @@ app.include_router(geminicli_anthropic_router, prefix="", tags=["Geminicli Anthr
141
  # Panel路由 - 包含认证、凭证管理和控制面板功能
142
  app.include_router(panel_router, prefix="", tags=["Panel Interface"])
143
 
 
 
 
 
 
 
 
 
144
  # 静态文件路由 - 服务docs目录下的文件
145
  app.mount("/docs", StaticFiles(directory="docs"), name="docs")
146
 
 
141
  # Panel路由 - 包含认证、凭证管理和控制面板功能
142
  app.include_router(panel_router, prefix="", tags=["Panel Interface"])
143
 
144
+ # ===== XTC 听歌识曲路由(无侵入集成,失败不影响主服务) =====
145
+ try:
146
+ from src.router.xtc_recognize import router as xtc_recognize_router
147
+ app.include_router(xtc_recognize_router, tags=["XTC Recognize"])
148
+ log.info("XTC 听歌识曲路由加载成功")
149
+ except Exception as e:
150
+ log.error(f"XTC 听歌识曲路由加载失败(不影响主服务): {e}")
151
+
152
  # 静态文件路由 - 服务docs目录下的文件
153
  app.mount("/docs", StaticFiles(directory="docs"), name="docs")
154