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.