Skip to content

Commit b039631

Browse files
committed
Customizations!!
1 parent 8731b36 commit b039631

5 files changed

Lines changed: 459 additions & 71 deletions

File tree

inject.js

Lines changed: 176 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,83 @@
2323
maxTextWidthPx: 720,
2424
playerPaddingPx: 32,
2525
fontSizeRatio: 0.0155,
26-
minFontPx: 20,
27-
maxFontPx: 28,
26+
minFontPx: 16,
27+
maxFontPx: 32,
2828
lineHeight: 1.4,
2929
};
3030

31+
const SETTINGS_STORAGE_KEY = 'ketuviaSettings';
32+
const DEFAULT_SETTINGS = {
33+
targetLines: 2,
34+
textSize: 'medium',
35+
background: 'medium',
36+
position: 'center-low',
37+
};
38+
const TEXT_SIZE_SCALE = {
39+
small: 0.9,
40+
medium: 1.3,
41+
large: 1.7,
42+
};
43+
const BACKGROUND_OPACITY = {
44+
light: 0.3,
45+
medium: 0.45,
46+
dark: 0.78,
47+
};
48+
const OVERLAY_POSITIONS = {
49+
'left-top': { x: 'left', y: '8%' },
50+
'center-top': { x: 'center', y: '8%' },
51+
'right-top': { x: 'right', y: '8%' },
52+
'left-high': { x: 'left', y: '20%' },
53+
'center-high': { x: 'center', y: '20%' },
54+
'right-high': { x: 'right', y: '20%' },
55+
'left-highish': { x: 'left', y: '35%' },
56+
'center-highish': { x: 'center', y: '35%' },
57+
'right-highish': { x: 'right', y: '35%' },
58+
'left-middle': { x: 'left', y: '50%' },
59+
'center-middle': { x: 'center', y: '50%' },
60+
'right-middle': { x: 'right', y: '50%' },
61+
'left-lowish': { x: 'left', y: '68%' },
62+
'center-lowish': { x: 'center', y: '68%' },
63+
'right-lowish': { x: 'right', y: '68%' },
64+
'left-low': { x: 'left', y: '84%' },
65+
'center-low': { x: 'center', y: '84%' },
66+
'right-low': { x: 'right', y: '84%' },
67+
'left-bottom': { x: 'left', y: '94%' },
68+
'center-bottom': { x: 'center', y: '94%' },
69+
'right-bottom': { x: 'right', y: '94%' },
70+
};
71+
72+
function readSettings() {
73+
try {
74+
const parsed = JSON.parse(window.localStorage.getItem(SETTINGS_STORAGE_KEY) || '{}');
75+
return normalizeSettings(parsed);
76+
} catch {
77+
return { ...DEFAULT_SETTINGS };
78+
}
79+
}
80+
81+
function normalizeSettings(settings) {
82+
const targetLines = Number(settings?.targetLines);
83+
const textSize = String(settings?.textSize || DEFAULT_SETTINGS.textSize);
84+
const background = String(settings?.background || DEFAULT_SETTINGS.background);
85+
const position = String(settings?.position || DEFAULT_SETTINGS.position);
86+
87+
return {
88+
targetLines: [1, 2, 3].includes(targetLines)
89+
? targetLines
90+
: DEFAULT_SETTINGS.targetLines,
91+
textSize: Object.hasOwn(TEXT_SIZE_SCALE, textSize)
92+
? textSize
93+
: DEFAULT_SETTINGS.textSize,
94+
background: Object.hasOwn(BACKGROUND_OPACITY, background)
95+
? background
96+
: DEFAULT_SETTINGS.background,
97+
position: Object.hasOwn(OVERLAY_POSITIONS, position)
98+
? position
99+
: DEFAULT_SETTINGS.position,
100+
};
101+
}
102+
31103
const DEBUG = {
32104
enabled: false,
33105
maxChunkLogs: 80,
@@ -69,8 +141,30 @@
69141
triggerRetryId: null,
70142
triggerAttempts: 0,
71143
debugChunks: [],
144+
settings: readSettings(),
72145
};
73146

