View Source Quickstart

Let's take a little peek at what Vtc can do, for you! Note that printing calls like inspect/1 have been elided from these examples.

These are the three main modules that make up the Vtc API:

alias Vtc.Framerate
alias Vtc.Rates
alias Vtc.Framestamp

Let's start with a 23.98 NTSC timecode. We use the with_frames constructor here since timecode is really a human-readable way to represent frame count. The Vtc.Rates module defines a number of Vtc.Framerate values found in the wild. Most common, by far, is 23.98 NTSC, which is shorthand for video footage running at 24000/1001 frames-per-second.

iex> framestamp = Framestamp.with_frames!("17:23:13:02", Rates.f23_98())
"<17:23:00:02 <23.98 NTSC>>"

Once we have a Vtc.Framestamp struct, we can render all sorts of commonly used framestamp representations, like SMPTE timecode:

unit-conversions

Unit Conversions

smpte_timecode/2

iex> Framestamp.smpte_timecode(framestamp)
"17:23:00:02"

frames/2

iex> Framestamp.frames(framestamp)
1501922

seconds

iex> framestamp.seconds
"Ratio.new(751711961, 12000)"

runtime/2

iex> Framestamp.runtime(framestamp, 3)
"17:24:15.676"

premiere_ticks/2

iex> Framestamp.premiere_ticks(framestamp)
15915544300656000

physical film length in feet_and_frames/2

iex> Framestamp.feet_and_frames(framestamp)
"<93889+10 :ff35mm_4perf>"

framerate-information

Framerate Information

ntsc

iex> framestamp.rate.ntsc
:non_drop

playback speed

iex> framestamp.rate.playback
"Ratio.new(24000, 1001)"

timebase/1 logical speed

iex> Framerate.smpte_timebase(framestamp.rate)
24

.

parsing

Parsing

Parsing is flexible, we can pass in partial or malformed timecode.

In Vtc, there are only two ways to parse framestamps, either with Timecode.with_frames/2 for formats that represent a discrete frame count, or Timecode.with_seconds/2 for formats that represent a number of real-world, elapsed seconds were those frames to be played back at the framestamp's rate.

examples

Examples

frames-formats

Frames Formats

timecode

iex> Framestamp.with_frames!("3:12", Rates.f23_98())
"<03:00:00:12 <23.98 NTSC>>"

malformed timecode

iex> Framestamp.with_frames!("3:12", Rates.f23_98())
"<03:00:00:12 <23.98 NTSC>>"

frame count

iex> Framestamp.with_frames!(24, Rates.f23_98())
"<00:00:01:00 <23.98 NTSC>>"

physical film length in feet+frames

iex> Framestamp.with_frames!("1+08", Rates.f23_98())
"<00:00:01:00 <23.98 NTSC>>"

seconds-formats

Seconds Formats

seconds

iex> Framestamp.with_seconds!(1.5, Rates.f23_98())
"<00:05:23:04 <23.98 NTSC>>"

runtime

iex> Framestamp.with_seconds!("00:05:23.5", Rates.f23_98())
"<00:05:23:04 <23.98 NTSC>>"

malformed runtime

iex> Framestamp.with_seconds!("5:23.5", Rates.f23_98())
"<00:05:23:04 <23.98 NTSC>>"

premiere ticks

iex> input = %PremiereTicks{in: 254_016_000_000}
iex> Framestamp.with_seconds!(input, Rates.f23_98())
"<00:00:01:00 <23.98 NTSC>>"

other-film-formats

Other film formats

By default, feet+frames is interpreted as 35mm, 4perf film. You can use the FeetAndFrames struct to parse other film formats.

16mm feet + frames

iex> alias Vtc.Source.Frames.FeetAndFrames
iex> 
iex> {:ok, feet_and_frames} = FeetAndFrames.from_string("5400+00", film_format: :ff16mm)
iex> 
iex> Framestamp.with_frames(feet_and_frames, Rates.f23_98())
"{:ok, <01:15:00:00 <23.98 NTSC>>}"

arithmetic

Arithmetic

-1

.

add/3

iex> a = Framestamp.with_frames!("18:23:13:02", Rates.f23_98())
iex> b = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> 
iex> framestamp = Framestamp.add(a, b)
"<18:23:13:02 <23.98 NTSC>>"

add/3 with string

iex> Framestamp.add(tc, "00:10:00:00")
"<18:33:13:02 <23.98 NTSC>>"

add/3 with ints means adding frames

iex> Framestamp.add(tc, 38)
"<18:33:14:16 <23.98 NTSC>>"

sub/3

iex> Framestamp.sub(tc, "01:00:00:00")
"<17:33:14:16 <23.98 NTSC>>"

.

minus/1

iex> Framestamp.minus(framestamp)
"<-17:33:14:16 <23.98 NTSC>>"

abs/1

