mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
commit
27e73a8ad4
4 changed files with 121 additions and 11 deletions
|
@ -317,7 +317,7 @@
|
||||||
"unknownOption": "Unknown"
|
"unknownOption": "Unknown"
|
||||||
},
|
},
|
||||||
"subtitles": {
|
"subtitles": {
|
||||||
"customChoice": "Select subtitle from file",
|
"customChoice": "Drop or upload file",
|
||||||
"customizeLabel": "Customize",
|
"customizeLabel": "Customize",
|
||||||
"offChoice": "Off",
|
"offChoice": "Off",
|
||||||
"settings": {
|
"settings": {
|
||||||
|
@ -326,7 +326,8 @@
|
||||||
"fixCapitals": "Fix capitalization"
|
"fixCapitals": "Fix capitalization"
|
||||||
},
|
},
|
||||||
"title": "Subtitles",
|
"title": "Subtitles",
|
||||||
"unknownLanguage": "Unknown"
|
"unknownLanguage": "Unknown",
|
||||||
|
"dropSubtitleFile": "Drop subtitle file here"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|
51
src/components/DropFile.tsx
Normal file
51
src/components/DropFile.tsx
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import type { DragEvent, ReactNode } from "react";
|
||||||
|
|
||||||
|
interface FileDropHandlerProps {
|
||||||
|
children: ReactNode;
|
||||||
|
className: string;
|
||||||
|
onDrop: (event: DragEvent<HTMLDivElement>) => void;
|
||||||
|
onDraggingChange: (isDragging: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FileDropHandler(props: FileDropHandlerProps) {
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
|
|
||||||
|
const handleDragEnter = (event: DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setDragging(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragLeave = (event: DragEvent<HTMLDivElement>) => {
|
||||||
|
if (!event.currentTarget.contains(event.relatedTarget as Node)) {
|
||||||
|
setDragging(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (event: DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setDragging(false);
|
||||||
|
|
||||||
|
props.onDrop(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
props.onDraggingChange(dragging);
|
||||||
|
}, [dragging, props]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
className={props.className}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -64,6 +64,7 @@ export enum Icons {
|
||||||
DONATION = "donation",
|
DONATION = "donation",
|
||||||
CIRCLE_QUESTION = "circle_question",
|
CIRCLE_QUESTION = "circle_question",
|
||||||
BRUSH = "brush",
|
BRUSH = "brush",
|
||||||
|
UPLOAD = "upload",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IconProps {
|
export interface IconProps {
|
||||||
|
@ -134,6 +135,7 @@ const iconList: Record<Icons, string> = {
|
||||||
donation: `<svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="currentColor" d="M163.9 136.9c-29.4-29.8-29.4-78.2 0-108s77-29.8 106.4 0l17.7 18 17.7-18c29.4-29.8 77-29.8 106.4 0s29.4 78.2 0 108L310.5 240.1c-6.2 6.3-14.3 9.4-22.5 9.4s-16.3-3.1-22.5-9.4L163.9 136.9zM568.2 336.3c13.1 17.8 9.3 42.8-8.5 55.9L433.1 485.5c-23.4 17.2-51.6 26.5-80.7 26.5H192 32c-17.7 0-32-14.3-32-32V416c0-17.7 14.3-32 32-32H68.8l44.9-36c22.7-18.2 50.9-28 80-28H272h16 64c17.7 0 32 14.3 32 32s-14.3 32-32 32H288 272c-8.8 0-16 7.2-16 16s7.2 16 16 16H392.6l119.7-88.2c17.8-13.1 42.8-9.3 55.9 8.5zM193.6 384l0 0-.9 0c.3 0 .6 0 .9 0z"/></svg>`,
|
donation: `<svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="currentColor" d="M163.9 136.9c-29.4-29.8-29.4-78.2 0-108s77-29.8 106.4 0l17.7 18 17.7-18c29.4-29.8 77-29.8 106.4 0s29.4 78.2 0 108L310.5 240.1c-6.2 6.3-14.3 9.4-22.5 9.4s-16.3-3.1-22.5-9.4L163.9 136.9zM568.2 336.3c13.1 17.8 9.3 42.8-8.5 55.9L433.1 485.5c-23.4 17.2-51.6 26.5-80.7 26.5H192 32c-17.7 0-32-14.3-32-32V416c0-17.7 14.3-32 32-32H68.8l44.9-36c22.7-18.2 50.9-28 80-28H272h16 64c17.7 0 32 14.3 32 32s-14.3 32-32 32H288 272c-8.8 0-16 7.2-16 16s7.2 16 16 16H392.6l119.7-88.2c17.8-13.1 42.8-9.3 55.9 8.5zM193.6 384l0 0-.9 0c.3 0 .6 0 .9 0z"/></svg>`,
|
||||||
circle_question: `<svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="currentColor" d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm169.8-90.7c7.9-22.3 29.1-37.3 52.8-37.3h58.3c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24V250.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1H222.6c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>`,
|
circle_question: `<svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path opacity="1" fill="currentColor" d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm169.8-90.7c7.9-22.3 29.1-37.3 52.8-37.3h58.3c34.9 0 63.1 28.3 63.1 63.1c0 22.6-12.1 43.5-31.7 54.8L280 264.4c-.2 13-10.9 23.6-24 23.6c-13.3 0-24-10.7-24-24V250.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1c0-8.4-6.8-15.1-15.1-15.1H222.6c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>`,
|
||||||
brush: `<svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M162.4 6c-1.5-3.6-5-6-8.9-6h-19c-3.9 0-7.5 2.4-8.9 6L104.9 57.7c-3.2 8-14.6 8-17.8 0L66.4 6c-1.5-3.6-5-6-8.9-6H48C21.5 0 0 21.5 0 48V224v22.4V256H9.6 374.4 384v-9.6V224 48c0-26.5-21.5-48-48-48H230.5c-3.9 0-7.5 2.4-8.9 6L200.9 57.7c-3.2 8-14.6 8-17.8 0L162.4 6zM0 288v32c0 35.3 28.7 64 64 64h64v64c0 35.3 28.7 64 64 64s64-28.7 64-64V384h64c35.3 0 64-28.7 64-64V288H0zM192 432a16 16 0 1 1 0 32 16 16 0 1 1 0-32z" fill="currentColor"/></svg>`,
|
brush: `<svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M162.4 6c-1.5-3.6-5-6-8.9-6h-19c-3.9 0-7.5 2.4-8.9 6L104.9 57.7c-3.2 8-14.6 8-17.8 0L66.4 6c-1.5-3.6-5-6-8.9-6H48C21.5 0 0 21.5 0 48V224v22.4V256H9.6 374.4 384v-9.6V224 48c0-26.5-21.5-48-48-48H230.5c-3.9 0-7.5 2.4-8.9 6L200.9 57.7c-3.2 8-14.6 8-17.8 0L162.4 6zM0 288v32c0 35.3 28.7 64 64 64h64v64c0 35.3 28.7 64 64 64s64-28.7 64-64V384h64c35.3 0 64-28.7 64-64V288H0zM192 432a16 16 0 1 1 0 32 16 16 0 1 1 0-32z" fill="currentColor"/></svg>`,
|
||||||
|
upload: `<svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path opacity="1" fill="currentColor" d="M320 480H64c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32H192V144c0 26.5 21.5 48 48 48H352V448c0 17.7-14.3 32-32 32zM240 160c-8.8 0-16-7.2-16-16V32.5c2.8 .7 5.4 2.1 7.4 4.2L347.3 152.6c2.1 2.1 3.5 4.6 4.2 7.4H240zM64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V163.9c0-12.7-5.1-24.9-14.1-33.9L254.1 14.1c-9-9-21.2-14.1-33.9-14.1H64zM208 278.6l52.7 52.7c6.2 6.2 16.4 6.2 22.6 0s6.2-16.4 0-22.6l-80-80c-6.2-6.2-16.4-6.2-22.6 0l-80 80c-6.2 6.2-6.2 16.4 0 22.6s16.4 6.2 22.6 0L176 278.6V400c0 8.8 7.2 16 16 16s16-7.2 16-16V278.6z"/></svg>`,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChromeCastButton() {
|
function ChromeCastButton() {
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
import classNames from "classnames";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { useMemo, useRef, useState } from "react";
|
import { type DragEvent, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAsyncFn } from "react-use";
|
import { useAsyncFn } from "react-use";
|
||||||
import { convert } from "subsrt-ts";
|
import { convert } from "subsrt-ts";
|
||||||
|
|
||||||
import { subtitleTypeList } from "@/backend/helpers/subs";
|
import { subtitleTypeList } from "@/backend/helpers/subs";
|
||||||
|
import { FileDropHandler } from "@/components/DropFile";
|
||||||
import { FlagIcon } from "@/components/FlagIcon";
|
import { FlagIcon } from "@/components/FlagIcon";
|
||||||
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
import { useCaptions } from "@/components/player/hooks/useCaptions";
|
import { useCaptions } from "@/components/player/hooks/useCaptions";
|
||||||
import { Menu } from "@/components/player/internals/ContextMenu";
|
import { Menu } from "@/components/player/internals/ContextMenu";
|
||||||
import { Input } from "@/components/player/internals/ContextMenu/Input";
|
import { Input } from "@/components/player/internals/ContextMenu/Input";
|
||||||
|
@ -123,6 +126,34 @@ export function CaptionsView({ id }: { id: string }) {
|
||||||
const { selectCaptionById, disable } = useCaptions();
|
const { selectCaptionById, disable } = useCaptions();
|
||||||
const captionList = usePlayerStore((s) => s.captionList);
|
const captionList = usePlayerStore((s) => s.captionList);
|
||||||
const getHlsCaptionList = usePlayerStore((s) => s.display?.getCaptionList);
|
const getHlsCaptionList = usePlayerStore((s) => s.display?.getCaptionList);
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
|
const setCaption = usePlayerStore((s) => s.setCaption);
|
||||||
|
|
||||||
|
function onDrop(event: DragEvent<HTMLDivElement>) {
|
||||||
|
const files = event.dataTransfer.files;
|
||||||
|
const firstFile = files[0];
|
||||||
|
if (!files || !firstFile) return;
|
||||||
|
|
||||||
|
const fileExtension = `.${firstFile.name.split(".").pop()}`;
|
||||||
|
if (!fileExtension || !subtitleTypeList.includes(fileExtension)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener("load", (e) => {
|
||||||
|
if (!e.target || typeof e.target.result !== "string") return;
|
||||||
|
|
||||||
|
const converted = convert(e.target.result, "srt");
|
||||||
|
|
||||||
|
setCaption({
|
||||||
|
language: "custom",
|
||||||
|
srtData: converted,
|
||||||
|
id: "custom-caption",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.readAsText(firstFile);
|
||||||
|
}
|
||||||
|
|
||||||
const captions = useMemo(
|
const captions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -164,6 +195,20 @@ export function CaptionsView({ id }: { id: string }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"absolute inset-0 flex items-center justify-center text-white z-10 pointer-events-none transition-opacity duration-300",
|
||||||
|
dragging ? "opacity-100" : "opacity-0",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<Icon className="text-5xl mb-4" icon={Icons.UPLOAD} />
|
||||||
|
<span className="text-xl weight font-medium">
|
||||||
|
{t("player.menus.subtitles.dropSubtitleFile")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Menu.BackLink
|
<Menu.BackLink
|
||||||
onClick={() => router.navigate("/")}
|
onClick={() => router.navigate("/")}
|
||||||
rightSide={
|
rightSide={
|
||||||
|
@ -178,17 +223,28 @@ export function CaptionsView({ id }: { id: string }) {
|
||||||
>
|
>
|
||||||
{t("player.menus.subtitles.title")}
|
{t("player.menus.subtitles.title")}
|
||||||
</Menu.BackLink>
|
</Menu.BackLink>
|
||||||
|
</div>
|
||||||
|
<FileDropHandler
|
||||||
|
className={`transition duration-300 ${dragging ? "opacity-20" : ""}`}
|
||||||
|
onDraggingChange={(isDragging) => {
|
||||||
|
setDragging(isDragging);
|
||||||
|
}}
|
||||||
|
onDrop={(event) => onDrop(event)}
|
||||||
|
>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Input value={searchQuery} onInput={setSearchQuery} />
|
<Input value={searchQuery} onInput={setSearchQuery} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Menu.ScrollToActiveSection className="!pt-1 mt-2 pb-3">
|
||||||
<Menu.ScrollToActiveSection className="!pt-1 mt-2 pb-3">
|
<CaptionOption
|
||||||
<CaptionOption onClick={() => disable()} selected={!selectedCaptionId}>
|
onClick={() => disable()}
|
||||||
{t("player.menus.subtitles.offChoice")}
|
selected={!selectedCaptionId}
|
||||||
</CaptionOption>
|
>
|
||||||
<CustomCaptionOption />
|
{t("player.menus.subtitles.offChoice")}
|
||||||
{content}
|
</CaptionOption>
|
||||||
</Menu.ScrollToActiveSection>
|
<CustomCaptionOption />
|
||||||
|
{content}
|
||||||
|
</Menu.ScrollToActiveSection>
|
||||||
|
</FileDropHandler>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue