Packages
vtc
0.2.3
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 Vtc.Private.DropFrame do
@moduledoc false
alias Vtc.Framerate
alias Vtc.Timecode
alias Vtc.Utils.Rational
# 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(Timecode.Sections.t(), Framerate.t()) ::
{:ok, integer()} | {:error, Timecode.ParseError.t()}
def parse_adjustment(sections, %Framerate{ntsc: :drop} = rate) do
drop_rate = frames_dropped_per_minute(rate)
with :ok <- parse_adjustment_validate(sections, drop_rate) do
total_minutes = 60 * sections.hours + sections.minutes
adjustment = drop_rate * (total_minutes - div(total_minutes, 10))
{:ok, -adjustment}
end
end
def parse_adjustment(_, _), do: {:ok, 0}
@spec parse_adjustment_validate(Timecode.Sections.t(), integer()) ::
:ok | {:error, Timecode.ParseError.t()}
defp parse_adjustment_validate(sections, drop_rate) do
tenth_minute? = match?({_, 0}, Rational.divmod(sections.minutes, 10))
# We have a bad frame value if our frames place is less than the drop_frames we
# are supposed to skip.
if sections.frames < drop_rate and not tenth_minute? do
{:error, %Timecode.ParseError{reason: :bad_drop_frames}}
else
:ok
end
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(), Framerate.t()) :: integer()
def frame_num_adjustment(frame_number, rate) do
timebase = Framerate.timebase(rate)
drop_rate = frames_dropped_per_minute(rate)
# Get the number frames-per-minute at the whole-frame rate
frames_per_minute_whole = Ratio.mult(timebase, 60)
# Get the number of frames are in a minute where we have dropped frames at the
# beginning
frames_per_minute_with_drop = Ratio.sub(frames_per_minute_whole, drop_rate)
# 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_10_minutes_drop =
frames_per_minute_with_drop |> Ratio.mult(9) |> Ratio.add(frames_per_minute_whole)
# Get the number of 10s of minutes in this count, and the remaining frames.
{tens_of_minutes, frames} = Rational.divmod(frame_number, frames_per_10_minutes_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 |> Ratio.mult(drop_rate) |> Ratio.mult(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 Ratio.compare(frames, frames_per_minute_whole) == :lt do
Ratio.add(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 = Ratio.sub(frames, timebase)
adjustment = Ratio.add(adjustment, drop_rate)
# Get the number of remaining drop-minutes present, and add a drop adjustment for
# each.
minutes_drop = frames |> Ratio.div(frames_per_minute_with_drop) |> Ratio.floor()
adjustment = minutes_drop |> Ratio.mult(drop_rate) |> Ratio.add(adjustment)
# Return our original frame number adjusted by our calculated adjustment.
Ratio.add(frame_number, adjustment)
end
end
# Get the number of frames that need to be dropped per minute (minus the 10th miute).
@spec frames_dropped_per_minute(Framerate.t()) :: integer()
defp frames_dropped_per_minute(rate) do
rate
|> Framerate.timebase()
|> Ratio.mult(Ratio.new(0.066666, 1))
|> Rational.round()
end
end