Skip to content

Commit

Permalink
Merge pull request #679 from bitmovin/feature/subtitle-styling-edgeco…
Browse files Browse the repository at this point in the history
…lor-fontstyle

Support for customizing the font style and character edge color of subtitles/captions
  • Loading branch information
dweinber authored Jan 15, 2025
2 parents 69b0981 + 8c5bab4 commit 46c7e33
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 18 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Added
- Support for changing the font style of subtitles and closed captions
- Support for changing the edge color of characters of subtitles and closed captions

## [3.83.0] - 2025-01-14

## Fixed
Expand Down
15 changes: 15 additions & 0 deletions src/scss/skin-modern/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,18 @@
outline: none;
}
}

/// Replace `$search` with `$replace` in `$string`. From https://css-tricks.com/snippets/sass/str-replace-function/
/// @param {String} $string - Initial string
/// @param {String} $search - Substring to replace
/// @param {String} $replace ('') - New value
/// @return {String} - Updated string
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);

@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}

@return $string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@

$character-edges: (
'none': 'none',
'raised': '0px 0px 4px rgba(0, 0, 0, 0.9), 0px 1px 4px rgba(0, 0, 0, 0.9), 0px 2px 4px rgba(0, 0, 0, 0.9)',
'depressed': 'rgba(0, 0, 0, 0.8) 0px -2px 1px',
'uniform': '-2px 0px 1px rgba(0, 0, 0, 0.8), 2px 0px 1px rgba(0, 0, 0, 0.8), 0px -2px 1px rgba(0, 0, 0, 0.8), 0px 2px 1px rgba(0, 0, 0, 0.8), -1px 1px 1px rgba(0, 0, 0, 0.8), 1px 1px 1px rgba(0, 0, 0, 0.8), 1px -1px 1px rgba(0, 0, 0, 0.8), 1px 1px 1px rgba(0, 0, 0, 0.8)',
'dropshadowed': ' 0px 2px 1px rgba(0, 0, 0, 0.8)',
'raised': '0px 0px 4px $COLOR$, 0px 1px 4px $COLOR$, 0px 2px 4px $COLOR$',
'depressed': '$COLOR$ 0px -2px 1px',
'uniform': '-2px 0px 1px $COLOR$, 2px 0px 1px $COLOR$, 0px -2px 1px $COLOR$, 0px 2px 1px $COLOR$, -1px 1px 1px $COLOR$, 1px 1px 1px $COLOR$, 1px -1px 1px $COLOR$, 1px 1px 1px $COLOR$',
'dropshadowed': ' 0px 2px 1px $COLOR$',
);

$font-sizes: (
Expand Down Expand Up @@ -79,11 +79,13 @@
}
}

// Font character edge
@each $name, $value in $character-edges {
&.#{$prefix}-characteredge-#{$name} {
.#{$prefix}-ui-subtitle-label {
text-shadow: unquote($value);
// Font character edge and character edge color
@each $color-name, $color-value in $colors {
@each $name, $value in $character-edges {
&.#{$prefix}-characteredge-#{$name}-#{$color-name} {
.#{$prefix}-ui-subtitle-label {
text-shadow: unquote(str-replace($value, '$COLOR$', $color-value));
}
}
}
}
Expand Down Expand Up @@ -131,4 +133,17 @@
font-variant: small-caps;
}
}

// Font Style
&.#{$prefix}-fontstyle-italic {
.#{$prefix}-ui-subtitle-label {
font-style: italic;
}
}

