first commit

This commit is contained in:
Chan
2025-01-08 14:01:53 +09:00
commit e387382951
21 changed files with 72432 additions and 0 deletions

51
src/WhisperMedia.js Normal file
View 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
View 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
View 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
View 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
View 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>
`;
}
}