#
View Source Vtc.Framestamp (vtc v0.13.15)

Represents a particular frame in a video clip.

New Framestamp values are created with the `with_seconds/3` and `with_frames/2`, and other function prefaced by `with_*`.

Vtc express a philosophy of working with Timecode that is defined by two major conceits:

1. A frame identifier is incomplete without a framerate. More here.

2. All frame identifiers commonly used in Video production boil down to being an expression of either the real-world seconds of a frame, OR a squential index number. More here.

##

what-is-a-framestamp

What is a framestamp?

Framestamps are an expression of Vtc's philosophy about working with timecode in application code. 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.

So 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, shown above, is the canonical way we identify an individual frame 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 identify a frame in a specific video stream.

- Sort by real-world occurrence.

- Add / subtract values.

- 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 footage and audio between two cameras -- 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 arithamtic and comparison operations where each frame on the clock is issued a continuous index, zero `0` is `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](why we use rational values.

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 timezone 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 the `Framestamp`.

##

parsing-seconds-t-or-frames-t

Parsing: Seconds.t() or Frames.t()

Parsing functions preappend `with_` to their name. When you give a value to a parsing function, it is the same value that would be returned by the euivalent 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, likt `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

maybe_round()

As `round/0`, but includes `:off` option to disable rounding entirely. Not all functions exposed by this module make logical sense without some form of rouding, so `:off` will not be accepted by all functions.

parse_result()

Type returned by `with_seconds/3` and `with_frames/3`.

round()

Valid values for rounding options.

t()

Framestamp type.

## Parse

with_frames(frames, rate)

Returns a new Framestamp with a `frames/2` return value equal to the `frames` arg.

with_frames!(frames, rate)

As `Framestamp.with_frames/3`, but raises on error.

with_seconds(seconds, rate, opts \\ [])

Returns a new Framestamp with a `:seconds` field value equal to the `seconds` arg.

with_seconds!(seconds, rate, opts \\ [])

As `with_seconds/3`, but raises on error.

## Manipulate

rebase(framestamp, rate)

Rebases `framestamp` to a new framerate.

rebase!(framestamp, new_rate)

As `rebase/2`, but raises on error.

## Compare

compare(a, b)

Comapare the values of `a` and `b`.

eq?(a, b)

Returns `true` if `a` is eqaul to `b`.

gt?(a, b)

Returns `true` if `a` is greater than `b`.

gte?(a, b)

Returns `true` if `a` is greater than or eqaul to `b`.

lt?(a, b)

Returns `true` if `a` is less than `b`.

lte?(a, b)

Returns `true` if `a` is less than or equal to `b`.

## Arithmetic

abs(framestamp)

Returns the absolute value of `tc`.

add(a, b, opts \\ [])

Add two framestamps.

div(dividend, divisor, opts \\ [])

Divides `dividend` by `divisor`.

divrem(dividend, divisor, opts \\ [])

Divides the total frame count of `dividend` by `divisor` and returns both a quotient and a remainder.

eval(opts \\ [], body)

Evalutes Framestamp mathematical expressions in a `do` block.

minus(framestamp)

As the kernel `-/1` function.

mult(a, b, opts \\ [])

Scales `a` by `b`.

rem(dividend, divisor, opts \\ [])

Devides the total frame count of `dividend` by `devisor`, and returns the remainder.

sub(a, b, opts \\ [])

Subtract `b` from `a`.

## Convert

feet_and_frames(framestamp, opts \\ [])

Returns the number of physical film feet and frames `framestamp` represents if shot on film.

frames(framestamp, opts \\ [])

Returns the number of frames that would have elapsed between `00:00:00:00` and Framestamp.

premiere_ticks(framestamp, opts \\ [])

Returns the number of elapsed ticks `framestamp` represents in Adobe Premiere Pro.

runtime(framestamp, opts \\ [])

Runtime Returns the true, real-world runtime of `framestamp` in `HH:MM:SS.FFFFFFFFF` format.

smpte_timecode(framestamp, opts \\ [])

Returns the the formatted SMPTE timecode for a Framestamp.

smpte_timecode_sections(framestamp, opts \\ [])

The individual sections of a SMPTE timecode string as i64 values.

## Functions

