diff --git a/src/__tests__/subtitles/subtitles.test.ts b/src/__tests__/subtitles/subtitles.test.ts new file mode 100644 index 00000000..90d659dd --- /dev/null +++ b/src/__tests__/subtitles/subtitles.test.ts @@ -0,0 +1,136 @@ +import { describe, it } from "vitest"; +import { + isSupportedSubtitle, + getMWCaptionTypeFromUrl, + parseSubtitles, +} from "@/backend/helpers/captions"; +import { MWCaptionType } from "@/backend/helpers/streams"; +import { ass, srt, visibleSubtitlesTestVtt, vtt } from "./testdata"; + +describe("subtitles", () => { + it("should return true if given url ends with a known subtitle type", ({ + expect, + }) => { + expect(isSupportedSubtitle("https://example.com/test.srt")).toBe(true); + expect(isSupportedSubtitle("https://example.com/test.vtt")).toBe(true); + expect(isSupportedSubtitle("https://example.com/test.txt")).toBe(false); + }); + + it("should return corresponding MWCaptionType", ({ expect }) => { + expect(getMWCaptionTypeFromUrl("https://example.com/test.srt")).toBe( + MWCaptionType.SRT + ); + expect(getMWCaptionTypeFromUrl("https://example.com/test.vtt")).toBe( + MWCaptionType.VTT + ); + expect(getMWCaptionTypeFromUrl("https://example.com/test.txt")).toBe( + MWCaptionType.UNKNOWN + ); + }); + + it("should throw when empty text is given", ({ expect }) => { + expect(() => parseSubtitles("")).toThrow("Given text is empty"); + }); + + it("should parse srt", ({ expect }) => { + const parsed = parseSubtitles(srt); + const parsedSrt = [ + { + type: "caption", + index: 1, + start: 0, + end: 0, + duration: 0, + content: "Test", + text: "Test", + }, + { + type: "caption", + index: 2, + start: 0, + end: 0, + duration: 0, + content: "Test", + text: "Test", + }, + ]; + expect(parsed).toHaveLength(2); + expect(parsed).toEqual(parsedSrt); + }); + + it("should parse vtt", ({ expect }) => { + const parsed = parseSubtitles(vtt); + const parsedVtt = [ + { + type: "caption", + index: 1, + start: 0, + end: 4000, + duration: 4000, + content: "Where did he go?", + text: "Where did he go?", + }, + { + type: "caption", + index: 2, + start: 3000, + end: 6500, + duration: 3500, + content: "I think he went down this lane.", + text: "I think he went down this lane.", + }, + { + type: "caption", + index: 3, + start: 4000, + end: 6500, + duration: 2500, + content: "What are you waiting for?", + text: "What are you waiting for?", + }, + ]; + expect(parsed).toHaveLength(3); + expect(parsed).toEqual(parsedVtt); + }); + + it("should parse ass", ({ expect }) => { + const parsed = parseSubtitles(ass); + expect(parsed).toHaveLength(3); + }); + + it("should delay subtitles when given a delay", ({ expect }) => { + const videoTime = 11; + let delayedSeconds = 0; + const parsed = parseSubtitles(visibleSubtitlesTestVtt); + const isVisible = (start: number, end: number, delay: number): boolean => { + const delayedStart = start / 1000 + delay; + const delayedEnd = end / 1000 + delay; + return ( + Math.max(0, delayedStart) <= videoTime && + Math.max(0, delayedEnd) >= videoTime + ); + }; + const visibleSubtitles = parsed.filter((c) => + isVisible(c.start, c.end, delayedSeconds) + ); + expect(visibleSubtitles).toHaveLength(1); + + delayedSeconds = 10; + const delayedVisibleSubtitles = parsed.filter((c) => + isVisible(c.start, c.end, delayedSeconds) + ); + expect(delayedVisibleSubtitles).toHaveLength(1); + + delayedSeconds = -10; + const delayedVisibleSubtitles2 = parsed.filter((c) => + isVisible(c.start, c.end, delayedSeconds) + ); + expect(delayedVisibleSubtitles2).toHaveLength(1); + + delayedSeconds = -20; + const delayedVisibleSubtitles3 = parsed.filter((c) => + isVisible(c.start, c.end, delayedSeconds) + ); + expect(delayedVisibleSubtitles3).toHaveLength(1); + }); +}); diff --git a/src/__tests__/subtitles/testdata.ts b/src/__tests__/subtitles/testdata.ts new file mode 100644 index 00000000..a121e705 --- /dev/null +++ b/src/__tests__/subtitles/testdata.ts @@ -0,0 +1,56 @@ +const srt = ` +1 +00:00:00,000 --> 00:00:00,000 +Test + +2 +00:00:00,000 --> 00:00:00,000 +Test +`; +const vtt = ` +WEBVTT + +00:00:00.000 --> 00:00:04.000 position:10%,line-left align:left size:35% +Where did he go? + +00:00:03.000 --> 00:00:06.500 position:90% align:right size:35% +I think he went down this lane. + +00:00:04.000 --> 00:00:06.500 position:45%,line-right align:center size:35% +What are you waiting for? +`; +const ass = `[Script Info] +; Generated by Ebby.co +Title: +Original Script: +ScriptType: v4.00+ +Collisions: Normal +PlayResX: 384 +PlayResY: 288 +PlayDepth: 0 +Timer: 100.0 +WrapStyle: 0 + +[v4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default, Arial, 16, &H00FFFFFF, &H00000000, &H00000000, &H00000000, 0, 0, 0, 0, 100, 100, 0, 0, 1, 1, 0, 2, 15, 15, 15, 0 + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,0:00:10.00,0:00:20.00,Default,,0000,0000,0000,,This is the first subtitle. +Dialogue: 0,0:00:30.00,0:00:34.00,Default,,0000,0000,0000,,This is the second. +Dialogue: 0,0:00:34.00,0:00:35.00,Default,,0000,0000,0000,,Third`; + +const visibleSubtitlesTestVtt = `WEBVTT + +00:00:00.000 --> 00:00:10.000 position:10%,line-left align:left size:35% +Test 1 + +00:00:10.000 --> 00:00:20.000 position:90% align:right size:35% +Test 2 + +00:00:20.000 --> 00:00:31.000 position:45%,line-right align:center size:35% +Test 3 +`; + +export { vtt, srt, ass, visibleSubtitlesTestVtt };