@@ -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 执行首选截图路径,并根据字幕类型选择对应的渲染方案。
345374func (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
0 commit comments