// ==UserScript==
// @name SF转写识别插件使用
// @namespace Violentmonkey Scripts
// @match http://159.75.194.76/index.php
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @grant GM_info
// @grant GM_getValue
// @grant GM_setValue
// @version 1.1
// @author -
// @description 11/30/2023, 10:02:12 AM
// ==/UserScript==
(function () {
'use strict';
const VERSION = GM_info.script.version; // 脚本@version
const ADDRESS = 'http://ai.speechocean.com'; // 预识别服务器地址
const WAVEITEMS = new Array(); // 音频信息集合
const QUERYTIMESPAN = 1000; // 长音频轮询间隔
var APIKEY = ""; // 预识别apikey
/**
* 添加界面APIKEY输入
*/
(function () {
const style = ".__j-container { top: 15%; font-size: 12px; right: 0px; background:violet; padding:5px; position: fixed; z-index: 100000; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none}.__j-input { width: 350px; display:none; }.__j-container:hover .__j-input { display:inline; }";
var html = '<div class="__j-container">\n' +
' <label class="__j-label">KEY</label>\n' +
' <input class="__j-input" type="text" name="APIKEY"></input>\n' +
'</div>' +
'';
var stylenode = document.createElement('style');
stylenode.setAttribute("type", "text/css");
if (stylenode.styleSheet) {// IE
stylenode.styleSheet.cssText = style;
} else {// w3c
var cssText = document.createTextNode(style);
stylenode.appendChild(cssText);
}
var node = document.createElement('div');
node.innerHTML = html;
document.head.appendChild(stylenode);
document.body.appendChild(node);
APIKEY = GM_getValue(GM_info.script.name + "-APIKEY", "");
node.getElementsByClassName("__j-input")[0].value = APIKEY;
node.getElementsByClassName("__j-input")[0].onchange = (e) => {
APIKEY = e.srcElement.value;
GM_setValue(GM_info.script.name + "-APIKEY", APIKEY);
};
const work_type_node = document.getElementById('worktype');
if(!work_type_node || typeof(work_type_node) == 'undefined') {
return;
}
const work_type = parseInt(work_type_node.value);
if(work_type % 2 == 1) {
// let select_node = document.createElement('select');
// select_node.setAttribute('id','audio-channel');
// const channles =new Array("左声道","右声道");
// for(let i = 0; i < channles.length; i++) {
// let option_node = document.createElement('option');
// option_node.setAttribute('value',i);
// option_node.appendChild(document.createTextNode(channles[i]));
// select_node.appendChild(option_node);
// }
let btn_node = document.createElement('input');
btn_node.setAttribute('type','button');
btn_node.setAttribute('class','btn btn-gray btn-pre-identify');
btn_node.setAttribute('value','预识别');
btn_node.onclick = (e) => {
check();
};
let workspace = document.body.getElementsByClassName('workspace-left')[0];
let btn_wrap = workspace.children[3];
//btn_wrap.appendChild(select_node)
btn_wrap.appendChild(btn_node)
}
})();
function addLabel(text) {
let label = document.getElementById("label-txt");
if (label.value != text)
label.value = text;
if (document.getElementById("AutoFill") == null) {
let btn = document.getElementById("UpperToLow");
var nbtn = btn.cloneNode(true);
nbtn.id = "AutoFill";
nbtn.value = "自动填充"
nbtn.onclick = (e) => {
document.getElementById("trans-text").contentDocument.getElementsByTagName("body")[0].innerText = label.value;
};
btn.insertAdjacentElement("afterend", nbtn);
}
}
function audioBufferToWav(buffer, opt) {
opt = opt || {};
const numChannels = buffer.numberOfChannels;
const sampleRate = opt.sampleRate || buffer.sampleRate;
const format = opt.float32 ? 3 : 1;
const bitDepth = format === 3 ? 32 : 16;
let result;
if (numChannels === 2) {
result = interleave(buffer.getChannelData(0), buffer.getChannelData(1));
} else {
result = buffer.getChannelData(0);
}
return encodeWAV(result, format, sampleRate, numChannels, bitDepth);
}
function encodeWAV(samples, format, sampleRate, numChannels, bitDepth) {
const bytesPerSample = bitDepth / 8;
const blockAlign = numChannels * bytesPerSample;
let buffer = new ArrayBuffer(44 + samples.length * bytesPerSample);
let view = new DataView(buffer);
writeString(view, 0, "RIFF");
view.setUint32(4, 36 + samples.length * bytesPerSample, true);
writeString(view, 8, "WAVE");
writeString(view, 12, "fmt ");
view.setUint32(16, 16, true);
view.setUint16(20, format, true);
view.setUint16(22, numChannels, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * blockAlign, true);
view.setUint16(32, blockAlign, true);
view.setUint16(34, bitDepth, true);
writeString(view, 36, "data");
view.setUint32(40, samples.length * bytesPerSample, true);
if (format === 1) {
floatTo16BitPCM(view, 44, samples);
} else {
writeFloat32(view, 44, samples);
}
return buffer;
}
function interleave(inputL, inputR) {
let length = inputL.length + inputR.length;
let result = new Float32Array(length);
let index = 0;
let inputIndex = 0;
while (index < length) {
result[index++] = inputL[inputIndex];
result[index++] = inputR[inputIndex];
inputIndex++;
}
return result;
}
function writeFloat32(output, offset, input) {
for (let i = 0; i < input.length; i++, offset += 4) {
output.setFloat32(offset, input[i], true);
}
}
function floatTo16BitPCM(output, offset, input) {
for (let i = 0; i < input.length; i++, offset += 2) {
let s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
}
}
function writeString(view, offset, string) {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
function resetSeletorAudio() {
const annotations = document.getElementById("annotations");
const selectorArea = annotations.getElementsByClassName("success")[0];
if(typeof(selectorArea) == 'undefined') {
const layerCtx = window.unsafeWindow.layer;
layerCtx.msg("请选择已切分音频段^_^",{time:2000});
return;
}
const selectorComments = selectorArea.getElementsByClassName("wavesurfer-tier-Comments")[0];
const channelOption = selectorComments.getElementsByTagName('span');
const channel = channelOption && channelOption.length > 6 ? parseInt(channelOption[6].innerText) - 1 : 0;
const selectorTimeText = selectorArea.getElementsByClassName("wavesurfer-time")[0].innerText;
const times = selectorTimeText.split(/–/g);
const startTime = parseFloat(times[0])
const endTime = parseFloat(times[1])
let audioBuffer = window.unsafeWindow.wavesurfer.backend.buffer
const startOffset = parseInt(startTime * audioBuffer.sampleRate);
const endOffset = parseInt(endTime * audioBuffer.sampleRate);
const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); // 创建音频上下文
// const selectorChannel = document.getElementById("audio-channel");
// const channel = selectorChannel ? parseInt(selectorChannel[selectorChannel.selectedIndex].value) : 0;
let newAudioBuffer = audioCtx.createBuffer(1, endOffset - startOffset, audioBuffer.sampleRate);
newAudioBuffer.getChannelData(0).set(audioBuffer.getChannelData(channel).slice(startOffset, endOffset));
return newAudioBuffer;
}
async function check() {
let newAudioBuffer = resetSeletorAudio();
let audio = audioBufferToWav(newAudioBuffer);
const layerCtx = window.unsafeWindow.layer;
layerCtx.msg("努力识别中...,请稍安勿躁^_^",{icon:16,time:-1});
let result = await postWave({
"address": ADDRESS,
"authKey": "Bearer " + APIKEY,
"Authorization": "Bearer "+ APIKEY,
"blob": new Blob([audio], { "type": "audio/wav" }),
"type": 2,
"normal": true,
queryInterval: QUERYTIMESPAN,
version: VERSION,
});
layerCtx.closeAll();
if (result.code != 200) {
alert(result.message);
return
}
const content = result.data.segments.map(_ => _.text).join(" ")
document.getElementById("trans-text").contentDocument.getElementsByTagName("body")[0].innerText = content;
}
//var sid = setInterval(check, 1000); // 页面轮询
function queryTaskId(waveInfo, taskId, reslove) {
let ids = new Array();
ids.push(taskId);
GM_xmlhttpRequest({
url: `${waveInfo.address}/speech/api/v1/asr/task/query`,
method: "POST",
headers: {
"Authorization": waveInfo.authKey,
"Version": waveInfo.version,
"Content-Type": "application/json"
},
data: JSON.stringify({
task_ids: ids
}),
onreadystatechange: function (responseDetails) {
if (responseDetails.readyState === 4) {
let queryResponse = JSON.parse(responseDetails.response);
if (queryResponse["data"][0]["task_status"].toLowerCase() == "succeed") {
queryResponse["data"] = queryResponse["data"][0];
reslove(queryResponse);
}
else {
// 根据指定的时间间隔轮询
setTimeout(queryTaskId, waveInfo.queryInterval, waveInfo, taskId, reslove);
}
}
}
});
}
/**
* 提交音频进行预识别
* @param {{
* address:String;
* authKey:String;
* blob:Blob;
* type:Number;
* normal:Boolean;
* queryInterval:Number;
* version:String;
* }} waveInfo 音频信息
* @returns {Promise} aa
*/
function postWave(waveInfo) {
return new Promise((reslove, reject) => {
let blob = waveInfo.blob;
let headerdata = {
"Authorization": waveInfo.authKey,
"Version": waveInfo.version,
};
if (waveInfo.normal == true)
headerdata["Knative-Serving-Tag"] = "normal";
if (waveInfo.type == 2) { // 短音频
let formdata = new FormData();
formdata.append("domain", "ct_gz");
formdata.append("wav_path", blob, "proxy.wav");
GM_xmlhttpRequest({
url: `${waveInfo.address}/speech/api/v2/asr/recognize`,
method: "POST",
headers: headerdata,
data: formdata,
onreadystatechange: function (responseDetails) {
if (responseDetails.readyState === 4) {
reslove(JSON.parse(responseDetails.response));
}
}
});
}
else if (waveInfo.type == 1) { // 长音频
let formdata = new FormData();
formdata.append("domain", "ct_gz");
formdata.append("file_path", blob, "proxy.wav");
GM_xmlhttpRequest({
url: `${waveInfo.address}/speech/api/v1/asr/task/create`,
method: "POST",
headers: headerdata,
data: formdata,
onreadystatechange: function (responseDetails) {
if (responseDetails.readyState === 4) {
let createResponse = JSON.parse(responseDetails.response);
queryTaskId(waveInfo, createResponse["data"]["task_id"], reslove);
}
}
});
} else {
reject(`audio type error ${waveInfo.type}`);
}
});
}
/**
* 提交数据到后端服务
* @param {{
* address:String;
* authKey:String;
* version:String;
* data:object;
* }} postData
* @param {*} callback
*/
function postResult(postData) {
return new Promise((reslove, reject) => {
try {
GM_xmlhttpRequest({
url: `${postData.address}/speech/api/v1/asr/label_result`,
method: "POST",
headers: {
"Authorization": postData.authKey,
"Version": postData.version,
"Content-Type": "application/json"
},
data: JSON.stringify(postData.data),
onreadystatechange: function (responseDetails) {
if (responseDetails.readyState === 4) {
reslove(JSON.parse(responseDetails.response));
}
}
});
}
catch (e) {
reject(e);
}
})
}
})();