Skip to content

feat: include future secondary subtitle overlaps - #177

Open
kuator wants to merge 1 commit into
Ajatt-Tools:masterfrom
kuator:feat/full-track-secondary-overlap
Open

kuator wants to merge 1 commit into
Ajatt-Tools:masterfrom
kuator:feat/full-track-secondary-overlap

Conversation

@kuator

@kuator kuator commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Why

The subtitle observer only knows secondary cues that mpv has already displayed. If the selected primary subtitle window extends beyond the current playback position, later secondary cues that overlap that window are missing from both the card and its OSD preview.

What changed

  • Look ahead on demand with sub-step 1 secondary instead of parsing or extracting the subtitle track.
  • Capture each stepped cue's raw timestamps and normalize them with the original secondary-sub-delay and audio-delay.
  • Restore secondary-sub-delay after lookahead, including when stepping fails.
  • Use the same time-overlap resolver for card export and the OSD preview.
  • Require 75% overlap when the shorter cue is under one second, and 50% otherwise.
  • Ignore subtitle observer callbacks while lookahead temporarily changes the visible secondary cue.

This performs no file I/O and launches no subprocess. For embedded tracks, lookahead is naturally limited to cues inside mpv's subtitle prefetch range.

Verification

  • luajit tests/run.lua
  • Real mpv probe with external SRT tracks: repeated stepping exposed all test cues and exact raw timestamps, then restored the original delay.
  • Real mpv probe with embedded SRT tracks: stepping exposed the prefetched cues and restored the original delay.
  • Regression tests retain the incidental-overlap cases and the 50%/75% thresholds.

@kuator
kuator force-pushed the feat/full-track-secondary-overlap branch from a96890e to 928ad11 Compare August 9, 2026 17:03
@tatsumoto-ren

tatsumoto-ren commented Aug 9, 2026

Copy link
Copy Markdown
Member

Extracting a subtitle file from a video can take a few seconds and requires resources, so until now we didn't want to implement it in mpvacious. Plus, it requires writing a lot of code. Before settling on this solution, let's try a proven method. In our sub-transition script we look ahead by changing sub-delay via mp.commandv("no-osd", "sub-step", 1), which causes the next subtitle line to appear. So you can grab the next subtitle this way, then restore sub-delay with mp.set_property_number("sub-delay", initial_sub_delay).

sub-step <skip> [<flags>]

    Change subtitle timing such, that the subtitle event after the next <skip> subtitle events is displayed. <skip> can be negative to step backwards.

    Secondary argument:

    primary (default)
        Steps through the primary subtitles.
    secondary
        Steps through the secondary subtitles.

for secondary subs, use the secondary argument, then restore secondary-sub-delay instead of sub-delay.

@kuator kuator changed the title feat: include complete secondary subtitle overlaps feat: include future secondary subtitle overlaps Aug 9, 2026
@kuator

kuator commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Implemented the sub-step approach in eea9b58. I removed the full-track parser/extractor and now step through the secondary track on demand, capture each cue using its raw timestamps, normalize with the original secondary/audio delays, and restore secondary-sub-delay afterward. The existing 50%/75% overlap filter is unchanged and is applied after cue discovery. luajit tests/run.lua passes. I also probed this in mpv with both external and embedded SRT tracks; state restoration worked in both cases. As expected, embedded lookahead is limited to mpv’s prefetch range.

@kuator
kuator force-pushed the feat/full-track-secondary-overlap branch from 89c24f9 to eea9b58 Compare August 9, 2026 19:47
@kuator

kuator commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

I think I should try using this for a few days before it becomes reviewable

@kuator

kuator commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

I think I have to make it handle backwards subtitle overlaps as well, because sometimes I jump into the middle of a primary cue and mpvacious doesn't see a backward secondary cue

@tatsumoto-ren

Copy link
Copy Markdown
Member

Simply rewinding back and forth is enough for mpvacious to memorize subtitle lines. shift+h and shift+l are mentioned in README as aliases for seek back and seek forward.

@kuator

kuator commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Simply rewinding back and forth is enough for mpvacious to memorize subtitle lines. shift+h and shift+l are mentioned in README as aliases for seek back and seek forward.

Okay, I'll roll back the change

@kuator
kuator force-pushed the feat/full-track-secondary-overlap branch 2 times, most recently from 6ddc4f8 to 81ca880 Compare August 11, 2026 05:34
@kuator

kuator commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Removed the backwards seek

@tatsumoto-ren

Copy link
Copy Markdown
Member

Removed the backwards seek