embed_as(_)

Callback implementation for `Ecto.Type.embed_as/1`.

equal?(term1, term2)

Callback implementation for `Ecto.Type.equal?/2`.

validate_constraints(changeset, field, opts \\ [])

See `Vtc.Ecto.Postgres.PgFramestamp.validate_constraints/3`.

# Link to this section Types

Link to this type

# maybe_round()

View Source

    @type maybe_round() :: round() | :off

As `round/0`, but includes `:off` option to disable rounding entirely. Not all functions exposed by this module make logical sense without some form of rouding, so `:off` will not be accepted by all functions.

Link to this type

# parse_result()

View Source

    @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`.

Link to this type

# round()

View Source

    @type round() :: :closest | :floor | :ceil | :trunc

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.

Link to this type

# t()

View Source

    @type t() :: %Vtc.Framestamp{rate: Vtc.Framerate.t(), seconds: Ratio.t()}

Framestamp type.

# Link to this section Parse

Link to this function

# with_frames(frames, rate)

View Source

    @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>>}"

Link to this function

# with_frames!(frames, rate)

View Source

    @spec with_frames!(Vtc.Source.Frames.t(), Vtc.Framerate.t()) :: t()

As `Framestamp.with_frames/3`, but raises on error.

Link to this function

# with_seconds(seconds, rate, opts \\ [])

View Source

    @spec with_seconds(
      Vtc.Source.Seconds.t(),
      Vtc.Framerate.t(),
      opts :: [round: maybe_round(), allow_partial_frames?: boolean()]
    ) :: 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. Default: `:closest`.

- `allow_partial_frames?`: If true, when `round` is :off, will allow a `seconds` value that is not cleanly divisible by `rate.playback`. Otherwise, will return an error. Default: `false`.

##

examples

Examples

Accetps 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>>"

Link to this function

# with_seconds!(seconds, rate, opts \\ [])

View Source

    @spec with_seconds!(
      Vtc.Source.Seconds.t(),
      Vtc.Framerate.t(),
      opts :: [round: maybe_round(), allow_partial_frames?: boolean()]
    ) :: t()

As `with_seconds/3`, but raises on error.

# Link to this section Manipulate

Link to this function

# rebase(framestamp, rate)

View Source

    @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>>"

Link to this function

# rebase!(framestamp, new_rate)

View Source

    @spec rebase!(t(), Vtc.Framerate.t()) :: t()

As `rebase/2`, but raises on error.

# Link to this section Compare

Link to this function

# compare(a, b)

View Source

    @spec compare(a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t()) ::
      :lt | :eq | :gt

Comapare the values of `a` and `b`.

Compatible with `Enum.sort/2`. For more on sorting non-builtin values, see the Elixir ducumentation.

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")

Link to this function

# eq?(a, b)

View Source

    @spec eq?(a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t()) ::
      boolean()

Returns `true` if `a` is eqaul 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 timecofe 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)

Link to this function

# gt?(a, b)

View Source

    @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.

Link to this function

# gte?(a, b)

View Source

    @spec gte?(a :: t() | Vtc.Source.Frames.t(), b :: t() | Vtc.Source.Frames.t()) ::
      boolean()

Returns `true` if `a` is greater than or eqaul to `b`.

auto-casts Frames values. See `eq?/2` for more information on how equality is determined.

Link to this function

# lt?(a, b)

View Source

    @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)

Link to this function

# lte?(a, b)

View Source

    @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

Link to this function

# abs(framestamp)

View Source

    @spec abs(t()) :: t()

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>>"

Link to this function

# add(a, b, opts \\ [])

View Source

    @spec add(
      a :: t() | Vtc.Source.Frames.t(),
      b :: t() | Vtc.Source.Frames.t(),
      opts :: [round: maybe_round(), allow_partial_frames?: boolean()]
    ) :: t()

Add two framestamps.

Uses the real-world seconds representation. When the rates of `a` and `b` are not equal, the result will inheret 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

- `round`: How to round the result with respect to whole-frames when mixing framerates. Default: `:closest`.

- `allow_partial_frames?`: If true, when `round` is :off, will allow a `seconds` value that is not cleanly divisible by `rate.playback`. Otherwise, will return an error. Default: `false`.

##

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:00", Rates.f23_98())
    iex> b = Framestamp.with_frames!("00:00:00:02", Rates.f47_95())
    iex>
    iex> result = Framestamp.add(a, b)
    iex> inspect(result)
    "<01:00:00:01 <23.98 NTSC>>"

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>>"

Link to this function

# div(dividend, divisor, opts \\ [])

View Source

    @spec div(
      dividend :: t(),
      divisor :: Ratio.t() | number(),
      opts :: [round: maybe_round(), allow_partial_frames?: boolean()]
    ) :: t()

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 match `divmod` and the expected meaning of `div` to mean integer division in elixir.

- `allow_partial_frames?`: If true, when `round` is :off, will allow a `seconds` value that is not cleanly divisible by `rate.playback`. Otherwise, will return an error. Default: `false`.

##

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>>"

Link to this function

# divrem(dividend, divisor, opts \\ [])

View Source

    @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>>}"

Link to this macro

# eval(opts \\ [], body)

View Source (macro)

    @spec eval(
      [at: Vtc.Framerate.t() | number() | Ratio.t(), ntsc: Vtc.Framerate.ntsc()],
      Macro.input()
    ) ::
      Macro.t()

Evalutes 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`: The `ntsc` value to use when creating a new Framerate with `at`. Not needed if `at` 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>>"

