You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

630 lines
20 KiB
HTML

5 months ago
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title data-cn="语音识别转文字" data-en="Speech Recognition"></title>
<meta name="renderer" content="webkit" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/static/layui/css/layui.css" rel="stylesheet" />
<style>
.preview-scroll {
max-height: 450px;
overflow: auto;
}
.flex {
display: flex;
justify-content: center;
align-items: center;
margin-top: 10px;
margin-left: 200px;
}
.flex-left {
display: flex;
align-items: center;
}
.my-1 {
margin-top: 10px;
margin-bottom: 10px;
}
.p-2 {
padding: 15px;
}
.text-center {
text-align: center;
}
.name {
margin-right: 8px;
width: 200px;
}
#upload {
display: block;
margin-bottom: 10px;
border-style: solid;
padding: 50px 30px;
}
.layui-form {
margin: 15px auto;
}
.result-list {
margin-top: 8px;
margin-bottom: 8px;
padding: 5px;
border-bottom: 1px solid #f1f1f1;
}
.result-list .name {
width: 150px;
white-space: nowrap;
text-overflow: ellipsis;
text-align: left;
}
#content {
width: 80%;
min-width: 800px;
max-width: 1400px;
margin: 75px auto 50px;
}
.worktype {
color: #999;
font-size: 12px;
}
.text-res {
margin-bottom: 15px;
}
.text-res-file {
margin-bottom: 6px;
}
.status {
margin-left: 20px;
width: 200px;
white-space: break-spaces;
}
</style>
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header" style="background: #16b777">
<div
class="layui-logo layui-hide-xs"
style="color: #fff"
data-en="Speech Recognition {{ version }}"
data-cn="语音识别 {{ version }}"
></div>
<!-- 头部区域可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-right">
<!-- 移动端显示 -->
<li id="checkupdate" class="layui-nav-item layui-hide">
<a
href="https://github.com/jianchang512/sts/releases"
class="layui-font-red"
target="_blank"
></a>
</li>
<li class="layui-nav-item layui-hide-xs">
<a href="https://github.com/jianchang512/sts" target="_blank"
>Github</a
>
</li>
<li class="layui-nav-item layui-hide-xs">
<a
href="https://github.com/jianchang512/sts/issue"
target="_blank"
data-en="Post Isue"
data-cn="遇到问题?"
></a>
</li>
<li class="layui-nav-item layui-hide-xs">
<a href="https://discord.gg/TMCM2PfHzQ" target="_blank">Discord</a>
</li>
<li class="layui-nav-item layui-hide-xs">
<a
href="https://github.com/jianchang512/sts/releases/tag/0.0"
target="_blank"
data-cn="下载模型"
data-en="Download models"
></a>
</li>
<li class="layui-nav-item layui-hide-xs">
<a
href="javascript:;"
onclick="showjz(this)"
data-cn="捐助本项目"
data-en="Donate the project models"
></a>
</li>
</ul>
</div>
<div id="content">
<!-- 内容主体区域 -->
<div class="layui-upload-drag layui-border-green" id="upload">
<i class="layui-icon layui-icon-upload layui-font-green"></i>
<div
data-cn="点击上传,或将音频视频文件拖拽到此处(wav,mp3,flac,mp4,mov,mkv,avi,mpeg)"
data-en="click to upload or drag video or audio to here(wav,mp3,flac,mp4,mov,mkv,avi,mpeg)"
></div>
<div class="layui-hide my-1" id="preview">
<div class="preview-scroll"></div>
</div>
</div>
<form class="layui-form text-center">
<div class="layui-form-item layui-inline">
<label
class="layui-form-label"
style="width: auto"
data-cn="选择发音语言"
data-en="Choose Pronunciation Language"
></label>
<div class="layui-input-inline">
<select name="language">
{% for name,val in lang_code.items() %}
<option value="{{ val[0] }}">{{ name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="layui-form-item layui-inline">
<label
class="layui-form-label"
style="width: auto"
data-cn="返回文字格式"
data-en="Return to text format"
></label>
<div class="layui-input-inline">
<select id="data_type" name="data_type">
<option
value="srt"
data-cn="srt字幕格式"
data-en="Subtitles srt files"
></option>
<option value="json">json</option>
<option value="text" data-cn="纯文字" data-en="text"></option>
</select>
</div>
</div>
<!-- 自动导出开关 -->
<div class="layui-form-item layui-inline">
<label
class="layui-form-label"
style="width: auto"
data-cn="自动导出开关"
data-en="Return to text format"
></label>
<div class="layui-input-inline">
<select id="switch" name="switch">
<option value="off" data-cn="关闭" data-en="OFF"></option>
<option value="on" data-cn="开启" data-en="ON"></option>
</select>
</div>
</div>
<!-- 是否独立导出 -->
<div class="layui-form-item layui-inline">
<label
class="layui-form-label"
style="width: auto"
data-cn="是否独立导出"
data-en="Return to text format"
></label>
<div class="layui-input-inline">
<select id="isIndependent" name="isIndependent">
<option value="off" data-cn="否" data-en="OFF"></option>
<option value="on" data-cn="是" data-en="ON"></option>
</select>
</div>
</div>
<div
class="layui-label-text layui-font-12 my-1 layui-font-gray"
data-en="The recognition of base to large-v3 models is becoming increasingly accurate, but it also consumes more resources. If you do not have the CUDA acceleration environment, do not choose large series models"
data-cn="base到large-v3模型识别越来越精确但也更消耗资源如果不具备CUDA加速环境请勿选用large系模型"
></div>
<div class="layui-form-item layui-inline">
<label
class="layui-form-label"
style="width: auto"
data-cn="选择要使用的模型"
data-en="Select model"
></label>
<div class="layui-input-inline">
<select name="model">
<option value="base">base</option>
<option value="small">small</option>
<option value="medium">medium</option>
<option value="large-v3">large-v3</option>
</select>
</div>
</div>
<div class="layui-form-item layui-form-block">
<input type="hidden" id="wav_name" name="wav_name" />
<button
type="submit"
class="layui-btn layui-btn-danger"
lay-submit
lay-filter="submit"
data-cn="立即识别({{ devtype.upper()}}"
data-en="Start Separate ({{ devtype.upper()}})"
id="submit-btn"
>
<i
style="display: none"
class="layui-icon-loading layui-icon layui-anim layui-anim-rotate layui-anim-loop"
></i>
</button>
<div
class="layui-btn layui-btn-disabled"
data-cn="导出文本"
data-en="Export Text"
id="export-btn"
></div>
</div>
</form>
<!-- <div
id="progressbar"
class="layui-progress layui-hide"
lay-filter="progressbar"
>
<div class="layui-progress-bar" lay-showpercent></div>
</div> -->
<div class="layui-card">
<div class="layui-card-body text-contain" style="padding: 10px 0">
<textarea
placeholder-cn="识别结果在此显示"
placeholder-en="Result list in here"
class="layui-textarea"
id="result"
readonly
cols="30"
rows="10"
></textarea>
</div>
</div>
</div>
</div>
<script src="/static/layui/layui.js"></script>
<script>
let language = "{{ language }}";
window.$ = layui.$;
let intervalId = null;
if (language === "zh") {
$("[data-cn]").each(function () {
$(this).html($(this).html() + $(this).attr("data-cn"));
});
$("[placeholder-cn]").each(function () {
$(this).attr("placeholder", $(this).attr("placeholder-cn"));
});
} else {
$("[data-en]").each(function () {
$(this).html($(this).html() + $(this).attr("data-en"));
});
$("[placeholder-en]").each(function () {
$(this).attr("placeholder", $(this).attr("placeholder-en"));
});
}
/** 即将要处理的文件信息 */
var pending_files = [];
var processing = false;
function get_file_el(file_name) {
return $(`.flex[name="${file_name}"]`);
}
function set_status(file_name, status, color) {
var file_element = get_file_el(file_name);
file_element.find(".status").html(status).show().css("color", color);
}
var getprogress = function (file_name, field, star_time) {
var file_element = get_file_el(file_name);
var done = null;
var handler = function () {
$.post("/progressbar", field, function (res) {
if (res.code !== 0) {
done({
error: res.msg,
file_name,
});
set_status(file_name, res.msg, "#ff5722");
}
const percentage = `${(res["data"] * 100).toFixed(2)}%`;
set_status(file_name, percentage, "#16b777");
if (res["data"] >= 1) {
var sec = +new Date() - star_time;
set_status(
file_name,
`100% ${(sec / 1000).toFixed(2)} sec`,
"#16b777"
);
if (done) {
done({
res,
file_name,
});
}
return;
}
setTimeout(() => {
handler();
}, 500);
}).fail(function () {
setTimeout(() => {
handler();
}, 500);
});
};
handler();
return {
done: function (cb) {
done = cb;
},
};
};
function process(file, field) {
return new Promise(function (resolve) {
set_status(file.data, "0%", "#16b777");
$.post("/process", field, function (res) {
if (res.code !== 0) {
resolve({
error: res.msg,
file_name: file.data,
});
set_status(file.data, res.msg, "#ff5722");
return;
}
getprogress(file.data, field, +new Date()).done(function (res) {
resolve(res);
});
}).fail(function (msg) {
set_status(file.data, msg.statusText, "#ff5722");
resolve({
error: msg.statusText,
file_name: file.data,
});
});
});
}
function process_loading() {
return {
start: function () {
processing = true;
$("#submit-btn").addClass("layui-btn-disabled").find("i").show();
},
end: function () {
processing = false;
$("#submit-btn").removeClass("layui-btn-disabled").find("i").hide();
},
};
}
//JS
layui.use(function () {
var element = layui.element;
var layer = layui.layer;
var upload = layui.upload;
let form = layui.form;
// 渲染
let layindex1 = null;
upload.render({
elem: "#upload",
field: "audio",
accept: "file",
exts: "mp4|mp3|flac|wav|avi|mkv|mpeg|mov",
multiple: true,
url: "/upload", // 实际使用时改成您自己的上传接口即可。
choose: function () {
pending_files = [];
},
before: function () {
if (processing) {
return false;
}
},
done: function (res) {
pending_files.push(res);
/* $('#wav_name').val(res.data); */
console.log(res);
},
allDone: function () {
const file_element = pending_files.reduce((prev, cur) => {
return `${prev}
<div class="flex" name="${cur.data}">
<span class="name">${cur.msg} ${cur.data || ""}</span>
<audio src="/static/tmp/${cur.data}" controls></audio>
<div class="status"></div>
</div>
`;
}, "");
$("#preview").removeClass("layui-hide").html(`
<hr>
<div class="preview-scroll">${file_element}</div>
`);
},
});
// 导出
function exportText(title, text) {
var file_type = $("#data_type").val();
var file_name = title.replace('wav', file_type)
var blob = new Blob([text], {
type: "text/plain",
});
var url = URL.createObjectURL(blob);
var aEl = document.createElement("a");
aEl.href = url;
aEl.target = "_blank";
aEl.download = file_name;
aEl.click();
aEl.remove();
URL.revokeObjectURL(url);
}
// 手动导出
$("#export-btn").click(function () {
if ($(this).hasClass("layui-btn-disabled")) {
return;
}
var textElList = $(".text-res");
if (!textElList.length) {
layer.alert(
language === "zh"
? "请先识别后再进行导出!"
: "Please process first before exporting!",
{ title: false }
);
return;
}
// 是否独立导出
if($("#isIndependent").val() === 'on') {
var file_type = $("#data_type").val();
[...textElList].forEach((el) => {
var title = $(el).find(".text-res-file").text();
var text = $(el).find("textarea").val();
exportText(title, text)
});
}else {
var exportInfos = [...textElList]
.map(function (el) {
var info = {
title: $(el).find(".text-res-file").text(),
text: $(el).find("textarea").val(),
};
return `${info.text}`;
})
.join("\n\n\n");
var blob = new Blob([exportInfos], {
type: 'text/plain'
})
var url = URL.createObjectURL(blob)
var aEl = document.createElement('a')
aEl.href = url
aEl.target = '_blank'
aEl.download = '音频文本.txt'
aEl.click()
aEl.remove()
URL.revokeObjectURL(url)
}
});
// 提交事件
form.on("submit(submit)", function (data) {
console.log('submit', data)
if (processing) {
return;
}
var field = data.field; // 获取表单全部字段值
if (!pending_files.length) {
layer.alert(
language === "zh"
? "必须先上传要识别的音频或视频文件!"
: "The file to be recognition must be uploaded first!",
{ title: false }
);
return false;
}
// 开始识别
process_loading().start();
// 按钮禁用
$("#export-btn").addClass("layui-btn-disabled");
$(".text-contain").html(`
<textarea
placeholder="${
language === "zh" ? "识别结果在此显示" : "Result list in here"
}"
class="layui-textarea res-placeholder"
id="result"
readonly
cols="30"
rows="10"
></textarea>
`);
function getText(res) {
try {
if (typeof res === "string") {
return res;
}
return JSON.stringify(res);
} catch (e) {
return res;
}
}
// 批量处理文件
Promise.allSettled(
pending_files.map(function (file) {
return process(file, {
...field,
wav_name: file.data,
}).then(function (res) {
// 识别结果文本区域以及导出避免误操作
$(".res-placeholder").hide();
$("#export-btn").removeClass("layui-btn-disabled");
// 文件名显示
var texts_el = document.createElement("div");
texts_el.className = "text-res";
$(texts_el).html(
`<div class="text-res-file">${res.file_name}</div>
<textarea
name="${res.file_name}"
class="layui-textarea"
cols="30"
rows="10"
></textarea>`
);
$(".text-contain").append(texts_el);
$(texts_el).find("textarea").val(getText(res.res.result));
// 自动导出
if (field.switch === "on") {
exportText(res.file_name, getText(res.res.result))
}
});
})
).finally(function () {
// 识别按钮解禁
process_loading().end();
});
return false; // 阻止默认 form 跳转
});
setTimeout(() => {
$.get("/checkupdate", function (res) {
if (res.code === 0 && res.msg) {
$("#checkupdate").removeClass("layui-hide");
$("#checkupdate a").text(res.msg);
}
});
}, 60000);
});
function showjz(el) {
let lag =
language === "zh"
? "如果项目对你有帮助,节省了时间和金钱,请考虑小额资助开发者,帮助项目能够长期保持更新和维护。"
: "If the project is helpful to you, saving time and money, please consider providing small funding to developers help the project can be updated and maintained for a long time";
layui.layer.open({
type: 1, // page 层类型
area: ["300px", "450px"],
title: false,
shade: 0.6, // 遮罩透明度
shadeClose: true, // 点击遮罩区域,关闭弹层
maxmin: true, // 允许全屏最小化
anim: 0, // 0-6 的动画形式,-1 不开启
content: `<div style="padding: 10px; "><div class="fs-6 text-black-500">${lag}</div><img src="/static/images/wx.png" width="200px" style="display:block;margin:10px auto;"><img src="/static/images/alipay.png" width="200px" style="display:block;margin:10px auto"></div>`,
});
}
</script>
</body>
</html>