Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSD #76

Merged
merged 15 commits into from
Jul 25, 2024
36 changes: 36 additions & 0 deletions lib/media/ver10/get_osd.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule Onvif.Media.Ver10.GetOSD do
import SweetXml
import XmlBuilder

alias Onvif.Device
alias Onvif.Media.Ver10.OSD

@spec soap_action :: String.t()
def soap_action, do: "http://www.onvif.org/ver10/media/wsdl/GetOSD"

@spec request(Device.t(), list) :: {:ok, any} | {:error, map()}
def request(device, args),
do: Onvif.Media.Ver10.Media.request(device, args, __MODULE__)

def request_body(token) do
element(:"s:Body", [
element(:"trt:GetOSD", [
element(:"tt:OSDToken", token)
])
])
end

@spec response(any) :: {:error, Ecto.Changeset.t()} | {:ok, struct()}
def response(xml_response_body) do
xml_response_body
|> parse(namespace_conformant: true, quiet: true)
|> xpath(
~x"//s:Envelope/s:Body/trt:GetOSDResponse/trt:OSD"e
|> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope")
|> add_namespace("trt", "http://www.onvif.org/ver10/media/wsdl")
|> add_namespace("tt", "http://www.onvif.org/ver10/schema")
)
|> OSD.parse()
|> OSD.to_struct()
end
end
55 changes: 55 additions & 0 deletions lib/media/ver10/get_osds.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
defmodule Onvif.Media.Ver10.GetOSDs do
import SweetXml
import XmlBuilder
require Logger

alias Onvif.Media.Ver10.OSD

def soap_action, do: "http://www.onvif.org/ver10/media/wsdl/GetOSDs"

def request(device),
do: Onvif.Media.Ver10.Media.request(device, __MODULE__)

def request(device, args),
do: Onvif.Media.Ver10.Media.request(device, args, __MODULE__)

def request_body() do
element(:"s:Body", [
element(:"trt:GetOSDs")
])
end

def request_body(video_source_token) do
element(:"s:Body", [
element(:"trt:GetOSDs", [
element(:"trt:ConfigurationToken", video_source_token)
])
])
end

@spec response(any) :: {:error, Ecto.Changeset.t()} | {:ok, struct()}
def response(xml_response_body) do
response =
xml_response_body
|> parse(namespace_conformant: true, quiet: true)
|> xpath(
~x"//s:Envelope/s:Body/trt:GetOSDsResponse/trt:OSDs"el
|> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope")
|> add_namespace("trt", "http://www.onvif.org/ver10/media/wsdl")
|> add_namespace("tt", "http://www.onvif.org/ver10/schema")
)
|> Enum.map(&OSD.parse/1)
|> Enum.reduce([], fn raw_osd, acc ->
case OSD.to_struct(raw_osd) do
{:ok, osd} ->
[osd | acc]

{:error, changeset} ->
Logger.error("Discarding invalid service: #{inspect(changeset)}")
acc
end
end)

{:ok, response}
end
end
207 changes: 207 additions & 0 deletions lib/media/ver10/osd.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
defmodule Onvif.Media.Ver10.OSD do
@moduledoc """
OSD (On-Screen Display) specification.
"""

use Ecto.Schema
import Ecto.Changeset
import SweetXml

@primary_key false
@derive Jason.Encoder
@required [:token, :video_source_configuration_token, :type]
@optional []

embedded_schema do
field(:token, :string)
field(:video_source_configuration_token, :string)
field(:type, Ecto.Enum, values: [text: "Text", image: "Image", extended: "Extended"])

embeds_one :position, Position, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:type, Ecto.Enum,
values: [
upper_left: "UpperLeft",
upper_right: "UpperRight",
lower_left: "LowerLeft",
lower_right: "LowerRight",
custom: "Custom"
]
)

field(:pos, :map)
end

embeds_one :text_string, TextString, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:is_persistent_text, :boolean)

field(:type, Ecto.Enum,
values: [plain: "Plain", date: "Date", time: "Time", date_and_time: "DateAndTime"]
)

field(:date_format, :string)
field(:time_format, :string)
field(:font_size, :integer)

embeds_one :font_color, FontColor, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:transparent, :boolean)
field(:color, :map)
end

embeds_one :background_color, BackgroundColor, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:transparent, :boolean)
field(:color, :string)
end

field(:plain_text, :string)
end

embeds_one :image, Image, primary_key: false, on_replace: :update do
@derive Jason.Encoder
field(:image_path, :string)
end
end

def parse(nil), do: nil
def parse([]), do: nil

