1
0
Fork 0
mirror of https://github.com/sussy-code/smov.git synced 2024-12-21 14:47:41 +01:00

add caption settings popout

This commit is contained in:
frost768 2023-03-09 20:09:48 +03:00
parent 875be16c4c
commit ef4cb064e7
3 changed files with 341 additions and 58 deletions

View file

@ -4,14 +4,16 @@ import {
CUSTOM_CAPTION_ID, CUSTOM_CAPTION_ID,
} from "@/backend/helpers/captions"; } from "@/backend/helpers/captions";
import { MWCaption } from "@/backend/helpers/streams"; import { MWCaption } from "@/backend/helpers/streams";
import { IconButton } from "@/components/buttons/IconButton";
import { Icon, Icons } from "@/components/Icon"; import { Icon, Icons } from "@/components/Icon";
import { useLoading } from "@/hooks/useLoading"; import { useLoading } from "@/hooks/useLoading";
import { useVideoPlayerDescriptor } from "@/video/state/hooks"; import { useVideoPlayerDescriptor } from "@/video/state/hooks";
import { useControls } from "@/video/state/logic/controls"; import { useControls } from "@/video/state/logic/controls";
import { useMeta } from "@/video/state/logic/meta"; import { useMeta } from "@/video/state/logic/meta";
import { useSource } from "@/video/state/logic/source"; import { useSource } from "@/video/state/logic/source";
import { ChangeEvent, useMemo, useRef } from "react"; import { ChangeEvent, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { CaptionSettingsPopout } from "./CaptionSettingsPopout";
import { PopoutListEntry, PopoutSection } from "./PopoutUtils"; import { PopoutListEntry, PopoutSection } from "./PopoutUtils";
function makeCaptionId(caption: MWCaption, isLinked: boolean): string { function makeCaptionId(caption: MWCaption, isLinked: boolean): string {
@ -64,69 +66,80 @@ export function CaptionSelectionPopout() {
const captionFile = e.target.files[0]; const captionFile = e.target.files[0];
setCustomCaption(captionFile); setCustomCaption(captionFile);
} }
const [showCaptionSettings, setShowCaptionSettings] =
useState<boolean>(false);
return ( return (
<> <>
<PopoutSection className="bg-ash-100 font-bold text-white"> <PopoutSection className="flex flex-row justify-between bg-ash-100 font-bold text-white">
<div>{t("videoPlayer.popouts.captions")}</div> <div>{t("videoPlayer.popouts.captions")}</div>
<IconButton
icon={Icons.SETTINGS}
onClick={() => {
setShowCaptionSettings((old) => !old);
}}
/>
</PopoutSection> </PopoutSection>
<div className="relative overflow-y-auto"> {showCaptionSettings ? (
<PopoutSection> <CaptionSettingsPopout />
<PopoutListEntry ) : (
active={!currentCaption} <div className="relative overflow-y-auto">
onClick={() => { <PopoutSection>
controls.clearCaption(); <PopoutListEntry
controls.closePopout(); active={!currentCaption}
}} onClick={() => {
> controls.clearCaption();
{t("videoPlayer.popouts.noCaptions")} controls.closePopout();
</PopoutListEntry> }}
<PopoutListEntry >
key={CUSTOM_CAPTION_ID} {t("videoPlayer.popouts.noCaptions")}
active={currentCaption === CUSTOM_CAPTION_ID} </PopoutListEntry>
loading={loadingCustomCaption} <PopoutListEntry
errored={!!errorCustomCaption} key={CUSTOM_CAPTION_ID}
onClick={() => { active={currentCaption === CUSTOM_CAPTION_ID}
customCaptionUploadElement.current?.click(); loading={loadingCustomCaption}
}} errored={!!errorCustomCaption}
> onClick={() => {
{currentCaption === CUSTOM_CAPTION_ID customCaptionUploadElement.current?.click();
? t("videoPlayer.popouts.customCaption") }}
: t("videoPlayer.popouts.uploadCustomCaption")} >
<input {currentCaption === CUSTOM_CAPTION_ID
ref={customCaptionUploadElement} ? t("videoPlayer.popouts.customCaption")
type="file" : t("videoPlayer.popouts.uploadCustomCaption")}
onChange={handleUploadCaption} <input
className="hidden" ref={customCaptionUploadElement}
accept=".vtt, .srt" type="file"
/> onChange={handleUploadCaption}
</PopoutListEntry> className="hidden"
</PopoutSection> accept=".vtt, .srt"
/>
</PopoutListEntry>
</PopoutSection>
<p className="sticky top-0 z-10 flex items-center space-x-1 bg-ash-200 px-5 py-3 text-sm font-bold uppercase"> <p className="sticky top-0 z-10 flex items-center space-x-1 bg-ash-200 px-5 py-3 text-sm font-bold uppercase">
<Icon className="text-base" icon={Icons.LINK} /> <Icon className="text-base" icon={Icons.LINK} />
<span>{t("videoPlayer.popouts.linkedCaptions")}</span> <span>{t("videoPlayer.popouts.linkedCaptions")}</span>
</p> </p>
<PopoutSection className="pt-0"> <PopoutSection className="pt-0">
<div> <div>
{linkedCaptions.map((link) => ( {linkedCaptions.map((link) => (
<PopoutListEntry <PopoutListEntry
key={link.langIso} key={link.langIso}
active={link.id === currentCaption} active={link.id === currentCaption}
loading={loading && link.id === loadingId.current} loading={loading && link.id === loadingId.current}
errored={error && link.id === loadingId.current} errored={error && link.id === loadingId.current}
onClick={() => { onClick={() => {
loadingId.current = link.id; loadingId.current = link.id;
setCaption(link, true); setCaption(link, true);
}} }}
> >
{link.langIso} {link.langIso}
</PopoutListEntry> </PopoutListEntry>
))} ))}
</div> </div>
</PopoutSection> </PopoutSection>
</div> </div>
)}
</> </>
); );
} }

