package main import ( "os" "path/filepath" "reflect" "strings" "testing" ) func TestParseSRT(t *testing.T) { src := "\uFEFF1\r\n00:00:01,000 --> 00:00:02,500\r\nHello\r\nworld\r\n\r\n" + "3\r\n00:00:05,5 --> 00:00:06.25 X1:10 X2:20\r\nSecond\r\n" + // short ms, dot, position "2\r\n00:00:03,000 --> 00:00:04,000\r\n Missing blank line before \r\n\r\n\r\n" + "4\n00:00:07,000 --> 00:00:07,000\nzero length\n\n" + "5\n01:02,000 --> 01:03,000\nno hours\n\n" + "not a cue\n" got := parseSRT(decodeText([]byte(src))) want := []Cue{ {1, 2.5, "Hello\nworld"}, {3, 4, "Missing blank line before"}, {5.5, 6.25, "Second"}, {62, 63, "no hours"}, } if !reflect.DeepEqual(got, want) { t.Fatalf("parseSRT:\n got %+v\nwant %+v", got, want) } } func TestDecodeText(t *testing.T) { for name, tc := range map[string]struct { in []byte want string }{ "utf8": {[]byte("caf\u00e9"), "caf\u00e9"}, "utf8 bom": {[]byte("\xEF\xBB\xBFcaf\u00e9"), "caf\u00e9"}, "utf16le": {[]byte{0xFF, 0xFE, 'h', 0, 0xE9, 0}, "h\u00e9"}, "utf16be": {[]byte{0xFE, 0xFF, 0, 'h', 0, 0xE9}, "h\u00e9"}, "cp1252": {[]byte("\x93caf\xe9\x94 \x80"), "\u201ccaf\u00e9\u201d \u20ac"}, } { if got := decodeText(tc.in); got != tc.want { t.Errorf("%s: got %q, want %q", name, got, tc.want) } } } func TestWriteClipCaptions(t *testing.T) { cues := []Cue{ {0.5, 1.5, "before"}, {9, 11, "spans In"}, {12.0333, 13, "inside"}, {19, 25, "spans Out"}, {30, 31, "after"}, } clips := []clipJob{{in: 10, out: 20, cues: cues}, {in: 40, out: 50, cues: cues}, {in: 0, out: 5}} dir, err := writeClipCaptions(clips) if dir != "" { defer os.RemoveAll(dir) } if err != nil { t.Fatal(err) } if clips[1].captions != "" || clips[2].captions != "" { t.Errorf("clips without captions in range got files: %q, %q", clips[1].captions, clips[2].captions) } b, err := os.ReadFile(clips[0].captions) if err != nil { t.Fatal(err) } want := "1\n00:00:00,000 --> 00:00:01,000\nspans In\n\n" + "2\n00:00:02,033 --> 00:00:03,000\ninside\n\n" + "3\n00:00:09,000 --> 00:00:10,000\nspans Out\n\n" if string(b) != want { t.Errorf("clip captions:\n got %q\nwant %q", b, want) } if again := parseSRT(string(b)); len(again) != 3 { t.Errorf("written file parses into %d cues, want 3", len(again)) } } func TestSRTTime(t *testing.T) { for sec, want := range map[float64]string{ 0: "00:00:00,000", 1.0333333: "00:00:01,033", 2.9999: "00:00:02,999", // rounds down, like the subtitles filter 0.1 + 0.2: "00:00:00,300", // but not below float noise 3723.5: "01:02:03,500", 36000.0005: "10:00:00,000", } { if got := srtTime(sec); got != want { t.Errorf("srtTime(%v) = %s, want %s", sec, got, want) } } } func TestFilterArg(t *testing.T) { // Both parsers turn a backslash followed by any character into that // character, so undoing the two levels must give back the original. unescape := func(s string) string { var b strings.Builder for i := 0; i < len(s); i++ { if s[i] == '\\' && i+1 < len(s) { i++ } b.WriteByte(s[i]) } return b.String() } for _, s := range []string{ `/tmp/vibedit-captions-1/clip_0001.srt`, `C:\Users\Zoƫ O'Brien\AppData\Local\Temp\vibedit-captions-1\clip_0001.srt`, `/weird/[dir];a,b:c'd\e/clip.srt`, `Fontsize=16,BorderStyle=3,OutlineColour=&H00000000`, } { esc := filterArg(s) if got := unescape(unescape(esc)); got != s { t.Errorf("filterArg(%q) = %q, which unescapes to %q", s, esc, got) } // After the graph level, no character that ends the option value may // be left unescaped. lvl1 := unescape(esc) for i := 0; i < len(lvl1); i++ { if lvl1[i] == '\\' { i++ } else if strings.IndexByte(`':`, lvl1[i]) >= 0 { t.Errorf("filterArg(%q): unescaped %q at option level: %q", s, lvl1[i], lvl1) } } } if got := filterArg(`C:\a,b`); got != `C\\:\\\\a\,b` { t.Errorf("filterArg(`C:\\a,b`) = %s", got) } } func TestCaptionFilter(t *testing.T) { dir := t.TempDir() srt := filepath.Join(dir, "clip_0001.srt") for _, tc := range []struct { w, h int want string }{ {1920, 1080, "PlayResX=512\\,PlayResY=288\\,ScaledBorderAndShadow=yes\\,Fontsize=16\\,"}, {1080, 1920, "PlayResX=288\\,PlayResY=512\\,ScaledBorderAndShadow=yes\\,Fontsize=16\\,"}, } { got := captionFilter(srt, outFormat{W: tc.w, H: tc.h, CaptionSize: "medium", CaptionStyle: "box"}) if !strings.HasPrefix(got, "subtitles=filename=") || !strings.Contains(got, ":force_style="+tc.want+"BorderStyle=3") { t.Errorf("%dx%d: %s", tc.w, tc.h, got) } } }