147+
function applySettings(nextSettings) {
148+
STATE.settings = normalizeSettings(nextSettings);
149+
window.__ketuviaSettings = { ...STATE.settings };
150+
151+
try {
152+
window.localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(STATE.settings));
153+
} catch {}
154+
155+
rebuildChunksForLayout();
156+
renderCurrentCaption(true);
157+
158+
return { ...STATE.settings };
159+
}
160+
161+
window.__ketuviaSettings = { ...STATE.settings };
162+
window.__ketuviaApplySettings = applySettings;
163+
164+
window.addEventListener('ketuvia-settings-change', event => {
165+
applySettings(event.detail);
166+
});
167+
74168
const log = (...a) => {
75169
if (!DEBUG.enabled) return;
76170
console.log(
@@ -97,6 +191,13 @@
97191
return document.querySelector('#movie_player') || document.querySelector('.html5-video-player');
98192
}
99193

194+
function getRuntimeConfig() {
195+
return {
196+
...CFG,
197+
targetLines: STATE.settings.targetLines,
198+
};
199+
}
200+
100201
function joinWords(words) {
101202
let text = '';
102203

@@ -121,9 +222,12 @@
121222
function getLayoutMetrics(player) {
122223
const playerWidth = Math.max(0, player?.clientWidth || 0);
123224
if (!playerWidth) return null;
225+
const settings = STATE.settings;
226+
const runtimeConfig = getRuntimeConfig();
227+
const textSizeScale = TEXT_SIZE_SCALE[settings.textSize] || TEXT_SIZE_SCALE.medium;
124228

125229
const fontSizePx = clamp(
126-
Math.round(playerWidth * CFG.fontSizeRatio * 10) / 10,
230+
Math.round(playerWidth * CFG.fontSizeRatio * textSizeScale * 10) / 10,
127231
CFG.minFontPx,
128232
CFG.maxFontPx
129233
);
@@ -140,7 +244,7 @@
140244
textWidthPx,
141245
fontSizePx,
142246
lineHeight: CFG.lineHeight,
143-
targetLines: CFG.targetLines,
247+
targetLines: runtimeConfig.targetLines,
144248
};
145249
}
146250

@@ -201,10 +305,34 @@
201305

202306
function applyLayout(node, layout) {
203307
if (!node || !layout) return;
308+
const position = OVERLAY_POSITIONS[STATE.settings.position] || OVERLAY_POSITIONS[DEFAULT_SETTINGS.position];
309+
204310
node.style.setProperty('--rechunk-text-width', layout.textWidthPx + 'px');
205311
node.style.setProperty('--rechunk-font-size', layout.fontSizePx + 'px');
206312
node.style.setProperty('--rechunk-line-height', String(layout.lineHeight));
207313
node.style.setProperty('--rechunk-target-lines', String(layout.targetLines));
314+
node.style.setProperty(
315+
'--rechunk-bg-opacity',
316+
String(BACKGROUND_OPACITY[STATE.settings.background] || BACKGROUND_OPACITY.medium)
317+
);
318+
node.style.top = position.y;
319+
node.style.bottom = 'auto';
320+
node.style.left = '';
321+
node.style.right = '';
322+
323+
if (position.x === 'left') {
324+
node.style.left = '8px';
325+
node.style.transform = 'translateY(-50%)';
326+
node.style.textAlign = 'left';
327+
} else if (position.x === 'right') {
328+
node.style.right = '8px';
329+
node.style.transform = 'translateY(-50%)';
330+
node.style.textAlign = 'right';
331+
} else {
332+
node.style.left = '50%';
333+
node.style.transform = 'translate(-50%, -50%)';
334+
node.style.textAlign = 'center';
335+
}
208336
}
209337

210338
function mountOverlay() {
@@ -283,7 +411,7 @@
283411
const player = mountOverlay();
284412
if (!player || !STATE.words.length) return;
285413

286-
const chunkResult = chunkWords(STATE.words, CFG);
414+
const chunkResult = chunkWords(STATE.words, getRuntimeConfig());
287415
STATE.chunks = chunkResult.chunks;
288416
STATE.debugChunks = chunkResult.debugChunks;
289417
log('rebuilt ' + STATE.chunks.length + ' chunks for layout width=' + STATE.layout.textWidthPx);
@@ -495,7 +623,7 @@
495623
}
496624
STATE.words = words;
497625
mountOverlay();
498-
const chunkResult = chunkWords(words, CFG);
626+
const chunkResult = chunkWords(words, getRuntimeConfig());
499627
STATE.chunks = chunkResult.chunks;
500628
STATE.debugChunks = chunkResult.debugChunks;
501629
log('built ' + STATE.chunks.length + ' chunks from ' + words.length + ' words');
@@ -888,37 +1016,44 @@ function chunkWords(words, cfg) {
8881016
const _captionHideStyle = document.createElement('style');
8891017
_captionHideStyle.textContent = '.ytp-caption-window-container{visibility:hidden!important}';
8901018

1019+
function renderCurrentCaption(force = false) {
1020+
if (!STATE.overlay || !STATE.enabled) return;
1021+
const video = document.querySelector('video.html5-main-video') || document.querySelector('video');
1022+
if (!video) return;
1023+
1024+
const ms = (video.currentTime || 0) * 1000 + CFG.lookaheadMs;
1025+
let active = '';
1026+
let activeIndex = -1;
1027+
const N = STATE.chunks.length;
1028+
1029+
for (let i = 0; i < N; i++) {
1030+
const c = STATE.chunks[i];
1031+
const next = STATE.chunks[i + 1];
1032+
const winEnd = next ? next.startMs : c.endMs;
1033+
if (ms >= c.startMs && ms < winEnd) {
1034+
active = c.text;
1035+
activeIndex = i;
1036+
break;
1037+
}
1038+
if (ms < c.startMs) break;
1039+
}
1040+
1041+
if (!force && active === STATE.lastText) return;
1042+
1043+
if (STATE.overlayText) {
1044+
STATE.overlayText.textContent = active;
1045+
}
1046+
STATE.overlay.dataset.empty = active ? '0' : '1';
1047+
if (active && activeIndex >= 0) {
1048+
logRenderedChunk(activeIndex, STATE.chunks[activeIndex]);
1049+
}
1050+
STATE.lastText = active;
1051+
}
1052+
8911053
function startPolling() {
8921054
if (STATE.pollId) return;
8931055
const tick = () => {
894-
if (!STATE.overlay || !STATE.enabled) return;
895-
const video = document.querySelector('video.html5-main-video') || document.querySelector('video');
896-
if (!video) return;
897-
const ms = (video.currentTime || 0) * 1000 + CFG.lookaheadMs;
898-
let active = '';
899-
let activeIndex = -1;
900-
const N = STATE.chunks.length;
901-
for (let i = 0; i < N; i++) {
902-
const c = STATE.chunks[i];
903-
const next = STATE.chunks[i + 1];
904-
const winEnd = next ? next.startMs : c.endMs;
905-
if (ms >= c.startMs && ms < winEnd) {
906-
active = c.text;
907-
activeIndex = i;
908-
break;
909-
}
910-
if (ms < c.startMs) break;
911-
}
912-
if (active !== STATE.lastText) {
913-
if (STATE.overlayText) {
914-
STATE.overlayText.textContent = active;
915-
}
916-
STATE.overlay.dataset.empty = active ? '0' : '1';
917-
if (active && activeIndex >= 0) {
918-
logRenderedChunk(activeIndex, STATE.chunks[activeIndex]);
919-
}
920-
STATE.lastText = active;
921-
}
1056+
renderCurrentCaption();
9221057
};
9231058
STATE.pollId = setInterval(tick, CFG.pollMs);
9241059
}
@@ -978,8 +1113,8 @@ function chunkWords(words, cfg) {
9781113
btn.id = 'rechunk-toggle';
9791114
btn.className = 'ytp-button';
9801115
btn.type = 'button';
981-
btn.setAttribute('aria-label', 'Toggle Rechunk Captions');
982-
btn.title = 'Rechunk Captions';
1116+
btn.setAttribute('aria-label', 'Turn Ketuvia captions on or off');
1117+
btn.title = 'Turn Ketuvia captions on or off';
9831118
btn.textContent = 'CC+';
9841119
btn.addEventListener('click', () => {
9851120
STATE.enabled = !STATE.enabled;
@@ -1011,11 +1146,11 @@ function chunkWords(words, cfg) {
10111146
b.dataset.status = STATE.statusMode;
10121147
b.dataset.enabled = STATE.enabled ? '1' : '0';
10131148
const labels = {
1014-
idle: 'Rechunk Captions: waiting for video',
1015-
loading: 'Rechunk Captions: loading',
1016-
active: 'Rechunk Captions: ON (click to disable)',
1017-
unavailable: 'Rechunk Captions: no auto-captions on this video',
1018-
error: 'Rechunk Captions: failed to load',
1149+
idle: 'Ketuvia: waiting for video',
1150+
loading: 'Ketuvia: loading captions',
1151+
active: 'Ketuvia is on (click to turn off)',
1152+
unavailable: 'Ketuvia: no auto-captions on this video',
1153+
error: 'Ketuvia: failed to load captions',
10191154
};
10201155
b.title = (STATE.enabled ? '' : '(disabled) ') + (labels[STATE.statusMode] || '');
10211156
}

overlay.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
--rechunk-font-size: 24px;
1818
--rechunk-line-height: 1.4;
1919
--rechunk-target-lines: 2;
20+
--rechunk-bg-opacity: 0.45;
2021

2122
position: absolute;
2223
left: 50%;
@@ -40,7 +41,7 @@
4041

4142
color: #fff;
4243
text-align: center;
43-
background: rgba(0, 0, 0, 0.45);
44+
background: rgba(0, 0, 0, var(--rechunk-bg-opacity));
4445
border-radius: 6px;
4546
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
4647

@@ -63,7 +64,7 @@
6364
width: 100%;
6465
white-space: pre-wrap;
6566
word-wrap: break-word;
66-
text-align: center;
67+
text-align: inherit;
6768
}
6869

6970
#rechunk-measurer {
@@ -89,6 +90,7 @@
8990
line-height: var(--rechunk-line-height);
9091
font-weight: 400;
9192
letter-spacing: 0.01em;
93+
--rechunk-bg-opacity: 0.45;
9294

9395
-webkit-font-smoothing: antialiased;
9496
text-rendering: optimizeLegibility;

0 commit comments

Comments
 (0)