plume-asr/plume/utils/gentle_preview/index.html

409 lines
10 KiB
HTML

<html>
<head>
<meta charset="utf-8" />
<style>
html, body {
margin: 0;
padding: 0;
}
#header {
position: fixed;
top: 0;
left: 0;
height: 50px;
line-height: 50px;
width: 100%;
background-color: #999;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.5);
font-family: Helvetica, sans-serif;
}
#header, #header a {
color: white;
}
#downloads {
float: right;
background: #999;
}
.download {
float: right;
background: #999;
padding: 0 5px;
}
.home {
margin: 0;
font-size: 125%;
font-weight: lighter;
text-transform: lowercase;
}
.home a {
margin: 0;
background: #666;
padding-left: 25px;
padding-right: 30px;
margin-right: 20px;
float: left;
text-decoration: none;
}
.home:hover a {
background: #555;
}
#audio {
margin-top: 9px;
width: 50%;
display: inline-block;
}
#transcript {
margin: 0 15px;
margin-top: 70px;
margin-bottom: 5em;
white-space: pre-wrap;
line-height: 2em;
max-width: 600px;
color: #999;
}
#transcript.status {
background-color: #333;
color: #fff;
font-family: Courier, mono;
line-height: 1em;
font-size: 10pt;
max-width: 100%;
}
#transcript.status h2 {
padding: 10px;
}
#transcript.status .entry {
margin-bottom: 10px;
padding: 10px;
}
#transcript.status progress {
width: 100%;
height: 30px;
margin-bottom: 20px;
}
.success {
color: black;
}
.success:hover {
text-decoration: underline;
}
.active {
color: magenta;
}
#preloader {
visibility: hidden;
}
.phactive {
text-decoration: underline;
}
.phones {
position: absolute;
color: #333;
}
.phones .phone {
margin-right: 5px;
font-family: Helvetica, sans-serif;
text-transform: uppercase;
font-size: 50%;
}
.phones .phone:last-child {
margin-right: 0;
}
#footer {
margin-top: 100px;
border-top: 1px dotted black;
font-size: 8pt;
font-style: italic;
font-family: Helvetica, sans-serif;
padding: 10px;
}
</style>
</head>
<body>
<div id="header">
<!-- <h1 class="home"><a href="/">Gentle</a></h1> -->
<audio id="audio" src="a.wav" controls="true" preload="auto"></audio>
<img src="/preloader.gif" id="preloader" alt="loading...">
<span id="downloads"> </div>
</div>
<div id="transcript"></div>
<!-- <div id="footer">
<a href="https://lowerquality.com/gentle">Gentle</a> is free software released under the <a href="https://opensource.org/licenses/MIT">MIT license</a>. <a href="https://lowerquality.com/gentle">Homepage</a> | <a href="https://github.com/lowerquality/gentle">Source code</a>.
</div> -->
<script>
function get(url, cb) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function() {
cb(this.responseText);
}
xhr.send();
}
function get_json(url, cb) {
get(url, function(x) {
cb(JSON.parse(x));
});
}
var $a = document.getElementById("audio");
window.onkeydown = function(ev) {
if(ev.keyCode == 32) {
ev.preventDefault();
$a.pause();
}
}
var $trans = document.getElementById("transcript");
var $preloader = document.getElementById('preloader');
var wds = [];
var cur_wd;
var $phones = document.createElement("div");
$phones.className = "phones";
document.body.appendChild($phones);
var cur_phones$ = []; // List of phoneme $divs
var $active_phone;
function render_phones(wd) {
cur_phones$ = [];
$phones.innerHTML = "";
$active_phone = null;
$phones.style.top = wd.$div.offsetTop + 18;
$phones.style.left = wd.$div.offsetLeft;
var dur = wd.end - wd.start;
var start_x = wd.$div.offsetLeft;
wd.phones
.forEach(function(ph){
var $p = document.createElement("span");
$p.className = "phone";
$p.textContent = ph.phone.split("_")[0];
$phones.appendChild($p);
cur_phones$.push($p);
});
var offsetToCenter = (wd.$div.offsetWidth - $phones.offsetWidth) / 2;
$phones.style.left = wd.$div.offsetLeft + offsetToCenter;
}
function highlight_phone(t) {
if(!cur_wd) {
$phones.innerHTML = "";
return;
}
var hit;
var cur_t = cur_wd.start;
cur_wd.phones.forEach(function(ph, idx) {
if(cur_t <= t && cur_t + ph.duration >= t) {
hit = idx;
}
cur_t += ph.duration;
});
if(hit) {
var $ph = cur_phones$[hit];
if($ph != $active_phone) {
if($active_phone) {
$active_phone.classList.remove("phactive");
}
if($ph) {
$ph.classList.add("phactive");
}
}
$active_phone = $ph;
}
}
function highlight_word() {
var t = $a.currentTime;
// XXX: O(N); use binary search
var hits = wds.filter(function(x) {
return (t - x.start) > 0.01 && (x.end - t) > 0.01;
}, wds);
var next_wd = hits[hits.length - 1];
if(cur_wd != next_wd) {
var active = document.querySelectorAll('.active');
for(var i = 0; i < active.length; i++) {
active[i].classList.remove('active');
}
if(next_wd && next_wd.$div) {
next_wd.$div.classList.add('active');
render_phones(next_wd);
}
}
cur_wd = next_wd;
highlight_phone(t);
window.requestAnimationFrame(highlight_word);
}
window.requestAnimationFrame(highlight_word);
$trans.innerHTML = "Loading...";
function render(ret) {
wds = ret['words'] || [];
transcript = ret['transcript'];
$trans.innerHTML = '';
var currentOffset = 0;
wds.forEach(function(wd) {
if(wd.case == 'not-found-in-transcript') {
// TODO: show phonemes somewhere
var txt = ' ' + wd.word;
var $plaintext = document.createTextNode(txt);
$trans.appendChild($plaintext);
return;
}
// Add non-linked text
if(wd.startOffset > currentOffset) {
var txt = transcript.slice(currentOffset, wd.startOffset);
var $plaintext = document.createTextNode(txt);
$trans.appendChild($plaintext);
currentOffset = wd.startOffset;
}
var $wd = document.createElement('span');
var txt = transcript.slice(wd.startOffset, wd.endOffset);
var $wdText = document.createTextNode(txt);
$wd.appendChild($wdText);
wd.$div = $wd;
if(wd.start !== undefined) {
$wd.className = 'success';
}
$wd.onclick = function() {
if(wd.start !== undefined) {
console.log(wd.start);
$a.currentTime = wd.start;
$a.play();
}
};
$trans.appendChild($wd);
currentOffset = wd.endOffset;
});
var txt = transcript.slice(currentOffset, transcript.length);
var $plaintext = document.createTextNode(txt);
$trans.appendChild($plaintext);
currentOffset = transcript.length;
}
function show_downloads() {
var $d = document.getElementById("downloads");
$d.textContent = "Download as: ";
var uid = window.location.pathname.split("/")[2];
// Name, path, title, inhibit-on-file:///
[["CSV", "align.csv", "Word alignment CSV"],
["JSON", "align.json", "JSON word/phoneme alignment data"],
["Zip", "/zip/" + uid + ".zip", "Standalone zipfile", true]]
.forEach(function(x) {
var $a = document.createElement("a");
$a.className = "download";
$a.textContent = x[0];
$a.href = x[1];
$a.title = x[2];
if(!x[3] || window.location.protocol != "file:") {
$d.appendChild($a);
}
});
}
var status_init = false;
var status_log = []; // [ status ]
var $status_pro;
function render_status(ret) {
if(!status_init) {
// Clobber the $trans div and use it for status updates
$trans.innerHTML = "<h2>transcription in progress</h2>";
$trans.className = "status";
$status_pro = document.createElement("progress");
$status_pro.setAttribute("min", "0");
$status_pro.setAttribute("max", "100");
$status_pro.value = 0;
$trans.appendChild($status_pro);
status_init = true;
}
if(ret.status !== "TRANSCRIBING") {
if(ret.percent) {
$status_pro.value = (100*ret.percent);
}
}
else if(ret.percent && (status_log.length == 0 || status_log[status_log.length-1].percent+0.0001 < ret.percent)) {
// New entry
var $entry = document.createElement("div");
$entry.className = "entry";
$entry.textContent = ret.message;
ret.$div = $entry;
if(ret.percent) {
$status_pro.value = (100*ret.percent);
}
if(status_log.length > 0) {
$trans.insertBefore($entry, status_log[status_log.length-1].$div);
}
else {
$trans.appendChild($entry);
}
status_log.push(ret);
}
}
function update() {
if(INLINE_JSON) {
// We want this to work from file:/// domains, so we provide a
// mechanism for inlining the alignment data.
render(INLINE_JSON);
// show_downloads();
}
else {
// Show the status
get_json('status.json', function(ret) {
$a.style.visibility = 'hidden';
if (ret.status == 'ERROR') {
$preloader.style.visibility = 'hidden';
$trans.innerHTML = '<b>' + ret.status + ': ' + ret.error + '</b>';
} else if (ret.status == 'TRANSCRIBING' || ret.status == 'ALIGNING') {
$preloader.style.visibility = 'visible';
render_status(ret);
setTimeout(update, 2000);
} else if (ret.status == 'OK') {
// show_downloads();
$preloader.style.visibility = 'hidden';
// XXX: should we fetch the align.json?
// window.location.reload();
$a.style.visibility = 'visible';
render(ret);
} else if (ret.status == 'ENCODING' || ret.status == 'STARTED') {
$preloader.style.visibility = 'visible';
$trans.innerHTML = 'Encoding, please wait...';
setTimeout(update, 2000);
} else {
console.log("unknown status", ret);
$preloader.style.visibility = 'hidden';
$trans.innerHTML = ret.status + '...';
setTimeout(update, 5000);
}
});
}
}
var INLINE_JSON;
update();
</script></body></html>