sub-step is a pretty clever hack that can be applied in both directions if you actually need it. My comment was more about whether this is something the users might need or not.

You can probably edit scan_secondary_forward() to include direction as a parameter, so you don't have to duplicate much code.

Also, consider splitting this PR into smaller ones if possible.

Please comment when this PR is ready.

@kuator
kuator force-pushed the feat/full-track-secondary-overlap branch from 9cd020f to 4b6ea3a Compare September 6, 2026 08:22
@kuator
kuator marked this pull request as ready for review September 6, 2026 08:26
@kuator

kuator commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

bump

@kuator

kuator commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

bump @tatsumoto-ren

@tatsumoto-ren

tatsumoto-ren commented Sep 16, 2026

Copy link
Copy Markdown
Member

@kuator I'm trying to understand what's going on, but honestly, it's not smooth. There are some strong hints that this PR was mostly not written by a human. It makes it difficult to review. I'll try to figure it out and maybe come up with some solutions.

Comment thread mpvacious/main.lua
-- it's okay to print them on the screen.
osd:submenu("Secondary text"):newline()
for idx, s_line in ipairs(subs_observer.recorded_secondary_subs()) do
for idx, s_line in ipairs(subs_observer.get_selected_secondary_subs()) do

@tatsumoto-ren tatsumoto-ren Sep 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_selected_secondary_subs() is not a getter. It can mutate secondary-sub-delay, run many sub-step commands, and insert subtitles. Every menu update triggers scan. This is likely very expensive.

A function named get_* should normally be read-only.

logger = logger or require('mp.msg')
local scanning = false

local function scan(observed, window, initial_delay, normalized_delay, direction)

@tatsumoto-ren tatsumoto-ren Sep 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is longer than 21 lines, lacks a docstring, has ambiguous name, and its parameters are vaguely named. Also, it takes more than 4 parameters. usually, if a function takes more than 4 parameters, it should be refactored. Maybe some parameters need to be turned into structs.

  • observed is an observed secondary sub_list.
  • window is a combined primary subtitle.

Comment on lines +16 to +17
mp_api = mp_api or require('mp')
logger = logger or require('mp.msg')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports inside functions do not match the project's convention.

Prefer:

local mp = require('mp')
local msg = require('mp.msg')

then default to mp / msg inside new.

}
end

local function run_scan_case(case)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is specifically for testing only. If so, it should be separated from production code visually.

------------------------------------------------------------
-- tests

local function run_scan_case(case)
...
end

Copyright: Ajatt-Tools and contributors; https://github.com/Ajatt-Tools
License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html

Secondary scanner discovers subtitle cues outside the currently visible event.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I don't know what cue means. It is the wrong term here. We already have Subtitle, sub, dialogs, and sub_list. Introducing cue creates a second vocabulary for the same thing.

Subtitle.cues → source_subtitles
cue_index → subtitle_index
current_cue() → current_subtitle()
cues → subtitles
"collect N cues" → "collect N subtitle events"
"a cue is accepted" → "a subtitle event is accepted"

@tatsumoto-ren

Copy link
Copy Markdown
Member

some suggestions:

  1. Split functions over the 21 line limit (scan(), run_scan_case() )

  2. Don't use imports inside functions

  3. Add missing comments and docstrings

  4. Use existing project terminology (no "cue")

  5. Avoid scanning from Menu:update(). Resolve and cache the secondary selection when primary selection/timing changes or before export.

  6. Make the scanner responsible only for discovery. The scanner should discover missing secondary subtitle events.

    • Insert the currently visible secondary subtitle.
    • Step backward and forward.
    • Normalize timestamps.
    • Insert discovered secondary subtitle events.
    • Restore secondary sub delay

    It should not:

    • Apply overlap thresholds.
    • Build flattened text.
    • Return { text, subs }.
  7. Subtitle should represent one subtitle span. It should not know which other subtitle events were used to build it. Remove the cues field and remove Subtitle:timing_windows().

  8. Introduce an observer-owned primary selection

    {
       subtitle = combined_primary_subtitle,
       windows = {
          Subtitle:from_text('', start_time, end_time),
          ---
       },
    }
    

@kuator

kuator commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Okay, I'll try to understand what's happening and maybe there's a way to split this merge request

@kuator

kuator commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

There's also an option of not merging this mr, I can just keep it in my fork since I understand that any extra code is a maintenance burden

@tatsumoto-ren

Copy link
Copy Markdown
Member

You could probably ask an LLM chatbot to refactor the code. These days they can write code better than humans if you direct them properly, but they can also write shitty code that is unreadable and full of issues.

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