a3216 commited on
Commit
53cce4e
·
verified ·
1 Parent(s): 04b9a37

sync from GitHub ac31920: feat(music): 新增听歌识曲后端接口

Browse files

Auto-synced from GitHub commit ac31920f56fbfb31fc366176f93585d163f1bccb

Dockerfile CHANGED
@@ -2,9 +2,14 @@ FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
- # 系统依赖:libheif 用于 HEIC/HEIF 解码(图片修正增强)
 
 
 
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
7
  libheif1 libheif-dev \
 
 
8
  && rm -rf /var/lib/apt/lists/*
9
 
10
  COPY requirements.txt .
 
2
 
3
  WORKDIR /app
4
 
5
+ # 系统依赖:
6
+ # - libheif:用于 HEIC/HEIF 解码(图片修正增强)
7
+ # - ffmpeg:听歌识曲 AMR→PCM 转码
8
+ # - nodejs:运行 afp.wasm 指纹生成脚本
9
  RUN apt-get update && apt-get install -y --no-install-recommends \
10
  libheif1 libheif-dev \
11
+ ffmpeg \
12
+ nodejs \
13
  && rm -rf /var/lib/apt/lists/*
14
 
15
  COPY requirements.txt .
app/api/music.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """听歌识曲相关路由 /v1/music/*。
2
+
3
+ 所有接口复用 require_access_key 鉴权,与现有 /v1/xtc/* 接口安全级别一致。
4
+ 响应格式统一 { ok: bool, ... },错误走全局 HttpError handler。
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+ from typing import Optional
10
+
11
+ from fastapi import APIRouter, Depends, File, Query, UploadFile
12
+ from fastapi.responses import JSONResponse
13
+
14
+ from ..auth import require_access_key
15
+ from ..errors import HttpError
16
+ from ..services import music_service
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ router = APIRouter(prefix="/v1/music", tags=["music"])
21
+
22
+
23
+ @router.post("/recognize")
24
+ async def recognize(
25
+ file: UploadFile = File(..., description="AMR 录音文件"),
26
+ duration: int = Query(8, ge=3, le=15, description="录音时长(秒)"),
27
+ _key: str = Depends(require_access_key),
28
+ ):
29
+ """听歌识曲:上传 AMR 录音,返回识别结果列表。
30
+
31
+ 链路:AMR → ffmpeg 转 PCM → node wasm 生成指纹 → 网易云 audio_match。
32
+ """
33
+ amr_bytes = await file.read()
34
+ if not amr_bytes:
35
+ raise HttpError("empty file", status=400, code="bad_request", hint="录音文件为空")
36
+ # 限制上传大小 2MB(8 秒 AMR 约 50KB,留充足余量)
37
+ if len(amr_bytes) > 2 * 1024 * 1024:
38
+ raise HttpError("file too large", status=413, code="bad_request", hint="录音文件超过 2MB 限制")
39
+ try:
40
+ result = await music_service.recognize(amr_bytes, duration=duration)
41
+ return JSONResponse(result)
42
+ except HttpError:
43
+ raise
44
+ except Exception as e:
45
+ logger.exception("[music/recognize] unexpected error")
46
+ raise HttpError(
47
+ f"recognize failed: {e}", status=500, code="internal_error",
48
+ hint="识曲失败,请稍后重试",
49
+ )
50
+
51
+
52
+ @router.get("/song/url")
53
+ async def song_url(
54
+ id: int = Query(..., description="歌曲 ID"),
55
+ level: str = Query("standard", description="音质: standard/exhigh/lossless"),
56
+ unblock: bool = Query(False, description="无 URL 时尝试换源(酷我)"),
57
+ name: str = Query("", description="歌曲名(换源搜索用)"),
58
+ artist: str = Query("", description="歌手名(换源搜索用)"),
59
+ _key: str = Depends(require_access_key),
60
+ ):
61
+ """获取歌曲播放 URL。"""
62
+ try:
63
+ result = await music_service.get_song_url(
64
+ id, level=level, unblock=unblock, song_name=name, artist=artist,
65
+ )
66
+ return JSONResponse(result)
67
+ except HttpError:
68
+ raise
69
+ except Exception as e:
70
+ logger.exception("[music/song/url] unexpected error")
71
+ raise HttpError(
72
+ f"get song url failed: {e}", status=500, code="internal_error",
73
+ )
74
+
75
+
76
+ @router.get("/song/detail")
77
+ async def song_detail(
78
+ ids: str = Query(..., description="歌曲 ID 列表,逗号分隔"),
79
+ _key: str = Depends(require_access_key),
80
+ ):
81
+ """批量获取歌曲详情。"""
82
+ try:
83
+ id_list = [int(x.strip()) for x in ids.split(",") if x.strip()][:50]
84
+ except ValueError:
85
+ raise HttpError("invalid ids format", status=400, code="bad_request", hint="ids 应为逗号分隔的数字")
86
+ try:
87
+ result = await music_service.get_song_details(id_list)
88
+ return JSONResponse(result)
89
+ except HttpError:
90
+ raise
91
+ except Exception as e:
92
+ logger.exception("[music/song/detail] unexpected error")
93
+ raise HttpError(
94
+ f"get song detail failed: {e}", status=500, code="internal_error",
95
+ )
app/main.py CHANGED
@@ -246,6 +246,7 @@ def create_app() -> FastAPI:
246
  health,
247
  image_fix,
248
  logs,
 
249
  openai_compat,
250
  pseudo_stream,
251
  request_logs,
@@ -276,6 +277,7 @@ def create_app() -> FastAPI:
276
  app.include_router(user_html_router)
277
  app.include_router(user_files.router)
278
  app.include_router(user_backups.router)
 
279
  app.include_router(logs.router)
280
 
281
  @app.options("/{path:path}")
 
246
  health,
247
  image_fix,
248
  logs,
249
+ music,
250
  openai_compat,
251
  pseudo_stream,
252
  request_logs,
 
277
  app.include_router(user_html_router)
278
  app.include_router(user_files.router)
279
  app.include_router(user_backups.router)
280
+ app.include_router(music.router)
281
  app.include_router(logs.router)
282
 
283
  @app.options("/{path:path}")
app/music/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;
app/music/afp.wasm.js ADDED
The diff for this file is too large to render. See raw diff
 
app/music/genfp.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict'
2
+ // Node 包装脚本:从 stdin 读取 base64 编码的 Float32 PCM(8000Hz 单声道),
3
+ // 调用 afp.js 的 GenerateFP 生成音频指纹,输出 base64 指印到 stdout。
4
+ //
5
+ // 用法: echo <base64-pcm> | node genfp.js
6
+ // 输出: <base64-fingerprint>(成功)或以非 0 退出码失败(stderr 打印错误)
7
+ //
8
+ // 注意:afp.js 内部每次 GenerateFP 都会重新实例化 wasm(PythonMonkey 的坑),
9
+ // Node 下也有这个机制,因此每次调用都是独立的,无状态,可安全并发调用。
10
+
11
+ const path = require('path')
12
+ const afp = require(path.join(__dirname, 'afp.js'))
13
+
14
+ let chunks = []
15
+ process.stdin.on('data', (c) => chunks.push(c))
16
+ process.stdin.on('end', () => {
17
+ const b64 = Buffer.concat(chunks).toString('utf8').trim()
18
+ if (!b64) {
19
+ console.error('[genfp] empty stdin')
20
+ process.exit(2)
21
+ }
22
+ let bytes
23
+ try {
24
+ bytes = Buffer.from(b64, 'base64')
25
+ } catch (e) {
26
+ console.error('[genfp] base64 decode failed: ' + e.message)
27
+ process.exit(2)
28
+ }
29
+ // bytes 是 little-endian float32 原始字节,直接共享 buffer 构造 Float32Array
30
+ const floatArr = new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4)
31
+ afp.GenerateFP(floatArr).then((fp) => {
32
+ process.stdout.write(fp)
33
+ process.exit(0)
34
+ }).catch((e) => {
35
+ console.error('[genfp] GenerateFP failed: ' + (e && e.message ? e.message : String(e)))
36
+ process.exit(1)
37
+ })
38
+ })
39
+ process.stdin.on('error', (e) => {
40
+ console.error('[genfp] stdin error: ' + e.message)
41
+ process.exit(1)
42
+ })
app/services/music_service.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """听歌识曲业务服务。
2
+
3
+ 链路:手表上传 AMR 录音 → ffmpeg 转 PCM(8000Hz mono f32le) → node genfp.js 生成指纹
4
+ → 调网易云 audio_match 识别 → 返回结果列表。
5
+
6
+ 歌曲 URL 获取走网易云 weapi 加密接口;unblock=true 时若网易云无 URL 则尝试酷我换源。
7
+
8
+ 安全:本模块不接触任何用户凭证,所有网易云调用均为匿名(无 cookie)。
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import base64
14
+ import binascii
15
+ import json
16
+ import logging
17
+ import os
18
+ import random
19
+ import shutil
20
+ import struct
21
+ import subprocess
22
+ import tempfile
23
+ import time
24
+ from typing import Any, Optional
25
+
26
+ import httpx
27
+ from Crypto.Cipher import AES
28
+
29
+ from ..errors import HttpError
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ # 网易云 weapi 加密常量(公开固定值,非密钥)
34
+ _WEAPI_AES_KEY = b"0CoJUm6Qyw8W8jud"
35
+ _WEAPI_AES_IV = b"0102030405060708"
36
+ _WEAPI_RSA_PUBKEY = "010001"
37
+ _WEAPI_RSA_MODULUS = (
38
+ "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725"
39
+ "152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312"
40
+ "ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424"
41
+ "d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
42
+ )
43
+
44
+ # 指纹生成脚本与 Node 可执行路径(启动时探测)
45
+ _GENFP_JS = os.path.join(os.path.dirname(os.path.dirname(__file__)), "music", "genfp.js")
46
+ _NODE_BIN = os.environ.get("NODE_BIN", "node")
47
+ _FFMPEG_BIN = os.environ.get("FFMPEG_BIN", "ffmpeg")
48
+
49
+ # 识曲接口(无需登录/加密,直接 GET)
50
+ _AUDIO_MATCH_URL = (
51
+ "https://interface.music.163.com/api/music/audio/match"
52
+ "?sessionId=0123456789abcdef&algorithmCode=shazam_v2"
53
+ "&duration={duration}&rawdata={rawdata}&times=1&decrypt=1"
54
+ )
55
+
56
+
57
+ # ===== weapi 加密 =====
58
+
59
+ def _aes_cbc_encrypt(text: str, key: bytes) -> str:
60
+ pad = 16 - len(text.encode("utf-8")) % 16
61
+ text = text + chr(pad) * pad
62
+ cipher = AES.new(key, AES.MODE_CBC, _WEAPI_AES_IV)
63
+ return base64.b64encode(cipher.encrypt(text.encode("utf-8"))).decode()
64
+
65
+
66
+ def _rsa_encrypt(sec_key: str) -> str:
67
+ text = sec_key[::-1].encode("utf-8")
68
+ rs = pow(
69
+ int(binascii.hexlify(text), 16),
70
+ int(_WEAPI_RSA_PUBKEY, 16),
71
+ int(_WEAPI_RSA_MODULUS, 16),
72
+ )
73
+ return format(rs, "0>256x")
74
+
75
+
76
+ def _weapi(data: dict) -> dict:
77
+ sec_key = "".join(chr(random.randint(33, 126)) for _ in range(16))
78
+ enc_text = _aes_cbc_encrypt(_aes_cbc_encrypt(json.dumps(data), _WEAPI_AES_KEY), sec_key.encode("utf-8"))
79
+ enc_sec_key = _rsa_encrypt(sec_key)
80
+ return {"params": enc_text, "encSecKey": enc_sec_key}
81
+
82
+
83
+ # ===== ffmpeg + node 指纹生成 =====
84
+
85
+ async def _amr_to_float32_pcm(amr_bytes: bytes) -> bytes:
86
+ """用 ffmpeg 把 AMR 字节流转成 8000Hz 单声道 f32le 原始 PCM 字节。"""
87
+ if not shutil.which(_FFMPEG_BIN) and not os.path.exists(_FFMPEG_BIN):
88
+ raise HttpError(
89
+ "ffmpeg not installed on server", status=500, code="server_misconfigured",
90
+ hint="管理员需在 Dockerfile 安装 ffmpeg",
91
+ )
92
+ with tempfile.NamedTemporaryFile(suffix=".amr", delete=False) as fin:
93
+ fin.write(amr_bytes)
94
+ fin_path = fin.name
95
+ out_path = fin_path + ".f32le"
96
+ try:
97
+ proc = await asyncio.create_subprocess_exec(
98
+ _FFMPEG_BIN, "-y", "-i", fin_path,
99
+ "-f", "f32le", "-acodec", "pcm_f32le",
100
+ "-ar", "8000", "-ac", "1", out_path,
101
+ stdout=subprocess.DEVNULL, stderr=subprocess.PIPE,
102
+ )
103
+ _, stderr = await proc.communicate()
104
+ if proc.returncode != 0:
105
+ err = stderr.decode("utf-8", "replace")[-500:]
106
+ raise HttpError(
107
+ f"ffmpeg decode failed: {err}", status=400, code="bad_request",
108
+ hint="录音文件可能损坏或非 AMR 格式",
109
+ )
110
+ with open(out_path, "rb") as f:
111
+ return f.read()
112
+ finally:
113
+ for p in (fin_path, out_path):
114
+ try:
115
+ os.unlink(p)
116
+ except OSError:
117
+ pass
118
+
119
+
120
+ async def _generate_fingerprint(pcm_bytes: bytes, duration: int) -> str:
121
+ """调 node genfp.js 生成指纹。返回 base64 指纹字符串。"""
122
+ if not os.path.exists(_GENFP_JS):
123
+ raise HttpError(
124
+ "genfp.js not found on server", status=500, code="server_misconfigured",
125
+ )
126
+ pcm_b64 = base64.b64encode(pcm_bytes).decode("ascii")
127
+ try:
128
+ proc = await asyncio.create_subprocess_exec(
129
+ _NODE_BIN, _GENFP_JS,
130
+ stdin=asyncio.subprocess.PIPE,
131
+ stdout=asyncio.subprocess.PIPE,
132
+ stderr=asyncio.subprocess.PIPE,
133
+ )
134
+ except FileNotFoundError:
135
+ raise HttpError(
136
+ "node not installed on server", status=500, code="server_misconfigured",
137
+ hint="管理员需在 Dockerfile 安装 nodejs",
138
+ )
139
+ stdout, stderr = await proc.communicate(pcm_b64.encode("ascii"))
140
+ if proc.returncode != 0:
141
+ err = stderr.decode("utf-8", "replace")[-500:]
142
+ raise HttpError(
143
+ f"fingerprint generation failed: {err}", status=500, code="internal_error",
144
+ )
145
+ fp = stdout.decode("ascii").strip()
146
+ if not fp:
147
+ raise HttpError("empty fingerprint", status=500, code="internal_error")
148
+ return fp
149
+
150
+
151
+ # ===== 网易云 API 调用 =====
152
+
153
+ async def _call_audio_match(fp: str, duration: int, client: httpx.AsyncClient) -> dict:
154
+ """调网易云识曲接口(无需登录)。返回原始 data 字段。"""
155
+ import urllib.parse
156
+ url = _AUDIO_MATCH_URL.format(duration=duration, rawdata=urllib.parse.quote(fp, safe=""))
157
+ resp = await client.get(url, timeout=15.0)
158
+ resp.raise_for_status()
159
+ data = resp.json()
160
+ return data.get("data", {})
161
+
162
+
163
+ async def _call_song_url(song_id: int, level: str, client: httpx.AsyncClient) -> dict:
164
+ """调网易云 weapi /song/enhance/player/url/v1 获取播放 URL(匿名)。"""
165
+ payload = _weapi({"ids": [song_id], "level": level, "encodeType": "flac"})
166
+ resp = await client.post(
167
+ "https://music.163.com/weapi/song/enhance/player/url/v1",
168
+ data=payload,
169
+ headers={
170
+ "Content-Type": "application/x-www-form-urlencoded",
171
+ "User-Agent": "Mozilla/5.0 (Watch; Linux) AppleWebKit/537.36",
172
+ "Referer": "https://music.163.com",
173
+ },
174
+ timeout=10.0,
175
+ )
176
+ resp.raise_for_status()
177
+ body = resp.json()
178
+ if body.get("code") != 200:
179
+ return {}
180
+ data_arr = body.get("data") or []
181
+ return data_arr[0] if data_arr else {}
182
+
183
+
184
+ async def _call_song_detail(song_ids: list[int], client: httpx.AsyncClient) -> list[dict]:
185
+ """调网易云 weapi /v3/song/detail 获取歌曲详情(歌名/歌手/专辑/封面)。"""
186
+ c = [{"id": sid, "v": 0} for sid in song_ids]
187
+ payload = _weapi({"c": json.dumps(c, separators=(",", ":")), "n": 1000})
188
+ resp = await client.post(
189
+ "https://music.163.com/weapi/v3/song/detail",
190
+ data=payload,
191
+ headers={
192
+ "Content-Type": "application/x-www-form-urlencoded",
193
+ "User-Agent": "Mozilla/5.0 (Watch; Linux) AppleWebKit/537.36",
194
+ "Referer": "https://music.163.com",
195
+ },
196
+ timeout=10.0,
197
+ )
198
+ resp.raise_for_status()
199
+ body = resp.json()
200
+ return body.get("songs", []) if body.get("code") == 200 else []
201
+
202
+
203
+ # ===== 酷我换源(unblock 备选) =====
204
+
205
+ async def _kuwo_search_and_url(song_name: str, artist: str, client: httpx.AsyncClient) -> Optional[dict]:
206
+ """从酷我音乐搜索并获取播放 URL。失败返回 None,不抛异常(仅做备选)。"""
207
+ query = f"{song_name} {artist}".strip()
208
+ if not query:
209
+ return None
210
+ try:
211
+ # 搜索
212
+ search_url = "http://search.kuwo.cn/r.s"
213
+ resp = await client.get(
214
+ search_url,
215
+ params={"all": query, "ft": "music", "itemset": "ctrl", "rformat": "json", "encoding": "utf8", "pn": 0, "rn": 1},
216
+ headers={"User-Agent": "Mozilla/5.0", "Referer": "http://www.kuwo.cn/"},
217
+ timeout=8.0,
218
+ )
219
+ resp.raise_for_status()
220
+ # 酷我搜索返回可能是 json 或 weird 格式,try parse
221
+ text = resp.text.strip()
222
+ if text.startswith("{") and text.endswith("}"):
223
+ data = json.loads(text)
224
+ else:
225
+ return None
226
+ abslist = data.get("abslist") or []
227
+ if not abslist:
228
+ return None
229
+ rid = abslist[0].get("MUSICRID", "").replace("MUSIC_", "")
230
+ if not rid:
231
+ return None
232
+ # 获取播放 URL
233
+ play_url = f"http://antiserver.kuwo.cn/anti.s?type=convert_url3&rid={rid}&format=mp3"
234
+ resp2 = await client.get(play_url, headers={"User-Agent": "Mozilla/5.0", "Referer": "http://www.kuwo.cn/"}, timeout=8.0)
235
+ resp2.raise_for_status()
236
+ data2 = resp2.json()
237
+ url = data2.get("data", {}).get("url")
238
+ if url:
239
+ return {"url": url, "type": "mp3", "source": "kuwo", "freeTrialInfo": None, "fee": 0}
240
+ except Exception as e:
241
+ logger.info("[kuwo] fallback failed for %s: %s", query, e)
242
+ return None
243
+
244
+
245
+ # ===== 对外接口 =====
246
+
247
+ async def recognize(amr_bytes: bytes, duration: int = 8) -> dict:
248
+ """完整识曲流程:AMR → PCM → 指纹 → 网易云识别。返回格式化结果列表。"""
249
+ t0 = time.time()
250
+ pcm_bytes = await _amr_to_float32_pcm(amr_bytes)
251
+ logger.info("[recognize] ffmpeg decode done, pcm bytes=%d, elapsed=%dms",
252
+ len(pcm_bytes), int((time.time() - t0) * 1000))
253
+
254
+ fp = await _generate_fingerprint(pcm_bytes, duration)
255
+ logger.info("[recognize] fingerprint generated, fp_len=%d, elapsed=%dms",
256
+ len(fp), int((time.time() - t0) * 1000))
257
+
258
+ async with httpx.AsyncClient() as client:
259
+ raw = await _call_audio_match(fp, duration, client)
260
+
261
+ result = raw.get("result")
262
+ if not result:
263
+ return {"ok": True, "result": [], "message": "未识别到��曲,可重试", "elapsed_ms": int((time.time() - t0) * 1000)}
264
+
265
+ # 格式化:每首歌提取 id/name/artists/album/startTime
266
+ songs = []
267
+ for item in result:
268
+ song = item.get("song") or {}
269
+ if not song.get("id"):
270
+ continue
271
+ songs.append({
272
+ "id": song.get("id"),
273
+ "name": song.get("name", ""),
274
+ "artists": "/".join(a.get("name", "") for a in (song.get("artists") or [])),
275
+ "album": (song.get("album") or {}).get("name", ""),
276
+ "startTime": item.get("startTime", 0),
277
+ })
278
+ return {
279
+ "ok": True,
280
+ "result": songs,
281
+ "count": len(songs),
282
+ "elapsed_ms": int((time.time() - t0) * 1000),
283
+ }
284
+
285
+
286
+ async def get_song_url(song_id: int, level: str = "standard", unblock: bool = False,
287
+ song_name: str = "", artist: str = "") -> dict:
288
+ """获取歌曲播放 URL。unblock=true 时若网易云无 URL 则尝试酷我换源。"""
289
+ async with httpx.AsyncClient() as client:
290
+ ncm_data = await _call_song_url(song_id, level, client)
291
+
292
+ url = ncm_data.get("url")
293
+ if url:
294
+ return {
295
+ "ok": True,
296
+ "url": url,
297
+ "type": ncm_data.get("type", "mp3"),
298
+ "level": ncm_data.get("level", level),
299
+ "freeTrialInfo": ncm_data.get("freeTrialInfo"),
300
+ "fee": ncm_data.get("fee", 0),
301
+ "source": "netease",
302
+ }
303
+
304
+ # 网易云无 URL(通常是 VIP/版权曲),尝试换源
305
+ if unblock:
306
+ kw = await _kuwo_search_and_url(song_name, artist, client)
307
+ if kw:
308
+ return {"ok": True, **kw}
309
+
310
+ return {
311
+ "ok": False,
312
+ "url": None,
313
+ "message": "无可用播放源" + ("(已尝试换源)" if unblock else "(可在设置开启换源尝试)"),
314
+ "source": "none",
315
+ }
316
+
317
+
318
+ async def get_song_details(song_ids: list[int]) -> dict:
319
+ """批量获取歌曲详情。"""
320
+ if not song_ids:
321
+ return {"ok": True, "songs": []}
322
+ async with httpx.AsyncClient() as client:
323
+ songs = await _call_song_detail(song_ids, client)
324
+ out = []
325
+ for s in songs:
326
+ out.append({
327
+ "id": s.get("id"),
328
+ "name": s.get("name", ""),
329
+ "artists": "/".join(a.get("name", "") for a in (s.get("ar") or [])),
330
+ "album": (s.get("al") or {}).get("name", ""),
331
+ "picUrl": (s.get("al") or {}).get("picUrl", ""),
332
+ })
333
+ return {"ok": True, "songs": out}
requirements.txt CHANGED
@@ -13,5 +13,7 @@ huggingface-hub==0.27.0
13
  cachetools==5.5.0
14
  python-dotenv==1.0.1
15
  edge-tts==7.2.8
 
 
16
  # 可选:启用 HTTP/2(默认关闭,HTTP/1.1 已足够)
17
  # h2==4.1.0
 
13
  cachetools==5.5.0
14
  python-dotenv==1.0.1
15
  edge-tts==7.2.8
16
+ # 听歌识曲:weapi 加密调用网易云接口
17
+ pycryptodome==3.21.0
18
  # 可选:启用 HTTP/2(默认关闭,HTTP/1.1 已足够)
19
  # h2==4.1.0