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