Allow txt export with timestampts

This commit is contained in:
Lucas Winther
2026-07-17 14:14:43 +02:00
parent a5cfd219f6
commit 770cfc9ff7
3 changed files with 121 additions and 2 deletions
+22 -1
View File
@@ -1,5 +1,10 @@
import { useEffect, useState } from "react";
import { exportFilename, transcriptToSrt, transcriptToTxt } from "../lib/export";
import {
exportFilename,
transcriptToSrt,
transcriptToTimestampedTxt,
transcriptToTxt,
} from "../lib/export";
import type { TranscriptChunk } from "../worker/protocol";
interface TranscriptViewProps {
@@ -121,6 +126,22 @@ export function TranscriptView({
.srt
</button>
)}
{result && result.chunks.length > 0 && (
<button
type="button"
className="copy-button"
title="Text with timestamps"
onClick={() =>
download(
transcriptToTimestampedTxt(result.chunks),
exportFilename(exportName, "timestamps.txt"),
"text/plain",
)
}
>
.txt+ts
</button>
)}
<button
type="button"
className="copy-button"
+30 -1
View File
@@ -59,10 +59,39 @@ export function transcriptToSrt(chunks: TranscriptChunk[]): string {
return cues.length === 0 ? "" : `${cues.join("\n\n")}\n`;
}
/** Seconds → compact clock timestamp, e.g. 3.21 → "00:03.2" (mm:ss.s). */
export function formatClockTimestamp(seconds: number): string {
const totalTenths = Math.max(0, Math.round(seconds * 10));
const tenths = totalTenths % 600;
const m = (totalTenths - tenths) / 600;
return `${String(m).padStart(2, "0")}:${(tenths / 10).toFixed(1).padStart(4, "0")}`;
}
/**
* Timestamped chunks → plain text, one line per chunk:
* "[00:03.2 – 00:07.9] text". A null end time — Whisper sometimes never
* closes the last segment — omits the range end: "[00:03.2] text".
* Empty-text chunks are skipped, mirroring transcriptToSrt.
*/
export function transcriptToTimestampedTxt(chunks: TranscriptChunk[]): string {
const lines = chunks
.filter((chunk) => chunk.text.trim() !== "")
.map((chunk) => {
const [start, end] = chunk.timestamp;
const range =
end === null
? formatClockTimestamp(start)
: `${formatClockTimestamp(start)} – ${formatClockTimestamp(end)}`;
return `[${range}] ${chunk.text.trim()}`;
});
return lines.length === 0 ? "" : `${lines.join("\n")}\n`;
}
/**
* Download filename for an export: the source file's base name with the
* extension swapped ("talk.m4a" → "talk.srt"), or "transcript.<ext>" when no
* usable source name is known.
* usable source name is known. `ext` may be compound ("timestamps.txt") so
* sibling exports of the same format get distinct names.
*/
export function exportFilename(sourceName: string | null | undefined, ext: string): string {
const trimmed = sourceName?.trim() ?? "";