Rounding / truncating error with subtitle timecodes?

I’ve have come across a possible bug with the timecode calculations. I’ve only been using KDEnlive for a few weeks, so I want to make sure I’m not just missing something. It was seen on MacOS and Linux (both 24.12.1 x86 AppImage.)

The following, saved as an SRT file, explains and demonstrates the problem. The start time of #3 is the issue.

1
00:00:00,000 → 00:00:02,000
Import this at 00:00:51.03
into a 23.98FPS project.
Save, close, and reopen.

2
00:03:16,071 → 00:03:17,698
On reopening, this
(ASS end time 00:04:08.83)
will be overlapped…

3
00:03:17,864 → 00:03:19,866
…by this overlapper
with an ASS start time of 00:04:08.100
which seems to be read as …08.10

Nice catch - this is a fun one. It’s not so much a problem with the calculation as with converting it to the required precision.

00:04:08.100 is 00:04:08.10 that part is fine.

The core problem is that timeshifting 00:03:17,864 by your 51.03 offset gives us a time of 00:04:08.996, but since ASS only gives us 10ms precision, and the code is using Qt string handling to truncate 8.996 to 2 significant digits, the 0.99 gets rounded to 0.100 instead of the correct value of 00:04:09.00

The new code I have in the pipeline handle this differently and doesn’t have that problem, so this will be fixed in a future release.

1 Like

Ok, and for bonus points, you’ve actually found three bugs here - though only two of them are ours …

I woke up with the nagging thought “how did the initial import even look right in the first place??” given that we’d already mangled 8.996 to 8.100 at that point … and given that subtle math bugs often come in clusters it seemed worth doing a bit more checking.

And it seems the answer to this is: when you first import and offset this SRT, we create that invalid three decimal place 8.100 timestamp which we pass to MLT who passes it to ffmpeg - and ffmpeg interprets that as 100 x 10ms not 0.100 sec - as do we when we display the clip on the timeline. [And if you look very closely we round that to a frame number differently to them, and the subtitle isn’t actually rendered in the monitor when you place the cursor on the first frame of the last timeline clip, it appears one frame later than that.]

It then breaks more obviously after you round trip it by saving and loading it, because unlike the SRT converter, our old/current ASS parser sees 8.100, interprets that ‘correctly’ as 8.1, then rounds it to 8.08 which is the nearest frame boundary. And that’s what will end up in the saved file instead of 8.100 if you save it for a second time, at which point the error becomes stable.

That doesn’t change that it’s already fixed in the new code, and that us not passing an invalid ASS timestamp to ffmpeg makes its handing of that moot - but I figured I’d note the full story for posterity in case there is another case/place this shakes out in again later (and I need to remember what we figured out about it Last Time : )

[linking this to 24.12.0 subtitle editing to remind me to add unit tests for cases like this.]

1 Like

I found a similar problem with two differences. (1) It doesn’t involve an SRT, and (2) the minutes seem to matter.

To see it in action, use the following Events section in an ASS file. On reopening, the middle pair of subs are fine despite having the same starting seconds/fractions.

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:05:00.00,Default,,0,0,0,,Import at 1:46.22 into a 23.98FPS file \NSave, Close, Reopen
Dialogue: 0,0:09:16.35,0:09:19.01,Default,,0,0,0,,Overlappee\N Now Ends 11:05.91
Dialogue: 0,0:09:19.12,0:09:22.18,Default,,0,0,0,,Overlapper\N Now Starts 11:05.100
Dialogue: 0,0:40:16.35,0:40:19.01,Default,,0,0,0,,Fine\N Now Ends 42:05.90
Dialogue: 0,0:40:19.12,0:40:22.18,Default,,0,0,0,,Fine\N Now Starts 42:06.02
Dialogue: 0,0:50:16.35,0:50:19.01,Default,,0,0,0,,Overlappee\N Now Ends 52:05.91
Dialogue: 0,0:50:19.12,0:50:22.18,Default,,0,0,0,,Overlapper\N Now Starts 52:05.100

Ah, yes - because the actually buggy operation isn’t in the SRT converter, it’s in adding the offset. The difference in reloading isn’t so much SRT vs ASS as that the ASS parser ‘fixes’ the bug that the code adding the offset introduced (or at least truncates the time stamp back to 2 sig figures).

I’m not quite sure that I’m sure of what you mean by “the minutes matter”? At 23.976 frames per second, the mapping between frame number (which is what the timeline really works on) and time in seconds (which ASS and SRT use) is a bit fuzzy because we’ll always round to a time that matches an integer frame number and vice versa. If you watch the time counter which displays hh::mm::ss::frames, most seconds will appear to have 24 frames, but once in a blue moon some will have 23 (for exactly the same reason as getting two full moons in a month).

So changing the frame rate will change the precise times and offsets this bug can occur at, but it isn’t specific to any particular frame rate.

Do you mean something different to that?

Unless you do and I’m missing it, this does look like the same bug to me, and I can confirm the new code handles this one correctly too. It gives me:

Dialogue: 0,00:01:46.90,00:06:46.90,Default,,0,0,0,,Import at 1:46.22 into a 23.98FPS file \NSave, Close, Reopen
Dialogue: 0,00:11:03.25,00:11:05.91,Default,,0,0,0,,Overlappee\N Now Ends 11:05.91
Dialogue: 0,00:11:06.02,00:11:09.08,Default,,0,0,0,,Overlapper\N Now Starts 11:05.100
Dialogue: 0,00:42:03.25,00:42:05.91,Default,,0,0,0,,Fine\N Now Ends 42:05.90
Dialogue: 0,00:42:06.02,00:42:09.08,Default,,0,0,0,,Fine\N Now Starts 42:06.02
Dialogue: 0,00:52:03.25,00:52:05.91,Default,,0,0,0,,Overlappee\N Now Ends 52:05.91
Dialogue: 0,00:52:06.02,00:52:09.08,Default,,0,0,0,,Overlapper\N Now Starts 52:05.100

Can I ask what you’re doing that you seem to be hitting these regularly? I’m in the middle of reworking the subtitle handling, so if you’ve got some special requirements from it, or something (else! : ) that you wish it might do better, now is probably a pretty good time to bring that sort of thing up.

Ah, I should have realized that’s was what was going on. The minutes was just the obvious difference in the timecodes, so that’s what I latched on to.

I’m matching videos to an audio track (something like Rifftrax) so the subs often need to be offset.

Things that would be nice to have (some may exist and I just haven’t found them yet):

  • Converting italic tags when importing SRTs (and other html, just for completeness)
  • A button to clear all ASS tags from a subtitle
  • A Character Per Second (exclusive of whitespace) count
  • A way to ‘marry’ a subtitle track to another track, so they can be moved in tandem

Moved as in up and down the timeline to a different start position?
You can group clips, transiently or more permanently. Shift click or shift drag to select the ones you want - then either act on them as a group while selected, or right-click → group clips to bind them together until you explicitly ungroup them again.

That should be pretty trivial, we already show a character count, so showing some math next to it will be easy to do in this round of changes.

There’s unvalidated, trivial replacement of <b> <i> <u> and their closing tags with ASS inline equivalents (but not for shouty <HTML>, though that’s easy to fix). The main one we’re missing that seems to be supported is font colours - but that’s less trivial without a Real html parser, so I’d want to see some real demand for that first.

If we naively cut everything matching something like: {\\[^}]+}, is that going to hit anything we wouldn’t want to remove?

1 Like

The other option for this would be to use a nested sequence. Align your original video and subtitles on one sequence, then nest that as an atomic unit into another sequence where your overlay audio is added.

Both options have their own small pros and cons for different use cases.

I had a couple of rabbit holes I needed to get to the bottom of first before making new changes to this widget, but:

This was indeed easy enough to add, including finding and fixing a bug where the cursor would count characters in option blocks after the first one and then ‘uncount’ them again once it moved past it, and a couple of other little glitches in there.

It displays normally until CPS rises above 15cps, and then is highlighted in increasingly angry shades of red for larger values.

It turns out this already exists. The option is just hidden in the hamburger menu under the bottom right corner of the text entry box. And it does exactly what I proposed above.

Possibly we should rename “Clear all custom formatting” to something like “Clear all inline tags” though? Open for opinions on that one. It doesn’t clear “custom formatting” which is part of the selected style, and option blocks can contain more than just formatting …


And I’ve fixed the basic html tag conversion to be case insensitive, so if the normal grouping works for you, all of this is now at various points of In The Pipeline.

1 Like