View Source Vtc.Framestamp (vtc v0.17.5)
Identifies a particular frame in a media stream.
New Framestamp values are created by with_seconds/3
and with_frames/2
.
Vtc's philosophy of working with Timecode is defined by two major conceits:
A frame identifier is incomplete without a framerate. More here.
All frame identifiers commonly used in Video production boil down to either the real-world seconds-since-midnight that frame occurred, OR a sequential index number. More here.
what-is-a-framestamp
What is a framestamp?
Framestamps are a way to identify a media stream frame without requiring any additional information. On a technical level, a framestamp is comprised of:
The real-world time that a frame occurred at, as represented by a rational value, measured in seconds since SMPTE timecode "midnight".
The framerate of the media the framestamp was generated for, as represented by a rational frames-per-second value.
Any associated metadata about the source representation the framestamp was parsed from, such as SMPTE NTSC non-drop timecode.
A fully-formed framestamp for 01:00:00:00
at 23.98 NTSC
would be
18018/5 @ 24000/1001 NTSC non-drop
.
why-prefer-seconds
Why prefer seconds?
SMPTE timecode is the canonical way frames are identified in professional video workflows. As a human-readable data type, timecode strings are great! You can easily locate, compare, and add timecode strings at-a-glance.
Why then, does Vtc come up with a new representation?
Well, SMPTE timecode strings are not as great for computers. Let's take a quick look at what we want from a good frame identifier:
Uniquely identifies a frame in a specific video stream.
Sortable by real-world occurrence.
Easily added/subtracted to each other.
All of the above, in mixed-framerate contexts.
The last point is key, timecode is great... if all of your media is running at the
same framerate. For instance, when syncing media streams between two devices -- one
running at 24fps, and one running at 48fps -- 01:00:00:13
and 01:00:00:26
are
equivalent values, as they were captured at the same point in time, and should be
synced together. Timecode is an expression of frame index more than frame seconds,
and as such, cannot be lexically sorted in mixed-rate settings. Further,
a computer cannot add "01:30:00:00" to "01:00:00:00" without converting it to some
sort of numerical value.
Many programs convert timecode directly to an integer frame number for arithmetic and
comparison operations where each frame on the clock is issued a continuous index,
with 0
as 00:00:00:00
. Frame numbers, though, have the same issue with mixed-rate
values as timecode; 26
at 48 frames-per-second represents the same real-world time
as 13
at 24 frames-per-seconds, and preserving that equality is important for
operations like jam-syncing.
So that leaves us with real-world seconds. Convert timecode values -- even ones captured in mixed rates -- to seconds, then add and sort to your heart's content.
why-rational-numbers
Why rational numbers?
We'll avoid a deep-dive over why we use a rational value over a float or decimal, but you can read more on that choice here.
The short version is that many common SMPTE-specified framerates are defined as
irrational numbers. For instance, 23.98 NTSC
is defined as 24000/1001
frames-per-second.
In order to avoid off-by-one errors when using seconds
, we need to avoid resolving
values like 1001/24000
-- the value for frame 1
at 23.98 NTSC
-- into any sort
of decimal representation, since 1001/24000
is an irrational value and cannot be
cleanly represented as a decimal. It's digits ride off into the sunset.
why-include-framerate
Why include framerate?
SMPTE timecode does not include a framerate in it's specification for frame
identifiers, i.e 01:00:00:00
. So why does Vtc?
Lets say that we are working with a given video file, and you are handed the timecode
01:00:00:12
. What frame does that belong to?
Without a framerate, you cannot know. If we are talking about 23.98 NTSC
media, it
belongs to frame 86,400
, but if we are talking about 59.94 NTSC NDF
, frame then
it belongs to frame 216,000
, and if we are talking about 59.94 NTSC DF
media then
it belongs to frame 215,784
.
What about the other direction? We need to calculate the SMPTE timecode for frame
48
, which we previously parsed from a timecode. Well if it was originally parsed
using 23.98 NTSC
footage, then it is TC 00:00:02:00
, but if it is 59.94 NTSC
then it is TC 00:00:00:48
. Framerate is implicitly required for a SMPTE timecode
to be comprehensible.
The story is the same with seconds. How many seconds does 01:00:00:00
represent?
At 23.98 NTSC
, it represents 18018/5
seconds, but at 24fps true
it represents
3600/1
seconds.
We cannot know what frame a seconds value represents, or what seconds value a frame represents, without knowing that scalar value's associated framerate. It's like having a timestamp without a timezone. Even in systems where all timestamps are converted to UTC, we often keep the timezone information around because it's just too useful in mixed-timezone settings, and you can't be sure what a given timestamp represents in a vacuum if you don't have the associated timezone.
Framerate -- especially in mixed rate settings, which Vtc considers a first-class use case -- is required to sensibly execute many operations, like casting in an out of SMPTE Timecode, adding two timecodes together, etc.
For this reason we package the framerate of our media stream together with the scalar value that represents a frame in that stream, and take the onus of transporting these two values together off of the caller.
struct-fields
Struct Fields
seconds
: The real-world seconds elapsed since 'midnight' as a rational value.rate
: the Framerate of theFramestamp
.
parsing-seconds-t-or-frames-t
Parsing: Seconds.t() or Frames.t()
Parsing functions pre-append with_
to their name. When you give a value to a parsing
function, it is the same value that would be returned by the equivalent unit
conversion. So a value passed to with_frames is the
same value frames would return:
iex> {:ok, framestamp} = Framestamp.with_frames(24, Rates.f23_98())
iex> inspect(framestamp)
"<00:00:01:00 <23.98 NTSC>>"
iex> Framestamp.frames(framestamp)
24
The Framestamp
module only has two basic construction / parsing methods:
with_seconds and
with_frames.
At first blush, this may seem... odd. Where is with_timecode/2
? Or
with_premiere_ticks/2
? We can render these formats, so why isn't there a parser for
them? Well there is, sort of: the two functions above.
Vtc's second major conceit is that all of the various ways of representing a video frame's timestamp boil down to EITHER:
- a) A representation of an index number for that frame
OR
- b) A representation of the real-world seconds the frame occurred at.
SMPTE timecode is really a human-readable way to represent a frame number. Same with film feet+frames.
Premiere Ticks, on the other hand, represents a real-world seconds value, as broken
down in 1/254_016_000_000ths
of a second.
Instead of polluting the module's namespace with a range of constructors, Vtc declares a Frames protocol for types that represent a frame count, and a Seconds protocol for types that represent a time-scalar.
All framestamp representations eventually get funneled through one of these
protocols. For instance, when the String
implementation of the protocol detects a
SMPTE timecode string, it wraps the value in a
SMPTETimecodeStr struct which handles converting that
string to a frame number thorough implementing the Frames
protocol. That frame number is then taken by
with_frames and converted to a rational seconds
value.
Going through protocols allows callers to define their own types that work with Vtc's parsing functions directly.
sorting-support
Sorting Support
Framestamp implements compare/2
, and as such, can be used wherever
the standard library calls for a Sorter
module. Let's see it in action:
iex> stamp_01 = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> stamp_02 = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
iex>
iex> sorted = Enum.sort([stamp_02, stamp_01], Framestamp)
iex> inspect(sorted)
"[<01:00:00:00 <23.98 NTSC>>, <02:00:00:00 <23.98 NTSC>>]"
iex> sorted = Enum.sort([stamp_01, stamp_02], {:desc, Framestamp})
iex> inspect(sorted)
"[<02:00:00:00 <23.98 NTSC>>, <01:00:00:00 <23.98 NTSC>>]"
iex> max = Enum.max([stamp_02, stamp_01], Framestamp)
iex> inspect(max)
"<02:00:00:00 <23.98 NTSC>>"
iex> min = Enum.min([stamp_02, stamp_01], Framestamp)
iex> inspect(min)
"<01:00:00:00 <23.98 NTSC>>"
iex> data_01 = %{id: 2, tc: stamp_01}
iex> data_02 = %{id: 1, tc: stamp_02}
iex> sorted = Enum.sort_by([data_02, data_01], & &1.tc, Framestamp)
iex> inspect(sorted)
"[%{id: 2, tc: <01:00:00:00 <23.98 NTSC>>}, %{id: 1, tc: <02:00:00:00 <23.98 NTSC>>}]"
arithmetic-autocasting
Arithmetic Autocasting
For operators that take two Framestamp
values, like add/3
or compare/2
, as long
as one argument is a Framestamp value, a
or b
May be any value
that implements the Frames protocol, such as a timecode string,
and will be assumed to be the same framerate as the other.
Production code
Autocasting exists to support quick scratch scripts and we suggest that it not be relied upon in production application code.
If parsing the value fails during casting, the function raises a
Vtc.Framestamp.ParseError
.
using-as-an-ecto-type
Using as an Ecto Type
See PgFramestamp for information on how to use
Framerate
in your postgres database as a native type.
Link to this section Summary
Types
Describes which side to inherit the framerate from in mixed-rate arithmetic.
Type returned by with_seconds/3
and with_frames/3
.
Valid values for rounding options.
Framestamp type.
Parse
Return a Framestamp for SMPTE timecode 24:00:00:00
at rate
.
As smpte_midnight/1
, but raises on error.
Returns a new Framestamp with a frames/2
return value equal to the
frames
arg.
As Framestamp.with_frames/3
, but raises on error.
Returns a new Framestamp with a :seconds
field value equal to the
seconds
arg.
As with_seconds/3
, but raises on error.
Manipulate
Rebases framestamp
to a new framerate.
As rebase/2
, but raises on error.
Wrap value
to the nearest valid TOD (time-of-day) timecode.
As smpte_wrap_tod/1
, but raises on error.
Compare
Compare the values of a
and b
.
Returns true
if a
is equal to b
.
Returns true
if a
is greater than b
.
Returns true
if a
is greater than or equal to b
.
Returns true
if a
is less than b
.
Returns true
if a
is less than or equal to b
.
Arithmetic
Returns the absolute value of tc
.
Add two framestamps.
Divides dividend
by divisor
.
Divides the total frame count of dividend
by divisor
and returns both a quotient
and a remainder.
Evaluates Framestamp mathematical expressions in a do
block.
As the kernel -/1
function.
Scales a
by b
.
Divides the total frame count of dividend
by devisor
, and returns the remainder.
Subtract b
from a
.
Convert
Returns the number of physical film feet and frames framestamp
represents if shot
on film.
Returns the number of frames that would have elapsed between 00:00:00:00
and
Framestamp.
Returns the number of elapsed ticks framestamp
represents in Adobe Premiere Pro.
Runtime Returns the true, real-world runtime of framestamp
in HH:MM:SS.FFFFFFFFF
format.
Returns the the formatted SMPTE timecode for a Framestamp.
The individual sections of a SMPTE timecode string as i64 values.
Ecto Migrations
The database type for PgFramestamp.
Changeset Validations
Adds all constraints created by PgFramestamp.Migrations.create_constraints/3 to changeset to be added as changeset errors rather than raised.
Functions
Callback implementation for Ecto.Type.embed_as/1
.
Callback implementation for Ecto.Type.equal?/2
.
Link to this section Types
@type inherit_opt() :: :left | :right | false
Describes which side to inherit the framerate from in mixed-rate arithmetic.
@type parse_result() :: {:ok, t()} | {:error, Vtc.Framestamp.ParseError.t() | %ArgumentError{__exception__: true, message: term()}}
Type returned by with_seconds/3
and with_frames/3
.
@type round() :: :closest | :floor | :ceil | :trunc | :off
Valid values for rounding options.
:closest
: Round the to the closet whole frame. Rounds away from zero when value is equidistant from two whole-frames.:floor
: Always round down to the closest whole-frame. Negative numbers round away from zero:ciel
: Always round up to the closest whole-frame. Negative numbers round towards zero.:trunc
: Always round towards zero to the closest whole frame. Negative numbers round up and positive numbers round down.:off
: Do not round. Will always raise if result would represent a non-whole-frame value.
@type t() :: %Vtc.Framestamp{rate: Vtc.Framerate.t(), seconds: Ratio.t()}
Framestamp type.
Link to this section Parse
@spec smpte_midnight(Vtc.Framerate.t()) :: {:ok, t()} | {:error, Vtc.Framerate.InvalidSMPTEValueError.t()}
Return a Framestamp for SMPTE timecode 24:00:00:00
at rate
.
Since all non-drop timecodes share the same midnight seconds value, and all drop timecodes similarly share the same midnight value, this function is significantly more performant than using the regular parsing functions.
Will return error if rate
is not a drop, non-drop, or whole-frame SMPTE timecode.
examples
Examples
iex> {:ok, framestamp} = Framestamp.smpte_midnight(Rates.f23_98())
iex> inspect(framestamp)
"<24:00:00:00 <23.98 NTSC>>"
@spec smpte_midnight!(Vtc.Framerate.t()) :: t()
As smpte_midnight/1
, but raises on error.
raises
Raises
- InvalidSMPTEValueError if
rate
is not NTSC drop or non-drop.
@spec with_frames(Vtc.Source.Frames.t(), Vtc.Framerate.t()) :: parse_result()
Returns a new Framestamp with a frames/2
return value equal to the
frames
arg.
arguments
Arguments
frames
: A value which can be represented as a frame number / frame count. Must implement the Frames protocol.rate
: Frame-per-second playback value of the framestamp.
options
Options
round
: How to round the result with regards to whole-frames. Default::closest
.
examples
Examples
Accepts SMPTE timecode strings...
iex> result = Framestamp.with_frames("01:00:00:00", Rates.f23_98())
iex> inspect(result)
"{:ok, <01:00:00:00 <23.98 NTSC>>}"
... feet+frames strings...
iex> result = Framestamp.with_frames("5400+00", Rates.f23_98())
iex> inspect(result)
"{:ok, <01:00:00:00 <23.98 NTSC>>}"
By default, feet+frames is interpreted as 35mm, 4perf film. You can use the FeetAndFrames struct to parse other film formats:
iex> alias Vtc.Source.Frames.FeetAndFrames
iex>
iex> {:ok, feet_and_frames} = FeetAndFrames.from_string("5400+00", film_format: :ff16mm)
iex>
iex> result = Framestamp.with_frames(feet_and_frames, Rates.f23_98())
iex> inspect(result)
"{:ok, <01:15:00:00 <23.98 NTSC>>}"
... integers...
iex> result = Framestamp.with_frames(86_400, Rates.f23_98())
iex> inspect(result)
"{:ok, <01:00:00:00 <23.98 NTSC>>}"
... and integer strings.
iex> result = Framestamp.with_frames("86400", Rates.f23_98())
iex> inspect(result)
"{:ok, <01:00:00:00 <23.98 NTSC>>}"
@spec with_frames!(Vtc.Source.Frames.t(), Vtc.Framerate.t()) :: t()
As Framestamp.with_frames/3
, but raises on error.
@spec with_seconds( Vtc.Source.Seconds.t(), Vtc.Framerate.t(), opts :: [{:round, round() | :off}] ) :: parse_result()
Returns a new Framestamp with a :seconds
field value equal to the
seconds
arg.
arguments
Arguments
seconds
: A value which can be represented as a number of real-world seconds. Must implement the Seconds protocol.rate
: Frame-per-second playback value of the framestamp.
options
Options
round
: How to round the result with regards to whole-frames. If set to:off
, will return an error if the providedseconds
value does not exactly represent a whole-number frame count. Default::closest
.
examples
Examples
Accepts runtime strings...
iex> result = Framestamp.with_seconds("01:00:00.5", Rates.f23_98())
iex> inspect(result)
"{:ok, <00:59:56:22 <23.98 NTSC>>}"
... floats...
iex> result = Framestamp.with_seconds(3600.5, Rates.f23_98())
iex> inspect(result)
"{:ok, <00:59:56:22 <23.98 NTSC>>}"
... integers...
iex> result = Framestamp.with_seconds(3600, Rates.f23_98())
iex> inspect(result)
"{:ok, <00:59:56:10 <23.98 NTSC>>}"
... integer strings...
iex> result = Framestamp.with_seconds("3600", Rates.f23_98())
iex> inspect(result)
"{:ok, <00:59:56:10 <23.98 NTSC>>}"
... and float strings.
iex> result = Framestamp.with_seconds("3600.5", Rates.f23_98())
iex> inspect(result)
"{:ok, <00:59:56:22 <23.98 NTSC>>}"
premiere-ticks
Premiere Ticks
The Vtc.Source.Seconds.PremiereTicks
struck implements the
Seconds protocol and can be used to parse the format. This
struct is not a general-purpose Module for the unit, and only exists to hint to the
parsing function how it should be processed:
iex> alias Vtc.Source.Seconds.PremiereTicks
iex>
iex> input = %PremiereTicks{in: 254_016_000_000}
iex>
iex> result = Framestamp.with_seconds!(input, Rates.f23_98())
iex> inspect(result)
"<00:00:01:00 <23.98 NTSC>>"
@spec with_seconds!( Vtc.Source.Seconds.t(), Vtc.Framerate.t(), opts :: [{:round, round() | :off}] ) :: t()
As with_seconds/3
, but raises on error.
Link to this section Manipulate
@spec rebase(t(), Vtc.Framerate.t()) :: parse_result()
Rebases framestamp
to a new framerate.
The real-world seconds are recalculated using the same frame count as if they were
being played back at new_rate
instead of framestamp.rate
.
examples
Examples
iex> framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> {:ok, rebased} = Framestamp.rebase(framestamp, Rates.f47_95())
iex> inspect(rebased)
"<00:30:00:00 <47.95 NTSC>>"
@spec rebase!(t(), Vtc.Framerate.t()) :: t()
As rebase/2
, but raises on error.
@spec smpte_wrap_tod(t()) :: {:ok, t()} | {:error, Vtc.Framerate.InvalidSMPTEValueError.t()}
Wrap value
to the nearest valid TOD (time-of-day) timecode.
Framestamps with a SMPTE timecode of less than 00:00:00:00
will have 24:00:00:00
recursively added until they are positive.
Framestamps with a SMPTE timecode of greater than or equal to 24:00:00:00
will have
24:00:00:00
subtracted until they are less than 24:00:00:00
.
Returns error if value.rate
is not NTSC or whole-frame. Time-of-day timecode is not
defined for non-SMPTE framerates.
examples
Examples
iex> {:ok, stamp} = Framestamp.with_frames("01:30:21:17", Rates.f23_98())
iex> {:ok, stamp} = Framestamp.smpte_wrap_tod(stamp)
iex> inspect(stamp)
"<01:30:21:17 <23.98 NTSC>>"
iex> {:ok, stamp} = Framestamp.with_frames("24:30:21:17", Rates.f23_98())
iex> {:ok, stamp} = Framestamp.smpte_wrap_tod(stamp)
iex> inspect(stamp)
"<00:30:21:17 <23.98 NTSC>>"
iex> {:ok, stamp} = Framestamp.with_frames("-01:00:00:00", Rates.f23_98())
iex> {:ok, stamp} = Framestamp.smpte_wrap_tod(stamp)
iex> inspect(stamp)
"<23:00:00:00 <23.98 NTSC>>"
iex> {:ok, stamp} = Framestamp.with_frames("24:00:00:00", Rates.f23_98())
iex> {:ok, stamp} = Framestamp.smpte_wrap_tod(stamp)
iex> inspect(stamp)
"<00:00:00:00 <23.98 NTSC>>"
As smpte_wrap_tod/1
, but raises on error.
raises
Raises
- InvalidSMPTEValueError if
rate
is not NTSC drop or non-drop.
Link to this section Compare
@spec compare(a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t()) :: :lt | :eq | :gt
Compare the values of a
and b
.
Compatible with Enum.sort/2
. For more on sorting non-builtin values, see
the Elixir documentation.
auto-casts Frames values.
See eq?/2
for more information on how equality is determined.
examples
Examples
Using two framestamps parsed from SMPTE timecode, 01:00:00:00
NTSC is greater than
01:00:00:00
true because it represents more real-world time.
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("01:00:00:00", Rates.f24())
iex> :gt = Framestamp.compare(a, b)
Using a framestamp and a bare string:
iex> framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> :eq = Framestamp.compare(framestamp, "01:00:00:00")
@spec eq?(a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t()) :: boolean()
Returns true
if a
is equal to b
.
auto-casts Frames values.
examples
Examples
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> true = Framestamp.eq?(a, b)
Framestamps with the same string timecode representation, but different real-world seconds values, are not equal:
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("01:00:00:00", Rates.f24())
iex> false = Framestamp.eq?(a, b)
But Framestamps with the different SMPTE timecode string representation, but the same real-world seconds values, are equal:
iex> a = Framestamp.with_frames!("01:00:00:12", Rates.f23_98())
iex> b = Framestamp.with_frames!("01:00:00:24", Rates.f47_95())
iex> true = Framestamp.eq?(a, b)
@spec gt?(a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t()) :: boolean()
Returns true
if a
is greater than b
.
auto-casts Frames values.
See eq?/2
for more information on how equality is determined.
@spec gte?(a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t()) :: boolean()
Returns true
if a
is greater than or equal to b
.
auto-casts Frames values.
See eq?/2
for more information on how equality is determined.
@spec lt?(a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t()) :: boolean()
Returns true
if a
is less than b
.
auto-casts Frames values.
See eq?/2
for more information on how equality is determined.
examples
Examples
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
iex> true = Framestamp.lt?(a, b)
iex> false = Framestamp.lt?(b, a)
@spec lte?(a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t()) :: boolean()
Returns true
if a
is less than or equal to b
.
auto-casts Frames values.
See eq?/2
for more information on how equality is determined.
Link to this section Arithmetic
Returns the absolute value of tc
.
examples
Examples
iex> stamp = Framestamp.with_frames!("-01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.abs(stamp)
iex> inspect(result)
"<01:00:00:00 <23.98 NTSC>>"
iex> stamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.abs(stamp)
iex> inspect(result)
"<01:00:00:00 <23.98 NTSC>>"
@spec add( a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t(), opts :: [inherit_rate: inherit_opt(), round: round()] ) :: t()
Add two framestamps.
Uses the real-world seconds representation. When the rates of a
and b
are not
equal, the result will inherit the framerate of a
and be rounded to the seconds
representation of the nearest whole-frame at that rate.
auto-casts Frames values.
options
Options
inherit_rate
: Which side to inherit the framerate from in mixed-rate calculations. Iffalse
, this function will raise ifa.rate
does not matchb.rate
. Default:false
.round
: How to round the result with respect to whole-frames when mixing framerates. Default::closest
.
examples
Examples
Two framestamps running at the same rate:
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("01:30:21:17", Rates.f23_98())
iex>
iex> result = Framestamp.add(a, b)
iex> inspect(result)
"<02:30:21:17 <23.98 NTSC>>"
Two framestamps running at different rates:
iex> a = Framestamp.with_frames!("01:00:00:02", Rates.f23_98())
iex> b = Framestamp.with_frames!("00:00:00:02", Rates.f47_95())
iex>
iex> result = Framestamp.add(a, b, inherit_rate: :left)
iex> inspect(result)
"<01:00:00:03 <23.98 NTSC>>"
iex>
iex> result = Framestamp.add(a, b, inherit_rate: :right)
iex> inspect(result)
"<01:00:00:06 <47.95 NTSC>>"
If :inherit_rate
is not set...
iex> a = Framestamp.with_frames!("01:00:00:02", Rates.f23_98())
iex> b = Framestamp.with_frames!("00:00:00:02", Rates.f47_95())
iex> Framestamp.add(a, b)
** (Vtc.Framestamp.MixedRateArithmeticError) attempted `Framestamp.add(a, b)` where `a.rate` does not match `b.rate`. try `:inherit_rate` option to `:left` or `:right`. alternatively, do your calculation in seconds, then cast back to `Framestamp` with the appropriate framerate using `with_seconds/3`
Using a framestamps and a bare string:
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.add(a, "01:30:21:17")
iex> inspect(result)
"<02:30:21:17 <23.98 NTSC>>"
Divides dividend
by divisor
.
The result will inherit the framerate of dividend
and rounded to the nearest
whole-frame based on the :round
option.
options
Options
round
: How to round the result with respect to whole-frame values. Defaults to:trunc
to matchdivmod
and the expected meaning ofdiv
to mean integer division in elixir.
examples
Examples
iex> dividend = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.div(dividend, 2)
iex> inspect(result)
"<00:30:00:00 <23.98 NTSC>>"
iex> dividend = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.div(dividend, 0.5)
iex> inspect(result)
"<02:00:00:00 <23.98 NTSC>>"
@spec divrem( dividend :: t(), divisor :: Ratio.t() | number(), opts :: [round_frames: round(), round_remainder: round()] ) :: {t(), t()}
Divides the total frame count of dividend
by divisor
and returns both a quotient
and a remainder.
The quotient returned is equivalent to Framestamp.div/3
with the :round
option set
to :trunc
.
options
Options
round_frames
: How to round the frame count before doing the divrem operation. Default::closest
.round_remainder
: How to round the remainder frames when a non-whole frame would be the result. Default::closest
.
examples
Examples
iex> dividend = Framestamp.with_frames!("01:00:00:01", Rates.f23_98())
iex>
iex> result = Framestamp.divrem(dividend, 4)
iex> inspect(result)
"{<00:15:00:00 <23.98 NTSC>>, <00:00:00:01 <23.98 NTSC>>}"
@spec eval( [at: Vtc.Framerate.t() | number() | Ratio.t(), ntsc: Vtc.Framerate.ntsc()], Macro.input() ) :: Macro.t()
Evaluates Framestamp mathematical expressions in a do
block.
Any code captured within this macro can use Kernel operators to work with
Framestamp values instead of module functions like add/2
.
options
Options
at
: The Framerate to cast non-Framestamp values to. If this value is not set, then at least one value in each operation must be a Framestamp. This value can be any value accepted by Framerate.new/2.ntsc
: Thentsc
value to use when creating a new Framerate withat
. Not needed ifat
is a Framerate value.
examples
Examples
Use eval to do some quick math. The block captures variables from the outer scope,
but contains the expression within its own scope, just like an if
or with
statement.
iex> require Framestamp
iex>
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("00:30:00:00", Rates.f23_98())
iex> c = Framestamp.with_frames!("00:15:00:00", Rates.f23_98())
iex>
iex> result =
iex> Framestamp.eval do
iex> a + b * 2 - c
iex> end
iex>
iex> inspect(result)
"<01:45:00:00 <23.98 NTSC>>"
Or if you want to do it in one line:
iex> require Framestamp
iex>
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("00:30:00:00", Rates.f23_98())
iex> c = Framestamp.with_frames!("00:15:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.eval(a + b * 2 - c)
iex>
iex> inspect(result)
"<01:45:00:00 <23.98 NTSC>>"
Just like the regular Framestamp functions, only one value in an
arithmetic expression needs to be a Framestamp value. In the case
above, since multiplication happens first, that's b
:
iex> b = Framestamp.with_frames!("00:30:00:00", Rates.f23_98())
iex>
iex> result =
iex> Framestamp.eval do
iex> "01:00:00:00" + b * 2 - "00:15:00:00"
iex> end
iex>
iex> inspect(result)
"<01:45:00:00 <23.98 NTSC>>"
You can supply a default framerate if you just want to do some quick calculations. This framerate is inherited by every value that implements the Frames protocol in the block, including integers:
iex> result =
iex> Framestamp.eval at: Rates.f23_98() do
iex> "01:00:00:00" + "00:30:00:00" * 2 - "00:15:00:00"
iex> end
iex>
iex> inspect(result)
"<01:45:00:00 <23.98 NTSC>>"
You can use any value that can be parsed by Framerate.new/2
.
iex> result =
iex> Framestamp.eval at: 23.98 do
iex> "01:00:00:00" + "00:30:00:00" * 2 - "00:15:00:00"
iex> end
iex>
iex> inspect(result)
"<01:45:00:00 <23.98 NTSC>>"
ntsc: :non_drop, coerce_ntsc?: true
is assumed by default, but you can set a
different value with the :ntsc
option:
iex> result =
iex> Framestamp.eval at: 24, ntsc: nil do
iex> "01:00:00:00" + "00:30:00:00" * 2 - "00:15:00:00"
iex> end
iex>
iex> inspect(result)
"<01:45:00:00 <24.0 fps>>"
As the kernel -/1
function.
- Makes a positive
tc
value negative. - Makes a negative
tc
value positive.
examples
Examples
iex> stamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.minus(stamp)
iex> inspect(result)
"<-01:00:00:00 <23.98 NTSC>>"
iex> stamp = Framestamp.with_frames!("-01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.minus(stamp)
iex> inspect(result)
"<01:00:00:00 <23.98 NTSC>>"
Scales a
by b
.
The result will inherit the framerate of a
and be rounded to the seconds
representation of the nearest whole-frame based on the :round
option.
options
Options
round
: How to round the result with respect to whole-frame values. Defaults to:closest
.
examples
Examples
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.mult(a, 2)
iex> inspect(result)
"<02:00:00:00 <23.98 NTSC>>"
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.mult(a, 0.5)
iex> inspect(result)
"<00:30:00:00 <23.98 NTSC>>"
@spec rem( dividend :: t(), divisor :: Ratio.t() | number(), opts :: [round_frames: round(), round_remainder: round()] ) :: t()
Divides the total frame count of dividend
by devisor
, and returns the remainder.
The quotient is truncated before the remainder is calculated.
options
Options
round_frames
: How to round the frame count before doing the rem operation. Default::closest
.round_remainder
: How to round the remainder frames when a non-whole frame would be the result. Default::closest
.
examples
Examples
iex> dividend = Framestamp.with_frames!("01:00:00:01", Rates.f23_98())
iex>
iex> result = Framestamp.rem(dividend, 4)
iex> inspect(result)
"<00:00:00:01 <23.98 NTSC>>"
@spec sub( a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t(), opts :: [inherit_rate: inherit_opt(), round: round()] ) :: t()
Subtract b
from a
.
Uses their real-world seconds representation. When the rates of a
and b
are not
equal, the result will inherit the framerate of a
and be rounded to the seconds
representation of the nearest whole-frame at that rate.
auto-casts Frames values.
options
Options
inherit_rate
: Which side to inherit the framerate from in mixed-rate calculations. Iffalse
, this function will raise ifa.rate
does not matchb.rate
. Default:false
.round
: How to round the result with respect to whole-frames when mixing framerates. Default::closest
.
examples
Examples
Two framestamps running at the same rate:
iex> a = Framestamp.with_frames!("01:30:21:17", Rates.f23_98())
iex> b = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.sub(a, b)
iex> inspect(result)
"<00:30:21:17 <23.98 NTSC>>"
Two framestamps running at different rates:
iex> a = Framestamp.with_frames!("01:00:00:02", Rates.f23_98())
iex> b = Framestamp.with_frames!("00:00:00:02", Rates.f47_95())
iex>
iex> result = Framestamp.sub(a, b, inherit_rate: :left)
iex> inspect(result)
"<01:00:00:01 <23.98 NTSC>>"
iex>
iex> result = Framestamp.sub(a, b, inherit_rate: :right)
iex> inspect(result)
"<01:00:00:02 <47.95 NTSC>>"
If :inherit_rate
is not set...
iex> a = Framestamp.with_frames!("01:00:00:02", Rates.f23_98())
iex> b = Framestamp.with_frames!("00:00:00:02", Rates.f47_95())
iex> Framestamp.sub(a, b)
** (Vtc.Framestamp.MixedRateArithmeticError) attempted `Framestamp.sub(a, b)` where `a.rate` does not match `b.rate`. try `:inherit_rate` option to `:left` or `:right`. alternatively, do your calculation in seconds, then cast back to `Framestamp` with the appropriate framerate using `with_seconds/3`
When b
is greater than a
, the result is negative:
iex> a = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.sub(a, b)
iex> inspect(result)
"<-01:00:00:00 <23.98 NTSC>>"
Using a framestamps and a bare string:
iex> a = Framestamp.with_frames!("01:30:21:17", Rates.f23_98())
iex>
iex> result = Framestamp.sub(a, "01:00:00:00")
iex> inspect(result)
"<00:30:21:17 <23.98 NTSC>>"
Link to this section Convert
@spec feet_and_frames(t(), opts :: [film_format: Vtc.FilmFormat.t(), round: round()]) :: Vtc.Source.Frames.FeetAndFrames.t()
Returns the number of physical film feet and frames framestamp
represents if shot
on film.
Ex: '5400+13'.
options
Options
round
: How to round the internal frame count before conversion. Default::closest
.film_format
: The film format to use when doing the calculation. For more on film formats, seeVtc.FilmFormat
. Default::ff35mm_4perf
, by far the most common format used to shoot Hollywood movies.
what-it-is
What it is
On physical film, each foot contains a certain number of frames. For 35mm, 4-perf film (the most common type on Hollywood movies), this number is 16 frames per foot. Feet-And-Frames was often used in place of Keycode to quickly reference a frame in the edit.
where-you-see-it
Where you see it
For the most part, feet + frames has died out as a reference, because digital media is not measured in feet. The most common place it is still used is Studio Sound Departments. Many Sound Mixers and Designers intuitively think in feet + frames, and it is often burned into the reference picture for them.
- Telecine.
- Sound turnover reference picture.
- Sound turnover change lists.
For more information on individual film formats, see the Vtc.FilmFormat
module.
examples
Examples
Defaults to 35mm, 4perf:
iex> framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.feet_and_frames(framestamp)
iex> inspect(result)
"<5400+00 :ff35mm_4perf>"
Use String.Chars
to convert the resulting struct to a traditional F=F string:
iex> alias Vtc.Source.Frames.FeetAndFrames
iex>
iex> framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.feet_and_frames(framestamp)
iex> String.Chars.to_string(result)
"5400+00"
Outputting as a different film format:
examples-1
Examples
iex> framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.feet_and_frames(framestamp, film_format: :ff16mm)
iex> inspect(result)
"<4320+00 :ff16mm>"
Returns the number of frames that would have elapsed between 00:00:00:00
and
Framestamp.
options
Options
round
: How to round the resulting frame number.
what-it-is
What it is
Frame number / frames count is the number of a frame if the SMPTE timecode started at 00:00:00:00 and had been running until the current value. A SMPTE timecode of '00:00:00:10' has a frame number of 10. A SMPTE timecode of '01:00:00:00' has a frame number of 86400.
where-you-see-it
Where you see it
Frame-sequence files: 'my_vfx_shot.0086400.exr'
FCP7XML cut lists:
<timecode> <rate> <timebase>24</timebase> <ntsc>TRUE</ntsc> </rate> <string>01:00:00:00</string> <frame>86400</frame> <!-- <====THIS LINE--> <displayformat>NDF</displayformat> </timecode>
examples
Examples
iex> framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> Framestamp.frames(framestamp)
86400
Returns the number of elapsed ticks framestamp
represents in Adobe Premiere Pro.
options
Options
round
: How to round the resulting ticks.
what-it-is
What it is
Internally, Adobe Premiere Pro uses ticks to divide up a second, and keep track of how far into that second we are. There are 254016000000 ticks in a second, regardless of framerate in Premiere.
where-you-see-it
Where you see it
Premiere Pro Panel functions and scripts.
FCP7XML cutlists generated from Premiere:
<clipitem id="clipitem-1"> ... <in>158</in> <out>1102</out> <pproTicksIn>1673944272000</pproTicksIn> <pproTicksOut>11675231568000</pproTicksOut> ... </clipitem>
examples
Examples
iex> framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> Framestamp.premiere_ticks(framestamp)
915372057600000
@spec runtime(t(), precision: non_neg_integer(), trim_zeros?: boolean()) :: String.t()
Runtime Returns the true, real-world runtime of framestamp
in HH:MM:SS.FFFFFFFFF
format.
Trailing zeroes are trimmed from the end of the return value. If the entire fractal seconds value would be trimmed, '.0' is used.
options
Options
precision
: The number of places to round to. Extra trailing 0's will still be trimmed. Default:9
.trim_zeros?
: Whether to trim trailing zeroes. Default:true
.
what-it-is
What it is
The human-readable version of seconds
. It looks like timecode, but with a decimal
seconds value instead of a frame number place.
where-you-see-it
Where you see it
• Anywhere real-world time is used.
• FFMPEG commands:
ffmpeg -ss 00:00:30.5 -i input.mov -t 00:00:10.25 output.mp4
note
Note
The true runtime will often diverge from the hours, minutes, and seconds value of the SMPTE timecode representation when dealing with non-whole-frame framerates. Even drop-frame timecode does not continuously adhere 1:1 to the actual runtime. For instance, <01:00:00;00 <29.97 NTSC DF>> has a true runtime of '00:59:59.9964', and <01:00:00:00 <23.98 NTSC>> has a true runtime of '01:00:03.6'
examples
Examples
iex> framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> Framestamp.runtime(framestamp)
"01:00:03.6"
Returns the the formatted SMPTE timecode for a Framestamp.
Ex: 01:00:00:00
. Drop frame timecode will be rendered with a ';' separator before the
frames field.
options
Options
round
: How to round the resulting frames field.
what-it-is
What it is
Timecode is used as a human-readable way to represent the id of a given frame. It is
formatted to give a rough sense of where to find a frame:
{HOURS}:{MINUTES}:{SECONDS}:{FRAME}
. For more on timecode, see Frame.io's
excellent post on the
subject.
where-you-see-it
Where you see it
Timecode is ubiquitous in video editing, a small sample of places you might see timecode:
- Source and Playback monitors in your favorite NLE.
- Burned into the footage for dailies.
- Cut lists like an EDL.
examples
Examples
iex> framestamp = Framestamp.with_frames!(86_400, Rates.f23_98())
iex> Framestamp.smpte_timecode(framestamp)
"01:00:00:00"
@spec smpte_timecode_sections(t(), opts :: [{:round, round()}]) :: Vtc.SMPTETimecode.Sections.t()
The individual sections of a SMPTE timecode string as i64 values.
examples
Examples
iex> framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex>
iex> result = Framestamp.smpte_timecode_sections(framestamp)
iex> inspect(result)
"%Vtc.SMPTETimecode.Sections{negative?: false, hours: 1, minutes: 0, seconds: 0, frames: 0}"
Link to this section Ecto Migrations
@spec type() :: atom()
The database type for PgFramestamp.
Can be used in migrations as the fields type.
Link to this section Changeset Validations
@spec validate_constraints( Ecto.Changeset.t(data), atom(), [Vtc.Ecto.Postgres.PgFramestamp.Migrations.constraint_opt()] ) :: Ecto.Changeset.t(data) when data: any()
Adds all constraints created by PgFramestamp.Migrations.create_constraints/3 to changeset to be added as changeset errors rather than raised.
options
Options
Pass the same options that were passed to PgFramestamp.Migrations.create_constraints/3
Link to this section Functions
Callback implementation for Ecto.Type.embed_as/1
.
Callback implementation for Ecto.Type.equal?/2
.