Link to this function

# minus(framestamp)

View Source

    @spec minus(t()) :: t()

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>>"

Link to this function

# mult(a, b, opts \\ [])

View Source

    @spec mult(
      a :: t(),
      b :: Ratio.t() | number(),
      opts :: [round: maybe_round(), allow_partial_frames?: boolean()]
    ) :: t()

Scales `a` by `b`.

The result will inheret 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`.

- `allow_partial_frames?`: If true, when `round` is :off, will allow a `seconds` value that is not cleanly divisible by `rate.playback`. Otherwise, will return an error. Default: `false`.

##

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>>"

Link to this function

# rem(dividend, divisor, opts \\ [])

View Source

    @spec rem(
      dividend :: t(),
      divisor :: Ratio.t() | number(),
      opts :: [round_frames: round(), round_remainder: round()]
    ) :: t()

Devides 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>>"

Link to this function

# sub(a, b, opts \\ [])

View Source

    @spec sub(
      a :: t(),
      b :: t() | Vtc.Source.Frames.t(),
      opts :: [round: maybe_round(), allow_partial_frames?: boolean()]
    ) :: t()

Subtract `b` from `a`.

Uses their real-world seconds representation. When the rates of `a` and `b` are not equal, the result will inheret 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

- `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>>"

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>>"

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)
    iex> inspect(result)
    "<01:00:00:01 <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

Link to this function

# feet_and_frames(framestamp, opts \\ [])

View Source

    @spec feet_and_frames(t(), opts :: [fiim_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`.

- `fiim_format`: The film format to use when doing the calculation. For more on film formats, see `Vtc.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>"

Link to this function

# frames(framestamp, opts \\ [])

View Source

    @spec frames(t(), opts :: [{:round, round()}]) :: integer()

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

Link to this function

# premiere_ticks(framestamp, opts \\ [])

View Source

    @spec premiere_ticks(t(), opts :: [{:round, round()}]) :: integer()

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

Link to this function

# runtime(framestamp, opts \\ [])

View Source

    @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"

Link to this function

# smpte_timecode(framestamp, opts \\ [])

View Source

    @spec smpte_timecode(t(), opts :: [{:round, round()}]) :: String.t()

Returns the the formatted SMPTE timecode for a Framestamp.

Ex: `01:00:00:00`. Drop frame timecode will be rendered with a ';' sperator 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"

Link to this function

# smpte_timecode_sections(framestamp, opts \\ [])

View Source

    @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 Functions

Link to this function

# embed_as(_)

View Source

Callback implementation for `Ecto.Type.embed_as/1`.

Link to this function

# equal?(term1, term2)

View Source

Callback implementation for `Ecto.Type.equal?/2`.

Link to this function

# validate_constraints(changeset, field, opts \\ [])

View Source

See `Vtc.Ecto.Postgres.PgFramestamp.validate_constraints/3`.