Skip to content

Commit 53594a5

Browse files
committed
修复文本字幕字幕轴
1 parent 78dfdb0 commit 53594a5

2 files changed

Lines changed: 62 additions & 19 deletions

File tree

internal/screenshot/capture.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,35 @@ func (r *screenshotRunner) capturePGSBitmapWithOutputArgs(coarseHMS string, fine
341341
return r.runFFmpeg(args, fineSecond)
342342
}
343343

344+
// captureTextSubtitleWithOutputArgs 会让文字字幕渲染和视频取帧共用同一条时间轴,避免字幕与画面错位。
345+
func (r *screenshotRunner) captureTextSubtitleWithOutputArgs(aligned float64, outputArgs []string, path string) error {
346+
subFilter := r.buildTextSubtitleFilter()
347+
if strings.TrimSpace(subFilter) == "" {
348+
return fmt.Errorf("text subtitle filter is empty")
349+
}
350+
351+
coarseBack := r.renderCoarseBack()
352+
coarseSecond := int(math.Max(math.Floor(aligned)-float64(coarseBack), 0))
353+
coarseHMS := formatTimestamp(coarseSecond)
354+
filterChain := r.buildTextSubtitleRenderChain(float64(coarseSecond), aligned, subFilter)
355+
356+
args := []string{
357+
"-v", "error",
358+
"-fflags", "+genpts",
359+
"-ss", coarseHMS,
360+
"-probesize", r.settings.ProbeSize,
361+
"-analyzeduration", r.settings.Analyze,
362+
"-i", r.sourcePath,
363+
"-map", "0:v:0",
364+
"-y",
365+
"-frames:v", "1",
366+
"-vf", filterChain,
367+
}
368+
args = append(args, outputArgs...)
369+
args = append(args, path)
370+
return r.runFFmpeg(args, aligned-float64(coarseSecond))
371+
}
372+
344373
// capturePrimary 执行首选截图路径,并根据字幕类型选择对应的渲染方案。
345374
func (r *screenshotRunner) capturePrimary(aligned float64, path string) error {
346375
if r.subtitle.Mode == "external" {
@@ -367,7 +396,7 @@ func (r *screenshotRunner) capturePrimary(aligned float64, path string) error {
367396
filterChain := joinFilters(r.colorChain, r.displayAspectFilter())
368397

369398
if subFilter := r.buildTextSubtitleFilter(); subFilter != "" {
370-
filterChain = r.buildTextSubtitleRenderChain(aligned, subFilter)
399+
return r.captureTextSubtitleWithOutputArgs(aligned, r.primaryOutputArgs(), path)
371400
}
372401

373402
args := []string{
@@ -415,7 +444,11 @@ func (r *screenshotRunner) capturePNGReencoded(aligned float64, path string) err
415444

416445
filterChain := joinFilters(r.colorChain, r.displayAspectFilter())
417446
if subFilter := r.buildTextSubtitleFilter(); subFilter != "" {
418-
filterChain = r.buildTextSubtitleRenderChain(aligned, subFilter)
447+
return r.captureTextSubtitleWithOutputArgs(aligned, []string{
448+
"-c:v", "png",
449+
"-compression_level", "9",
450+
"-pred", "mixed",
451+
}, path)
419452
}
420453

421454
args := []string{
@@ -499,7 +532,10 @@ func (r *screenshotRunner) captureJPGReencoded(aligned float64, path string) err
499532

500533
filterChain := joinFilters(r.colorChain, r.displayAspectFilter())
501534
if subFilter := r.buildTextSubtitleFilter(); subFilter != "" {
502-
filterChain = r.buildTextSubtitleRenderChain(aligned, subFilter)
535+
return r.captureTextSubtitleWithOutputArgs(aligned, []string{
536+
"-c:v", "mjpeg",
537+
"-q:v", strconv.Itoa(quality),
538+
}, path)
503539
}
504540

505541
args := []string{
@@ -1107,19 +1143,25 @@ func parseFFmpegSpeed(raw string) (float64, bool) {
11071143
return value, true
11081144
}
11091145

1110-
// buildTextSubtitleRenderChain 会按 libass 需要的时间线顺序拼接文字字幕渲染链。
1111-
func (r *screenshotRunner) buildTextSubtitleRenderChain(aligned float64, subFilter string) string {
1146+
// buildTextSubtitleRenderChain 会让文字字幕渲染与视频选帧共享同一条绝对时间轴。
1147+
func (r *screenshotRunner) buildTextSubtitleRenderChain(timelineBase, aligned float64, subFilter string) string {
1148+
baseTimeline := fmt.Sprintf("setpts=PTS-STARTPTS+%s/TB", formatFloat(timelineBase))
1149+
selectFrame := fmt.Sprintf("select='gte(t,%s)'", formatFloat(aligned))
11121150
if r.usesLibplaceboColorspace() {
11131151
return joinFilters(
1152+
baseTimeline,
1153+
selectFrame,
11141154
r.colorChain,
1115-
fmt.Sprintf("setpts=PTS-STARTPTS+%s/TB", formatFloat(aligned)),
11161155
subFilter,
1156+
r.displayAspectFilter(),
11171157
)
11181158
}
11191159
return joinFilters(
1120-
fmt.Sprintf("setpts=PTS-STARTPTS+%s/TB", formatFloat(aligned)),
1160+
baseTimeline,
1161+
selectFrame,
11211162
subFilter,
11221163
r.colorChain,
1164+
r.displayAspectFilter(),
11231165
)
11241166
}
11251167

internal/screenshot/capture_test.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -182,29 +182,30 @@ func TestRenderCoarseBackUsesBitmapOverrideWhenPresent(t *testing.T) {
182182
}
183183
}
184184

185-
// TestShellStyleTextSubtitleChain 验证文字字幕过滤器链保持 shell 的 setpts 后接 subtitles 顺序。
186-
func TestShellStyleTextSubtitleChain(t *testing.T) {
187-
filter := joinFilters(
188-
"setpts=PTS-STARTPTS+61.000/TB",
189-
"subtitles='/media/example/video.mkv':original_size=3840x2160:si=1",
190-
)
191-
192-
expected := "setpts=PTS-STARTPTS+61.000/TB,subtitles='/media/example/video.mkv':original_size=3840x2160:si=1"
185+
func TestBuildTextSubtitleRenderChainUsesTimelineBaseAndSelect(t *testing.T) {
186+
runner := &screenshotRunner{
187+
aspectChain: "setsar=1",
188+
}
189+
190+
filter := runner.buildTextSubtitleRenderChain(60, 61, "subtitles='/media/example/video.mkv':original_size=3840x2160:si=1")
191+
192+
expected := "setpts=PTS-STARTPTS+60.000/TB,select='gte(t,61.000)',subtitles='/media/example/video.mkv':original_size=3840x2160:si=1,setsar=1"
193193
if filter != expected {
194-
t.Fatalf("expected shell-style filter chain %q, got %q", expected, filter)
194+
t.Fatalf("expected timeline-based text subtitle chain %q, got %q", expected, filter)
195195
}
196196
}
197197

198198
func TestBuildTextSubtitleRenderChainUsesLibplaceboBeforeSubtitles(t *testing.T) {
199199
runner := &screenshotRunner{
200200
colorChain: "libplacebo=colorspace=gbr",
201+
aspectChain: "setsar=1",
201202
}
202203

203-
filter := runner.buildTextSubtitleRenderChain(61, "subtitles='/media/example/video.mkv':original_size=3840x2160:si=1")
204+
filter := runner.buildTextSubtitleRenderChain(60, 61, "subtitles='/media/example/video.mkv':original_size=3840x2160:si=1")
204205

205-
expected := "libplacebo=colorspace=gbr,setpts=PTS-STARTPTS+61.000/TB,subtitles='/media/example/video.mkv':original_size=3840x2160:si=1"
206+
expected := "setpts=PTS-STARTPTS+60.000/TB,select='gte(t,61.000)',libplacebo=colorspace=gbr,subtitles='/media/example/video.mkv':original_size=3840x2160:si=1,setsar=1"
206207
if filter != expected {
207-
t.Fatalf("expected libplacebo-first text subtitle chain %q, got %q", expected, filter)
208+
t.Fatalf("expected libplacebo-before-subtitles text subtitle chain %q, got %q", expected, filter)
208209
}
209210
}
210211

0 commit comments

Comments
 (0)