Skip to content

options: add --sub-pause to pause on each subtitle's last frame - #18408

Open
feldgendler wants to merge 1 commit into
mpv-player:masterfrom
feldgendler:sub_pause
Open

options: add --sub-pause to pause on each subtitle's last frame#18408
feldgendler wants to merge 1 commit into
mpv-player:masterfrom
feldgendler:sub_pause

Conversation

@feldgendler

@feldgendler feldgendler commented Aug 25, 2026

Copy link
Copy Markdown

Pauses playback on the last video frame on which each primary subtitle is still visible. Resuming plays to the end of the next subtitle, so repeated resuming steps through dialogue line by line. Off by default.

This is meant for language learners, who want to stop on each line long enough to read it, look a word up, or replay the audio. Pausing by hand always lands after the line has left the screen, so it has to be recalled rather than read, and pausing on a timer drifts as soon as the dialogue does.

It has been written as a user script several times, but those are all subtly wrong when --sub-delay is set: sub_get_times() returns subtitle-timebase values and pts_to_subtitle() is static in dec_sub.c, so a pause point computed from the sub-end property is off by exactly the delay. A script also only learns about the transition after the line is already gone. sub_ends_between() compares in the subtitle timebase, so --sub-delay and --sub-speed are accounted for.

The manual lists the cases where this does nothing, mostly for want of a following frame to pause on: audio-only playback, cover art, the last frame of a file, --untimed and --video-latency-hacks. It also does nothing for subtitles with unknown end times and for subrandr, which has no get_times, and with ASS it stops on signs too, since get_times() covers every event at that PTS.

Tested with SRT and embedded ASS, with --sub-delay both ways and --sub-speed at 0.5 and 2, and with subtitles hidden, disabled, and during backward playback.

Written with AI assistance, and reviewed with AI tooling. I take full responsibility for the code: I understand what it changes and why, I have tested it myself, I will respond to review in my own words, and it can be submitted under the same license as the files it touches.

Pauses playback on the last video frame on which each primary subtitle
is still visible. Resuming plays to the end of the next subtitle, so
repeated resuming steps through dialogue line by line.

Finding that frame means comparing the frame being displayed against the
one about to be queued, which is only possible in write_video(). The
pause is placed after the frame stepping block so that a frame step gets
to clear step_frames and unmute first.

sub_ends_between() compares in the subtitle timebase, so --sub-delay and
--sub-speed are accounted for. This adds a get_times() call per
displayed frame while the option is enabled.

Also saved in watch later files, like the other sub-* options.

Tested with SRT and embedded ASS, and with --sub-delay and --sub-speed
set both ways.
@llyyr

llyyr commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

is there any scenario where the following profile fails?

[sub-pause]
profile-cond=time_pos > sub_end + get("sub-delay") - 0.1 and time_pos < sub_end + get("sub-delay")
pause

It has been written as a user script several times, but those are all subtly
wrong when --sub-delay is set: sub_get_times() returns subtitle-timebase values
and pts_to_subtitle() is static in dec_sub.c, so a pause point computed from the
sub-end property is off by exactly the delay. A script also only learns about
the transition after the line is already gone. sub_ends_between() compares in
the subtitle timebase, so --sub-delay and --sub-speed are accounted for.

It may be worth discussing whether sub-start and sub-end should account for sub-delay by default or not, I'm not sure such a niche option is worth adding. Especially one that's 3 lines with auto profiles

@feldgendler

feldgendler commented Aug 25, 2026

Copy link
Copy Markdown
Author

is there any scenario where the following profile fails?

[sub-pause]
profile-cond=time_pos > sub_end + get("sub-delay") - 0.1 and time_pos < sub_end + get("sub-delay")
pause

A few scenarios:

  • sub-speed
  • < 10 fps, so the 0.1 window is missed
  • one-key toggle (language learners want this mode on only during fast-paced dialogue)
  • frame-dropping

Here is a steel-man version that addresses the first three:

mpv.conf

[sub-pause]
profile-cond=get("user-data/subpause","no")=="yes" and sub_end ~= nil and time_pos >= sub_end*get("sub-speed")+get("sub-delay")-1.5/get("container-fps",25) and time_pos < sub_end*get("sub-speed")+get("sub-delay")
pause

input.conf

a set user-data/subpause "${?user-data/subpause==yes:no}${!user-data/subpause==yes:yes}"; show-text "Auto-pause on subtitles: ${user-data/subpause}"

The 1.5/fps formula replaces the fixed window (1/fps can be too little e.g. with PGS).

As for frame-dropping, I haven't figured out how to fix it. If the last frame with the subtitle visible gets dropped, my sub-pause code adapts and pauses on the preceding frame that is actually shown, but the profile condition doesn't. Widening the window trades precision for reliability. So the two narrowly surviving reasons to take this PR are:

  • ease of toggling (the configuration above works but it's ugly, binding cycle sub-pause is a lot easier)
  • precision AND reliability under frame-dropping

I understand if these don't clear the bar. Thanks for considering it, anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants