Packages
vtc
0.1.7
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.2
0.16.1
0.16.0
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.15
0.13.14
0.13.13
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.10
0.10.9
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
A SMPTE timecode library for Elixir
Current section
Files
Jump to
Current section
Files
lib/drop_frame.ex
defmodule Private.Drop do
@moduledoc false
use Ratio
# Adjusts the frame number based on drop-frame TC conventions.
#
# Algorithm adapted from:
# https://www.davidheidelberger.com/2010/06/10/drop-frame-timecode/
@spec parse_adjustment(Vtc.Timecode.Sections.t(), Vtc.Framerate.t()) ::
{:ok, integer} | {:error, Vtc.Timecode.ParseError.t()}
def parse_adjustment(sections, %Vtc.Framerate{ntsc: ntsc} = rate) when ntsc == :Drop do
drop_frames = get_drop_frames(rate)
# We have a bad frame value if our 'frames' place is less than the drop_frames we
# skip on minutes not divisible by 10.
has_bad_frame = sections.frames < drop_frames
{_, remainder} = Private.Rat.divmod(sections.minutes, 10)
is_tenth_minute = remainder == 0
if has_bad_frame and not is_tenth_minute do
{:error, %Vtc.Timecode.ParseError{reason: :bad_drop_frames}}
else
total_minutes = 60 * sections.hours + sections.minutes
adjustment = drop_frames * (total_minutes - div(total_minutes, 10))
{:ok, -adjustment}
end
end
# If this is not an NTSC framerate, there is not adjustment
def parse_adjustment(_sections, %Vtc.Framerate{ntsc: ntsc} = _rate) when ntsc != :Drop do
{:ok, 0}
end
# Adjusts the frame number of a timecode so the proper drop-frame value is printed.
#
# Algorithm adapted from:
# https://www.davidheidelberger.com/2010/06/10/drop-frame-timecode/
@spec frame_num_adjustment(integer, Vtc.Framerate.t()) :: integer
def frame_num_adjustment(frame_number, rate) do
timebase = Vtc.Framerate.timebase(rate)
drop_frames = get_drop_frames(rate)
# Get the number frames-per-minute at the whole-frame rate
frames_per_minute_whole = timebase * 60
# Get the number of frames are in a minute where we have dropped frames at the
# beginning
frames_per_minute_with_drop = frames_per_minute_whole - drop_frames
# Get the number of actual frames in a 10-minute span for drop frame timecode. Since
# we drop 9 times a minute, it will be 9 drop-minute frame counts + 1 whole-minute
# frame count.
frames_per_10minutes_drop = frames_per_minute_with_drop * 9 + frames_per_minute_whole
# Get the number of 10s of minutes in this count, and the remaining frames.
{tens_of_minutes, frames} = Private.Rat.divmod(frame_number, frames_per_10minutes_drop)
# Create an adjustment for the number of 10s of minutes. It will be 9 times the
# drop value (we drop for the first 9 minutes, then leave the 10th alone).
adjustment = 9 * drop_frames * tens_of_minutes
# If our remaining frames are less than a whole minute, we aren't going to drop
# again. Add the adjustment and return.
if frames < frames_per_minute_whole do
frame_number + adjustment
else
# Remove the first full minute (we don't drop until the next minute) and add the
# drop-rate to the adjustment.
frames = frames - timebase
adjustment = adjustment + drop_frames
# Get the number of remaining drop-minutes present, and add a drop adjustment for
# each.
minutes_drop = floor(div(frames, frames_per_minute_with_drop))
adjustment = adjustment + minutes_drop * drop_frames
# Return our original frame number adjusted by our calculated adjustment.
frame_number + adjustment
end
end
@spec get_drop_frames(Vtc.Framerate.t()) :: integer
def get_drop_frames(rate) do
timebase = Vtc.Framerate.timebase(rate)
Private.Rat.round_ratio?(timebase * Ratio.new(0.066666, 1))
end
end