mirror of
https://github.com/p-stream/p-stream.git
synced 2026-04-14 12:20:20 +00:00
add subtitle track option
This commit is contained in:
parent
3428abdbc1
commit
dd1af01c57
3 changed files with 230 additions and 179 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue