Spaces:
Sleeping
Sleeping
Commit
·
217d1ce
1
Parent(s):
f077067
Add hhit color
Browse files
server-plugins/generate-captions.js
CHANGED
|
@@ -63,9 +63,16 @@ export class CaptionPlugin extends Plugin {
|
|
| 63 |
fontSize = 72,
|
| 64 |
wordsPerGroup = 4,
|
| 65 |
videoWidth = 1920,
|
| 66 |
-
videoHeight = 1080
|
|
|
|
|
|
|
| 67 |
} = options;
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
// Read and parse JSON file
|
| 70 |
const jsonData = JSON.parse(readFileSync(captionFilePath, 'utf-8'));
|
| 71 |
const transcript = jsonData.transcript || '';
|
|
@@ -86,7 +93,19 @@ export class CaptionPlugin extends Plugin {
|
|
| 86 |
const output = createWriteStream(outputFilePath);
|
| 87 |
|
| 88 |
// Write header with calculated margins
|
| 89 |
-
output.write(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
// Process words in groups respecting sentence boundaries
|
| 92 |
let i = 0;
|
|
@@ -135,7 +154,7 @@ export class CaptionPlugin extends Plugin {
|
|
| 135 |
const captionParts = wordGroup.map((w, idx) => {
|
| 136 |
if (idx === wordIdx) {
|
| 137 |
// Current word - highlighted in green
|
| 138 |
-
return `{\\c
|
| 139 |
} else {
|
| 140 |
// Other words - white
|
| 141 |
return w.word;
|
|
@@ -234,7 +253,17 @@ function assignSentenceToWords(words, transcript) {
|
|
| 234 |
* @param {number} marginV
|
| 235 |
* @returns {string}
|
| 236 |
*/
|
| 237 |
-
function createASSHeader(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
return `[Script Info]
|
| 239 |
Title: Word-by-Word Captions
|
| 240 |
ScriptType: v4.00+
|
|
@@ -245,11 +274,27 @@ ScaledBorderAndShadow: yes
|
|
| 245 |
|
| 246 |
[V4+ Styles]
|
| 247 |
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
| 248 |
-
Style: Default,${fontName},${fontSize},&
|
| 249 |
-
Style: Highlight,${fontName},${fontSize},&
|
| 250 |
|
| 251 |
[Events]
|
| 252 |
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
| 253 |
`;
|
| 254 |
}
|
| 255 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
fontSize = 72,
|
| 64 |
wordsPerGroup = 4,
|
| 65 |
videoWidth = 1920,
|
| 66 |
+
videoHeight = 1080,
|
| 67 |
+
fontColor = '#FFFFFF',
|
| 68 |
+
fontHighlightColor = '#00FF00'
|
| 69 |
} = options;
|
| 70 |
|
| 71 |
+
const assFontColor = hexToASSColor(fontColor);
|
| 72 |
+
const assHighlightColor = hexToASSColor(fontHighlightColor);
|
| 73 |
+
const assHighlightColorInline = `${assHighlightColor}&`;
|
| 74 |
+
const assFontColorInline = `${assFontColor}&`;
|
| 75 |
+
|
| 76 |
// Read and parse JSON file
|
| 77 |
const jsonData = JSON.parse(readFileSync(captionFilePath, 'utf-8'));
|
| 78 |
const transcript = jsonData.transcript || '';
|
|
|
|
| 93 |
const output = createWriteStream(outputFilePath);
|
| 94 |
|
| 95 |
// Write header with calculated margins
|
| 96 |
+
output.write(
|
| 97 |
+
createASSHeader(
|
| 98 |
+
videoWidth,
|
| 99 |
+
videoHeight,
|
| 100 |
+
fontName,
|
| 101 |
+
fontSize,
|
| 102 |
+
translateY,
|
| 103 |
+
sideMargin,
|
| 104 |
+
sideMargin,
|
| 105 |
+
assFontColor,
|
| 106 |
+
assHighlightColor
|
| 107 |
+
)
|
| 108 |
+
);
|
| 109 |
|
| 110 |
// Process words in groups respecting sentence boundaries
|
| 111 |
let i = 0;
|
|
|
|
| 154 |
const captionParts = wordGroup.map((w, idx) => {
|
| 155 |
if (idx === wordIdx) {
|
| 156 |
// Current word - highlighted in green
|
| 157 |
+
return `{\\c${assHighlightColorInline}}${w.word}{\\c${assFontColorInline}}`;
|
| 158 |
} else {
|
| 159 |
// Other words - white
|
| 160 |
return w.word;
|
|
|
|
| 253 |
* @param {number} marginV
|
| 254 |
* @returns {string}
|
| 255 |
*/
|
| 256 |
+
function createASSHeader(
|
| 257 |
+
videoWidth = 1920,
|
| 258 |
+
videoHeight = 1080,
|
| 259 |
+
fontName = 'Impact',
|
| 260 |
+
fontSize = 72,
|
| 261 |
+
marginV = 200,
|
| 262 |
+
marginL = 10,
|
| 263 |
+
marginR = 10,
|
| 264 |
+
primaryColor = '&H00FFFFFF',
|
| 265 |
+
highlightColor = '&H0000FF00'
|
| 266 |
+
) {
|
| 267 |
return `[Script Info]
|
| 268 |
Title: Word-by-Word Captions
|
| 269 |
ScriptType: v4.00+
|
|
|
|
| 274 |
|
| 275 |
[V4+ Styles]
|
| 276 |
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
| 277 |
+
Style: Default,${fontName},${fontSize},${primaryColor},&H000000FF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,3,2,2,${marginL},${marginR},${marginV},1
|
| 278 |
+
Style: Highlight,${fontName},${fontSize},${highlightColor},&H000000FF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,3,2,2,${marginL},${marginR},${marginV},1
|
| 279 |
|
| 280 |
[Events]
|
| 281 |
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
| 282 |
`;
|
| 283 |
}
|
| 284 |
|
| 285 |
+
function hexToASSColor(hexValue) {
|
| 286 |
+
if (typeof hexValue !== 'string') {
|
| 287 |
+
throw new Error('fontColor values must be hex strings like #RRGGBB');
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
const normalized = hexValue.trim().replace('#', '');
|
| 291 |
+
if (!/^[0-9a-fA-F]{6}$/.test(normalized)) {
|
| 292 |
+
throw new Error(`Invalid hex color provided: ${hexValue}`);
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
const r = normalized.slice(0, 2);
|
| 296 |
+
const g = normalized.slice(2, 4);
|
| 297 |
+
const b = normalized.slice(4, 6);
|
| 298 |
+
return `&H00${b}${g}${r}`.toUpperCase();
|
| 299 |
+
}
|
| 300 |
+
|