&.#{$prefix}-fontstyle-bold {
.#{$prefix}-ui-subtitle-label {
font-weight: bold;
}
}
}
2 changes: 1 addition & 1 deletion src/ts/components/listselector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export abstract class ListSelector<Config extends ListSelectorConfig> extends Co
* @param sortedInsert whether the item should be added respecting the order of keys
* @param ariaLabel custom aria label for the listItem
*/
addItem(key: string, label: LocalizableText, sortedInsert = false, ariaLabel = '') {
addItem(key: string|null, label: LocalizableText, sortedInsert = false, ariaLabel = '') {
const listItem = { key: key, label: i18n.performLocalization(label), ...(ariaLabel && { ariaLabel })};

// Apply filter function
Expand Down
57 changes: 57 additions & 0 deletions src/ts/components/subtitlesettings/characteredgecolorselectbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { SubtitleSettingSelectBox, SubtitleSettingSelectBoxConfig } from './subtitlesettingselectbox';
import { UIInstanceManager } from '../../uimanager';
import { PlayerAPI } from 'bitmovin-player';
import { i18n } from '../../localization/i18n';

/**
* A select box providing a selection of different character edge colors.
*
* @category Components
*/
export class CharacterEdgeColorSelectBox extends SubtitleSettingSelectBox {

constructor(config: SubtitleSettingSelectBoxConfig) {
super(config);

this.config = this.mergeConfig(config, {
cssClasses: ['ui-subtitle-settings-character-edge-color-select-box'],
}, this.config);
}

configure(player: PlayerAPI, uimanager: UIInstanceManager): void {
super.configure(player, uimanager);

this.addItem(null, i18n.getLocalizer('default'));
this.addItem('white', i18n.getLocalizer('colors.white'));
this.addItem('black', i18n.getLocalizer('colors.black'));
this.addItem('red', i18n.getLocalizer('colors.red'));
this.addItem('green', i18n.getLocalizer('colors.green'));
this.addItem('blue', i18n.getLocalizer('colors.blue'));
this.addItem('cyan', i18n.getLocalizer('colors.cyan'));
this.addItem('yellow', i18n.getLocalizer('colors.yellow'));
this.addItem('magenta', i18n.getLocalizer('colors.magenta'));

this.onItemSelected.subscribe((sender, key: string) => {
this.settingsManager.characterEdgeColor.value = key;

// Edge type and color go together, so we need to...
if (!this.settingsManager.characterEdgeColor.isSet()) {
// ... clear the edge type when the color is not set
this.settingsManager.characterEdge.clear();
} else if (!this.settingsManager.characterEdge.isSet()) {
// ... set a edge type when the color is set
this.settingsManager.characterEdge.value = 'uniform';
}
});

// Update selected item when value is set from somewhere else
this.settingsManager.characterEdgeColor.onChanged.subscribe((sender, property) => {
this.selectItem(property.value);
});

// Load initial value
if (this.settingsManager.characterEdgeColor.isSet()) {
this.selectItem(this.settingsManager.characterEdgeColor.value);
}
}
}
28 changes: 21 additions & 7 deletions src/ts/components/subtitlesettings/characteredgeselectbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,34 @@ export class CharacterEdgeSelectBox extends SubtitleSettingSelectBox {
this.addItem('uniform', i18n.getLocalizer('settings.subtitles.characterEdge.uniform'));
this.addItem('dropshadowed', i18n.getLocalizer('settings.subtitles.characterEdge.dropshadowed'));

this.settingsManager.characterEdge.onChanged.subscribe((sender, property) => {
if (property.isSet()) {
this.toggleOverlayClass('characteredge-' + property.value);
const setColorAndEdgeType = () => {
if (this.settingsManager.characterEdge.isSet() && this.settingsManager.characterEdgeColor.isSet()) {
this.toggleOverlayClass('characteredge-' + this.settingsManager.characterEdge.value + '-' + this.settingsManager.characterEdgeColor.value);
} else {
this.toggleOverlayClass(null);
}

// Select the item in case the property was set from outside
this.selectItem(property.value);
});
}

this.onItemSelected.subscribe((sender, key: string) => {
this.settingsManager.characterEdge.value = key;
});

this.settingsManager.characterEdge.onChanged.subscribe((sender, property) => {
// Edge type and color go together, so we need to...
if (!this.settingsManager.characterEdge.isSet()) {
// ... clear the color when the edge type is not set
this.settingsManager.characterEdgeColor.clear();
} else if (!this.settingsManager.characterEdgeColor.isSet()) {
// ... set a color when the edge type is set
this.settingsManager.characterEdgeColor.value = 'black';
}
this.selectItem(property.value);
setColorAndEdgeType();
});

this.settingsManager.characterEdgeColor.onChanged.subscribe(() => {
setColorAndEdgeType();
});

// Load initial value
if (this.settingsManager.characterEdge.isSet()) {
Expand Down
50 changes: 50 additions & 0 deletions src/ts/components/subtitlesettings/fontstyleselectbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { SubtitleSettingSelectBox, SubtitleSettingSelectBoxConfig } from './subtitlesettingselectbox';
import { UIInstanceManager } from '../../uimanager';
import { PlayerAPI } from 'bitmovin-player';
import { i18n } from '../../localization/i18n';

/**
* A select box providing a selection of different font styles.
*
* @category Components
*/
export class FontStyleSelectBox extends SubtitleSettingSelectBox {

constructor(config: SubtitleSettingSelectBoxConfig) {
super(config);

this.config = this.mergeConfig(config, {
cssClasses: ['ui-subtitle-settings-font-style-select-box'],
}, this.config);
}

configure(player: PlayerAPI, uimanager: UIInstanceManager): void {
super.configure(player, uimanager);

this.addItem(null, i18n.getLocalizer('default'));
this.addItem('italic', i18n.getLocalizer('settings.subtitles.font.style.italic'));
this.addItem('bold', i18n.getLocalizer('settings.subtitles.font.style.bold'));

this.settingsManager?.fontStyle.onChanged.subscribe((sender, property) => {
if (property.isSet()) {
this.toggleOverlayClass('fontstyle-' + property.value);
} else {
this.toggleOverlayClass(null);
}

// Select the item in case the property was set from outside
this.selectItem(property.value);
});

this.onItemSelected.subscribe((sender, key: string) => {
if (this.settingsManager) {
this.settingsManager.fontStyle.value = key;
}
});

// Load initial value
if (this.settingsManager?.fontStyle.isSet()) {
this.selectItem(this.settingsManager.fontStyle.value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class SubtitleSettingSelectBox extends SelectBox {
* Removes a previously set class and adds the passed in class.
* @param cssClass The new class to replace the previous class with or null to just remove the previous class
*/
protected toggleOverlayClass(cssClass: string): void {
protected toggleOverlayClass(cssClass: string|null): void {
// Remove previous class if existing
if (this.currentCssClass) {
this.overlay.getDomElement().removeClass(this.currentCssClass);
Expand Down
12 changes: 12 additions & 0 deletions src/ts/components/subtitlesettings/subtitlesettingsmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ interface SubtitleSettings {
fontOpacity?: string;
fontFamily?: string;
fontSize?: string;
fontStyle?: string;
characterEdge?: string;
characterEdgeColor?: string;
backgroundColor?: string;
backgroundOpacity?: string;
windowColor?: string;
Expand All @@ -30,7 +32,9 @@ export class SubtitleSettingsManager {
fontOpacity: new SubtitleSettingsProperty<string>(this),
fontFamily: new SubtitleSettingsProperty<string>(this),
fontSize: new SubtitleSettingsProperty<string>(this),
fontStyle: new SubtitleSettingsProperty<string>(this),
characterEdge: new SubtitleSettingsProperty<string>(this),
characterEdgeColor: new SubtitleSettingsProperty<string>(this),
backgroundColor: new SubtitleSettingsProperty<string>(this),
backgroundOpacity: new SubtitleSettingsProperty<string>(this),
windowColor: new SubtitleSettingsProperty<string>(this),
Expand Down Expand Up @@ -65,10 +69,18 @@ export class SubtitleSettingsManager {
return this._properties.fontSize;
}

public get fontStyle(): SubtitleSettingsProperty<string> {
return this._properties.fontStyle;
}

public get characterEdge(): SubtitleSettingsProperty<string> {
return this._properties.characterEdge;
}

public get characterEdgeColor(): SubtitleSettingsProperty<string> {
return this._properties.characterEdgeColor;
}

public get backgroundColor(): SubtitleSettingsProperty<string> {
return this._properties.backgroundColor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import {ContainerConfig} from '../container';
import {SubtitleSettingsManager} from './subtitlesettingsmanager';
import {Component, ComponentConfig} from '../component';
import {FontSizeSelectBox} from './fontsizeselectbox';
import {FontStyleSelectBox} from './fontstyleselectbox';
import {FontFamilySelectBox} from './fontfamilyselectbox';
import {FontColorSelectBox} from './fontcolorselectbox';
import {FontOpacitySelectBox} from './fontopacityselectbox';
import {CharacterEdgeSelectBox} from './characteredgeselectbox';
import {CharacterEdgeColorSelectBox} from './characteredgecolorselectbox';
import {BackgroundColorSelectBox} from './backgroundcolorselectbox';
import {BackgroundOpacitySelectBox} from './backgroundopacityselectbox';
import {WindowColorSelectBox} from './windowcolorselectbox';
Expand Down Expand Up @@ -48,6 +50,9 @@ export class SubtitleSettingsPanelPage extends SettingsPanelPage {
new SettingsPanelItem(i18n.getLocalizer('settings.subtitles.font.size'), new FontSizeSelectBox({
overlay: this.overlay,
})),
new SettingsPanelItem(i18n.getLocalizer('settings.subtitles.font.style'), new FontStyleSelectBox({
overlay: this.overlay,
})),
new SettingsPanelItem(i18n.getLocalizer('settings.subtitles.font.family'), new FontFamilySelectBox({
overlay: this.overlay,
})),
Expand All @@ -60,6 +65,9 @@ export class SubtitleSettingsPanelPage extends SettingsPanelPage {
new SettingsPanelItem(i18n.getLocalizer('settings.subtitles.characterEdge'), new CharacterEdgeSelectBox({
overlay: this.overlay,
})),
new SettingsPanelItem(i18n.getLocalizer('settings.subtitles.characterEdge.color'), new CharacterEdgeColorSelectBox({
overlay: this.overlay,
})),
new SettingsPanelItem(i18n.getLocalizer('settings.subtitles.background.color'), new BackgroundColorSelectBox({
overlay: this.overlay,
})),
Expand Down
4 changes: 4 additions & 0 deletions src/ts/localization/languages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
"off": "aus",
"settings.subtitles": "Untertitel",
"settings.subtitles.font.size": "Größe",
"settings.subtitles.font.style": "Schriftstil",
"settings.subtitles.font.style.bold": "Fett",
"settings.subtitles.font.style.italic": "Kursiv",
"settings.subtitles.font.family": "Schriftart",
"settings.subtitles.font.color": "Farbe",
"settings.subtitles.font.opacity": "Deckkraft",
"settings.subtitles.characterEdge": "Ränder",
"settings.subtitles.characterEdge.color": "Buchstabenrandfarbe",
"settings.subtitles.background.color": "Hintergrundfarbe",
"settings.subtitles.background.opacity": "Hintergrunddeckkraft",
"settings.subtitles.window.color": "Hintergrundfarbe",
Expand Down
4 changes: 4 additions & 0 deletions src/ts/localization/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
"colors.magenta": "magenta",
"percent": "{value}%",
"settings.subtitles.font.size": "Font size",
"settings.subtitles.font.style": "Font style",
"settings.subtitles.font.style.bold": "bold",
"settings.subtitles.font.style.italic": "italic",
"settings.subtitles.characterEdge": "Character edge",
"settings.subtitles.characterEdge.raised": "raised",
"settings.subtitles.characterEdge.depressed": "depressed",
"settings.subtitles.characterEdge.uniform": "uniform",
"settings.subtitles.characterEdge.dropshadowed": "drop shadowed",
"settings.subtitles.characterEdge.color": "Character edge color",
"settings.subtitles.font.family": "Font family",
"settings.subtitles.font.family.monospacedserif": "monospaced serif",
"settings.subtitles.font.family.proportionalserif": "proportional serif",
Expand Down
4 changes: 4 additions & 0 deletions src/ts/localization/languages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
"colors.magenta": "magenta",
"percent": "{value}%",
"settings.subtitles.font.size": "tamaño de Fuente",
"settings.subtitles.font.style": "Estilo de Fuente",
"settings.subtitles.font.style.bold": "negrita",
"settings.subtitles.font.style.italic": "cursiva",
"settings.subtitles.characterEdge": "borde del Caracter",
"settings.subtitles.characterEdge.raised": "alzado",
"settings.subtitles.characterEdge.depressed": "discreto",
"settings.subtitles.characterEdge.uniform": "uniforme",
"settings.subtitles.characterEdge.dropshadowed": "sombreado",
"settings.subtitles.characterEdge.color": "color de contorno de texto",
"settings.subtitles.font.family": "tipo de Fuente",
"settings.subtitles.font.family.monospacedserif": "monospaced serif",
"settings.subtitles.font.family.proportionalserif": "proportional serif",
Expand Down

0 comments on commit 46c7e33

Please sign in to comment.