Add vMix overlay style options

This commit is contained in:
steveseguin
2026-04-22 10:44:55 -04:00
parent dc7356d6c4
commit bc70594885
8 changed files with 665 additions and 35 deletions
+57 -7
View File
@@ -332,13 +332,63 @@ Examples:
.../overlay?room=abc123&showtime=1500 # Faster inactivity clear
.../overlay?room=abc123&maxlines=3 # Keep last 3 lines while active
.../overlay?room=abc123&maxage=10000 # Drop lines older than 10s
.../overlay?room=abc123&maxlines=2&maxage=8000 # Combine both
.../overlay?room=abc123&intermclear=1 # Only show the interim/current line
```
### Interim Text Handling
Browser speech recognition sometimes produces very long "interim" (non-finalized) results without breaking them into sentences. This is common when captioning talk shows, pre-recorded audio, or streams with continuous speech. The following options help manage long interim text blocks:
.../overlay?room=abc123&maxlines=2&maxage=8000 # Combine both
.../overlay?room=abc123&intermclear=1 # Only show the interim/current line
```
### Overlay Style Parameters
The default overlay styling is unchanged. Add these optional URL parameters to customize the visual style in OBS, vMix, or another browser-source tool:
| Parameter | Description | Example |
|-----------|-------------|---------|
| `&nobg` / `&bg=transparent` | Remove the black caption highlight behind text. | `&nobg` |
| `&bg=COLOR` | Set the caption highlight/background color. | `&bg=rgba(0,0,0,0.6)` |
| `&color=COLOR` / `&fontcolor=COLOR` | Set caption text color. | `&color=%23fff200` |
| `&fontsize=SIZE` / `&fs=SIZE` | Set text size. Numbers up to 10 use `em`; larger numbers use `px`. | `&fontsize=4em` |
| `&scale=N` | Scale the default font size. | `&scale=1.25` |
| `&font=FAMILY` | Set the font family. URL-encode spaces or quotes as needed. | `&font=Arial,sans-serif` |
| `&fontweight=VALUE` | Set font weight. | `&fontweight=700` |
| `&lineheight=VALUE` | Set line height. | `&lineheight=1.2` |
| `&letterspacing=SIZE` | Set letter spacing. | `&letterspacing=0.04em` |
| `&pad=SIZE` / `&padding=SIZE` | Set caption text padding. | `&pad=0` |
| `&padx=SIZE` / `&pady=SIZE` | Set horizontal or vertical caption padding. | `&padx=12&pady=4` |
| `&radius=SIZE` / `&rounded=SIZE` | Round caption highlight corners. | `&radius=6` |
| `&borderwidth=SIZE&bordercolor=COLOR` | Add a border around caption text spans. | `&borderwidth=2&bordercolor=%23fff` |
| `&shadow=none` / `&noshadow` | Remove the text shadow. | `&noshadow` |
| `&shadow=VALUE` | Replace the text shadow. URL-encode spaces. | `&shadow=2px%202px%204px%20%23000` |
| `&shadowcolor=COLOR` | Keep the default shadow size but change its color. | `&shadowcolor=%23000` |
| `&outline=SIZE&outlinecolor=COLOR` | Use a simple text outline via text-shadow. | `&outline=2&outlinecolor=%23000` |
| `&align=left|center|right` | Align caption lines horizontally. | `&align=center` |
| `&valign=top|middle|bottom` | Position the overlay vertically. | `&valign=top` |
| `&topoffset=SIZE` / `&bottomoffset=SIZE` | Offset the overlay from the top or bottom. | `&bottomoffset=40` |
| `&leftoffset=SIZE` / `&rightoffset=SIZE` | Offset the overlay horizontally. | `&leftoffset=30` |
| `&nowrap` | Keep each caption on one visual line with ellipsis overflow. | `&nowrap` |
| `&uppercase` / `&lowercase` | Transform caption text case. | `&uppercase` |
| `&opacity=N` | Set overall caption opacity. `0.0` to `1.0`, or `0` to `100`. | `&opacity=0.85` |
| `&bgopacity=N` | Apply opacity to hex/rgb caption backgrounds. | `&bg=%23000&bgopacity=0.5` |
| `&labelcolor=COLOR` / `&labelbg=COLOR` | Style speaker labels when labels are present. | `&labelcolor=%23ffd500` |
| `&translationcolor=COLOR` / `&originalcolor=COLOR` | Style dual-overlay translation/original lines. | `&originalcolor=%23ccc` |
| `&css=URL` | Load an external stylesheet for advanced customization. | `&css=https://example.com/captions.css` |
| `&base64css=...` | Inject base64-encoded CSS for advanced customization. | `&base64css=I291dHB1dHt9` |
Examples:
```
.../overlay.html?room=abc123&nobg&shadow=2px%202px%204px%20%23000
.../overlay.html?room=abc123&bg=rgba(0,0,0,0.45)&radius=6&padx=12&pady=4
.../overlay.html?room=abc123&color=%23fff200&fontsize=48px&align=center
```
For vMix users who want the black text highlight removed by default, use:
```
.../vmix.html?room=abc123
```
`vmix.html` forwards to `overlay.html` with `&nobg=1` unless a background/highlight parameter is already provided. Use `&bg=black` or another `&bg=COLOR` value to restore a caption highlight on that page.
### Interim Text Handling
Browser speech recognition sometimes produces very long "interim" (non-finalized) results without breaking them into sentences. This is common when captioning talk shows, pre-recorded audio, or streams with continuous speech. The following options help manage long interim text blocks:
#### Capture Page Options (index.html & translate.html)
+14 -8
View File
@@ -71,10 +71,14 @@
<body>
<input type="textarea" id="userinput" />
<div id="interm" class="output">
<font style='color:black;font-size:60%;line-height: 1.4em;' ><div >This is a free-to-use captioning tool.</div></font><font style='color:black;font-size:30%;line-height: 1.4em;' >
<div ><br /><br />
The output of this app is mirrored here: <a id="shareLink" href="overlay.html" target='_blank'>*ERROR GENERATING LINK*</a>. Add it to OBS as a browser source overlay if wishing to use it for a live stream.
</div></font>
<font style='color:black;font-size:60%;line-height: 1.4em;' ><div >This is a free-to-use captioning tool.</div></font><font style='color:black;font-size:30%;line-height: 1.4em;' >
<div ><br /><br />
The output of this app is mirrored here: <a id="shareLink" href="overlay.html" target='_blank'>*ERROR GENERATING LINK*</a>. Add it to OBS as a browser source overlay if wishing to use it for a live stream.
<br /><br />
For vMix, use this no-highlight overlay link: <a id="vmixLink" href="vmix.html" target='_blank'>*ERROR GENERATING LINK*</a>.
<br /><br />
Overlay style options include <code>&amp;nobg</code> or <code>&amp;bg=transparent</code> to remove the black highlight, <code>&amp;bg=COLOR</code> to set it, <code>&amp;color=COLOR</code>, <code>&amp;fontsize=SIZE</code>, <code>&amp;scale=N</code>, <code>&amp;align=center</code>, <code>&amp;valign=top</code>, <code>&amp;shadow=none</code>, and <code>&amp;outline=2&amp;outlinecolor=%23000</code>.
</div></font>
<br /><br />
</div>
<div id="output" class="output"></div>
@@ -170,10 +174,12 @@
var counter=0;
var url = document.URL.substr(0,document.URL.lastIndexOf('/'));
document.getElementById("shareLink").href= url+"/overlay?room="+roomID;
document.getElementById("shareLink").innerHTML = url+"/overlay?room="+roomID;
navigator.clipboard.writeText(url+"/overlay?room="+roomID).then(() => {
document.getElementById("shareLink").href= url+"/overlay?room="+roomID;
document.getElementById("shareLink").innerHTML = url+"/overlay?room="+roomID;
document.getElementById("vmixLink").href= url+"/vmix.html?room="+roomID;
document.getElementById("vmixLink").innerHTML = url+"/vmix.html?room="+roomID;
navigator.clipboard.writeText(url+"/overlay?room="+roomID).then(() => {
/* clipboard successfully set */
}, () => {
/* clipboard write failed */
+498
View File
@@ -0,0 +1,498 @@
(function () {
"use strict";
var params;
try {
params = window.urlParams || new URLSearchParams(window.location.search);
} catch (e) {
return;
}
function hasValue(value) {
return value !== null && value !== undefined && String(value).trim() !== "";
}
function isOff(value) {
if (!hasValue(value)) {
return false;
}
return /^(0|false|off|no|none|disable|disabled)$/i.test(String(value).trim());
}
function getParam(names) {
for (var i = 0; i < names.length; i++) {
if (params.has(names[i])) {
return params.get(names[i]);
}
}
return null;
}
function hasParam(names) {
for (var i = 0; i < names.length; i++) {
if (params.has(names[i])) {
return true;
}
}
return false;
}
function hasEnabledParam(names) {
for (var i = 0; i < names.length; i++) {
if (params.has(names[i]) && !isOff(params.get(names[i]))) {
return true;
}
}
return false;
}
function safeCssValue(value, property) {
if (!hasValue(value)) {
return null;
}
value = String(value).trim();
if (/[{};<>]/.test(value)) {
return null;
}
if (window.CSS && window.CSS.supports && !window.CSS.supports(property, value)) {
return null;
}
return value;
}
function normalizeColor(value) {
if (!hasValue(value)) {
return null;
}
value = String(value).trim();
if (isOff(value)) {
return "transparent";
}
if (/^[0-9a-f]{3,4}$/i.test(value) || /^[0-9a-f]{6}([0-9a-f]{2})?$/i.test(value)) {
value = "#" + value;
}
if (/[{};<>]/.test(value)) {
return null;
}
if (window.CSS && window.CSS.supports && window.CSS.supports("color", value)) {
return value;
}
if (/^#[0-9a-f]{3,8}$/i.test(value) || /^rgba?\([^)]+\)$/i.test(value) || /^hsla?\([^)]+\)$/i.test(value) || /^(transparent|currentcolor)$/i.test(value)) {
return value;
}
return null;
}
function cssLength(value, defaultUnit) {
if (!hasValue(value)) {
return null;
}
value = String(value).trim();
if (/[{};<>]/.test(value)) {
return null;
}
if (/^-?[\d.]+$/.test(value)) {
value += defaultUnit || "px";
}
if (window.CSS && window.CSS.supports && !window.CSS.supports("width", value) && !window.CSS.supports("font-size", value)) {
return null;
}
return value;
}
function cssFontSize(value) {
if (!hasValue(value)) {
return null;
}
value = String(value).trim();
if (/^-?[\d.]+$/.test(value)) {
var numeric = parseFloat(value);
value = (Math.abs(numeric) > 10) ? numeric + "px" : numeric + "em";
}
return safeCssValue(value, "font-size");
}
function cssBoxValue(value, defaultUnit) {
if (!hasValue(value)) {
return null;
}
value = String(value).trim().replace(/,/g, " ");
if (/[{};<>]/.test(value)) {
return null;
}
var parts = value.split(/\s+/).filter(Boolean);
if (parts.length < 1 || parts.length > 4) {
return null;
}
for (var i = 0; i < parts.length; i++) {
parts[i] = cssLength(parts[i], defaultUnit || "px");
if (!parts[i]) {
return null;
}
}
return parts.join(" ");
}
function cssFontFamily(value) {
if (!hasValue(value)) {
return null;
}
value = String(value).trim();
if (/[{};<>]/.test(value)) {
return null;
}
if (window.CSS && window.CSS.supports && !window.CSS.supports("font-family", value)) {
return null;
}
return value;
}
function parseOpacity(value) {
if (!hasValue(value)) {
return null;
}
var opacity = parseFloat(value);
if (isNaN(opacity)) {
return null;
}
if (opacity > 1) {
opacity = opacity / 100;
}
return Math.max(0, Math.min(1, opacity));
}
function withOpacity(color, opacity) {
if (!hasValue(color) || opacity === null || color === "transparent") {
return null;
}
var match;
color = String(color).trim();
if (/^#[0-9a-f]{3,4}$/i.test(color)) {
var shortHex = color.slice(1);
return "rgba(" +
parseInt(shortHex[0] + shortHex[0], 16) + ", " +
parseInt(shortHex[1] + shortHex[1], 16) + ", " +
parseInt(shortHex[2] + shortHex[2], 16) + ", " + opacity + ")";
}
if (/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(color)) {
var hex = color.slice(1);
return "rgba(" +
parseInt(hex.slice(0, 2), 16) + ", " +
parseInt(hex.slice(2, 4), 16) + ", " +
parseInt(hex.slice(4, 6), 16) + ", " + opacity + ")";
}
match = color.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i);
if (match) {
return "rgba(" + match[1] + ", " + match[2] + ", " + match[3] + ", " + opacity + ")";
}
return null;
}
function negativeLength(value) {
if (value === "0" || value === "0px" || value === "0em") {
return "0";
}
return String(value).charAt(0) === "-" ? String(value).slice(1) : "-" + value;
}
function buildOutline(width, color) {
var neg = negativeLength(width);
return [
neg + " " + neg + " 0 " + color,
"0 " + neg + " 0 " + color,
width + " " + neg + " 0 " + color,
neg + " 0 0 " + color,
width + " 0 0 " + color,
neg + " " + width + " 0 " + color,
"0 " + width + " 0 " + color,
width + " " + width + " 0 " + color
].join(", ");
}
var rules = {};
function addRule(selector, property, value) {
if (!hasValue(value)) {
return;
}
if (!rules[selector]) {
rules[selector] = [];
}
rules[selector].push(property + ": " + value + " !important");
}
function addCss(cssText) {
if (!hasValue(cssText)) {
return;
}
var style = document.createElement("style");
style.type = "text/css";
style.textContent = cssText;
document.head.appendChild(style);
}
var outputSelector = "#output, #interm";
var spanSelector = "#output span, #interm span";
var labelSelector = "#output .label, #interm .label";
var noBg = hasEnabledParam(["nobg", "no-bg", "nobox", "nohighlight", "no-highlight", "transparentbg", "transparent-bg"]);
var bgParam = getParam(["bg", "background", "captionbg", "captionbackground", "highlight", "highlightcolor", "boxcolor"]);
var bg = noBg ? "transparent" : normalizeColor(bgParam);
if (hasValue(bgParam) && isOff(bgParam)) {
bg = "transparent";
}
var bgOpacity = parseOpacity(getParam(["bgopacity", "backgroundopacity", "boxopacity", "highlightopacity"]));
var bgWithOpacity = withOpacity(bg, bgOpacity);
if (bgWithOpacity) {
bg = bgWithOpacity;
}
if (bg) {
addRule(spanSelector, "background-color", bg);
addRule(spanSelector, "background", bg);
}
var pageBg = normalizeColor(getParam(["pagebg", "bodybg", "screenbg", "canvasbg"]));
if (hasEnabledParam(["transparent", "transparency"])) {
pageBg = "transparent";
}
if (pageBg) {
addRule("html, body", "background-color", pageBg);
}
var textColor = normalizeColor(getParam(["color", "fg", "fontcolor", "textcolor"]));
if (textColor) {
addRule(outputSelector, "color", textColor);
}
var fontFamily = cssFontFamily(getParam(["font", "fontfamily", "font-family"]));
if (fontFamily) {
addRule(outputSelector, "font-family", fontFamily);
}
var fontSize = cssFontSize(getParam(["fontsize", "font-size", "fs", "size"]));
var scale = parseFloat(getParam(["scale", "zoom"]));
if (!fontSize && !isNaN(scale) && scale > 0) {
fontSize = (3.2 * scale).toFixed(3) + "em";
}
if (fontSize) {
addRule(outputSelector, "font-size", fontSize);
}
var fontWeight = safeCssValue(getParam(["fontweight", "font-weight", "weight"]), "font-weight");
if (fontWeight) {
addRule(outputSelector, "font-weight", fontWeight);
}
var lineHeight = safeCssValue(getParam(["lineheight", "line-height", "lh"]), "line-height");
if (lineHeight) {
addRule(outputSelector, "line-height", lineHeight);
}
var letterSpacing = cssLength(getParam(["letterspacing", "letter-spacing", "spacing", "ls"]), "em");
if (letterSpacing) {
addRule(outputSelector, "letter-spacing", letterSpacing);
}
var textTransform = getParam(["texttransform", "text-transform", "transform"]);
if (hasEnabledParam(["uppercase", "upper"])) {
textTransform = "uppercase";
} else if (hasEnabledParam(["lowercase", "lower"])) {
textTransform = "lowercase";
} else if (hasEnabledParam(["capitalize"])) {
textTransform = "capitalize";
}
textTransform = safeCssValue(textTransform, "text-transform");
if (textTransform) {
addRule(outputSelector, "text-transform", textTransform);
}
var padding = cssBoxValue(getParam(["padding", "pad", "textpadding", "textpad"]), "px");
if (padding) {
addRule(spanSelector, "padding", padding);
}
var padX = cssLength(getParam(["padx", "paddingx"]), "px");
var padY = cssLength(getParam(["pady", "paddingy"]), "px");
if (padX) {
addRule(spanSelector, "padding-left", padX);
addRule(spanSelector, "padding-right", padX);
}
if (padY) {
addRule(spanSelector, "padding-top", padY);
addRule(spanSelector, "padding-bottom", padY);
}
var radius = cssLength(getParam(["radius", "rounded", "borderradius", "border-radius"]), "px");
if (radius) {
addRule(spanSelector, "border-radius", radius);
}
var borderColor = normalizeColor(getParam(["bordercolor", "border-color"]));
var borderWidth = cssLength(getParam(["borderwidth", "border-width", "border"]), "px");
if (borderColor || borderWidth) {
addRule(spanSelector, "border-style", safeCssValue(getParam(["borderstyle", "border-style"]) || "solid", "border-style"));
addRule(spanSelector, "border-width", borderWidth || "1px");
addRule(spanSelector, "border-color", borderColor || "currentColor");
}
var captionOpacity = parseOpacity(getParam(["opacity", "captionopacity", "textopacity"]));
if (captionOpacity !== null) {
addRule(outputSelector, "opacity", captionOpacity);
}
var maxWidth = cssLength(getParam(["maxwidth", "max-width"]), "px");
if (maxWidth) {
addRule(spanSelector, "max-width", maxWidth);
}
var width = cssLength(getParam(["width"]), "px");
if (width) {
addRule(outputSelector, "width", width);
}
var align = getParam(["align", "textalign", "text-align"]);
if (hasEnabledParam(["center", "centre"])) {
align = "center";
} else if (hasEnabledParam(["right", "alignright"])) {
align = "right";
} else if (hasEnabledParam(["left", "alignleft"])) {
align = "left";
}
if (/^(left|right|center|justify)$/i.test(String(align || ""))) {
addRule("body", "left", "0");
addRule("body", "right", "0");
addRule("body", "width", "auto");
addRule(outputSelector, "text-align", align.toLowerCase());
}
var valign = getParam(["valign", "vertical", "position"]);
if (hasEnabledParam(["top", "aligntop"])) {
valign = "top";
} else if (hasEnabledParam(["middle", "alignmiddle"])) {
valign = "middle";
} else if (hasEnabledParam(["alignbottom"])) {
valign = "bottom";
}
if (/^(top|middle|center|bottom)$/i.test(String(valign || ""))) {
valign = valign.toLowerCase();
if (valign === "top") {
addRule("body", "top", "0");
addRule("body", "bottom", "auto");
addRule("body", "flex-direction", "column");
} else if (valign === "middle" || valign === "center") {
addRule("body", "top", "50%");
addRule("body", "bottom", "auto");
addRule("body", "transform", "translateY(-50%)");
addRule("body", "flex-direction", "column");
} else if (valign === "bottom") {
addRule("body", "top", "auto");
addRule("body", "bottom", "0");
}
}
var topPos = cssLength(getParam(["toppos", "topoffset"]), "px");
var bottomPos = cssLength(getParam(["bottompos", "bottomoffset"]), "px");
var leftPos = cssLength(getParam(["leftpos", "leftoffset"]), "px");
var rightPos = cssLength(getParam(["rightpos", "rightoffset"]), "px");
if (topPos) {
addRule("body", "top", topPos);
addRule("body", "bottom", "auto");
}
if (bottomPos) {
addRule("body", "bottom", bottomPos);
addRule("body", "top", "auto");
}
if (leftPos) {
addRule("body", "left", leftPos);
}
if (rightPos) {
addRule("body", "right", rightPos);
}
if (hasEnabledParam(["nowrap", "singleline"])) {
addRule(outputSelector, "white-space", "nowrap");
addRule(outputSelector, "overflow", "hidden");
addRule(spanSelector, "display", "inline-block");
addRule(spanSelector, "overflow", "hidden");
addRule(spanSelector, "text-overflow", "ellipsis");
} else if (hasEnabledParam(["wrap"])) {
addRule(outputSelector, "white-space", "normal");
}
if (hasEnabledParam(["block", "inlineblock", "inline-block"])) {
addRule(spanSelector, "display", "inline-block");
}
var shadowParam = getParam(["shadow", "textshadow", "text-shadow"]);
var noShadow = hasEnabledParam(["noshadow", "no-shadow"]) || (hasParam(["shadow", "textshadow", "text-shadow"]) && isOff(shadowParam));
if (noShadow) {
addRule(outputSelector, "text-shadow", "none");
} else {
var shadowValue = safeCssValue(shadowParam, "text-shadow");
if (shadowValue && !/^(1|true|on|yes)$/i.test(shadowValue)) {
addRule(outputSelector, "text-shadow", shadowValue);
} else {
var shadowColor = normalizeColor(getParam(["shadowcolor", "shadow-color"]));
var shadowX = cssLength(getParam(["shadowx", "shadow-x"]), "px");
var shadowY = cssLength(getParam(["shadowy", "shadow-y"]), "px");
var shadowBlur = cssLength(getParam(["shadowblur", "shadow-blur"]), "px");
if (shadowColor || shadowX || shadowY || shadowBlur) {
addRule(outputSelector, "text-shadow", (shadowX || "0.05em") + " " + (shadowY || "0.05em") + " " + (shadowBlur || "0px") + " " + (shadowColor || "rgba(0,0,0,1)"));
}
}
}
var outlineParam = getParam(["outline", "outlinewidth", "outline-width", "stroke", "strokewidth", "stroke-width"]);
if (hasEnabledParam(["nooutline", "no-outline"])) {
addRule(outputSelector, "text-shadow", "none");
} else if (hasValue(outlineParam) && !isOff(outlineParam)) {
var outlineWidth = cssLength(outlineParam === "" ? "2" : outlineParam, "px");
var outlineColor = normalizeColor(getParam(["outlinecolor", "outline-color", "strokecolor", "stroke-color"])) || "#000";
if (outlineWidth && outlineColor) {
addRule(outputSelector, "text-shadow", buildOutline(outlineWidth, outlineColor));
}
}
var labelColor = normalizeColor(getParam(["labelcolor", "label-color"]));
var labelBg = normalizeColor(getParam(["labelbg", "labelbackground", "label-bg"]));
if (labelColor) {
addRule(labelSelector, "color", labelColor);
}
if (labelBg) {
addRule(labelSelector, "background-color", labelBg);
addRule(labelSelector, "background", labelBg);
}
var translationColor = normalizeColor(getParam(["translationcolor", "translation-color"]));
var originalColor = normalizeColor(getParam(["originalcolor", "original-color"]));
if (translationColor) {
addRule("#output .dual-translation, #interm .dual-translation", "color", translationColor);
}
if (originalColor) {
addRule("#output .dual-original, #interm .dual-original", "color", originalColor);
}
var css = "";
for (var selector in rules) {
if (Object.prototype.hasOwnProperty.call(rules, selector) && rules[selector].length) {
css += selector + " { " + rules[selector].join("; ") + "; }\n";
}
}
if (css) {
addCss(css);
}
var cssUrl = getParam(["css", "customcssurl", "styleurl"]);
if (hasValue(cssUrl) && !/[<>"']/.test(cssUrl)) {
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = cssUrl;
document.head.appendChild(link);
}
var cssBase64 = getParam(["base64css", "b64css", "cssbase64", "cssb64"]);
if (hasValue(cssBase64)) {
try {
addCss(atob(cssBase64));
} catch (e) {
console.warn("Unable to decode overlay CSS parameter", e);
}
}
})();
+6 -5
View File
@@ -1258,8 +1258,9 @@
var bridgeUrl = window.location.origin + window.location.pathname.replace(/\/[^^/]*$/, '') + '/tts.rocks/caption-bridge.html?' + qs.toString();
window.open(bridgeUrl, '_blank', 'noopener');
}
} catch (e) { console.warn(e); }
</script>
</body>
</html>
} catch (e) { console.warn(e); }
</script>
<script src="overlay-style-params.js"></script>
</body>
</html>
+5 -4
View File
@@ -1016,8 +1016,9 @@
socket.onclose = function (){
setTimeout(function(){window.location.reload(true);},100);
};
</script>
</body>
};
</script>
<script src="overlay-style-params.js"></script>
</body>
</html>
+11
View File
@@ -30,6 +30,17 @@ This enhanced overlay supports local Mozilla Bergamot translation plus remote pr
- `intermclear=1` clears finished captions while interim text streams.
- `dual=1` (or `view=dual`) shows translated text above original text.
### 6. Overlay Style Parameters
- Existing overlay defaults are unchanged unless a style parameter is added.
- `nobg=1` or `bg=transparent` removes the black caption highlight.
- `bg=COLOR`, `color=COLOR`, `fontcolor=COLOR`, `fontsize=SIZE`, `scale=N`, `font=FAMILY`, `fontweight=VALUE`, `lineheight=VALUE`, and `letterspacing=SIZE` customize text appearance.
- `pad=SIZE`, `padx=SIZE`, `pady=SIZE`, `radius=SIZE`, `borderwidth=SIZE`, and `bordercolor=COLOR` customize the caption highlight box.
- `shadow=none`, `noshadow=1`, `shadow=VALUE`, `shadowcolor=COLOR`, `outline=SIZE`, and `outlinecolor=COLOR` customize text shadow/outline.
- `align=left|center|right`, `valign=top|middle|bottom`, `topoffset=SIZE`, `bottomoffset=SIZE`, `leftoffset=SIZE`, and `rightoffset=SIZE` customize placement.
- `nowrap=1`, `uppercase=1`, `lowercase=1`, `opacity=N`, `bgopacity=N`, `labelcolor=COLOR`, `labelbg=COLOR`, `translationcolor=COLOR`, and `originalcolor=COLOR` provide additional display control.
- `css=URL` and `base64css=...` allow advanced custom styles.
- `vmix.html?room=abc123` is a vMix-friendly shortcut that forwards to `overlay.html` with `nobg=1` unless a background/highlight parameter is already provided.
## Examples
### Google Cloud (Backward Compatible)
+17 -11
View File
@@ -24,17 +24,23 @@
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://caption.ninja/overlay</loc>
<lastmod>2025-01-12</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://caption.ninja/capture-pro</loc>
<lastmod>2025-01-12</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
<url>
<loc>https://caption.ninja/overlay</loc>
<lastmod>2025-01-12</lastmod>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://caption.ninja/vmix</loc>
<lastmod>2026-04-22</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://caption.ninja/capture-pro</loc>
<lastmod>2025-01-12</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://caption.ninja/manual</loc>
+57
View File
@@ -0,0 +1,57 @@
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta content="utf-8" http-equiv="encoding" />
<meta name="copyright" content="&copy; 2020 Steve Seguin" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<link rel="shortcut icon" href="favicon.svg" />
<title>Caption Overlay Window for vMix</title>
<meta name="title" content="CAPTION.Ninja: vMix Overlay" />
<meta name="description" content="vMix-friendly Caption Ninja overlay shortcut with transparent caption backing by default." />
<meta name="author" content="Steve Seguin" />
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: transparent;
overflow: hidden;
}
</style>
</head>
<body>
<script>
(function () {
var params = new URLSearchParams(window.location.search);
var backgroundParams = [
"nobg",
"no-bg",
"nobox",
"nohighlight",
"no-highlight",
"transparentbg",
"transparent-bg",
"bg",
"background",
"captionbg",
"captionbackground",
"highlight",
"highlightcolor",
"boxcolor"
];
var hasBackgroundChoice = backgroundParams.some(function (param) {
return params.has(param);
});
if (!hasBackgroundChoice) {
params.set("nobg", "1");
}
var target = "overlay.html";
var query = params.toString();
window.location.replace(target + (query ? "?" + query : ""));
})();
</script>
</body>
</html>