();
for (const segment of segments) {
const id = (segment.index || "").trim();
const match = id.match(/^Paragraph\s+(\d+)-/);
const paraNum = match?.[1] ?? "0";
if (!paragraphGroups.has(paraNum)) paragraphGroups.set(paraNum, []);
const group = paragraphGroups.get(paraNum);
if (group) group.push(segment);
}
const paragraphs = Array.from(paragraphGroups.entries()).map(
([_, groupSegments]) => {
const fullText = groupSegments.map((s) => s.text || "").join(" ");
const sentences = fullText.split(/(?<=[.!?])\s+/g).filter(Boolean);
const wordCounts = sentences.map(
(s) => s.split(/\s+/).filter(Boolean).length,
);
const totalWords = Math.max(
1,
wordCounts.reduce((a, b) => a + b, 0),
);
const paraStart = Math.min(...groupSegments.map((s) => s.start ?? 0));
const paraEnd = Math.max(
...groupSegments.map((s) => s.end ?? paraStart),
);
let acc = 0;
const paraDuration = paraEnd - paraStart;
return html`${sentences.map((sent, si) => {
const wordCount = wordCounts[si];
if (wordCount === undefined) return "";
const startOffset = (acc / totalWords) * paraDuration;
acc += wordCount;
const sentenceDuration = (wordCount / totalWords) * paraDuration;
const endOffset =
si < sentences.length - 1
? startOffset + sentenceDuration - 0.001
: paraEnd - paraStart;
const spanStart = paraStart + startOffset;
const spanEnd = paraStart + endOffset;
return html`${sent}${si < sentences.length - 1 ? " " : ""}`;
})}
`;
},
);
return html`${paragraphs}`;
}
private extractPlainText(): string {
if (!this.vttContent) return "";
const segments = parseVTT(this.vttContent);
// Group into paragraphs by index as in renderFromVTT
const paragraphGroups = new Map();
for (const s of segments) {
const id = (s.index || "").trim();
const match = id.match(/^Paragraph\s+(\d+)-/);
const paraNum = match?.[1] ?? "0";
if (!paragraphGroups.has(paraNum)) paragraphGroups.set(paraNum, []);
const group = paragraphGroups.get(paraNum);
if (group) group.push(s.text || "");
}
const paragraphs = Array.from(paragraphGroups.values()).map((group) =>
group.join(" ").replace(/\s+/g, " ").trim(),
);
return paragraphs.join("\n\n").trim();
}
private async copyTranscript(e?: Event) {
e?.stopPropagation();
const text = this.extractPlainText();
if (!text) return;
try {
if (navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
} else {
// Fallback
const ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
}
const btn = this.shadowRoot?.querySelector(
".copy-btn",
) as HTMLButtonElement | null;
if (btn) {
const orig = btn.innerText;
btn.innerText = "Copied!";
setTimeout(() => {
btn.innerText = orig;
}, 1500);
}
} catch {
// ignore
}
}
override render() {
return html`${this.renderFromVTT()}
`;
}
}