From c961eb631526b2836cc0011a3ddec1bcae3934b5 Mon Sep 17 00:00:00 2001 From: Ryan Lucia Date: Sun, 25 Oct 2026 15:35:34 -0700 Subject: [PATCH 1/2] Correct frame_from_ms and ms_from_frame semantics Both use Aegisub's START conversion: frame_from_ms rounds up to the first frame a line starting at that time is displayed on, and ms_from_frame returns the midpoint between the previous frame and the requested one, not the frame's beginning. Co-Authored-By: Claude Opus 5.5 --- .../3.5/Automation/Lua/Miscellaneous_APIs.md | 35 +++++++++++++------ .../Automation/Lua/Miscellaneous_APIs.md | 35 +++++++++++++------ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/content/en/docs/3.5/Automation/Lua/Miscellaneous_APIs.md b/content/en/docs/3.5/Automation/Lua/Miscellaneous_APIs.md index 51b6f3f48..244d2828f 100644 --- a/content/en/docs/3.5/Automation/Lua/Miscellaneous_APIs.md +++ b/content/en/docs/3.5/Automation/Lua/Miscellaneous_APIs.md @@ -99,6 +99,12 @@ its ending time. The ending time of a frame is the beginning time of the next. The beginning time of a frame is included in the range while the ending time is excluded from the range. +These functions convert between frames and *subtitle times* rather than +exact frame timestamps. A line is displayed on every frame that begins within +the line, so a line that starts after the beginning of the previous frame, up +to and including the beginning of a frame, first appears on that frame. This +is the same conversion Aegisub itself uses when timing lines to video. + ### aegisub.frame_from_ms Synopsis: `frame = aegisub.frame_from_ms(ms)` @@ -111,11 +117,13 @@ Use loaded frame rate data to convert an absolute time given in which to determine the frame number. `frame` (`number`) -: Frame number corresponding to the time in ms, or `nil` if there is no - frame rate data loaded. +: The first frame a line starting at `ms` is displayed on, i.e. the first + frame that begins at or after `ms`, or `nil` if there is no frame rate data + loaded. -If the time is in the middle of the frame it is **rounded down** to the frame -number that contains the given time. +If the time is in the middle of a frame it is **rounded up** to the next +frame. For a line's end time, this gives the first frame the line is no +longer displayed on. ### aegisub.ms_from_frame @@ -125,15 +133,20 @@ Use loaded frame rate data to convert a **frame** number of the video into an absolute time in **milliseconds**. `@frame` (`number`) -: Frame to obtain the beginning time of. +: Frame to obtain a time for. `ms` (`number`) -: First integer millisecond time stamp to lie within the frame, or `nil` if - there is no frame rate data loaded. - -Because beginning times of frames can have better precision than one -millisecond this function rounds up and returns the first whole millisecond -that is guaranteed to be within the frame. +: A time at which a line starting there is first displayed on `frame`, or + `nil` if there is no frame rate data loaded. + +This is halfway between the beginnings of the previous frame and of `frame`, +not the beginning of `frame` itself. For example, at 23.976 fps frame 24 +begins at 1001 ms, but `aegisub.ms_from_frame(24)` returns 980. The margin +means the time still lands on the right frame after being rounded to the +centiseconds subtitle files store, at frame rates up to 100 fps (above that, +frames are shorter than a centisecond). The two functions also round-trip: +`aegisub.frame_from_ms(aegisub.ms_from_frame(frame))` is always `frame`. +Using the result as a line's end time makes the line disappear on `frame`. ### aegisub.video_size diff --git a/content/en/docs/latest/Automation/Lua/Miscellaneous_APIs.md b/content/en/docs/latest/Automation/Lua/Miscellaneous_APIs.md index 36a95a3e8..b10e85ef2 100644 --- a/content/en/docs/latest/Automation/Lua/Miscellaneous_APIs.md +++ b/content/en/docs/latest/Automation/Lua/Miscellaneous_APIs.md @@ -138,6 +138,12 @@ its ending time. The ending time of a frame is the beginning time of the next. The beginning time of a frame is included in the range while the ending time is excluded from the range. +These functions convert between frames and *subtitle times* rather than +exact frame timestamps. A line is displayed on every frame that begins within +the line, so a line that starts after the beginning of the previous frame, up +to and including the beginning of a frame, first appears on that frame. This +is the same conversion Aegisub itself uses when timing lines to video. + ### aegisub.frame_from_ms Synopsis: `frame = aegisub.frame_from_ms(ms)` @@ -150,11 +156,13 @@ Use loaded frame rate data to convert an absolute time given in which to determine the frame number. `frame` (`number`) -: Frame number corresponding to the time in ms, or `nil` if there is no - frame rate data loaded. +: The first frame a line starting at `ms` is displayed on, i.e. the first + frame that begins at or after `ms`, or `nil` if there is no frame rate data + loaded. -If the time is in the middle of the frame it is **rounded down** to the frame -number that contains the given time. +If the time is in the middle of a frame it is **rounded up** to the next +frame. For a line's end time, this gives the first frame the line is no +longer displayed on. ### aegisub.ms_from_frame @@ -164,15 +172,20 @@ Use loaded frame rate data to convert a **frame** number of the video into an absolute time in **milliseconds**. `@frame` (`number`) -: Frame to obtain the beginning time of. +: Frame to obtain a time for. `ms` (`number`) -: First integer millisecond time stamp to lie within the frame, or `nil` if - there is no frame rate data loaded. - -Because beginning times of frames can have better precision than one -millisecond this function rounds up and returns the first whole millisecond -that is guaranteed to be within the frame. +: A time at which a line starting there is first displayed on `frame`, or + `nil` if there is no frame rate data loaded. + +This is halfway between the beginnings of the previous frame and of `frame`, +not the beginning of `frame` itself. For example, at 23.976 fps frame 24 +begins at 1001 ms, but `aegisub.ms_from_frame(24)` returns 980. The margin +means the time still lands on the right frame after being rounded to the +centiseconds subtitle files store, at frame rates up to 100 fps (above that, +frames are shorter than a centisecond). The two functions also round-trip: +`aegisub.frame_from_ms(aegisub.ms_from_frame(frame))` is always `frame`. +Using the result as a line's end time makes the line disappear on `frame`. ### aegisub.video_size From fc1820c2b6b5ab7e031966109d2f2826b5267d3a Mon Sep 17 00:00:00 2001 From: Ryan Lucia Date: Sat, 31 Oct 2026 21:48:26 -0700 Subject: [PATCH 2/2] Reword ms_from_frame return value per review Co-Authored-By: Claude Opus 5.5 --- content/en/docs/3.5/Automation/Lua/Miscellaneous_APIs.md | 7 ++++--- .../en/docs/latest/Automation/Lua/Miscellaneous_APIs.md | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/content/en/docs/3.5/Automation/Lua/Miscellaneous_APIs.md b/content/en/docs/3.5/Automation/Lua/Miscellaneous_APIs.md index 244d2828f..e5365b645 100644 --- a/content/en/docs/3.5/Automation/Lua/Miscellaneous_APIs.md +++ b/content/en/docs/3.5/Automation/Lua/Miscellaneous_APIs.md @@ -136,8 +136,10 @@ an absolute time in **milliseconds**. : Frame to obtain a time for. `ms` (`number`) -: A time at which a line starting there is first displayed on `frame`, or - `nil` if there is no frame rate data loaded. +: The start time that a subtitle line should have if it is supposed to first + become visible at the frame `frame`. This is also the end time that a + subtitle line should have if it is supposed to first become *in*visible at + the frame `frame`. If no frame rate data is loaded, returns `nil`. This is halfway between the beginnings of the previous frame and of `frame`, not the beginning of `frame` itself. For example, at 23.976 fps frame 24 @@ -146,7 +148,6 @@ means the time still lands on the right frame after being rounded to the centiseconds subtitle files store, at frame rates up to 100 fps (above that, frames are shorter than a centisecond). The two functions also round-trip: `aegisub.frame_from_ms(aegisub.ms_from_frame(frame))` is always `frame`. -Using the result as a line's end time makes the line disappear on `frame`. ### aegisub.video_size diff --git a/content/en/docs/latest/Automation/Lua/Miscellaneous_APIs.md b/content/en/docs/latest/Automation/Lua/Miscellaneous_APIs.md index b10e85ef2..bc17d0655 100644 --- a/content/en/docs/latest/Automation/Lua/Miscellaneous_APIs.md +++ b/content/en/docs/latest/Automation/Lua/Miscellaneous_APIs.md @@ -175,8 +175,10 @@ an absolute time in **milliseconds**. : Frame to obtain a time for. `ms` (`number`) -: A time at which a line starting there is first displayed on `frame`, or - `nil` if there is no frame rate data loaded. +: The start time that a subtitle line should have if it is supposed to first + become visible at the frame `frame`. This is also the end time that a + subtitle line should have if it is supposed to first become *in*visible at + the frame `frame`. If no frame rate data is loaded, returns `nil`. This is halfway between the beginnings of the previous frame and of `frame`, not the beginning of `frame` itself. For example, at 23.976 fps frame 24 @@ -185,7 +187,6 @@ means the time still lands on the right frame after being rounded to the centiseconds subtitle files store, at frame rates up to 100 fps (above that, frames are shorter than a centisecond). The two functions also round-trip: `aegisub.frame_from_ms(aegisub.ms_from_frame(frame))` is always `frame`. -Using the result as a line's end time makes the line disappear on `frame`. ### aegisub.video_size