39 changed files with 1005 additions and 684 deletions
@ -0,0 +1,152 @@
@@ -0,0 +1,152 @@
|
||||
import { describe, it } from "vitest"; |
||||
|
||||
import { |
||||
getMWCaptionTypeFromUrl, |
||||
isSupportedSubtitle, |
||||
parseSubtitles, |
||||
} from "@/backend/helpers/captions"; |
||||
import { MWCaptionType } from "@/backend/helpers/streams"; |
||||
|
||||
import { |
||||
ass, |
||||
multilineSubtitlesTestVtt, |
||||
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); |
||||
}); |
||||
|
||||
it("should parse multiline captions", ({ expect }) => { |
||||
const parsed = parseSubtitles(multilineSubtitlesTestVtt); |
||||
|
||||
expect(parsed[0].text).toBe(`- Test 1\n- Test 2\n- Test 3`); |
||||
expect(parsed[1].text).toBe(`- Test 4`); |
||||
expect(parsed[2].text).toBe(`- Test 6`); |
||||
}); |
||||
}); |
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
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 |
||||
`;
|
||||
|
||||
const multilineSubtitlesTestVtt = `WEBVTT
|
||||
|
||||
00:00:00.000 --> 00:00:10.000 |
||||
- Test 1\n- Test 2\n- Test 3 |
||||
|
||||
00:00:10.000 --> 00:00:20.000 |
||||
- Test 4 |
||||
|
||||
00:00:20.000 --> 00:00:31.000 |
||||
- Test 6 |
||||
`;
|
||||
|
||||
export { vtt, srt, ass, visibleSubtitlesTestVtt, multilineSubtitlesTestVtt }; |
Loading…
Reference in new issue