View file

@ -0,0 +1,189 @@
import { Dropdown, OptionItem } from "@/components/Dropdown";
import { useSettings } from "@/state/settings";
// import { useTranslation } from "react-i18next";
import { PopoutSection } from "./PopoutUtils";
export function CaptionSettingsPopout() {
// For now, won't add label texts to language files since options are prone to change
// const { t } = useTranslation();
const {
captionSettings,
setCaptionBackgroundColor,
setCaptionColor,
setCaptionDelay,
setCaptionFontSize,
setCaptionFontFamily,
setCaptionTextShadow,
} = useSettings();
// TODO: move it to context and specify which fonts to use
const fontFamilies: OptionItem[] = [
{ id: "Times New Roman", name: "Times New Roman" },
{ id: "monospace", name: "Monospace" },
{ id: "sans-serif", name: "Sans Serif" },
];
const selectedFont = fontFamilies.find(
(f) => f.id === captionSettings.style.fontFamily
) ?? { id: "monospace", name: "Monospace" };
// TODO: Slider and color picker styling or complete re-write
return (
<PopoutSection className="overflow-auto">
<Dropdown
setSelectedItem={(e) => {
setCaptionFontFamily(e.id);
}}
selectedItem={selectedFont}
options={fontFamilies}
/>
<div className="flex flex-row justify-between py-2">
<label className="font-bold text-white" htmlFor="fontSize">
Font Size ({captionSettings.style.fontSize})
</label>
<input
onChange={(e) => setCaptionFontSize(e.target.valueAsNumber)}
type="range"
name="fontSize"
id="fontSize"
max={30}
min={10}
step={1}
value={captionSettings.style.fontSize}
/>
</div>
<div className="flex flex-row justify-between py-2">
<label className="font-bold text-white">
Delay ({captionSettings.delay}s)
</label>
<input
onChange={(e) => setCaptionDelay(e.target.valueAsNumber)}
type="range"
max={10 * 1000}
min={-10 * 1000}
step={1}
/>
</div>
<div className="flex flex-row justify-between py-2">
<label className="font-bold text-white" htmlFor="captionColor">
Color
</label>
<input
onChange={(e) => setCaptionColor(e.target.value)}
type="color"
name="captionColor"
id="captionColor"
value={captionSettings.style.color}
/>
</div>
<div className="flex flex-row justify-between py-2">
<label className="font-bold text-white" htmlFor="backgroundColor">
Background Color
</label>
<input
onChange={(e) => setCaptionBackgroundColor(`${e.target.value}cc`)}
type="color"
name="backgroundColor"
id="backgroundColor"
value={captionSettings.style.backgroundColor}
/>
</div>
<div className="flex flex-row justify-between py-2">
<label
className="font-bold text-white"
htmlFor="backgroundColorOpacity"
>
Background Color Opacity
</label>
<input
onChange={(e) =>
setCaptionBackgroundColor(
`${captionSettings.style.backgroundColor.substring(
0,
7
)}${e.target.valueAsNumber.toString(16)}`
)
}
type="range"
min={0}
max={255}
name="backgroundColorOpacity"
id="backgroundColorOpacity"
value={Number.parseInt(
captionSettings.style.backgroundColor.substring(7, 9),
16
)}
/>
</div>
<div className="flex flex-row justify-between py-2">
<label className="font-bold text-white" htmlFor="textShadowColor">
Text Shadow Color
</label>
<input
onChange={(e) => {
const [offsetX, offsetY, blurRadius, color] =
captionSettings.style.textShadow.split(" ");
return setCaptionTextShadow(
`${offsetX} ${offsetY} ${blurRadius} ${e.target.value}`
);
}}
type="color"
name="textShadowColor"
id="textShadowColor"
value={captionSettings.style.textShadow.split(" ")[3]}
/>
</div>
<div className="flex flex-row justify-between py-2">
<label className="font-bold text-white">Text Shadow (Offset X)</label>
<input
onChange={(e) => {
const [offsetX, offsetY, blurRadius, color] =
captionSettings.style.textShadow.split(" ");
return setCaptionTextShadow(
`${e.target.valueAsNumber}px ${offsetY} ${blurRadius} ${color}`
);
}}
type="range"
min={-10}
max={10}
value={parseFloat(captionSettings.style.textShadow.split("px")[0])}
/>
</div>
<div className="flex flex-row justify-between py-2">
<label className="font-bold text-white">Text Shadow (Offset Y)</label>
<input
onChange={(e) => {
const [offsetX, offsetY, blurRadius, color] =
captionSettings.style.textShadow.split(" ");
return setCaptionTextShadow(
`${offsetX} ${e.target.value}px ${blurRadius} ${color}`
);
}}
type="range"
min={-10}
max={10}
value={parseFloat(captionSettings.style.textShadow.split("px")[1])}
/>
</div>
<div className="flex flex-row justify-between py-2">
<label className="font-bold text-white">Text Shadow Blur</label>
<input
onChange={(e) => {
const [offsetX, offsetY, blurRadius, color] =
captionSettings.style.textShadow.split(" ");
return setCaptionTextShadow(
`${offsetX} ${offsetY} ${e.target.valueAsNumber}px ${color}`
);
}}
type="range"
value={parseFloat(captionSettings.style.textShadow.split("px")[2])}
/>
</div>
</PopoutSection>
);
}

