diff --git a/lib/media/ver10/osd.ex b/lib/media/ver10/osd.ex index cb08e45..406b0d5 100644 --- a/lib/media/ver10/osd.ex +++ b/lib/media/ver10/osd.ex @@ -193,8 +193,22 @@ defmodule Onvif.Media.Ver10.OSD do ]) |> 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"]) + |> 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 diff --git a/lib/media/ver10/set_osd.ex b/lib/media/ver10/set_osd.ex new file mode 100644 index 0000000..b135444 --- /dev/null +++ b/lib/media/ver10/set_osd.ex @@ -0,0 +1,162 @@ +defmodule Onvif.Media.Ver10.SetOSD do + import SweetXml + import XmlBuilder + + alias Onvif.Media.Ver10.OSD.TextString.FontColor + alias Onvif.Media.Ver10.OSD.TextString.BackgroundColor + alias Onvif.Media.Ver10.OSD.Image + alias Onvif.Device + alias Onvif.Media.Ver10.OSD + + @spec soap_action :: String.t() + def soap_action, do: "http://www.onvif.org/ver10/media/wsdl/SetOSD" + + @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(%OSD{} = osd) do + element(:"s:Body", [ + element(:"trt:SetOSD", [ + element(:"trt:OSD", %{token: osd.token}, [ + element(:"tt:VideoSourceConfigurationToken", osd.video_source_configuration_token), + element( + :"tt:Type", + Keyword.fetch!(Ecto.Enum.mappings(osd.__struct__, :type), osd.type) + ), + element(:"tt:Position", [ + element( + :"tt:Type", + Keyword.fetch!( + Ecto.Enum.mappings(osd.position.__struct__, :type), + osd.position.type + ) + ), + element(:"tt:Pos", %{x: osd.position.pos.x, y: osd.position.pos.y}) + ]), + gen_element_type(osd.type, osd) + ]) + ]) + ]) + end + + defp gen_element_type(:text, osd) do + element( + :"tt:TextString", + [ + element_is_persistent_text(osd.text_string.is_persistent_text), + element( + :"tt:Type", + Keyword.fetch!( + Ecto.Enum.mappings(osd.text_string.__struct__, :type), + osd.text_string.type + ) + ), + element_font_size(osd.text_string.font_size), + font_color_element(osd.text_string.font_color), + background_color_element(osd.text_string.background_color) + ] ++ gen_text_type(osd.text_string.type, osd) + ) + end + + defp gen_element_type(:image, osd) do + image_element(osd.image) + end + + defp gen_text_type(:plain, osd) do + [element_plain_text(osd.text_string.plain_text)] + end + + defp gen_text_type(:date, osd) do + [element_date_format(osd.text_string.date_format)] + end + + defp gen_text_type(:time, osd) do + [element_time_format(osd.text_string.time_format)] + end + + defp gen_text_type(:date_and_time, osd) do + [ + element_date_format(osd.text_string.date_format), + element_time_format(osd.text_string.time_format) + ] + end + + defp element_is_persistent_text(nil), do: [] + + defp element_is_persistent_text(is_persistent_text) do + element(:"tt:IsPersistentText", is_persistent_text) + end + + defp element_date_format(nil), do: [] + + defp element_date_format(date_format) do + element(:"tt:DateFormat", date_format) + end + + defp element_time_format(nil), do: [] + + defp element_time_format(time_format) do + element(:"tt:TimeFormat", time_format) + end + + defp element_font_size(nil), do: [] + + defp element_font_size(font_size) do + element(:"tt:FontSize", font_size) + end + + defp element_plain_text(nil), do: [] + + defp element_plain_text(plain_text) do + element(:"tt:PlainText", plain_text) + end + + defp font_color_element(nil), do: [] + + defp font_color_element(%FontColor{} = font_color) do + element(:"tt:FontColor", [ + element(:"tt:Color", %{ + X: font_color.color.x, + Y: font_color.color.y, + Z: font_color.color.z, + Colorspace: font_color.color.colorspace + }) + ]) + end + + defp background_color_element(nil), do: [] + + defp background_color_element(%BackgroundColor{} = background_color) do + element(:BackgroundColor, [ + element(:"tt:Color", %{ + X: background_color.color.x, + Y: background_color.color.y, + Z: background_color.color.z, + Colorspace: background_color.color.colorspace + }) + ]) + end + + defp image_element(nil), do: [] + + defp image_element(%Image{} = image) do + element(:"tt:Image", [ + element(:"tt:ImagePath", image.image_path) + ]) + end + + def response(xml_response_body) do + res = + xml_response_body + |> parse(namespace_conformant: true, quiet: true) + |> xpath( + ~x"//s:Envelope/s:Body/trt:SetOSDResponse/text()"s + |> 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") + ) + + {:ok, res} + end +end diff --git a/test/media/ver10/fixtures/set_osd_response_error.xml b/test/media/ver10/fixtures/set_osd_response_error.xml new file mode 100644 index 0000000..6c099d9 --- /dev/null +++ b/test/media/ver10/fixtures/set_osd_response_error.xml @@ -0,0 +1,61 @@ + + + + + env:Sender + + ter:InvalidArgVal + + ter:InvalidParameter + + + + + the parameter value is illegal + + http://www.w3.org/2003/05/soap-envelope/node/ultimateReceiver + http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver + + token is null + + + + \ No newline at end of file diff --git a/test/media/ver10/fixtures/set_osd_response_success.xml b/test/media/ver10/fixtures/set_osd_response_success.xml new file mode 100644 index 0000000..78379c4 --- /dev/null +++ b/test/media/ver10/fixtures/set_osd_response_success.xml @@ -0,0 +1,44 @@ + + + + + + \ No newline at end of file diff --git a/test/media/ver10/set_osd_test.exs b/test/media/ver10/set_osd_test.exs new file mode 100644 index 0000000..627ae1a --- /dev/null +++ b/test/media/ver10/set_osd_test.exs @@ -0,0 +1,93 @@ +defmodule Onvif.Media.Ver10.SetOSDTest do + alias Onvif.Media.Ver10.OSD + use ExUnit.Case, async: true + + @moduletag capture_log: true + + describe "SetOSDs/2" do + test "should update the OSD" do + xml_response = File.read!("test/media/ver10/fixtures/set_osd_response_success.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, osd} = + Onvif.Media.Ver10.SetOSD.request(device, [ + %OSD{ + image: nil, + position: %OSD.Position{ + pos: %{x: "-1.000000", y: "0.866667"}, + type: :custom + }, + text_string: %OSD.TextString{ + background_color: nil, + date_format: "MM/dd/yyyy", + font_color: %OSD.TextString.FontColor{ + color: %{ + colorspace: "http://www.onvif.org/ver10/colorspace/YCbCr", + x: "0.000000", + y: "0.000000", + z: "0.000000" + }, + transparent: nil + }, + font_size: 30, + is_persistent_text: nil, + plain_text: nil, + time_format: "HH:mm:ss", + type: :date_and_time + }, + token: "OsdToken_101", + type: :text, + video_source_configuration_token: "VideoSourceToken" + } + ]) + + assert osd == "" + end + + test "shoud return an error" do + xml_response = File.read!("test/media/ver10/fixtures/set_osd_response_error.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 500, body: xml_response}} + end) + + {:error, error} = + Onvif.Media.Ver10.SetOSD.request(device, [ + %OSD{ + position: %OSD.Position{ + pos: %{x: "-1.000000", y: "0.866667"}, + type: :custom + }, + text_string: %OSD.TextString{ + date_format: "MM/dd/yyyy", + font_color: %OSD.TextString.FontColor{ + color: %{ + colorspace: "http://www.onvif.org/ver10/colorspace/YCbCr", + x: "0.000000", + y: "0.000000", + z: "0.000000" + }, + transparent: nil + }, + font_size: 30, + time_format: "HH:mm:ss", + type: :date_and_time + }, + token: nil, + type: :text, + video_source_configuration_token: "VideoSourceToken" + } + ]) + + assert error.reason == "Received 500 from Elixir.Onvif.Media.Ver10.SetOSD" + assert String.contains?(error.response, "token is null") + end + end +end