-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathformat.js
More file actions
157 lines (140 loc) · 4.61 KB
/
Copy pathformat.js
File metadata and controls
157 lines (140 loc) · 4.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"use strict";
export const TIMESTAMP_MODES = ["off", "s10", "s30", "s60", "all"];
export const TIMESTAMP_INTERVALS_MS = {
s10: 10 * 1000,
s30: 30 * 1000,
s60: 60 * 1000
};
export const DEFAULT_TIMESTAMP_MODE = "off";
// Fallback interval for explicit "copy with timestamps" requests when the
// global setting is "off".
export const FALLBACK_TIMESTAMP_MODE = "s30";
export function sanitizeTimestampMode(value) {
const text = String(value || "").trim();
return TIMESTAMP_MODES.includes(text) ? text : DEFAULT_TIMESTAMP_MODE;
}
export function sanitizeSentences(raw, maxSentences = 8000) {
if (!Array.isArray(raw)) {
return [];
}
const result = [];
for (const item of raw) {
const text = String(item?.text || "").trim();
const startMs = Number(item?.startMs);
const endMs = Number(item?.endMs);
if (!text || !Number.isFinite(startMs) || startMs < 0) {
continue;
}
result.push({
text,
startMs: Math.floor(startMs),
endMs: Number.isFinite(endMs) && endMs >= startMs ? Math.floor(endMs) : Math.floor(startMs)
});
if (result.length >= maxSentences) {
break;
}
}
return result;
}
export function formatTimestamp(ms) {
const totalSec = Math.max(0, Math.floor(Number(ms || 0) / 1000));
const sec = totalSec % 60;
const min = Math.floor(totalSec / 60) % 60;
const hours = Math.floor(totalSec / 3600);
const mm = String(min).padStart(2, "0");
const ss = String(sec).padStart(2, "0");
return hours > 0 ? `${hours}:${mm}:${ss}` : `${min}:${ss}`;
}
export function buildTimestampedText(sentences, mode) {
const list = sanitizeSentences(sentences);
if (!list.length) {
return "";
}
if (mode === "all") {
return list.map((s) => `[${formatTimestamp(s.startMs)}] ${s.text}`).join("\n");
}
const intervalMs = TIMESTAMP_INTERVALS_MS[mode] || TIMESTAMP_INTERVALS_MS[FALLBACK_TIMESTAMP_MODE];
const blocks = [];
let current = null;
for (const sentence of list) {
if (!current || sentence.startMs >= current.startMs + intervalMs) {
current = { startMs: sentence.startMs, parts: [] };
blocks.push(current);
}
current.parts.push(sentence.text);
}
return blocks
.map((block) => `[${formatTimestamp(block.startMs)}] ${block.parts.join(" ")}`)
.join("\n");
}
// format: "default" (respect settings), "clean" (plain text), "timestamps"
// (force timecodes even when the global setting is off).
export function buildTranscriptOutput(entry, settings, format = "default") {
const title = String(entry?.title || "").trim();
const tweetText = String(entry?.tweetText || "").trim();
const plain = String(entry?.text || "");
const sentences = sanitizeSentences(entry?.sentences);
const settingsMode = sanitizeTimestampMode(settings?.timestampMode);
let mode = "off";
if (format === "timestamps") {
mode = settingsMode === "off" ? FALLBACK_TIMESTAMP_MODE : settingsMode;
} else if (format !== "clean") {
mode = settingsMode;
}
let body = plain;
if (mode !== "off" && sentences.length) {
body = buildTimestampedText(sentences, mode) || plain;
}
const includeTitle = settings?.includeTitle !== false;
if (includeTitle && tweetText) {
const author = formatAuthorLine(entry);
const header = [
author,
`Tweet text:\n${tweetText}`,
`Video transcript:\n${body}`
].filter(Boolean);
return header.join("\n\n");
}
if (includeTitle && title) {
return `${title}\n\n${body}`;
}
return body;
}
export function formatAuthorLine(entry) {
const name = String(entry?.authorName || "").trim();
const handle = String(entry?.authorHandle || "").trim();
if (name && handle) {
return `Tweet author: ${name} (${handle})`;
}
if (name) {
return `Tweet author: ${name}`;
}
if (handle) {
return `Tweet author: ${handle}`;
}
return "";
}
export function buildSrt(sentences) {
const list = sanitizeSentences(sentences);
return list
.map((sentence, index) => {
const endMs = sentence.endMs > sentence.startMs ? sentence.endMs : sentence.startMs + 2000;
return [
String(index + 1),
`${formatSrtTime(sentence.startMs)} --> ${formatSrtTime(endMs)}`,
sentence.text,
""
].join("\n");
})
.join("\n");
}
export function formatSrtTime(ms) {
const total = Math.max(0, Math.floor(Number(ms || 0)));
const msPart = total % 1000;
const totalSec = Math.floor(total / 1000);
const sec = totalSec % 60;
const min = Math.floor(totalSec / 60) % 60;
const hours = Math.floor(totalSec / 3600);
const pad = (n, width = 2) => String(n).padStart(width, "0");
return `${pad(hours)}:${pad(min)}:${pad(sec)},${pad(msPart, 3)}`;
}