iex> Framestamp.abs(framestamp)
"<17:33:14:16 <23.98 NTSC>>"

mult/3

iex> Framestamp.mult(framestamp, 2)
"<35:06:29:08 <23.98 NTSC>>"

div/3

iex> Framestamp.div(framestamp, 2)
"<17:33:14:16 <23.98 NTSC>>"

divrem/3

iex> {dividend, remainder} = Framestamp.divrem(framestamp, 3)
iex> {dividend, remainder}
"{<05:51:04:21 <23.98 NTSC>>, <00:00:00:01 <23.98 NTSC>>}"

eval

Eval

Special Framestamp.eval do blocks let us use native operators.

eval/2

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> Framestamp.eval do
iex>   a + b * 2 - c
iex> end
"<01:45:00:00 <23.98 NTSC>>"

Or even do some quick scratch calculations in a given framerate:

scratch calculation

iex> Framestamp.eval at: 23.98 do
iex>   "01:00:00:00" + "00:30:00:00" * 2 - "00:15:00:00"
iex> end
"<01:45:00:00 <23.98 NTSC>>"

framerates

Framerates

We can make drop-frame framestamps for 29.97 or 59.94 using one of the pre-set framerates.

drop-frame

iex> Framestamp.with_frames!(15_000, Rates.f29_97_df())
"<00:08:20;18 <29.97 NTSC DF>>"

We can make new framestamps with arbitrary framerates if we want.

non-ntsc

iex> Framestamp.with_frames!("01:00:00:00", Framerate.new!(240, nil))
"<01:00:00:00 <240.0 fps>>"

Using :non_drop indicates this framestamp represents an NTSC timecode, and will convert whole-number timebases to the correct speed.

non-drop coercion

iex> Framestamp.with_frames!("01:00:00:00", Framerate.new!(48, :non_drop))
"<01:00:00:00 <47.95 NTSC>>"

We can also rebase the frames using a new framerate!

rebase

iex> Framestamp.rebase(tc, Rates.f23_98())
"<02:00:00:00 <23.98 NTSC>>"

comparisons-and-sorting

Comparisons and Sorting

It's easy to compare two framestamps.

compare/2

iex> a = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
iex> b = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> 
iex> Framestamp.compare(a, b)
:lt

There a host of other specific comparison functions like eq?/2, gt?/2 that return booleans.

Specific comparison

iex> Framestamp.lt?(a, b)
true

Like arithmetic, we can compare directly with a timecode string:

compare/2 with string

iex> Framestamp.compare(a, "00:59:00:00")
:gt

Sorting is supported through the compare/2 function.

sort through Framestamp

iex> framestamp_01 = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> framestamp_02 = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
iex> 
iex> data_01 = %{id: 2, tc: framestamp_01}
iex> data_02 = %{id: 1, tc: framestamp_02}
iex> 
iex> Enum.sort_by([data_02, data_01], & &1.tc, Framestamp)
"[%{id: 2, tc: <01:00:00:00 <23.98 NTSC>>}, %{id: 1, tc: <02:00:00:00 <23.98 NTSC>>}]"

ranges

Ranges

Range helps with common operations using in/out points. Let's set two of those up.

new/3

iex> a_in = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
iex> a_out = Framestamp.with_frames!("02:00:00:00", Rates.f23_98())
iex> 
iex> a = Framestamp.Range.new!(a_in, a_out)
"<01:00:00:00 - 02:00:00:00 :exclusive <23.98 NTSC>>"

By default, ranges are exclusive, meaning the out point represents the boundary where the clip ends, not the final frame that is part of the video clip. This way will be familiar to Premiere and Final Cut editors. But fear not, our Avid brethren, inclusive out points like you are used to are available as well!

Just like addition, we can write a bare timecode string as the out value if we want.

new/3 with string

iex> b_in = Framestamp.with_frames!("01:45:00:00", Rates.f23_98())
iex> 
iex> b = Framestamp.Range.new!(b_in, "02:30:00:00")
"<01:45:00:00 - 02:30:00:00 :exclusive <23.98 NTSC>>"

We can get the duration of a range.

duration/1

iex> Framestamp.Range.duration(b)
iex> "<00:45:00:00 <23.98 NTSC>>"

... see if a specific framestamp is in a range:

contains?/2

iex> Framestamp.Range.contains?(b, "02:00:00:00")
iex> true

... or see if it overlaps with another range.

overlaps?/2

iex> Framestamp.Range.overlaps?(a, b)
iex> true

We can even get the overlapping area as its own range!

intersection/2

iex> Framestamp.Range.intersection!(a, b)
"<01:45:00:00 - 02:00:00:00 :exclusive <23.98 NTSC>>"

postgres-types

Postgres Types

To include Postgres types with Ecto. Add the following into you applications configuration file:

config :vtc,
  env: config_env(),
  include_postgres_types?: true

See each Postgres type for information on it's configuration.