Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions extraResources/sherpa/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
封装 JS 复制自 npm `sherpa-onnx-node@1.13.2`(Apache-2.0)。

- `vendor/addon.js` 已替换为自定义加载器:从环境变量 `SHERPA_ONNX_LIB_DIR`
用 `process.dlopen` 加载 `sherpa-onnx.node`(其余 `vendor/*.js` 原样保留,
它们都经 `require('./addon.js')` 取原生模块)。
用 `process.dlopen` 加载 `sherpa-onnx.node`;vendor 文件都经
`require('./addon.js')` 取原生模块。
- `vendor/json-result.js` 由 `non-streaming-asr.js` 使用,处理原生识别结果中
的未转义控制字符;其余 `vendor/*.js` 原样保留。
- `vendor/addon-static-import.js` 在自定义加载器下不再被引用(保留以便升级对照)。

原生库**不在此处**,按需下载到 `userData/sherpa-onnx/current/`
Expand Down
74 changes: 74 additions & 0 deletions extraResources/sherpa/vendor/json-result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

// Native sherpa results are JSON strings. Some Qwen outputs may contain raw
// C0 control characters inside the transcript text, which JSON.parse rejects.
function sanitizeJsonControlCharacters(jsonStr) {
let sanitized = '';
let inString = false;
let escaped = false;

for (const char of String(jsonStr)) {
const code = char.charCodeAt(0);

if (inString) {
if (escaped) {
sanitized += char;
escaped = false;
continue;
}
if (char === '\\') {
sanitized += char;
escaped = true;
continue;
}
if (char === '"') {
sanitized += char;
inString = false;
continue;
}
if (code <= 0x1f) {
sanitized += escapeControlCharacter(char);
continue;
}
sanitized += char;
continue;
}

if (char === '"') {
inString = true;
sanitized += char;
} else if (code > 0x1f || char === '\t' || char === '\n' || char === '\r') {
// Tabs, line feeds, and carriage returns are valid JSON whitespace
// outside strings. Other C0 controls are never valid JSON tokens.
sanitized += char;
}
}

return sanitized;
}

function escapeControlCharacter(char) {
switch (char) {
case '\b':
return '\\b';
case '\f':
return '\\f';
case '\n':
return '\\n';
case '\r':
return '\\r';
case '\t':
return '\\t';
default:
return `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`;
}
}

function parseJsonResult(jsonStr) {
return JSON.parse(sanitizeJsonControlCharacters(jsonStr));
}

module.exports = {
parseJsonResult,
sanitizeJsonControlCharacters,
};
5 changes: 3 additions & 2 deletions extraResources/sherpa/vendor/non-streaming-asr.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

const addon = require('./addon.js');
const { parseJsonResult } = require('./json-result.js');

/**
* Internal symbol to mark async-created recognizers.
Expand Down Expand Up @@ -127,7 +128,7 @@ class OfflineRecognizer {
this.handle,
stream.handle,
);
return JSON.parse(jsonStr);
return parseJsonResult(jsonStr);
}

/**
Expand All @@ -137,7 +138,7 @@ class OfflineRecognizer {
*/
getResult(stream) {
const jsonStr = addon.getOfflineStreamResultAsJson(stream.handle);
return JSON.parse(jsonStr);
return parseJsonResult(jsonStr);
}
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"test:custom-languages": "tsc scripts/test-custom-languages.ts --outDir node_modules/.cache/custom-language-tests --module commonjs --moduleResolution node --target es2019 --esModuleInterop --skipLibCheck && node node_modules/.cache/custom-language-tests/scripts/test-custom-languages.js",
"test:refine": "tsc scripts/test-refine-units.ts --outDir node_modules/.cache/refine-tests --module commonjs --moduleResolution node --target es2019 --esModuleInterop --skipLibCheck --resolveJsonModule && node node_modules/.cache/refine-tests/scripts/test-refine-units.js",
"test:speaker-diarization": "node scripts/test-speaker-diarization-config.cjs && tsc scripts/test-speaker-diarization.ts --outDir node_modules/.cache/speaker-diarization-tests --module commonjs --moduleResolution node --target es2019 --esModuleInterop --skipLibCheck && node node_modules/.cache/speaker-diarization-tests/scripts/test-speaker-diarization.js",
"test:sherpa-json": "node scripts/test-sherpa-json-result.cjs",
"test:proofread-speakers": "tsc scripts/test-proofread-speakers.ts --outDir node_modules/.cache/proofread-speaker-tests --module commonjs --moduleResolution node --target es2019 --esModuleInterop --skipLibCheck && node node_modules/.cache/proofread-speaker-tests/scripts/test-proofread-speakers.js",
"test:manuscript": "tsc scripts/test-manuscript-matching.ts --outDir node_modules/.cache/manuscript-tests --module commonjs --moduleResolution node --target es2022 --esModuleInterop --skipLibCheck --resolveJsonModule && node node_modules/.cache/manuscript-tests/scripts/test-manuscript-matching.js",
"longgap:gen": "tsc scripts/longgap/gen-audio.ts --outDir node_modules/.cache/longgap --module commonjs --moduleResolution node --target es2019 --esModuleInterop --skipLibCheck --resolveJsonModule && node node_modules/.cache/longgap/scripts/longgap/gen-audio.js",
Expand Down
26 changes: 26 additions & 0 deletions scripts/test-sherpa-json-result.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

const assert = require('assert');
const {
parseJsonResult,
sanitizeJsonControlCharacters,
} = require('../extraResources/sherpa/vendor/json-result.js');

const rawResult = '{"text":"first\nsecond\t\u0000end","tokens":["first"]}';
assert.deepStrictEqual(parseJsonResult(rawResult), {
text: 'first\nsecond\t\u0000end',
tokens: ['first'],
});

const escapedResult = String.raw`{"text":"first\nsecond\t"}`;
assert.deepStrictEqual(parseJsonResult(escapedResult), {
text: 'first\nsecond\t',
});

assert.strictEqual(parseJsonResult('{"text":"ok"}\u0000\n').text, 'ok');
assert.strictEqual(
sanitizeJsonControlCharacters('{"text":"a\u000bb"}'),
'{"text":"a\\u000bb"}',
);

console.log('sherpa JSON result tests: 4 passed');
Loading