View file

@ -0,0 +1,81 @@
import { Dropdown, OptionItem } from "@/components/Dropdown";
import { useSettings } from "@/state/settings";
export function SettingsView() {
const languages: OptionItem[] = [
{ id: "en", name: "English" },
{ id: "tr", name: "Turkish" },
];
const {
language,
captionSettings,
setLanguage,
setCaptionBackgroundColor,
setCaptionColor,
setCaptionFontSize,
} = useSettings();
const selectedLanguage = languages.find((lang) => lang.id === language) || {
id: "en",
name: "English",
};
return (
<div className="flex aspect-square flex-row pl-28">
<div className="flex flex-col p-10">
<label className="font-bold text-white">Language</label>
<Dropdown
setSelectedItem={(item) => setLanguage(item.id)}
selectedItem={selectedLanguage}
options={languages}
/>
</div>
<div className="flex flex-col p-10">
<div className="font-bold text-white">Caption Settings</div>
<div className="flex flex-row">
<div className="flex flex-col">
<label className="font-bold text-white" htmlFor="fontSize">
Font Size
</label>
<input
onChange={(e) => setCaptionFontSize(e.target.valueAsNumber)}
type="range"
name="fontSize"
id="fontSize"
max={40}
min={10}
value={captionSettings.style.fontSize}
/>
<div className="flex flex-row justify-between">
<label className="font-bold text-white" htmlFor="color">
Color
</label>
<input
className="ml-10"
onChange={(e) => setCaptionColor(e.target.value)}
type="color"
name="color"
id="color"
value={captionSettings.style.color}
/>
</div>
<div className="flex flex-row justify-between">
<label className="font-bold text-white" htmlFor="bgColor">
Background Color
</label>
<input
className="ml-10"
onChange={(e) => setCaptionBackgroundColor(e.target.value)}
type="color"
name="bgColor"
id="bgColor"
value={captionSettings.style.backgroundColor}
/>
</div>
</div>
</div>
<div className="text-white">
{JSON.stringify(captionSettings, null, "\t\t")}
</div>
</div>
</div>
);
}