mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-08 06:16:37 +08:00
bug处理
This commit is contained in:
@@ -27,6 +27,7 @@ func srtToVTT(body string) string {
|
|||||||
func assToVTT(body string) string {
|
func assToVTT(body string) string {
|
||||||
out := strings.Builder{}
|
out := strings.Builder{}
|
||||||
out.WriteString("WEBVTT\n\n")
|
out.WriteString("WEBVTT\n\n")
|
||||||
|
seen := make(map[string]struct{})
|
||||||
for i, line := range strings.Split(body, "\n") {
|
for i, line := range strings.Split(body, "\n") {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if !strings.HasPrefix(line, "Dialogue:") {
|
if !strings.HasPrefix(line, "Dialogue:") {
|
||||||
@@ -36,11 +37,22 @@ func assToVTT(body string) string {
|
|||||||
if len(parts) < 10 {
|
if len(parts) < 10 {
|
||||||
continue
|
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",
|
fmt.Fprintf(&out, "%d\n%s --> %s\n%s\n\n",
|
||||||
i,
|
i,
|
||||||
normaliseTimecode(parts[1]),
|
start,
|
||||||
normaliseTimecode(parts[2]),
|
end,
|
||||||
stripASSTags(parts[9]),
|
text,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return out.String()
|
return out.String()
|
||||||
@@ -86,5 +98,9 @@ func normaliseTimecode(t string) string {
|
|||||||
var assTag = regexp.MustCompile(`\{[^}]*\}`)
|
var assTag = regexp.MustCompile(`\{[^}]*\}`)
|
||||||
|
|
||||||
func stripASSTags(s string) string {
|
func stripASSTags(s string) string {
|
||||||
return assTag.ReplaceAllString(s, "")
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
@@ -84,6 +85,25 @@ func TestNormaliseTimecode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAssToVTTDeduplicatesDialogueAndNormalisesLineBreaks(t *testing.T) {
|
||||||
|
body := strings.Join([]string{
|
||||||
|
`Dialogue: 0,0:00:01.00,0:00:02.00,Default,,0,0,0,,{\an2}第一行\N第二行`,
|
||||||
|
`Dialogue: 1,0:00:01.00,0:00:02.00,Copy,,0,0,0,,{\bord2}第一行\N第二行`,
|
||||||
|
`Dialogue: 0,0:00:03.00,0:00:04.00,Default,,0,0,0,,{\i1}`,
|
||||||
|
}, "\n")
|
||||||
|
|
||||||
|
got := assToVTT(body)
|
||||||
|
if strings.Count(got, "第一行\n第二行") != 1 {
|
||||||
|
t.Fatalf("duplicate ASS dialogue was not collapsed:\n%s", got)
|
||||||
|
}
|
||||||
|
if strings.Contains(got, `\N`) || strings.Contains(got, `\an2`) {
|
||||||
|
t.Fatalf("ASS control sequences leaked into WebVTT:\n%s", got)
|
||||||
|
}
|
||||||
|
if strings.Contains(got, "00:00:03.000 --> 00:00:04.000") {
|
||||||
|
t.Fatalf("empty styled dialogue should be omitted:\n%s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubtitleServeRawWritesSourceBytes(t *testing.T) {
|
func TestSubtitleServeRawWritesSourceBytes(t *testing.T) {
|
||||||
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ function parseWebVTTCues(body: string): SubtitleCue[] {
|
|||||||
return cues
|
return cues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uniqueSubtitleTexts(texts: string[]): string[] {
|
||||||
|
const unique = [...new Set(texts.map((text) => text.trim()).filter(Boolean))]
|
||||||
|
// ponytail: WebVTT cannot preserve ASS layers/positions. Keep at most two
|
||||||
|
// simultaneous blocks for bilingual subtitles; use libass if full ASS
|
||||||
|
// typesetting support is added later.
|
||||||
|
return unique.slice(-2)
|
||||||
|
}
|
||||||
|
|
||||||
type PlayerVideoStageProps = {
|
type PlayerVideoStageProps = {
|
||||||
media: Media | null
|
media: Media | null
|
||||||
/** 媒体元数据加载失败提示(非空时替代「加载中」展示)。 */
|
/** 媒体元数据加载失败提示(非空时替代「加载中」展示)。 */
|
||||||
@@ -255,6 +263,7 @@ export function PlayerVideoStage({
|
|||||||
const selectedTrack = subs[subtitleIndex]
|
const selectedTrack = subs[subtitleIndex]
|
||||||
if (
|
if (
|
||||||
!video ||
|
!video ||
|
||||||
|
!tracksArmed ||
|
||||||
subs.length === 0 ||
|
subs.length === 0 ||
|
||||||
subtitleIndex < 0 ||
|
subtitleIndex < 0 ||
|
||||||
!selectedTrack ||
|
!selectedTrack ||
|
||||||
@@ -267,9 +276,11 @@ export function PlayerVideoStage({
|
|||||||
const updateCue = () => {
|
const updateCue = () => {
|
||||||
const absoluteTime = video.currentTime + (streamOffset ?? 0)
|
const absoluteTime = video.currentTime + (streamOffset ?? 0)
|
||||||
if (subtitleTimeline?.path === selectedTrack.path) {
|
if (subtitleTimeline?.path === selectedTrack.path) {
|
||||||
const texts = subtitleTimeline.cues
|
const texts = uniqueSubtitleTexts(
|
||||||
.filter((cue) => absoluteTime >= cue.startTime && absoluteTime <= cue.endTime)
|
subtitleTimeline.cues
|
||||||
.map((cue) => cue.text)
|
.filter((cue) => absoluteTime >= cue.startTime && absoluteTime <= cue.endTime)
|
||||||
|
.map((cue) => cue.text),
|
||||||
|
)
|
||||||
setActiveCueText(texts.join('\n'))
|
setActiveCueText(texts.join('\n'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -304,7 +315,7 @@ export function PlayerVideoStage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setActiveCueText(texts.join('\n'))
|
setActiveCueText(uniqueSubtitleTexts(texts).join('\n'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const apply = () => {
|
const apply = () => {
|
||||||
@@ -359,7 +370,7 @@ export function PlayerVideoStage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [subtitleIndex, subs, videoRef, media, streamOffset, subtitleTimeline])
|
}, [subtitleIndex, subs, videoRef, media, streamOffset, subtitleTimeline, tracksArmed])
|
||||||
|
|
||||||
// 根据视频画面宽高比与舞台宽高比,确定视频在哪个轴向撑满 100%
|
// 根据视频画面宽高比与舞台宽高比,确定视频在哪个轴向撑满 100%
|
||||||
const isWiderThanStage =
|
const isWiderThanStage =
|
||||||
@@ -412,7 +423,6 @@ export function PlayerVideoStage({
|
|||||||
src={subtitlesAPI.url(media.id, track.path)}
|
src={subtitlesAPI.url(media.id, track.path)}
|
||||||
srcLang={track.lang}
|
srcLang={track.lang}
|
||||||
label={track.label || track.lang}
|
label={track.label || track.lang}
|
||||||
default={subtitleIndex === index}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user