add text styles to captions

This commit is contained in:
Pas 2025-05-02 19:19:15 -06:00
parent 53c6de1e6f
commit 3de83f28dd
5 changed files with 234 additions and 100 deletions

View file

@ -770,7 +770,15 @@
"textBoldLabel": "Bold text",
"verticalPositionLabel": "Vertical position",
"default": "Default",
"low": "Low"
"low": "Low",
"textStyle": {
"title": "Text style",
"default": "Default",
"raised": "Raised",
"depressed": "Depressed",
"uniform": "Uniform",
"dropShadow": "Drop Shadow"
}
},
"unsaved": "You have unsaved changes... ฅ^•ﻌ•^ฅ"
},

View file

@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/buttons/Button";
import { Toggle } from "@/components/buttons/Toggle";
import { Dropdown } from "@/components/form/Dropdown";
import { Icon, Icons } from "@/components/Icon";
import { Menu } from "@/components/player/internals/ContextMenu";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
@ -249,6 +250,7 @@ export function CaptionSettingsView({
size: 1,
backgroundBlur: 0.5,
bold: false,
fontStyle: "none",
});
};
@ -283,19 +285,6 @@ export function CaptionSettingsView({
/>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.textBoldLabel")}
</Menu.FieldTitle>
<div className="flex justify-center items-center">
<Toggle
enabled={styling.bold}
onClick={() =>
handleStylingChange({ ...styling, bold: !styling.bold })
}
/>
</div>
</div>
<Menu.Divider />
<CaptionSetting
label={t("settings.subtitles.backgroundLabel")}
@ -325,6 +314,91 @@ export function CaptionSettingsView({
onChange={(v) => handleStylingChange({ ...styling, size: v / 100 })}
value={styling.size * 100}
/>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.textStyle.title") || "Font Style"}
</Menu.FieldTitle>
<div className="w-64">
<Dropdown
options={[
{
id: "default",
name: t("settings.subtitles.textStyle.default"),
},
{
id: "raised",
name: t("settings.subtitles.textStyle.raised"),
},
{
id: "depressed",
name: t("settings.subtitles.textStyle.depressed"),
},
{
id: "uniform",
name: t("settings.subtitles.textStyle.uniform"),
},
{
id: "dropShadow",
name: t("settings.subtitles.textStyle.dropShadow"),
},
]}
selectedItem={{
id: styling.fontStyle,
name:
t(`settings.subtitles.textStyle.${styling.fontStyle}`) ||
styling.fontStyle,
}}
setSelectedItem={(item) =>
handleStylingChange({
...styling,
fontStyle: item.id,
})
}
/>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.textBoldLabel")}
</Menu.FieldTitle>
<div className="flex justify-center items-center">
<Toggle
enabled={styling.bold}
onClick={() =>
handleStylingChange({ ...styling, bold: !styling.bold })
}
/>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.colorLabel")}
</Menu.FieldTitle>
<div className="flex justify-center items-center space-x-2">
{colors.map((color) => (
<ColorOption
key={color}
color={color}
active={styling.color === color}
onClick={() => handleStylingChange({ ...styling, color })}
/>
))}
<div className="relative inline-block">
<input
type="color"
value={styling.color}
onChange={(e) => {
const color = e.target.value;
handleStylingChange({ ...styling, color });
}}
className="absolute opacity-0 cursor-pointer w-10 h-10"
/>
<div style={{ color: styling.color }}>
<Icon icon={Icons.BRUSH} className="text-2xl" />
</div>
</div>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.verticalPositionLabel")}
@ -366,36 +440,6 @@ export function CaptionSettingsView({
</button>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.colorLabel")}
</Menu.FieldTitle>
<div className="flex justify-center items-center space-x-2">
{colors.map((color) => (
<ColorOption
key={color}
color={color}
active={styling.color === color}
onClick={() => handleStylingChange({ ...styling, color })}
/>
))}
{/* Add Color Picker */}
<div className="relative inline-block">
<input
type="color"
value={styling.color}
onChange={(e) => {
const color = e.target.value;
handleStylingChange({ ...styling, color });
}}
className="absolute opacity-0 cursor-pointer w-10 h-10"
/>
<div style={{ color: styling.color }}>
<Icon icon={Icons.BRUSH} className="text-2xl" />
</div>
</div>
</div>
</div>
<Button
className="w-full md:w-auto"
theme="secondary"

View file

@ -56,9 +56,35 @@ export function CaptionCue({
return html;
}, [text, overrideCasing]);
const getTextEffectStyles = () => {
switch (styling.fontStyle) {
case "raised":
return {
textShadow: "0 2px 0 rgba(0,0,0,0.8), 0 1.5px 1.5px rgba(0,0,0,0.9)",
};
case "depressed":
return {
textShadow:
"0 -2px 0 rgba(0,0,0,0.8), 0 -1.5px 1.5px rgba(0,0,0,0.9)",
};
case "uniform":
return {
textShadow:
"1.5px 1.5px 1.5px rgba(0,0,0,0.8), -1.5px -1.5px 1.5px rgba(0,0,0,0.8), 1.5px -1.5px 1.5px rgba(0,0,0,0.8), -1.5px 1.5px 1.5px rgba(0,0,0,0.8)",
};
case "dropShadow":
return { textShadow: "2.5px 2.5px 4.5px rgba(0,0,0,0.9)" };
case "none":
default:
return { textShadow: "0 2px 4px rgba(0,0,0,0.5)" }; // Default is a light drop shadow
}
};
const textEffectStyles = getTextEffectStyles();
return (
<p
className="pointer-events-none mb-1 select-none rounded px-4 py-1 text-center leading-normal [text-shadow:0_2px_4px_rgba(0,0,0,0.5)]"
className="pointer-events-none mb-1 select-none rounded px-4 py-1 text-center leading-normal"
style={{
color: styling.color,
fontSize: `${(1.5 * styling.size).toFixed(2)}em`,
@ -68,6 +94,7 @@ export function CaptionCue({
? `blur(${Math.floor(styling.backgroundBlur * 64)}px)`
: "none",
fontWeight: styling.bold ? "bold" : "normal",
...textEffectStyles,
}}
>
<span

View file

@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/buttons/Button";
import { Toggle } from "@/components/buttons/Toggle";
import { Dropdown } from "@/components/form/Dropdown";
import { Icon, Icons } from "@/components/Icon";
import {
CaptionSetting,
@ -104,6 +105,7 @@ export function CaptionsPart(props: {
backgroundBlur: 0.5,
bold: false,
verticalPosition: 3,
fontStyle: "none",
});
};
@ -151,6 +153,104 @@ export function CaptionsPart(props: {
}
value={props.styling.size * 100}
/>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.textStyle.title")}
</Menu.FieldTitle>
<div className="w-30">
<Dropdown
options={[
{
id: "default",
name: t("settings.subtitles.textStyle.default"),
},
{
id: "raised",
name: t("settings.subtitles.textStyle.raised"),
},
{
id: "depressed",
name: t("settings.subtitles.textStyle.depressed"),
},
{
id: "uniform",
name: t("settings.subtitles.textStyle.uniform"),
},
{
id: "dropShadow",
name: t("settings.subtitles.textStyle.dropShadow"),
},
]}
selectedItem={{
id: props.styling.fontStyle,
name:
t(
`settings.subtitles.textStyle.${props.styling.fontStyle}`,
) || props.styling.fontStyle,
}}
setSelectedItem={(item) =>
handleStylingChange({
...props.styling,
fontStyle: item.id,
})
}
/>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.textBoldLabel")}
</Menu.FieldTitle>
<div className="flex justify-center items-center">
<Toggle
enabled={props.styling.bold}
onClick={() =>
handleStylingChange({
...props.styling,
bold: !props.styling.bold,
})
}
/>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.colorLabel")}
</Menu.FieldTitle>
<div className="flex justify-center items-center space-x-2">
{colors.map((v) => (
<ColorOption
onClick={() =>
handleStylingChange({
...props.styling,
color: v,
})
}
color={v}
active={props.styling.color === v}
key={v}
/>
))}
<div className="relative">
<input
type="color"
value={props.styling.color}
onChange={(e) => {
const color = e.target.value;
handleStylingChange({ ...props.styling, color });
subtitleStore.updateStyling({
...props.styling,
color,
});
}}
className="absolute opacity-0 cursor-pointer w-8 h-8"
/>
<div style={{ color: props.styling.color }}>
<Icon icon={Icons.BRUSH} className="text-2xl" />
</div>
</div>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.verticalPositionLabel")}
@ -192,61 +292,6 @@ export function CaptionsPart(props: {
</button>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.textBoldLabel")}
</Menu.FieldTitle>
<div className="flex justify-center items-center">
<Toggle
enabled={props.styling.bold}
onClick={() =>
handleStylingChange({
...props.styling,
bold: !props.styling.bold,
})
}
/>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.colorLabel")}
</Menu.FieldTitle>
<div className="flex justify-center items-center space-x-2">
{colors.map((v) => (
<ColorOption
onClick={() =>
handleStylingChange({
...props.styling,
color: v,
})
}
color={v}
active={props.styling.color === v}
key={v}
/>
))}
{/* Add Color Picker */}
<div className="relative">
<input
type="color"
value={props.styling.color}
onChange={(e) => {
const color = e.target.value;
handleStylingChange({ ...props.styling, color });
subtitleStore.updateStyling({
...props.styling,
color,
});
}}
className="absolute opacity-0 cursor-pointer w-8 h-8"
/>
<div style={{ color: props.styling.color }}>
<Icon icon={Icons.BRUSH} className="text-2xl" />
</div>
</div>
</div>
</div>
</div>
<CaptionPreview
show

View file

@ -33,6 +33,12 @@ export interface SubtitleStyling {
* vertical position percentage, ranges between 1 and 3 (rem)
*/
verticalPosition: number;
/**
* font style for text rendering
* "default" | "raised" | "depressed" | "uniform" | "dropShadow"
*/
fontStyle: string;
}
export interface SubtitleStore {
@ -76,6 +82,7 @@ export const useSubtitleStore = create(
backgroundBlur: 0.5,
bold: false,
verticalPosition: 3,
fontStyle: "none",
},
showDelayIndicator: false,
resetSubtitleSpecificSettings() {
@ -106,6 +113,8 @@ export const useSubtitleStore = create(
100,
Math.max(0, newStyling.verticalPosition),
);
if (newStyling.fontStyle !== undefined)
s.styling.fontStyle = newStyling.fontStyle;
});
},
resetStyling() {
@ -117,6 +126,7 @@ export const useSubtitleStore = create(
backgroundBlur: 0.5,
bold: false,
verticalPosition: 3,
fontStyle: "none",
};
});
},