add subtitle track option

This commit is contained in:
Pas 2025-05-18 14:15:59 -06:00
parent 3428abdbc1
commit dd1af01c57
3 changed files with 230 additions and 179 deletions

View file

@ -528,7 +528,9 @@
"dropSubtitleFile": "Drop subtitle file here! >_<",
"scrapeButton": "Scrape subtitles",
"empty": "There are no provided subtitles for this.",
"notFound": "None of the available options match your query"
"notFound": "None of the available options match your query",
"useNativeSubtitles": "Use native video subtitles",
"useNativeSubtitlesDescription": "May fix subtitles when casting and in PiP"
},
"watchparty": {
"watchpartyItem": "Watch Party",

View file

@ -9,6 +9,7 @@ import { Icon, Icons } from "@/components/Icon";
import { Menu } from "@/components/player/internals/ContextMenu";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { useProgressBar } from "@/hooks/useProgressBar";
import { usePlayerStore } from "@/stores/player/store";
import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles";
export function ColorOption(props: {
@ -234,6 +235,8 @@ export function CaptionSettingsView({
const setOverrideCasing = subtitleStore.setOverrideCasing;
const setDelay = subtitleStore.setDelay;
const updateStyling = subtitleStore.updateStyling;
const setCaptionAsTrack = usePlayerStore((s) => s.setCaptionAsTrack);
const captionAsTrack = usePlayerStore((s) => s.caption.asTrack);
useEffect(() => {
subtitleStore.updateStyling(styling);
@ -264,189 +267,229 @@ export function CaptionSettingsView({
{t("player.menus.subtitles.settings.backlink")}
</Menu.BackLink>
<Menu.Section className="space-y-6 pb-5">
<CaptionSetting
label={t("player.menus.subtitles.settings.delay")}
max={20}
min={-20}
onChange={(v) => setDelay(v)}
value={delay}
textTransformer={(s) => `${s}s`}
decimalsAllowed={1}
controlButtons
/>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("player.menus.subtitles.settings.fixCapitals")}
</Menu.FieldTitle>
<div className="flex justify-center items-center">
<Toggle
enabled={overrideCasing}
onClick={() => setOverrideCasing(!overrideCasing)}
/>
</div>
</div>
<Menu.Divider />
<CaptionSetting
label={t("settings.subtitles.backgroundLabel")}
max={100}
min={0}
onChange={(v) =>
handleStylingChange({ ...styling, backgroundOpacity: v / 100 })
}
value={styling.backgroundOpacity * 100}
textTransformer={(s) => `${s}%`}
/>
<CaptionSetting
label={t("settings.subtitles.backgroundBlurLabel")}
max={100}
min={0}
onChange={(v) =>
handleStylingChange({ ...styling, backgroundBlur: v / 100 })
}
value={styling.backgroundBlur * 100}
textTransformer={(s) => `${s}%`}
/>
<CaptionSetting
label={t("settings.subtitles.textSizeLabel")}
max={200}
min={1}
textTransformer={(s) => `${s}%`}
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" />
{!captionAsTrack ? (
<>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("player.menus.subtitles.useNativeSubtitles")}
</Menu.FieldTitle>
<div className="flex justify-center items-center">
<Toggle
enabled={captionAsTrack}
onClick={() => setCaptionAsTrack(!captionAsTrack)}
/>
</div>
</div>
</div>
</div>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("settings.subtitles.verticalPositionLabel")}
</Menu.FieldTitle>
<div className="flex justify-center items-center space-x-2">
<button
type="button"
className={classNames(
"px-3 py-1 rounded transition-colors duration-100",
styling.verticalPosition === 3
? "bg-video-context-buttonFocus"
: "bg-video-context-buttonFocus bg-opacity-0 hover:bg-opacity-50",
)}
onClick={() =>
handleStylingChange({
...styling,
verticalPosition: 3,
})
<span className="text-xs text-type-secondary">
{t("player.menus.subtitles.useNativeSubtitlesDescription")}
</span>
<CaptionSetting
label={t("player.menus.subtitles.settings.delay")}
max={20}
min={-20}
onChange={(v) => setDelay(v)}
value={delay}
textTransformer={(s) => `${s}s`}
decimalsAllowed={1}
controlButtons
/>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t("player.menus.subtitles.settings.fixCapitals")}
</Menu.FieldTitle>
<div className="flex justify-center items-center">
<Toggle
enabled={overrideCasing}
onClick={() => setOverrideCasing(!overrideCasing)}
/>
</div>
</div>
<Menu.Divider />
<CaptionSetting
label={t("settings.subtitles.backgroundLabel")}
max={100}
min={0}
onChange={(v) =>
handleStylingChange({ ...styling, backgroundOpacity: v / 100 })
}
>
{t("settings.subtitles.default")}
</button>
<button
type="button"
className={classNames(
"px-3 py-1 rounded transition-colors duration-100",
styling.verticalPosition === 1
? "bg-video-context-buttonFocus"
: "bg-video-context-buttonFocus bg-opacity-0 hover:bg-opacity-50",
)}
onClick={() =>
handleStylingChange({
...styling,
verticalPosition: 1,
})
value={styling.backgroundOpacity * 100}
textTransformer={(s) => `${s}%`}
/>
<CaptionSetting
label={t("settings.subtitles.backgroundBlurLabel")}
max={100}
min={0}
onChange={(v) =>
handleStylingChange({ ...styling, backgroundBlur: v / 100 })
}
value={styling.backgroundBlur * 100}
textTransformer={(s) => `${s}%`}
/>
<CaptionSetting
label={t("settings.subtitles.textSizeLabel")}
max={200}
min={1}
textTransformer={(s) => `${s}%`}
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")}
</Menu.FieldTitle>
<div className="flex justify-center items-center space-x-2">
<button
type="button"
className={classNames(
"px-3 py-1 rounded transition-colors duration-100",
styling.verticalPosition === 3
? "bg-video-context-buttonFocus"
: "bg-video-context-buttonFocus bg-opacity-0 hover:bg-opacity-50",
)}
onClick={() =>
handleStylingChange({
...styling,
verticalPosition: 3,
})
}
>
{t("settings.subtitles.default")}
</button>
<button
type="button"
className={classNames(
"px-3 py-1 rounded transition-colors duration-100",
styling.verticalPosition === 1
? "bg-video-context-buttonFocus"
: "bg-video-context-buttonFocus bg-opacity-0 hover:bg-opacity-50",
)}
onClick={() =>
handleStylingChange({
...styling,
verticalPosition: 1,
})
}
>
{t("settings.subtitles.low")}
</button>
</div>
</div>
<Button
className="w-full md:w-auto"
theme="secondary"
onClick={resetSubStyling}
>
{t("settings.subtitles.low")}
</button>
</div>
</div>
<Button
className="w-full md:w-auto"
theme="secondary"
onClick={resetSubStyling}
>
{t("settings.reset")}
</Button>
{t("settings.reset")}
</Button>
</>
) : (
<>
<div className="flex justify-between items-center">
<Menu.FieldTitle>
{t(
"player.menus.subtitles.settings.useNativeSubtitles",
"Use native video subtitles",
)}
</Menu.FieldTitle>
<div className="flex justify-center items-center">
<Toggle
enabled={captionAsTrack}
onClick={() => setCaptionAsTrack(!captionAsTrack)}
/>
</div>
</div>
<span className="text-xs text-type-secondary">
{t("player.menus.subtitles.useNativeSubtitlesDescription")}
</span>
</>
)}
</Menu.Section>
</>
);

View file

@ -90,6 +90,7 @@ export interface SourceSlice {
setSourceId(id: string | null): void;
enableAutomaticQuality(): void;
redisplaySource(startAt: number): void;
setCaptionAsTrack(asTrack: boolean): void;
}
export function metaToScrapeMedia(meta: PlayerMeta): ScrapeMedia {
@ -222,4 +223,9 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
const store = get();
store.display?.changeQuality(true, null);
},
setCaptionAsTrack(asTrack: boolean) {
set((s) => {
s.caption.asTrack = asTrack;
});
},
});