def parse(doc) do
xmap(
doc,
token: ~x"//@token"so,
video_source_configuration_token: ~x"./tt:VideoSourceConfigurationToken/text()"so,
type: ~x"./tt:Type/text()"so,
position: ~x"./tt:Position"eo |> transform_by(&parse_position/1),
text_string: ~x"./tt:TextString"eo |> transform_by(&parse_text_string/1),
image: ~x"./tt:Image"eo |> transform_by(&parse_image/1)
)
end

def parse_position([]), do: nil
def parse_position(nil), do: nil

def parse_position(doc) do
xmap(
doc,
type: ~x"./tt:Type/text()"so,
pos: ~x"./tt:Pos"eo |> transform_by(&parse_pos/1)
)
end

def parse_pos([]), do: nil
def parse_pos(nil), do: nil

def parse_pos(doc) do
%{
x: doc |> xpath(~x"./@x"s),
y: doc |> xpath(~x"./@y"s)
}
end

def parse_text_string([]), do: nil
def parse_text_string(nil), do: nil

def parse_text_string(doc) do
xmap(
doc,
is_persistent_text: ~x"./tt:IsPersistentText/text()"so,
type: ~x"./tt:Type/text()"so,
date_format: ~x"./tt:DateFormat/text()"so,
time_format: ~x"./tt:TimeFormat/text()"so,
font_size: ~x"./tt:FontSize/text()"io,
font_color: ~x"./tt:FontColor"eo |> transform_by(&parse_color/1),
background_color: ~x"./tt:BackgroundColor"eo |> transform_by(&parse_color/1),
plain_text: ~x"./tt:PlainText/text()"so
)
end

def parse_color([]), do: nil
def parse_color(nil), do: nil

def parse_color(doc) do
xmap(
doc,
transparent: ~x"./tt:Transparent/text()"so,
color: ~x"./tt:Color"eo |> transform_by(&parse_inner_color/1)
)
end

def parse_inner_color([]), do: nil
def parse_inner_color(nil), do: nil

def parse_inner_color(doc) do
%{
x: doc |> xpath(~x"./@X"s),
y: doc |> xpath(~x"./@Y"s),
z: doc |> xpath(~x"./@Z"s),
colorspace: doc |> xpath(~x"./@Colorspace"s)
}
end

def parse_image([]), do: nil
def parse_image(nil), do: nil

def parse_image(doc) do
xmap(
doc,
image_path: ~x"./tt:ImagePath/text()"so
)
end

def to_struct(parsed) do
%__MODULE__{}
|> changeset(parsed)
|> apply_action(:validate)
end

@spec to_json(%Onvif.Media.Ver10.OSD{}) ::
{:error,
%{
:__exception__ => any,
:__struct__ => Jason.EncodeError | Protocol.UndefinedError,
optional(atom) => any
}}
| {:ok, binary}
def to_json(%__MODULE__{} = schema) do
Jason.encode(schema)
end

def changeset(module, attrs) do
module
|> cast(attrs, @required ++ @optional)
|> validate_required(@required)
|> cast_embed(:position, with: &position_changeset/2)
|> cast_embed(:text_string, with: &text_string_changeset/2)
|> cast_embed(:image, with: &image_changeset/2)
end

def position_changeset(module, attrs) do
cast(module, attrs, [:type, :pos])
end

def text_string_changeset(module, attrs) do
cast(module, attrs, [
:is_persistent_text,
:type,
:date_format,
:time_format,
:font_size,
:plain_text
])
|> cast_embed(:font_color, with: &color_changeset/2)
|> cast_embed(:background_color, with: &color_changeset/2)
|> validate_inclusion(:date_format, ["M/d/yyyy", "MM/dd/yyyy", "dd/MM/yyyy", "yyyy/MM/dd", "yyyy-MM-dd", "dddd, MMMM dd, yyyy", "MMMM dd, yyyy", "dd MMMM, yyyy"])
|> validate_inclusion(:time_format, ["h:mm:ss tt", "hh:mm:ss tt", "H:mm:ss", "HH:mm:ss"])
end

def color_changeset(module, attrs) do
cast(module, attrs, [:transparent, :color])
end

def image_changeset(module, attrs) do
cast(module, attrs, [:image_path])
end
end
21 changes: 21 additions & 0 deletions test/media/ver10/fixtures/get_osds_empty.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsa5="http://www.w3.org/2005/08/addressing"
xmlns:tt="http://www.onvif.org/ver10/schema"
xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"
xmlns:tns1="http://www.onvif.org/ver10/topics"
xmlns:tnsaxis="http://www.axis.com/2009/event/topics"
xmlns:tev="http://www.onvif.org/ver10/events/wsdl"
xmlns:trt="http://www.onvif.org/ver10/media/wsdl"
xmlns:ter="http://www.onvif.org/ver10/error">
<SOAP-ENV:Body>
<trt:GetOSDsResponse></trt:GetOSDsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Loading