Packages
vtc
0.1.1
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/timcode.ex
defmodule Vtc.Timecode do
use Ratio, comparison: true
@enforce_keys [:seconds, :rate]
defstruct [:seconds, :rate]
@typedoc """
Type that represents Timecode struct with :seconds as the rational representation
of the number of seconds that have elapsed since 01:00:00:00 and :rate as the
framerate the timecode is being calculated at.
"""
@type t :: %Vtc.Timecode{seconds: Ratio.t(), rate: Vtc.Framerate.t()}
defmodule Sections do
@enforce_keys [:negative, :hours, :minutes, :seconds, :frames]
defstruct [:negative, :hours, :minutes, :seconds, :frames]
@type t :: %Sections{
negative: boolean,
hours: integer,
minutes: integer,
seconds: integer,
frames: integer
}
end
@spec frames(Vtc.Timecode.t()) :: integer
def frames(tc = %Vtc.Timecode{}) do
Private.round_ratio?(tc.seconds * tc.rate.playback)
end
@spec sections(Vtc.Timecode.t()) :: Sections.t()
def sections(tc = %Vtc.Timecode{}) do
timebase = Vtc.Framerate.timebase(tc.rate)
framesPerMinute = timebase * Private.secondsPerMinute()
framesPerHour = timebase * Private.secondsPerHour()
is_negative = tc.seconds < 0
frames = abs(frames(tc))
{hours, frames} = Private.divmod(frames, framesPerHour)
{minutes, frames} = Private.divmod(frames, framesPerMinute)
{seconds, frames} = Private.divmod(frames, timebase)
frames = Private.round_ratio?(frames)
%Sections{
negative: is_negative,
hours: hours,
minutes: minutes,
seconds: seconds,
frames: frames
}
end
@spec timecode(Vtc.Timecode.t()) :: String.t()
def timecode(tc = %Vtc.Timecode{}) do
sections = sections(tc)
# We'll add a negative sign if the timecode is negative.
sign =
if tc.seconds < 0 do
"-"
else
""
end
# If this is a drop-frame timecode, we need to use a ';' to separate the frames
# from the seconds.
frame_sep =
if tc.rate.ntsc == :Drop do
";"
else
":"
end
hours = sections.hours |> Integer.to_string() |> String.pad_leading(2, "0")
minutes = sections.minutes |> Integer.to_string() |> String.pad_leading(2, "0")
seconds = sections.seconds |> Integer.to_string() |> String.pad_leading(2, "0")
frames = sections.frames |> Integer.to_string() |> String.pad_leading(2, "0")
"#{sign}#{hours}:#{minutes}:#{seconds}#{frame_sep}#{frames}"
end
defmodule ParseError do
defexception [:reason]
@type t :: %ParseError{reason: :unrecognized_format}
@spec message(Vtc.Framerate.ParseError.t()) :: String.t()
def message(error) do
case error.reason do
:unrecognized_format -> "string format not recognized"
end
end
end
@type parse_result :: {:ok, Vtc.Timecode.t()} | {:error, ParseError.t()}
@spec with_seconds(Vtc.Sources.Seconds.t(), Vtc.Framerate.t()) :: parse_result
def with_seconds(seconds, %Vtc.Framerate{} = rate) do
case Vtc.Sources.Seconds.seconds(seconds, rate) do
{:ok, seconds} -> {:ok, %Vtc.Timecode{seconds: seconds, rate: rate}}
{:error, err} -> {:error, err}
end
end
@spec with_frames(Vtc.Sources.Frames.t(), Vtc.Framerate.t()) :: parse_result
def with_frames(frames, %Vtc.Framerate{} = rate) do
case Vtc.Sources.Frames.frames(frames, rate) do
{:ok, frames} ->
seconds = frames / rate.playback
with_seconds(seconds, rate)
{:error, err} ->
{:error, err}
end
end
end