defmodule Vtc.SMPTETimecode.Sections do @moduledoc """ Holds the individual sections of a SMPTE timecode for formatting / manipulation. ## Struct Fields - `negative`: Whether the timecode is less than 0. - `hours`: Hours place value. - `minutes`: Minutes place value. This is not the total minutes, but the minutes added to `hours` to get a final time. - `seconds`: Seconds place value. As minutes, remainder value rather than total value. - `frames`: Frames place value. As seconds, remainder value rather than total value. """ alias Vtc.Framerate alias Vtc.Framestamp alias Vtc.Utils.Consts alias Vtc.Utils.DropFrame alias Vtc.Utils.Rational @enforce_keys [:negative?, :hours, :minutes, :seconds, :frames] defstruct [:negative?, :hours, :minutes, :seconds, :frames] @typedoc """ Struct type. """ @type t :: %__MODULE__{ negative?: boolean(), hours: integer(), minutes: integer(), seconds: integer(), frames: integer() } @doc false @spec from_framestamp(Framestamp.t(), opts :: [round: Framestamp.round()]) :: t() def from_framestamp(framestamp, opts) do round = Keyword.get(opts, :round, :closest) rate = framestamp.rate timebase = Framerate.smpte_timebase(rate) frames_per_minute = Ratio.mult(timebase, Ratio.new(Consts.seconds_per_minute())) frames_per_hour = Ratio.mult(timebase, Ratio.new(Consts.seconds_per_hour())) total_frames = framestamp |> Framestamp.frames(opts) |> Kernel.abs() |> then(&(&1 + DropFrame.frame_num_adjustment(&1, rate))) {hours, remainder} = total_frames |> Ratio.new() |> Rational.divrem(frames_per_hour) {minutes, remainder} = Rational.divrem(remainder, frames_per_minute) {seconds, frames} = Rational.divrem(remainder, timebase) %__MODULE__{ negative?: Ratio.lt?(framestamp.seconds, 0), hours: hours, minutes: minutes, seconds: seconds, frames: Rational.round(frames, round) } end end