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/framerate.ex
defmodule Vtc.Ntsc do
@type t :: :None | :NonDrop | :Drop
@spec is_ntsc?(Vtc.Ntsc.t()) :: boolean
def is_ntsc?(:None) do
false
end
def is_ntsc?(ntsc) when is_atom(ntsc) do
true
end
end
defmodule Vtc.Framerate do
use Ratio, comparison: true
@enforce_keys [:playback, :ntsc]
defstruct [:playback, :ntsc]
@typedoc """
Type that represents Examples struct with :playback as rational
number which represents the playback speed of a timecode, and :ntsc, which
is an atom representing which, if any, NTSC convention this framerate
adheres to.
"""
@type t :: %Vtc.Framerate{playback: Ratio.t(), ntsc: Vtc.Ntsc.t()}
@spec timebase(Vtc.Framerate.t()) :: Ratio.t()
def timebase(framerate) do
if framerate.ntsc == :None do
framerate.playback
else
Private.round_ratio?(framerate.playback)
end
end
defmodule ParseError do
defexception [:reason]
@type t :: %ParseError{reason: :bad_drop_rate | :invalid_ntsc | :unrecognized_format}
@spec message(Vtc.Framerate.ParseError.t()) :: String.t()
def message(error) do
case error.reason do
:bad_drop_rate -> "drop-frame rates must be divisible by 30000/1001"
:invalid_ntsc -> "ntsc is not a valid atom. must be :NonDrop, :Drop, or None"
:unrecognized_format -> "framerate string format not recognized"
end
end
end
@spec new!(Ratio.t() | integer | float | String.t(), Vtc.Ntsc.t()) :: Vtc.Framerate.t()
def new!(rate, ntsc) do
case new?(rate, ntsc) do
{:ok, framerate} -> framerate
{:error, err} -> raise err
end
end
@type parse_result :: {:ok, Vtc.Framerate.t()} | {:error, Vtc.Framerate.ParseError.t()}
@spec new?(Ratio.t(), Vtc.Ntsc.t()) :: parse_result
def new?(
%Ratio{numerator: numerator, denominator: denominator} = rate,
ntsc
)
when is_integer(numerator) and is_integer(denominator) do
new_core(rate, ntsc)
end
@spec new?(integer, Vtc.Ntsc.t()) :: parse_result
def new?(rate, ntsc) when is_integer(rate) do
new_core(rate, ntsc)
end
@spec new?(float, Vtc.Ntsc.t()) :: parse_result
def new?(rate, ntsc) when is_float(rate) do
new?(Ratio.new(rate, 1.0), ntsc)
end
@spec new?(String.t(), Vtc.Ntsc.t()) :: parse_result
def new?(rate, ntsc) when is_bitstring(rate) do
result =
try do
new?(String.to_integer(rate), ntsc)
rescue
ArgumentError -> nil
end
result =
if result == nil do
try do
new?(String.to_float(rate), ntsc)
rescue
ArgumentError -> nil
end
else
result
end
result =
if result == nil do
try do
parse_rational_string(rate, ntsc)
rescue
ArgumentError -> {:error, %ParseError{reason: :unrecognized_format}}
end
else
result
end
result
end
@spec validate_drop(Ratio.t(), Vtc.Ntsc.t()) :: :ok | {:error, Vtc.Framerate.ParseError.t()}
defp validate_drop(rate, ntsc) do
# if this value does not go cleanly into 29.97, then it cannot be a drop-frame
# value.
cond do
ntsc != :Drop ->
:ok
not is_integer(rate / Ratio.new(30000, 1001)) ->
{:error, %ParseError{reason: :bad_drop_rate}}
true ->
:ok
end
end
@spec validate_ntsc(Vtc.Ntsc.t()) :: :ok | {:error, Vtc.Framerate.ParseError.t()}
defp validate_ntsc(ntsc) do
if ntsc not in [:Drop, :NonDrop, :None] do
{:error, %ParseError{reason: :invalid_ntsc}}
else
:ok
end
end
@spec coerce_ntsc_rate(Ratio.t(), Vtc.Ntsc.t()) :: Ratio.t()
defp coerce_ntsc_rate(rate, ntsc) do
if ntsc != :None and Ratio.denominator(rate) != 1001 do
rate = Private.round_ratio?(rate)
rate * 1000 / 1001
else
rate
end
end
@spec new_core(Ratio.t() | integer, Vtc.Ntsc.t()) :: parse_result
defp new_core(rate, ntsc) do
# validate that our ntsc atom is one of the acceptable values.
with :ok <- validate_ntsc(ntsc),
rate <- coerce_ntsc_rate(rate, ntsc),
:ok <- validate_drop(rate, ntsc) do
{:ok, %Vtc.Framerate{playback: rate, ntsc: ntsc}}
else
{:error, err} -> {:error, err}
end
end
@spec parse_rational_string(String.t(), Vtc.Ntsc.t()) :: parse_result
defp parse_rational_string(str, ntsc) do
split = String.split(str, "/")
if Enum.count(split) != 2 do
raise %ArgumentError{message: "not a fraction"}
else
[num_str, denom_str] = split
numerator = String.to_integer(num_str)
denominator = String.to_integer(denom_str)
[numerator, denominator] =
if denominator > numerator do
[denominator, numerator]
else
[numerator, denominator]
end
new?(Ratio.new(numerator, denominator), ntsc)
end
end
end