fix: make caption overlay line count configurable

Fixes collabora/WhisperLive#486
This commit is contained in:
nightcityblade
2026-05-20 23:13:46 +08:00
committed by Aaron Boxer
parent 6242ad7f81
commit d006d4abb3
6 changed files with 88 additions and 34 deletions
+19 -16
View File
@@ -3,6 +3,7 @@ var elem_text = null;
var segments = []; var segments = [];
var text_segments = []; var text_segments = [];
var captionLineCount = 3;
var allSegments = []; var allSegments = [];
var lastIncompleteSegment = null; var lastIncompleteSegment = null;
@@ -87,22 +88,23 @@ function showPopup(customText) {
} }
function init_element() { function init_element(lines = 3) {
captionLineCount = Math.min(Math.max(parseInt(lines, 10) || 3, 1), 8);
if (document.getElementById('transcription')) { if (document.getElementById('transcription')) {
return; return;
} }
elem_container = document.createElement('div'); elem_container = document.createElement('div');
elem_container.id = "transcription"; elem_container.id = "transcription";
elem_container.style.cssText = 'padding-top:16px;font-size:18px;position: fixed; top: 85%; left: 50%; transform: translate(-50%, -50%);line-height:18px;width:500px;height:90px;opacity:0.9;z-index:100;background:black;border-radius:10px;color:white;'; elem_container.style.cssText = 'padding-top:16px;font-size:18px;position: fixed; top: 85%; left: 50%; transform: translate(-50%, -50%);line-height:18px;width:500px;height:' + (captionLineCount * 30) + 'px;opacity:0.9;z-index:100;background:black;border-radius:10px;color:white;';
for (var i = 0; i < 4; i++) { for (var i = 0; i <= captionLineCount; i++) {
elem_text = document.createElement('span'); elem_text = document.createElement('span');
elem_text.style.cssText = 'position: absolute;padding-left:16px;padding-right:16px;'; elem_text.style.cssText = 'position: absolute;padding-left:16px;padding-right:16px;';
elem_text.id = "t" + i; elem_text.id = "t" + i;
elem_container.appendChild(elem_text); elem_container.appendChild(elem_text);
if (i == 3) { if (i == captionLineCount) {
elem_text.style.top = "-1000px" elem_text.style.top = "-1000px"
} }
} }
@@ -196,7 +198,7 @@ function get_lines(elem, line_height) {
function remove_element() { function remove_element() {
var elem = document.getElementById('transcription') var elem = document.getElementById('transcription')
for (var i = 0; i < 4; i++) { for (var i = 0; i <= captionLineCount; i++) {
document.getElementById("t" + i).remove(); document.getElementById("t" + i).remove();
} }
elem.remove() elem.remove()
@@ -205,6 +207,7 @@ function remove_element() {
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const { type, data } = request; const { type, data } = request;
const saveCaptions = data.saveCaptions; const saveCaptions = data.saveCaptions;
const captionLines = data.captionLines || captionLineCount;
if (type === "STOP") { if (type === "STOP") {
if (saveCaptions === true) { if (saveCaptions === true) {
@@ -234,7 +237,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
return true; return true;
} }
init_element(); init_element(captionLines);
try { try {
const message = JSON.parse(data.data); const message = JSON.parse(data.data);
@@ -262,11 +265,11 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
} }
text = text.replace(/(\r\n|\n|\r)/gm, ""); text = text.replace(/(\r\n|\n|\r)/gm, "");
var elem = document.getElementById('t3'); var elem = document.getElementById('t' + captionLineCount);
if (elem) { if (elem) {
elem.textContent = text; elem.textContent = text;
var line_height_style = getStyle('t3', 'line-height'); var line_height_style = getStyle('t' + captionLineCount, 'line-height');
var line_height = parseInt(line_height_style.substring(0, line_height_style.length - 2)); var line_height = parseInt(line_height_style.substring(0, line_height_style.length - 2));
var divHeight = elem.offsetHeight; var divHeight = elem.offsetHeight;
var lines = divHeight / line_height; var lines = divHeight / line_height;
@@ -276,27 +279,27 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
elem.textContent = ''; elem.textContent = '';
if (text_segments.length > 2) { if (text_segments.length > captionLineCount - 1) {
for (var i = 0; i < 3; i++) { for (var i = 0; i < captionLineCount; i++) {
document.getElementById('t' + i).textContent = text_segments[text_segments.length - 3 + i]; document.getElementById('t' + i).textContent = text_segments[text_segments.length - captionLineCount + i];
} }
} else { } else {
for (var i = 0; i < 3; i++) { for (var i = 0; i < captionLineCount; i++) {
document.getElementById('t' + i).textContent = ''; document.getElementById('t' + i).textContent = '';
} }
} }
if (text_segments.length <= 2) { if (text_segments.length <= captionLineCount - 1) {
for (var i = 0; i < text_segments.length; i++) { for (var i = 0; i < text_segments.length; i++) {
document.getElementById('t' + i).textContent = text_segments[i]; document.getElementById('t' + i).textContent = text_segments[i];
} }
} else { } else {
for (var i = 0; i < 3; i++) { for (var i = 0; i < captionLineCount; i++) {
document.getElementById('t' + i).textContent = text_segments[text_segments.length - 3 + i]; document.getElementById('t' + i).textContent = text_segments[text_segments.length - captionLineCount + i];
} }
} }
for (var i = 1; i < 3; i++) for (var i = 1; i < captionLineCount; i++)
{ {
var parent_elem = document.getElementById('t' + (i - 1)); var parent_elem = document.getElementById('t' + (i - 1));
var elem = document.getElementById('t' + i); var elem = document.getElementById('t' + i);
+8
View File
@@ -23,6 +23,14 @@
<input type="checkbox" id="saveCaptionsCheckbox"> <input type="checkbox" id="saveCaptionsCheckbox">
<label for="saveCaptions">Download SRT file at Stop Capture</label> <label for="saveCaptions">Download SRT file at Stop Capture</label>
</div> </div>
<div class="dropdown-container">
<label for="captionLinesDropdown">Caption Lines:</label>
<select id="captionLinesDropdown">
<option value="3" selected>3 lines</option>
<option value="5">5 lines</option>
<option value="8">8 lines</option>
</select>
</div>
<div class="dropdown-container"> <div class="dropdown-container">
<label for="languageDropdown">Select Language:</label> <label for="languageDropdown">Select Language:</label>
<select id="languageDropdown"> <select id="languageDropdown">
+17 -1
View File
@@ -9,9 +9,11 @@ document.addEventListener("DOMContentLoaded", function () {
const languageDropdown = document.getElementById('languageDropdown'); const languageDropdown = document.getElementById('languageDropdown');
const taskDropdown = document.getElementById('taskDropdown'); const taskDropdown = document.getElementById('taskDropdown');
const modelSizeDropdown = document.getElementById('modelSizeDropdown'); const modelSizeDropdown = document.getElementById('modelSizeDropdown');
const captionLinesDropdown = document.getElementById('captionLinesDropdown');
let selectedLanguage = null; let selectedLanguage = null;
let selectedTask = taskDropdown.value; let selectedTask = taskDropdown.value;
let selectedModelSize = modelSizeDropdown.value; let selectedModelSize = modelSizeDropdown.value;
let selectedCaptionLines = captionLinesDropdown.value;
// Add click event listeners to the buttons // Add click event listeners to the buttons
startButton.addEventListener("click", startCapture); startButton.addEventListener("click", startCapture);
@@ -66,6 +68,13 @@ document.addEventListener("DOMContentLoaded", function () {
} }
}); });
chrome.storage.local.get("selectedCaptionLines", ({ selectedCaptionLines: storedCaptionLines }) => {
if (storedCaptionLines !== undefined) {
captionLinesDropdown.value = storedCaptionLines;
selectedCaptionLines = storedCaptionLines;
}
});
// Function to handle the start capture button click event // Function to handle the start capture button click event
async function startCapture() { async function startCapture() {
// Ignore click if the button is disabled // Ignore click if the button is disabled
@@ -96,6 +105,7 @@ document.addEventListener("DOMContentLoaded", function () {
modelSize: selectedModelSize, modelSize: selectedModelSize,
useVad: useVadCheckbox.checked, useVad: useVadCheckbox.checked,
saveCaptions: saveCaptionsCheckbox.checked, saveCaptions: saveCaptionsCheckbox.checked,
captionLines: Number(selectedCaptionLines),
}, () => { }, () => {
// Update capturing state in storage and toggle the buttons // Update capturing state in storage and toggle the buttons
chrome.storage.local.set({ capturingState: { isCapturing: true } }, () => { chrome.storage.local.set({ capturingState: { isCapturing: true } }, () => {
@@ -143,7 +153,8 @@ document.addEventListener("DOMContentLoaded", function () {
saveCaptionsCheckbox.disabled = isCapturing; saveCaptionsCheckbox.disabled = isCapturing;
modelSizeDropdown.disabled = isCapturing; modelSizeDropdown.disabled = isCapturing;
languageDropdown.disabled = isCapturing; languageDropdown.disabled = isCapturing;
taskDropdown.disabled = isCapturing; taskDropdown.disabled = isCapturing;
captionLinesDropdown.disabled = isCapturing;
startButton.classList.toggle("disabled", isCapturing); startButton.classList.toggle("disabled", isCapturing);
stopButton.classList.toggle("disabled", !isCapturing); stopButton.classList.toggle("disabled", !isCapturing);
} }
@@ -183,6 +194,11 @@ document.addEventListener("DOMContentLoaded", function () {
chrome.storage.local.set({ selectedModelSize }); chrome.storage.local.set({ selectedModelSize });
}); });
captionLinesDropdown.addEventListener('change', function() {
selectedCaptionLines = captionLinesDropdown.value;
chrome.storage.local.set({ selectedCaptionLines });
});
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
if (request.action === "updateSelectedLanguage") { if (request.action === "updateSelectedLanguage") {
const detectedLanguage = request.detectedLanguage; const detectedLanguage = request.detectedLanguage;
+19 -16
View File
@@ -162,6 +162,7 @@ var elem_text = null;
var segments = []; var segments = [];
var text_segments = []; var text_segments = [];
var captionLineCount = 3;
function initPopupElement() { function initPopupElement() {
if (document.getElementById('popupElement')) { if (document.getElementById('popupElement')) {
@@ -209,22 +210,23 @@ function showPopup(customText) {
} }
function init_element() { function init_element(lines = 3) {
captionLineCount = Math.min(Math.max(parseInt(lines, 10) || 3, 1), 8);
if (document.getElementById('transcription')) { if (document.getElementById('transcription')) {
return; return;
} }
elem_container = document.createElement('div'); elem_container = document.createElement('div');
elem_container.id = "transcription"; elem_container.id = "transcription";
elem_container.style.cssText = 'padding-top:16px;font-size:18px;line-height:18px;position:fixed;top:85%;left:50%;transform:translate(-50%,-50%);width:500px;height:90px;opacity:0.9;z-index:100;background:black;border-radius:10px;color:white;'; elem_container.style.cssText = 'padding-top:16px;font-size:18px;line-height:18px;position:fixed;top:85%;left:50%;transform:translate(-50%,-50%);width:500px;height:' + (captionLineCount * 30) + 'px;opacity:0.9;z-index:100;background:black;border-radius:10px;color:white;';
for (var i = 0; i < 4; i++) { for (var i = 0; i <= captionLineCount; i++) {
elem_text = document.createElement('span'); elem_text = document.createElement('span');
elem_text.style.cssText = 'position: absolute;padding-left:16px;padding-right:16px;'; elem_text.style.cssText = 'position: absolute;padding-left:16px;padding-right:16px;';
elem_text.id = "t" + i; elem_text.id = "t" + i;
elem_container.appendChild(elem_text); elem_container.appendChild(elem_text);
if (i == 3) { if (i == captionLineCount) {
elem_text.style.top = "-1000px" elem_text.style.top = "-1000px"
} }
} }
@@ -318,7 +320,7 @@ function get_lines(elem, line_height) {
function remove_element() { function remove_element() {
var elem = document.getElementById('transcription') var elem = document.getElementById('transcription')
for (var i = 0; i < 4; i++) { for (var i = 0; i <= captionLineCount; i++) {
document.getElementById("t" + i).remove(); document.getElementById("t" + i).remove();
} }
elem.remove() elem.remove()
@@ -327,6 +329,7 @@ function remove_element() {
browser.runtime.onMessage.addListener((request, sender, sendResponse) => { browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
const { action, data } = request; const { action, data } = request;
const saveCaption = data.saveCaption || false; const saveCaption = data.saveCaption || false;
const captionLines = data.captionLines || captionLineCount;
if (action === "startCapture") { if (action === "startCapture") {
isCapturing = true; isCapturing = true;
@@ -364,7 +367,7 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
} else if (action === "show_transcript"){ } else if (action === "show_transcript"){
if (!isCapturing) return; if (!isCapturing) return;
init_element(); init_element(captionLines);
message = JSON.parse(data.data); message = JSON.parse(data.data);
message = message["segments"]; message = message["segments"];
@@ -391,10 +394,10 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
} }
text = text.replace(/(\r\n|\n|\r)/gm, ""); text = text.replace(/(\r\n|\n|\r)/gm, "");
var elem = document.getElementById('t3'); var elem = document.getElementById('t' + captionLineCount);
elem.textContent = text; elem.textContent = text;
var line_height_style = getStyle('t3', 'line-height'); var line_height_style = getStyle('t' + captionLineCount, 'line-height');
var line_height = parseInt(line_height_style.substring(0, line_height_style.length - 2)); var line_height = parseInt(line_height_style.substring(0, line_height_style.length - 2));
var divHeight = elem.offsetHeight; var divHeight = elem.offsetHeight;
var lines = divHeight / line_height; var lines = divHeight / line_height;
@@ -404,27 +407,27 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
elem.textContent = ''; elem.textContent = '';
if (text_segments.length > 2) { if (text_segments.length > captionLineCount - 1) {
for (var i = 0; i < 3; i++) { for (var i = 0; i < captionLineCount; i++) {
document.getElementById('t' + i).textContent = text_segments[text_segments.length - 3 + i]; document.getElementById('t' + i).textContent = text_segments[text_segments.length - captionLineCount + i];
} }
} else { } else {
for (var i = 0; i < 3; i++) { for (var i = 0; i < captionLineCount; i++) {
document.getElementById('t' + i).textContent = ''; document.getElementById('t' + i).textContent = '';
} }
} }
if (text_segments.length <= 2) { if (text_segments.length <= captionLineCount - 1) {
for (var i = 0; i < text_segments.length; i++) { for (var i = 0; i < text_segments.length; i++) {
document.getElementById('t' + i).textContent = text_segments[i]; document.getElementById('t' + i).textContent = text_segments[i];
} }
} else { } else {
for (var i = 0; i < 3; i++) { for (var i = 0; i < captionLineCount; i++) {
document.getElementById('t' + i).textContent = text_segments[text_segments.length - 3 + i]; document.getElementById('t' + i).textContent = text_segments[text_segments.length - captionLineCount + i];
} }
} }
for (var i = 1; i < 3; i++) for (var i = 1; i < captionLineCount; i++)
{ {
var parent_elem = document.getElementById('t' + (i - 1)); var parent_elem = document.getElementById('t' + (i - 1));
var elem = document.getElementById('t' + i); var elem = document.getElementById('t' + i);
+8
View File
@@ -24,6 +24,14 @@
<label for="saveCaption">Download SRT file at Stop Capture</label> <label for="saveCaption">Download SRT file at Stop Capture</label>
</div> </div>
<textarea id="waitTextBox" style="display: none;"></textarea> <textarea id="waitTextBox" style="display: none;"></textarea>
<div class="dropdown-container">
<label for="captionLinesDropdown">Caption Lines:</label>
<select id="captionLinesDropdown">
<option value="3" selected>3 lines</option>
<option value="5">5 lines</option>
<option value="8">8 lines</option>
</select>
</div>
<div class="dropdown-container"> <div class="dropdown-container">
<label for="languageDropdown">Select Language:</label> <label for="languageDropdown">Select Language:</label>
<select id="languageDropdown"> <select id="languageDropdown">
+17 -1
View File
@@ -8,9 +8,11 @@ document.addEventListener("DOMContentLoaded", function() {
const languageDropdown = document.getElementById('languageDropdown'); const languageDropdown = document.getElementById('languageDropdown');
const taskDropdown = document.getElementById('taskDropdown'); const taskDropdown = document.getElementById('taskDropdown');
const modelSizeDropdown = document.getElementById('modelSizeDropdown'); const modelSizeDropdown = document.getElementById('modelSizeDropdown');
const captionLinesDropdown = document.getElementById('captionLinesDropdown');
let selectedLanguage = null; let selectedLanguage = null;
let selectedTask = taskDropdown.value; let selectedTask = taskDropdown.value;
let selectedModelSize = modelSizeDropdown.value; let selectedModelSize = modelSizeDropdown.value;
let selectedCaptionLines = captionLinesDropdown.value;
browser.storage.local.get("capturingState") browser.storage.local.get("capturingState")
@@ -69,6 +71,13 @@ document.addEventListener("DOMContentLoaded", function() {
} }
}); });
browser.storage.local.get("selectedCaptionLines", ({ selectedCaptionLines: storedCaptionLines }) => {
if (storedCaptionLines !== undefined) {
captionLinesDropdown.value = storedCaptionLines;
selectedCaptionLines = storedCaptionLines;
}
});
startButton.addEventListener("click", function() { startButton.addEventListener("click", function() {
let host = "localhost"; let host = "localhost";
let port = "9090"; let port = "9090";
@@ -93,6 +102,7 @@ document.addEventListener("DOMContentLoaded", function() {
modelSize: selectedModelSize, modelSize: selectedModelSize,
useVad: useVadCheckbox.checked, useVad: useVadCheckbox.checked,
saveCaption: saveCaptionCheckbox.checked, saveCaption: saveCaptionCheckbox.checked,
captionLines: Number(selectedCaptionLines),
} }
}); });
toggleCaptureButtons(true); toggleCaptureButtons(true);
@@ -135,7 +145,8 @@ document.addEventListener("DOMContentLoaded", function() {
saveCaptionCheckbox.disabled = isCapturing; saveCaptionCheckbox.disabled = isCapturing;
modelSizeDropdown.disabled = isCapturing; modelSizeDropdown.disabled = isCapturing;
languageDropdown.disabled = isCapturing; languageDropdown.disabled = isCapturing;
taskDropdown.disabled = isCapturing; taskDropdown.disabled = isCapturing;
captionLinesDropdown.disabled = isCapturing;
startButton.classList.toggle("disabled", isCapturing); startButton.classList.toggle("disabled", isCapturing);
stopButton.classList.toggle("disabled", !isCapturing); stopButton.classList.toggle("disabled", !isCapturing);
} }
@@ -175,6 +186,11 @@ document.addEventListener("DOMContentLoaded", function() {
browser.storage.local.set({ selectedModelSize }); browser.storage.local.set({ selectedModelSize });
}); });
captionLinesDropdown.addEventListener('change', function() {
selectedCaptionLines = captionLinesDropdown.value;
browser.storage.local.set({ selectedCaptionLines });
});
browser.runtime.onMessage.addListener((request, sender, sendResponse) => { browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "updateSelectedLanguage") { if (request.action === "updateSelectedLanguage") {
const detectedLanguage = request.data; const detectedLanguage = request.data;