From ca402a219d0a2bfd5648ab33c755424fda1d2bb3 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 25 Oct 2023 19:14:41 +0200 Subject: [PATCH] fix custom subs + download --- src/components/Button.tsx | 2 +- .../player/atoms/settings/CaptionsView.tsx | 5 +++-- .../player/atoms/settings/Downloads.tsx | 16 ++++++++------ src/components/player/utils/captions.ts | 22 +++++++++++++++++-- src/stores/subtitles/index.ts | 7 ++++++ 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 288d1604..af75a9c4 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -13,7 +13,7 @@ interface Props { className?: string; href?: string; disabled?: boolean; - download?: boolean; + download?: string; } export function Button(props: Props) { diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index 5b0aa5ca..bf2708d0 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -15,6 +15,7 @@ import { Input } from "@/components/player/internals/ContextMenu/Input"; import { SelectableLink } from "@/components/player/internals/ContextMenu/Links"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { usePlayerStore } from "@/stores/player/store"; +import { useSubtitleStore } from "@/stores/subtitles"; export function CaptionOption(props: { countryCode?: string; @@ -94,6 +95,7 @@ function searchSubs( function CustomCaptionOption() { const lang = usePlayerStore((s) => s.caption.selected?.language); const setCaption = usePlayerStore((s) => s.setCaption); + const setCustomSubs = useSubtitleStore((s) => s.setCustomSubs); const fileInput = useRef(null); return ( @@ -118,6 +120,7 @@ function CustomCaptionOption() { language: "custom", srtData: converted, }); + setCustomSubs(); }); reader.readAsText(e.target.files[0], "utf-8"); }} @@ -126,9 +129,7 @@ function CustomCaptionOption() { ); } -// TODO on initialize, download captions // TODO fix language names, some are unknown -// TODO delay setting for captions export function CaptionsView({ id }: { id: string }) { const router = useOverlayRouter(id); const lang = usePlayerStore((s) => s.caption.selected?.language); diff --git a/src/components/player/atoms/settings/Downloads.tsx b/src/components/player/atoms/settings/Downloads.tsx index 252ea135..a230d87c 100644 --- a/src/components/player/atoms/settings/Downloads.tsx +++ b/src/components/player/atoms/settings/Downloads.tsx @@ -4,7 +4,7 @@ import { Button } from "@/components/Button"; import { Icon, Icons } from "@/components/Icon"; import { OverlayPage } from "@/components/overlays/OverlayPage"; import { Menu } from "@/components/player/internals/ContextMenu"; -import { convertSubtitlesToDataurl } from "@/components/player/utils/captions"; +import { convertSubtitlesToSrtDataurl } from "@/components/player/utils/captions"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { usePlayerStore } from "@/stores/player/store"; @@ -24,11 +24,13 @@ export function DownloadView({ id }: { id: string }) { const downloadUrl = useDownloadLink(); const selectedCaption = usePlayerStore((s) => s.caption?.selected); - const subtitleUrl = selectedCaption - ? convertSubtitlesToDataurl(selectedCaption?.srtData) - : null; - - console.log(subtitleUrl); + const subtitleUrl = useMemo( + () => + selectedCaption + ? convertSubtitlesToSrtDataurl(selectedCaption?.srtData) + : null, + [selectedCaption] + ); if (!downloadUrl) return null; @@ -66,7 +68,7 @@ export function DownloadView({ id }: { id: string }) { href={subtitleUrl ?? undefined} disabled={!subtitleUrl} theme="secondary" - download + download="subtitles.srt" > Download current caption diff --git a/src/components/player/utils/captions.ts b/src/components/player/utils/captions.ts index 182a3d08..4204ad0b 100644 --- a/src/components/player/utils/captions.ts +++ b/src/components/player/utils/captions.ts @@ -35,13 +35,31 @@ export function convertSubtitlesToVtt(text: string): string { return vtt; } +export function convertSubtitlesToSrt(text: string): string { + const textTrimmed = text.trim(); + if (textTrimmed === "") { + throw new Error("Given text is empty"); + } + const srt = convert(textTrimmed, "srt"); + if (detect(srt) === "") { + throw new Error("Invalid subtitle format"); + } + return srt; +} + export function parseSubtitles(text: string): CaptionCueType[] { const vtt = convertSubtitlesToVtt(text); return parse(vtt).filter((cue) => cue.type === "caption") as CaptionCueType[]; } -export function convertSubtitlesToDataurl(text: string): string { - return `data:text/vtt,${convertSubtitlesToVtt(text)}`; +function stringToBase64(input: string): string { + return btoa(String.fromCodePoint(...new TextEncoder().encode(input))); +} + +export function convertSubtitlesToSrtDataurl(text: string): string { + return `data:application/x-subrip;base64,${stringToBase64( + convertSubtitlesToSrt(text) + )}`; } export function convertSubtitlesToObjectUrl(text: string): string { diff --git a/src/stores/subtitles/index.ts b/src/stores/subtitles/index.ts index 16b34e00..9a7f1e86 100644 --- a/src/stores/subtitles/index.ts +++ b/src/stores/subtitles/index.ts @@ -27,6 +27,7 @@ export interface SubtitleStore { delay: number; updateStyling(newStyling: Partial): void; setLanguage(language: string | null): void; + setCustomSubs(): void; setOverrideCasing(enabled: boolean): void; setDelay(delay: number): void; } @@ -60,6 +61,12 @@ export const useSubtitleStore = create( if (lang) s.lastSelectedLanguage = lang; }); }, + setCustomSubs() { + set((s) => { + s.enabled = true; + s.lastSelectedLanguage = null; + }); + }, setOverrideCasing(enabled) { set((s) => { s.overrideCasing = enabled;