您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
11/30/2023, 10:02:12 AM
// ==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); } }) } })();