Packages
vtc
0.16.1
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.2
0.16.1
0.16.0
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.15
0.13.14
0.13.13
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.10
0.10.9
0.10.8
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
A SMPTE timecode library for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/postgres/pg_framestamp.ex
use Vtc.Ecto.Postgres.Utils
defpgmodule Vtc.Ecto.Postgres.PgFramestamp do
@moduledoc """
Defines a composite type for storing rational values as a
[PgRational](`Vtc.Ecto.Postgres.PgRational`) real-world playback seconds,
[PgFramerate](`Vtc.Ecto.Postgres.PgFramerate`) pair.
These values are cast to
[Framestamp](`Vtc.Framestamp`) structs for use in application code.
The composite types is defined as follows:
```sql
CREATE TYPE framestamp AS (
__seconds_n bigint,
__seconds_d bigint,
__rate_n bigint,
__rate_d bigint,
__rate_tags framerate_tags[]
);
```
> #### `Field Access` {: .warning}
>
> framestamp's inner fields are considered semi-private to end-users. For working with
> the seconds / rate values, see
> [create_func_seconds/0](`Vtc.Ecto.Postgres.PgFramestamp.Migrations.create_func_seconds/0`),
> [create_func_rate/0](`Vtc.Ecto.Postgres.PgFramestamp.Migrations.create_func_seconds/0`),
> which create Postgres functions to act as getter functions for working with inner
> framestamp data.
```sql
SELECT (18018, 5, 24000, 1001, '{non_drop}')::framestamp
```
## Field migrations
You can create `framerate` fields during a migration like so:
```elixir
alias Vtc.Framestamp
create table("events") do
add(:in, Framestamp.type())
add(:out, Framestamp.type())
end
```
[Framestamp](`Vtc.Framestamp`) re-exports the `Ecto.Type` implementation of this module,
and can be used any place this module would be used.
## Schema fields
Then in your schema module:
```elixir
defmodule MyApp.Event do
@moduledoc false
use Ecto.Schema
alias Vtc.Framestamp
@type t() :: %__MODULE__{
in: Framestamp.t(),
out: Framestamp.t()
}
schema "events" do
field(:in, Framestamp)
field(:out, Framestamp)
end
```
## Changesets
With the above setup, changesets should just work:
```elixir
def changeset(schema, attrs) do
schema
|> Changeset.cast(attrs, [:in, :out])
|> Changeset.validate_required([:in, :out])
end
```
Framerate values can be cast from the following values in changesets:
- [Framestamp](`Vtc.Framestamp`) structs.
- Maps with the following format:
```json
{
"smpte_timecode": "01:00:00:00",
"rate": {
"playback": [24000, 1001],
"ntsc": "non_drop"
}
}
```
Where `smpte_timecode` is properly formatted SMPTE timecode string and `playback`
is a map value supported by [PgFramerate](`Vtc.Ecto.Postgres.PgFramerate`) changeset
casts.
## Fragments
Framestamp values must be explicitly cast using
[type/2](https://hexdocs.pm/ecto/Ecto.Query.html#module-interpolation-and-casting):
```elixir
framestamp = Framestamp.with_frames!("01:00:00:00", Rates.f23_98())
query = Query.from(f in fragment("SELECT ? as r", type(^framestamp, Framestamp)), select: f.r)
```
"""
use Ecto.Type
alias Ecto.Changeset
alias Vtc.Ecto.Postgres.PgFramerate
alias Vtc.Ecto.Postgres.PgFramestamp
alias Vtc.Framerate
alias Vtc.Framestamp
alias Vtc.Source.Frames.SMPTETimecodeStr
@doc section: :ecto_migrations
@doc """
The database type for [PgFramestamp](`Vtc.Ecto.Postgres.PgFramestamp`).
Can be used in migrations as the fields type.
"""
@impl Ecto.Type
@spec type() :: atom()
def type, do: :framestamp
@typedoc """
Type of the raw composite value that will be sent to / received from the database.
"""
@type db_record() :: {non_neg_integer(), pos_integer(), non_neg_integer(), pos_integer(), [String.t()]}
@doc """
The database type for [PgFramerate](`Vtc.Ecto.Postgres.PgFramerate`).
Can be used in migrations as the fields type.
"""
@impl Ecto.Type
@spec cast(Framestamp.t() | %{String.t() => any()} | %{atom() => any()}) :: {:ok, Framerate.t()} | :error
def cast(%Framestamp{} = framestamp), do: {:ok, framestamp}
def cast(json) when is_map(json) do
schema = %{
smpte_timecode: :string,
rate: Framerate
}
changeset =
{%{}, schema}
|> Changeset.cast(json, [:smpte_timecode, :rate])
|> Changeset.validate_required([:smpte_timecode, :rate])
with {:ok, %{smpte_timecode: timecode_str, rate: rate}} <- Changeset.apply_action(changeset, :loaded),
{:ok, _} = result <- Framestamp.with_frames(%SMPTETimecodeStr{in: timecode_str}, rate) do
result
else
_ -> :error
end
end
def cast(_), do: :error
# Handles converting database records into Ratio structs to be used by the
# application.
@doc false
@impl Ecto.Type
@spec load(db_record()) :: {:ok, Framestamp.t()} | :error
def load({seconds_n, seconds_d, rate_n, rate_d, rate_tags}) do
seconds = Ratio.new(seconds_n, seconds_d)
with {:ok, framerate} <- PgFramerate.load({{rate_n, rate_d}, rate_tags}),
{:ok, _} = result <- Framestamp.with_seconds(seconds, framerate, round: :off) do
result
else
_ -> :error
end
end
def load(_), do: :error
# Handles converting Ratio structs into database records.
@doc false
@impl Ecto.Type
@spec dump(Framestamp.t()) :: {:ok, db_record()} | :error
def dump(%Framestamp{} = framestamp) do
with {:ok, {{rate_n, rate_d}, rate_tags}} <- PgFramerate.dump(framestamp.rate) do
{:ok, {framestamp.seconds.numerator, framestamp.seconds.denominator, rate_n, rate_d, rate_tags}}
end
end
def dump(_), do: :error
@doc section: :changeset_validators
@doc """
Adds all constraints created by
[PgFramestamp.Migrations.create_constraints/3](`Vtc.Ecto.Postgres.PgFramestamp.Migrations.create_constraints/3`)
to changeset.
## Arguments
- `changeset`: The changeset being validated.
- `field`: The field who's constraints are being checked.
## Options
Pass the same options that were passed to
[PgFramestamp.Migrations.create_constraints/3](`Vtc.Ecto.Postgres.PgFramestamp.Migrations.create_constraints/3`)
"""
@spec validate_constraints(Changeset.t(data), atom(), [PgFramestamp.Migrations.constraint_opt()]) :: Changeset.t(data)
when data: any()
def validate_constraints(changeset, field, opts \\ []) do
"dummy_table"
|> PgFramestamp.Migrations.build_constraint_list(field, opts)
|> Enum.reduce(changeset, fn constraint, changeset ->
Changeset.check_constraint(changeset, field, name: constraint.name)
end)
end
end