first commit
This commit is contained in:
51
src/WhisperMedia.js
Normal file
51
src/WhisperMedia.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { html, css, LitElement } from 'lit';
|
||||
|
||||
export class WhisperMedia extends LitElement {
|
||||
static styles = css`
|
||||
audio {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
video {
|
||||
max-height: 200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
static properties = {
|
||||
audio: {type: String},
|
||||
video: {type: String}
|
||||
};
|
||||
|
||||
updateTime(time) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("update-time", {
|
||||
detail: {
|
||||
time
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let media = null;
|
||||
if (this.audio) {
|
||||
media = document.createElement('audio', this.audio);
|
||||
media.src = this.audio;
|
||||
} else {
|
||||
media = document.createElement('video', this.video);
|
||||
media.src = this.video;
|
||||
}
|
||||
|
||||
if (media) {
|
||||
media.controls = true;
|
||||
media.preload = "auto";
|
||||
media.ontimeupdate = (_) => this.updateTime(media.currentTime);
|
||||
window.addEventListener('update-player-time', e => media.currentTime = e.detail.time);
|
||||
|
||||
return html`${media}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
82
src/WhisperSegment.js
Normal file
82
src/WhisperSegment.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { html, css, LitElement } from 'lit';
|
||||
|
||||
export class WhisperSegment extends LitElement {
|
||||
static properties = {
|
||||
text: { type: String },
|
||||
start: { type: Number },
|
||||
end: { type: Number },
|
||||
words: { type: Array },
|
||||
selected: { type: Boolean }
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.segment {
|
||||
border: 2px solid #333;
|
||||
padding: 5px;
|
||||
margin: 2px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: #555;
|
||||
border-color: black;
|
||||
}
|
||||
|
||||
.times {
|
||||
width: 325px;
|
||||
float: left;
|
||||
color: lightgray;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.words {
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.selected = false;
|
||||
|
||||
const that = this;
|
||||
window.addEventListener('update-time', e => that.updateTime(e.detail.time));
|
||||
}
|
||||
|
||||
updateTime(time) {
|
||||
if ((time >= this.start) && (time <= this.end)) {
|
||||
this.selected = true;
|
||||
} else {
|
||||
this.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.words) {
|
||||
return html`
|
||||
<li class="${this.selected ? 'selected' : ''} segment">
|
||||
<div class="times">${hms(this.start)} - ${hms(this.end)}</div>
|
||||
<div class="words">
|
||||
${this.words.map(w =>
|
||||
html`<whisper-word title="${w.confidence || w.probability}" word="${w.text || w.word}" start="${w.start}" end="${w.end}" probability="${w.confidence}" />`
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hms(secs) {
|
||||
const h = Math.trunc(secs / 60 / 60);
|
||||
const m = Math.trunc((secs - (h * 60)) / 60);
|
||||
const s = Math.trunc(secs) - (h * 60 + m * 60);
|
||||
|
||||
return `${pad(h)}:${pad(m)}:${pad(s)}`;
|
||||
}
|
||||
|
||||
function pad(i) {
|
||||
return String(i).padStart(2, '0');
|
||||
}
|
||||
79
src/WhisperTranscript.js
Normal file
79
src/WhisperTranscript.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { html, css, LitElement } from 'lit';
|
||||
|
||||
export class WhisperTranscript extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: 25px;
|
||||
color: var(--whisper-transcript-text-color, #000);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.media {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.whisper-transcript {
|
||||
background: black;
|
||||
color: white;
|
||||
}
|
||||
`;
|
||||
|
||||
static properties = {
|
||||
url: {type: String},
|
||||
audio: {type: String},
|
||||
video: {type: String},
|
||||
transcript: {type: Object, attribute: false},
|
||||
time: {type: Number}
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.time = 0;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.getTranscript();
|
||||
|
||||
const that = this;
|
||||
window.addEventListener('update-time', e => that.time = e.detail.time);
|
||||
}
|
||||
|
||||
async getTranscript() {
|
||||
const resp = await fetch(this.url);
|
||||
this.transcript = await resp.json();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (! this.transcript) {
|
||||
return html`Loading...`;
|
||||
}
|
||||
|
||||
let media = null;
|
||||
|
||||
|
||||
if (this.audio) {
|
||||
media = html`<whisper-media audio="${this.audio}"></whisper-media>`;
|
||||
} else {
|
||||
media = html`<whisper-media video="${this.video}"></whisper-media>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="whisper-transcript">
|
||||
<div class="media">
|
||||
${media}
|
||||
</div>
|
||||
<ul>
|
||||
${this.transcript.segments.map(s =>
|
||||
html`<whisper-segment .words="${s.words}" start="${s.start}" end="${s.end}" text="${s.text}" />`
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
91
src/WhisperWord.js
Normal file
91
src/WhisperWord.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import { html, css, LitElement } from 'lit';
|
||||
|
||||
export class WhisperWord extends LitElement {
|
||||
|
||||
static properties = {
|
||||
word: {type: String},
|
||||
probability: {type: Number},
|
||||
start: {type: Number},
|
||||
end: {type: Number},
|
||||
selected: {type: Boolean}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
span.word {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span.selected {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
span.mediocre {
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
span.poor {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
span.terrible {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.addEventListener('click', _ => this.updatePlayerTime());
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
const that = this;
|
||||
window.addEventListener('update-time', e => that.updateTime(e.detail.time));
|
||||
}
|
||||
|
||||
updatePlayerTime() {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("update-player-time", {
|
||||
detail: {
|
||||
time: this.start
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
updateTime(time) {
|
||||
if ((time >= this.start) && (time <= this.end)) {
|
||||
this.selected = true;
|
||||
} else {
|
||||
this.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
getCssClass() {
|
||||
let style = this.selected ? 'selected ' : '';
|
||||
if (this.probability > .9) {
|
||||
style += 'good';
|
||||
} else if (this.probability > .7) {
|
||||
style += 'mediocre';
|
||||
} else if (this.probability > .5) {
|
||||
style += 'poor';
|
||||
} else {
|
||||
style += 'terrible';
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<span
|
||||
data-start="${this.start}"
|
||||
data-end="${this.end}"
|
||||
class="word ${this.getCssClass()}">
|
||||
${this.word}
|
||||
</span>
|
||||
`
|
||||
}
|
||||
}
|
||||
56
src/tooltip.js
Normal file
56
src/tooltip.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { html, css, LitElement } from 'lit';
|
||||
|
||||
export class TooltipColorGuide extends LitElement {
|
||||
static styles = css`
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tooltip-text {
|
||||
visibility: hidden;
|
||||
width: 200px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
text-align: left;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 125%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.tooltip span {
|
||||
color : white;
|
||||
}
|
||||
.tooltip:hover .tooltip-text {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.color-box {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
`;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="tooltip">
|
||||
<span>Color Guide</span>
|
||||
<div class="tooltip-text">
|
||||
<div><span class="color-box" style="background-color: white;"></span>this.probability > 0.9: (White)</div>
|
||||
<div><span class="color-box" style="background-color: yellow;"></span>this.probability > 0.7: (Yellow)</div>
|
||||
<div><span class="color-box" style="background-color: orange;"></span>this.probability > 0.5: (Orange)</div>
|
||||
<div><span class="color-box" style="background-color: red;"></span>this.probability ≤ 0.5: (Red)</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user