mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
107 lines
2.5 KiB
Go
107 lines
2.5 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// srtToVTT performs the minimal SRT -> WebVTT transformation: prepend
|
|
// "WEBVTT\n\n" and replace ',' with '.' in the timecode separators.
|
|
func srtToVTT(body string) string {
|
|
body = strings.ReplaceAll(body, "\r\n", "\n")
|
|
out := strings.Builder{}
|
|
out.WriteString("WEBVTT\n\n")
|
|
for _, line := range strings.Split(body, "\n") {
|
|
if strings.Contains(line, "-->") {
|
|
line = strings.ReplaceAll(line, ",", ".")
|
|
}
|
|
out.WriteString(line)
|
|
out.WriteByte('\n')
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
// assToVTT extracts the dialogue lines from an ASS/SSA subtitle. Styling
|
|
// is dropped so the browser <track> element can still display text.
|
|
func assToVTT(body string) string {
|
|
out := strings.Builder{}
|
|
out.WriteString("WEBVTT\n\n")
|
|
seen := make(map[string]struct{})
|
|
for i, line := range strings.Split(body, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if !strings.HasPrefix(line, "Dialogue:") {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(line, ",", 10)
|
|
if len(parts) < 10 {
|
|
continue
|
|
}
|
|
start := normaliseTimecode(parts[1])
|
|
end := normaliseTimecode(parts[2])
|
|
text := stripASSTags(parts[9])
|
|
if text == "" {
|
|
continue
|
|
}
|
|
key := start + "\x00" + end + "\x00" + text
|
|
if _, duplicate := seen[key]; duplicate {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
fmt.Fprintf(&out, "%d\n%s --> %s\n%s\n\n",
|
|
i,
|
|
start,
|
|
end,
|
|
text,
|
|
)
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
func normaliseTimecode(t string) string {
|
|
t = strings.TrimSpace(t)
|
|
parts := strings.Split(t, ":")
|
|
if len(parts) != 3 {
|
|
return t
|
|
}
|
|
hh := parts[0]
|
|
if len(hh) == 1 {
|
|
hh = "0" + hh
|
|
}
|
|
// The final segment may be "ss" or "ss.mm"/"ss.mmm"/"ss.frames". WebVTT
|
|
// requires hh:mm:ss.mmm, so pad the millisecond part to 3 digits and cap
|
|
// at 3. Leaving 2-digit fractions (common in ASS: "ss.cc") breaks strict
|
|
// parsers.
|
|
last := strings.SplitN(parts[2], ".", 2)
|
|
sec := last[0]
|
|
if len(sec) == 1 {
|
|
sec = "0" + sec
|
|
}
|
|
frac := ""
|
|
if len(last) == 2 {
|
|
frac = last[1]
|
|
// ASS centiseconds -> WebVTT milliseconds: append a trailing zero so
|
|
// "51" becomes "510", "123" stays "123".
|
|
if len(frac) < 3 {
|
|
frac += strings.Repeat("0", 3-len(frac))
|
|
} else if len(frac) > 3 {
|
|
frac = frac[:3]
|
|
}
|
|
}
|
|
out := hh + ":" + parts[1] + ":" + sec
|
|
if frac != "" {
|
|
out += "." + frac
|
|
}
|
|
return out
|
|
}
|
|
|
|
var assTag = regexp.MustCompile(`\{[^}]*\}`)
|
|
|
|
func stripASSTags(s string) string {
|
|
s = assTag.ReplaceAllString(s, "")
|
|
s = strings.ReplaceAll(s, `\N`, "\n")
|
|
s = strings.ReplaceAll(s, `\n`, "\n")
|
|
s = strings.ReplaceAll(s, `\h`, "\u00a0")
|
|
return strings.TrimSpace(s)
|
|
}
|