From d82437699b0f8d87355756b0e253a4c082c69f31 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Fri, 15 Nov 2024 17:59:01 -0300 Subject: [PATCH 01/24] friday experiments --- lib/device.ex | 20 ++++++++ lib/recording.ex | 56 +++++++++++++++++++++++ lib/recording/get_recording_jobs.ex | 24 ++++++++++ lib/recording/get_recordings.ex | 0 lib/recording/get_replay_configuration.ex | 24 ++++++++++ lib/replay.ex | 56 +++++++++++++++++++++++ lib/replay/get_replay_uri.ex | 31 +++++++++++++ lib/replay/get_service_capabilities.ex | 36 +++++++++++++++ lib/replay/service_capabilities.ex | 4 ++ 9 files changed, 251 insertions(+) create mode 100644 lib/recording.ex create mode 100644 lib/recording/get_recording_jobs.ex create mode 100644 lib/recording/get_recordings.ex create mode 100644 lib/recording/get_replay_configuration.ex create mode 100644 lib/replay.ex create mode 100644 lib/replay/get_replay_uri.ex create mode 100644 lib/replay/get_service_capabilities.ex create mode 100644 lib/replay/service_capabilities.ex diff --git a/lib/device.ex b/lib/device.ex index c8280d0..60049e5 100644 --- a/lib/device.ex +++ b/lib/device.ex @@ -18,6 +18,8 @@ defmodule Onvif.Device do :ntp, :media_ver10_service_path, :media_ver20_service_path, + :recording_service_path, + :replay_service_path, :auth_type, :time_diff_from_system_secs, :port, @@ -41,6 +43,8 @@ defmodule Onvif.Device do field(:ntp, :string) field(:media_ver10_service_path, :string) field(:media_ver20_service_path, :string) + field(:recording_service_path, :string) + field(:replay_service_path, :string) embeds_one(:system_date_time, Onvif.Devices.SystemDateAndTime) embeds_many(:services, Onvif.Device.Service) @@ -288,6 +292,8 @@ defmodule Onvif.Device do device |> Map.put(:media_ver10_service_path, get_media_ver10_service_path(device.services)) |> Map.put(:media_ver20_service_path, get_media_ver20_service_path(device.services)) + |> Map.put(:recording_service_path, get_recoding_service_path(device.services)) + |> Map.put(:replay_service_path, get_replay_service_path(device.services)) end defp get_media_ver20_service_path(services) do @@ -303,4 +309,18 @@ defmodule Onvif.Device do %Onvif.Device.Service{} = service -> service.xaddr |> URI.parse() |> Map.get(:path) end end + + defp get_recoding_service_path(services) do + case Enum.find(services, &String.contains?(&1.namespace, "/recording")) do + nil -> nil + %Onvif.Device.Service{} = service -> service.xaddr |> URI.parse() |> Map.get(:path) + end + end + + defp get_replay_service_path(services) do + case Enum.find(services, &String.contains?(&1.namespace, "/replay")) do + nil -> nil + %Onvif.Device.Service{} = service -> service.xaddr |> URI.parse() |> Map.get(:path) + end + end end diff --git a/lib/recording.ex b/lib/recording.ex new file mode 100644 index 0000000..8f6cfb2 --- /dev/null +++ b/lib/recording.ex @@ -0,0 +1,56 @@ +defmodule Onvif.Recording do + @moduledoc """ + Interface for making requests to the Onvif recording service + http://www.onvif.org/onvif/ver10/recording.wsdl + """ + require Logger + alias Onvif.Device + + @namespaces [ + "xmlns:trc": "http://www.onvif.org/ver10/recording/wsdl", + "xmlns:tt": "http://www.onvif.org/ver10/schema" + ] + + @spec request(Device.t(), module()) :: {:ok, any} | {:error, map()} + def request(%Device{} = device, operation) do + content = generate_content(operation) + do_request(device, operation, content) + end + + def request(%Device{} = device, args, operation) do + content = generate_content(operation, args) + do_request(device, operation, content) + end + + defp do_request(device, operation, content) do + device + |> Onvif.API.client(service_path: :recording_service_path) + |> Tesla.request( + method: :post, + headers: [{"Content-Type", "application/soap+xml"}, {"SOAPAction", operation.soap_action()}], + body: %Onvif.Request{content: content, namespaces: @namespaces} + ) + |> parse_response(operation) + end + + defp generate_content(operation), do: operation.request_body() + defp generate_content(operation, args), do: operation.request_body(args) + + defp parse_response({:ok, %{status: 200, body: body}}, operation) do + operation.response(body) + end + + defp parse_response({:ok, %{status: status_code, body: body}}, operation) + when status_code >= 400, + do: + {:error, + %{ + status: status_code, + reason: "Received #{status_code} from #{operation}", + response: body + }} + + defp parse_response({:error, response}, operation) do + {:error, %{status: nil, reason: "Error performing #{operation}", response: response}} + end +end diff --git a/lib/recording/get_recording_jobs.ex b/lib/recording/get_recording_jobs.ex new file mode 100644 index 0000000..6fe1dda --- /dev/null +++ b/lib/recording/get_recording_jobs.ex @@ -0,0 +1,24 @@ + +defmodule Onvif.Recording.GetRecordingJobs do + import SweetXml + import XmlBuilder + require Logger + + alias Onvif.Device + + def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/GetRecordingJobs" + + def request(device) do + Onvif.Recordings.request(device, __MODULE__) + end + + def request_body(token) do + element(:"s:Body", [ + element(:"trc:GetRecordingJobs") + ]) + end + + def response(xml_response_body) do + IO.puts xml_response_body + end +end diff --git a/lib/recording/get_recordings.ex b/lib/recording/get_recordings.ex new file mode 100644 index 0000000..e69de29 diff --git a/lib/recording/get_replay_configuration.ex b/lib/recording/get_replay_configuration.ex new file mode 100644 index 0000000..eff5c07 --- /dev/null +++ b/lib/recording/get_replay_configuration.ex @@ -0,0 +1,24 @@ + +defmodule Onvif.Recording.GetServiceCapabilities do + import SweetXml + import XmlBuilder + require Logger + + alias Onvif.Device + + def soap_action, do: "http://www.onvif.org/ver10/replay/wsdl/GetServiceCapabilities" + + def request(device) do + Onvif.Recordings.request(device, __MODULE__) + end + + def request_body() do + element(:"s:Body", [ + element(:"trp:GetServiceCapabilities") + ]) + end + + def response(xml_response_body) do + IO.puts xml_response_body + end +end diff --git a/lib/replay.ex b/lib/replay.ex new file mode 100644 index 0000000..8ba725d --- /dev/null +++ b/lib/replay.ex @@ -0,0 +1,56 @@ +defmodule Onvif.Replay do + @moduledoc """ + Interface for making requests to the Onvif recording service + http://www.onvif.org/onvif/ver10/recording.wsdl + """ + require Logger + alias Onvif.Device + + @namespaces [ + "xmlns:trp": "http://www.onvif.org/ver10/replay/wsdl", + "xmlns:tt": "http://www.onvif.org/ver10/schema" + ] + + @spec request(Device.t(), module()) :: {:ok, any} | {:error, map()} + def request(%Device{} = device, operation) do + content = generate_content(operation) + do_request(device, operation, content) + end + + def request(%Device{} = device, args, operation) do + content = generate_content(operation, args) + do_request(device, operation, content) + end + + defp do_request(device, operation, content) do + device + |> Onvif.API.client(service_path: :replay_service_path) + |> Tesla.request( + method: :post, + headers: [{"Content-Type", "application/soap+xml"}, {"SOAPAction", operation.soap_action()}], + body: %Onvif.Request{content: content, namespaces: @namespaces} + ) + |> parse_response(operation) + end + + defp generate_content(operation), do: operation.request_body() + defp generate_content(operation, args), do: operation.request_body(args) + + defp parse_response({:ok, %{status: 200, body: body}}, operation) do + operation.response(body) + end + + defp parse_response({:ok, %{status: status_code, body: body}}, operation) + when status_code >= 400, + do: + {:error, + %{ + status: status_code, + reason: "Received #{status_code} from #{operation}", + response: body + }} + + defp parse_response({:error, response}, operation) do + {:error, %{status: nil, reason: "Error performing #{operation}", response: response}} + end +end diff --git a/lib/replay/get_replay_uri.ex b/lib/replay/get_replay_uri.ex new file mode 100644 index 0000000..5706608 --- /dev/null +++ b/lib/replay/get_replay_uri.ex @@ -0,0 +1,31 @@ +defmodule Onvif.Recording.GetReplayUri do + import SweetXml + import XmlBuilder + require Logger + + alias Onvif.Device + + def soap_action, do: "http://www.onvif.org/ver10/replay/wsdl/GetReplayUri" + + def request(device, args) do + Onvif.Replay.request(device, args, __MODULE__) + end + + def request_body(token) do + element(:"s:Body", [ + element(:"trp:GetReplayUri", [ + element(:"tt:RecordingToken", token), + element(:"tt:StreamSetup", [ + element(:"tt:Stream", "RTP-Unicast"), + element(:"tt:Transport", [ + element(:"tt:Protocol", "RTSP") + ]) + ]) + ]) + ]) + end + + def response(xml_response_body) do + IO.puts xml_response_body + end +end diff --git a/lib/replay/get_service_capabilities.ex b/lib/replay/get_service_capabilities.ex new file mode 100644 index 0000000..b2d32e8 --- /dev/null +++ b/lib/replay/get_service_capabilities.ex @@ -0,0 +1,36 @@ +defmodule Onvif.Replay.GetServiceCapabilities do + import SweetXml + import XmlBuilder + require Logger + + alias Onvif.Device + + def soap_action, do: "http://www.onvif.org/ver10/replay/wsdl/GetServiceCapabilities" + + def request(device) do + Onvif.Replay.request(device, __MODULE__) + end + + def request_body() do + element(:"s:Body", [ + element(:"trp:GetServiceCapabilities") + ]) + end + + def response(xml_response_body) do + doc = parse(xml_response_body, namespace_conformant: true, quiet: true) + + parsed_result = + xpath( + doc, + ~x"//s:Envelope/s:Body/trp:GetServiceCapabilitiesResponse/trp:Capabilities" + |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") + |> add_namespace("trp", "http://www.onvif.org/ver10/replay/wsdl"), + rtp_rtsp_tcp: ~x"//@RTP_RTSP_TCP"so, + reverse_playback: ~x"//@ReversePlayback"so, + session_timeout_range: ~x"//@SessionTimeoutRange"so, + rtsp_web_socket_uri: ~x"//@RTSPWebSocketUri"so + ) + {:ok, Map.merge(%Onvif.Replay.ServiceCapabilities{}, parsed_result)} + end +end diff --git a/lib/replay/service_capabilities.ex b/lib/replay/service_capabilities.ex new file mode 100644 index 0000000..2c6f154 --- /dev/null +++ b/lib/replay/service_capabilities.ex @@ -0,0 +1,4 @@ +defmodule Onvif.Replay.ServiceCapabilities do + @derive Jason.Encoder + defstruct [:rtp_rtsp_tcp, :reverse_playback, :session_timeout_range, :rtsp_web_socket_uri] +end From 26d8f95faa80d38f40863a7ff447108e5423ddfb Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Fri, 15 Nov 2024 18:07:28 -0300 Subject: [PATCH 02/24] fix typo --- lib/replay/get_replay_uri.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/replay/get_replay_uri.ex b/lib/replay/get_replay_uri.ex index 5706608..adefb64 100644 --- a/lib/replay/get_replay_uri.ex +++ b/lib/replay/get_replay_uri.ex @@ -1,4 +1,4 @@ -defmodule Onvif.Recording.GetReplayUri do +defmodule Onvif.Replay.GetReplayUri do import SweetXml import XmlBuilder require Logger From 79926aa2f2f8e4cb2eaa4db846d5f4b7ac8cea74 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 18 Nov 2024 20:45:36 -0300 Subject: [PATCH 03/24] create works --- lib/recording/create_recondig_job.ex | 21 +++++++ lib/recording/create_recording.ex | 41 +++++++++++++ lib/recording/get_recording_jobs.ex | 6 +- lib/recording/get_recordings.ex | 22 +++++++ lib/recording/get_replay_configuration.ex | 4 +- lib/replay/get_replay_uri.ex | 22 +++++-- test/recording/create_recording_test.exs | 0 .../fixture/create_recording_success.xml | 59 +++++++++++++++++++ 8 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 lib/recording/create_recondig_job.ex create mode 100644 lib/recording/create_recording.ex create mode 100644 test/recording/create_recording_test.exs create mode 100644 test/recording/fixture/create_recording_success.xml diff --git a/lib/recording/create_recondig_job.ex b/lib/recording/create_recondig_job.ex new file mode 100644 index 0000000..8dd10e6 --- /dev/null +++ b/lib/recording/create_recondig_job.ex @@ -0,0 +1,21 @@ +defmodule Onvif.Recording.CreateRecordingJob do + import SweetXml + import XmlBuilder + require Logger + + def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/CreateRecordingJob" + + def request(device) do + Onvif.Recording.request(device, __MODULE__) + end + + def request_body() do + element(:"s:Body", [ + element(:"trc:CreateRecordingJob") + ]) + end + + def response(xml_response_body) do + IO.puts xml_response_body + end +end diff --git a/lib/recording/create_recording.ex b/lib/recording/create_recording.ex new file mode 100644 index 0000000..8d5051c --- /dev/null +++ b/lib/recording/create_recording.ex @@ -0,0 +1,41 @@ +defmodule Onvif.Recording.CreateRecording do + import SweetXml + import XmlBuilder + require Logger + + def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/CreateRecording" + + def request(device, args) do + Onvif.Recording.request(device, args, __MODULE__) + end + + def request_body(%{name: name, content: content, max_retention: max_retention}) do + element(:"s:Body", [ + element(:"trc:CreateRecording", [ + element(:"trc:RecordingConfiguration", [ + element(:"tt:Source", [ + element(:"tt:SourceId", ""), + element(:"tt:Name", name), + element(:"tt:Location", ""), + element(:"tt:Description", ""), + element(:"tt:Address", "") + ]), + element(:"tt:Content", content), + element(:"tt:MaximumRetentionTime", max_retention) + ]) + ]) + ]) + end + + def response(xml_response_body) do + response_uri = + xml_response_body + |> parse(namespace_conformant: true, quiet: true) + |> xpath( + ~x"//s:Envelope/s:Body/trc:CreateRecordingResponse/trc:RecordingToken/text()"s + |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") + |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl") + ) + {:ok, response_uri} + end +end diff --git a/lib/recording/get_recording_jobs.ex b/lib/recording/get_recording_jobs.ex index 6fe1dda..b701d29 100644 --- a/lib/recording/get_recording_jobs.ex +++ b/lib/recording/get_recording_jobs.ex @@ -4,15 +4,13 @@ defmodule Onvif.Recording.GetRecordingJobs do import XmlBuilder require Logger - alias Onvif.Device - def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/GetRecordingJobs" def request(device) do - Onvif.Recordings.request(device, __MODULE__) + Onvif.Recording.request(device, __MODULE__) end - def request_body(token) do + def request_body() do element(:"s:Body", [ element(:"trc:GetRecordingJobs") ]) diff --git a/lib/recording/get_recordings.ex b/lib/recording/get_recordings.ex index e69de29..d3f89a3 100644 --- a/lib/recording/get_recordings.ex +++ b/lib/recording/get_recordings.ex @@ -0,0 +1,22 @@ + +defmodule Onvif.Recording.GetRecordings do + import SweetXml + import XmlBuilder + require Logger + + def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/GetRecordings" + + def request(device) do + Onvif.Recording.request(device, __MODULE__) + end + + def request_body() do + element(:"s:Body", [ + element(:"trc:GetRecordings") + ]) + end + + def response(xml_response_body) do + IO.puts xml_response_body + end +end diff --git a/lib/recording/get_replay_configuration.ex b/lib/recording/get_replay_configuration.ex index eff5c07..97c4aa0 100644 --- a/lib/recording/get_replay_configuration.ex +++ b/lib/recording/get_replay_configuration.ex @@ -4,12 +4,10 @@ defmodule Onvif.Recording.GetServiceCapabilities do import XmlBuilder require Logger - alias Onvif.Device - def soap_action, do: "http://www.onvif.org/ver10/replay/wsdl/GetServiceCapabilities" def request(device) do - Onvif.Recordings.request(device, __MODULE__) + Onvif.Recording.request(device, __MODULE__) end def request_body() do diff --git a/lib/replay/get_replay_uri.ex b/lib/replay/get_replay_uri.ex index adefb64..8e53ba9 100644 --- a/lib/replay/get_replay_uri.ex +++ b/lib/replay/get_replay_uri.ex @@ -14,18 +14,28 @@ defmodule Onvif.Replay.GetReplayUri do def request_body(token) do element(:"s:Body", [ element(:"trp:GetReplayUri", [ - element(:"tt:RecordingToken", token), - element(:"tt:StreamSetup", [ + element(:"trp:StreamSetup", [ element(:"tt:Stream", "RTP-Unicast"), element(:"tt:Transport", [ - element(:"tt:Protocol", "RTSP") - ]) - ]) + element(:"tt:Protocol", "UDP") + ]) + ]), + element(:"trp:RecordingToken", token) ]) ]) end def response(xml_response_body) do - IO.puts xml_response_body + doc = parse(xml_response_body, namespace_conformant: true, quiet: true) + parsed_result = + xpath( + doc, + ~x"//trp:Uri/text()"s + |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") + |> add_namespace("trp", "http://www.onvif.org/ver10/replay/wsdl") + ) + + IO.puts parsed_result end + end diff --git a/test/recording/create_recording_test.exs b/test/recording/create_recording_test.exs new file mode 100644 index 0000000..e69de29 diff --git a/test/recording/fixture/create_recording_success.xml b/test/recording/fixture/create_recording_success.xml new file mode 100644 index 0000000..586028b --- /dev/null +++ b/test/recording/fixture/create_recording_success.xml @@ -0,0 +1,59 @@ + + + + + SD_DISK_20200422_123501_A2388AB3 + + + \ No newline at end of file From f94a672b501c646e709e90b3d0a7e05d1157c962 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 18 Nov 2024 22:02:22 -0300 Subject: [PATCH 04/24] add more parsing --- lib/recording/get_recordings.ex | 26 ++- lib/recording/recordings.ex | 159 ++++++++++++++++ .../fixture/get_recordings_success.xml | 169 ++++++++++++++++++ .../get_replay_uri__recording_not_found.xml | 73 ++++++++ 4 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 lib/recording/recordings.ex create mode 100644 test/recording/fixture/get_recordings_success.xml create mode 100644 test/replay/fixtures/get_replay_uri__recording_not_found.xml diff --git a/lib/recording/get_recordings.ex b/lib/recording/get_recordings.ex index d3f89a3..ac07a59 100644 --- a/lib/recording/get_recordings.ex +++ b/lib/recording/get_recordings.ex @@ -4,6 +4,8 @@ defmodule Onvif.Recording.GetRecordings do import XmlBuilder require Logger + alias Onvif.Recording.Recordings + def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/GetRecordings" def request(device) do @@ -17,6 +19,28 @@ defmodule Onvif.Recording.GetRecordings do end def response(xml_response_body) do - IO.puts xml_response_body + + response = + xml_response_body + |> parse(namespace_conformant: true, quiet: true) + |> xpath( + ~x"//s:Envelope/s:Body/trc:GetRecordingsResponse/trc:RecordingItem"el + |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") + |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl") + |> add_namespace("tt", "http://www.onvif.org/ver10/schema") + ) + |> Enum.map(&Recordings.parse/1) + |> Enum.reduce([], fn raw_recording, acc -> + case Recordings.to_struct(raw_recording) do + {:ok, recording} -> + [recording | acc] + + {:error, changeset} -> + Logger.error("Discarding invalid recording: #{inspect(changeset)}") + acc + end + end) + {:ok, response} + end end diff --git a/lib/recording/recordings.ex b/lib/recording/recordings.ex new file mode 100644 index 0000000..68e7235 --- /dev/null +++ b/lib/recording/recordings.ex @@ -0,0 +1,159 @@ +defmodule Onvif.Recording.Recordings do + @moduledoc """ + Recordings. + """ + + use Ecto.Schema + import Ecto.Changeset + import SweetXml + + @primary_key false + @derive Jason.Encoder + @required [:recording_token] + @optional [] + + embedded_schema do + field(:recording_token, :string) + + embeds_one :configuration, Configuration, primary_key: false, on_replace: :update do + @derive Jason.Encoder + field(:content, :string) + field(:maximum_retention_time, :string) + embeds_one(:source, Source, primary_key: false, on_replace: :update) do + @derive Jason.Encoder + field(:source_id, :string) + field(:name, :string) + field(:location, :string) + field(:description, :string) + field(:address, :string) + end + end + + embeds_one(:tracks, Tracks, primary_key: false, on_replace: :update) do + @derive Jason.Encoder + embeds_many(:track, Track, primary_key: false, on_replace: :delete) do + @derive Jason.Encoder + field(:track_token, :string) + embeds_one(:configuration, Configuration, primary_key: false, on_replace: :update) do + @derive Jason.Encoder + field(:track_type, :string) + field(:description, :string) + end + end + end + end + + def parse(nil), do: nil + def parse([]), do: nil + def parse(doc) do + xmap( + doc, + recording_token: ~x"./tt:RecordingToken/text()"so, + configuration: ~x"./tt:Configuration"eo |> transform_by(&parse_configuration/1), + tracks: ~x"./tt:Tracks"eo |> transform_by(&parse_tracks/1), + ) + end + + def parse_configuration([]), do: nil + def parse_configuration(nil), do: nil + def parse_configuration(doc) do + xmap( + doc, + content: ~x"./tt:Content/text()"so, + maximum_retention_time: ~x"./tt:MaximumRetentionTime/text()"so, + source: ~x"./tt:Source"eo |> transform_by(&parse_source/1) + ) + end + + def parse_source([]), do: nil + def parse_source(nil), do: nil + def parse_source(doc) do + xmap( + doc, + source_id: ~x"./tt:SourceId/text()"so, + name: ~x"./tt:Name/text()"so, + location: ~x"./tt:Location/text()"so, + description: ~x"./tt:Description/text()"so, + address: ~x"./tt:Address/text()"so + ) + end + + def parse_tracks([]), do: nil + def parse_tracks(nil), do: nil + def parse_tracks(doc) do + xmap( + doc, + track: ~x"./tt:Track"elo |> transform_by(&parse_track/1) + ) + end + + def parse_track([]), do: nil + def parse_track(nil), do: nil + def parse_track(doc) do + xmap( + doc, + track_token: ~x"./tt:TrackToken/text()"so, + configuration: ~x"./tt:Configuration"eo |> transform_by(&parse_track_configuration/1) + ) + end + + def parse_track_configuration([]), do: nil + def parse_track_configuration(nil), do: nil + def parse_track_configuration(doc) do + xmap( + doc, + track_type: ~x"./tt:TrackType/text()"so, + description: ~x"./tt:Description/text()"so + ) + end + + def to_struct(parsed) do + %__MODULE__{} + |> changeset(parsed) + |> apply_action(:validate) + end + + @spec to_json(%Onvif.Recording.Recordings{}) :: + {: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(:configuration, with: &configuration_changeset/2) + |> cast_embed(:tracks, with: &tracks_changeset/2) + end + + def configuration_changeset(module, attrs) do + cast(module, attrs, [:content, :maximum_retention_time]) + |> cast_embed(:source, with: &source_changeset/2) + end + + def source_changeset(module, attrs) do + cast(module, attrs, [:source_id, :name, :location, :description, :address]) + end + + def tracks_changeset(module, attrs) do + cast(module, attrs, []) + |> cast_embed(:track, with: &track_changeset/2) + end + + def track_changeset(module, attrs) do + cast(module, attrs, [:track_token]) + |> cast_embed(:configuration, with: &track_configuration_changeset/2) + end + + def track_configuration_changeset(module, attrs) do + cast(module, attrs, [:track_type, :description]) + end + +end diff --git a/test/recording/fixture/get_recordings_success.xml b/test/recording/fixture/get_recordings_success.xml new file mode 100644 index 0000000..e035348 --- /dev/null +++ b/test/recording/fixture/get_recordings_success.xml @@ -0,0 +1,169 @@ + + + + + + SD_DISK_20200422_123501_A2388AB3 + + + + paolo + + + + + beta cam 1 + PT1H + + + + VIDEO001 + + Video + + + + + AUDIO001 + + Audio + + + + + META001 + + Metadata + + + + + + + SD_DISK_20200422_132613_45A883F5 + + + + paolo2 + + + + + beta cam 2 + PT1H + + + + VIDEO001 + + Video + + + + + AUDIO001 + + Audio + + + + + META001 + + Metadata + + + + + + + SD_DISK_20200422_132655_67086B52 + + + + paolo3 + + + + + beta cam 3 + PT1H + + + + VIDEO001 + + Video + + + + + AUDIO001 + + Audio + + + + + META001 + + Metadata + + + + + + + + \ No newline at end of file diff --git a/test/replay/fixtures/get_replay_uri__recording_not_found.xml b/test/replay/fixtures/get_replay_uri__recording_not_found.xml new file mode 100644 index 0000000..6f8e9bd --- /dev/null +++ b/test/replay/fixtures/get_replay_uri__recording_not_found.xml @@ -0,0 +1,73 @@ + + + + + + SOAP-ENV:Sender + + ter:InvalidArgVal + + ter:NoRecording + + + + + Recording not found + + + The requested recording with the specified RecordingToken can not be found + + + + \ No newline at end of file From 0d5f48f184052c6485ea2a1eda51fd88d355519b Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Tue, 19 Nov 2024 15:55:58 -0300 Subject: [PATCH 05/24] fix GetRecordings --- lib/recording/recordings.ex | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/recording/recordings.ex b/lib/recording/recordings.ex index 68e7235..c56717a 100644 --- a/lib/recording/recordings.ex +++ b/lib/recording/recordings.ex @@ -81,20 +81,22 @@ defmodule Onvif.Recording.Recordings do def parse_tracks([]), do: nil def parse_tracks(nil), do: nil def parse_tracks(doc) do - xmap( + xmap( doc, track: ~x"./tt:Track"elo |> transform_by(&parse_track/1) - ) + ) end def parse_track([]), do: nil def parse_track(nil), do: nil - def parse_track(doc) do + def parse_track(docs) do + Enum.map(docs, fn doc -> xmap( doc, track_token: ~x"./tt:TrackToken/text()"so, configuration: ~x"./tt:Configuration"eo |> transform_by(&parse_track_configuration/1) ) + end) end def parse_track_configuration([]), do: nil From 7488bf9eb4d54c0794bfca1c3c2f12b26da8e993 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Wed, 20 Nov 2024 10:32:15 -0300 Subject: [PATCH 06/24] update uri --- lib/replay/get_replay_uri.ex | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/replay/get_replay_uri.ex b/lib/replay/get_replay_uri.ex index 8e53ba9..df178f1 100644 --- a/lib/replay/get_replay_uri.ex +++ b/lib/replay/get_replay_uri.ex @@ -11,13 +11,13 @@ defmodule Onvif.Replay.GetReplayUri do Onvif.Replay.request(device, args, __MODULE__) end - def request_body(token) do + def request_body(token, ss_stream \\ "RTP-Unicast", ss_protocol \\ "UDP") do element(:"s:Body", [ element(:"trp:GetReplayUri", [ element(:"trp:StreamSetup", [ - element(:"tt:Stream", "RTP-Unicast"), + element(:"tt:Stream", ss_stream), element(:"tt:Transport", [ - element(:"tt:Protocol", "UDP") + element(:"tt:Protocol", ss_protocol) ]) ]), element(:"trp:RecordingToken", token) @@ -26,16 +26,16 @@ defmodule Onvif.Replay.GetReplayUri do end def response(xml_response_body) do - doc = parse(xml_response_body, namespace_conformant: true, quiet: true) parsed_result = - xpath( - doc, + xml_response_body + |> parse(namespace_conformant: true, quiet: true) + |> xpath( ~x"//trp:Uri/text()"s |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") |> add_namespace("trp", "http://www.onvif.org/ver10/replay/wsdl") ) - IO.puts parsed_result + {:ok, parsed_result} end end From 5a195e2b37541ce5dee0f748c9601f6f3ce90603 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Wed, 20 Nov 2024 10:36:04 -0300 Subject: [PATCH 07/24] add success uri fixture --- .../fixtures/get_replay_uri__success.xml | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/replay/fixtures/get_replay_uri__success.xml diff --git a/test/replay/fixtures/get_replay_uri__success.xml b/test/replay/fixtures/get_replay_uri__success.xml new file mode 100644 index 0000000..3bcacd5 --- /dev/null +++ b/test/replay/fixtures/get_replay_uri__success.xml @@ -0,0 +1,59 @@ + + + + + rtsp://192.168.1.136/onvif-media/record/play.amp?onvifreplayid=SD_DISK_20200422_132655_67086B52&onvifreplayext=1&streamtype=unicast&session_timeout=30 + + + \ No newline at end of file From 7146e06fc4d1b15f11c3b001dcbb2451208a4b8b Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Wed, 20 Nov 2024 12:14:36 -0300 Subject: [PATCH 08/24] updated code --- lib/recording/create_recondig_job.ex | 15 +++++++++++---- lib/replay/get_replay_uri.ex | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/recording/create_recondig_job.ex b/lib/recording/create_recondig_job.ex index 8dd10e6..bfe26da 100644 --- a/lib/recording/create_recondig_job.ex +++ b/lib/recording/create_recondig_job.ex @@ -5,16 +5,23 @@ defmodule Onvif.Recording.CreateRecordingJob do def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/CreateRecordingJob" - def request(device) do - Onvif.Recording.request(device, __MODULE__) + def request(device, args) do + Onvif.Recording.request(device, args, __MODULE__) end - def request_body() do + def request_body(recording_token) do element(:"s:Body", [ - element(:"trc:CreateRecordingJob") + element(:"trc:CreateRecordingJob", [ + element(:"trc:JobConfiguration", [ + element(:"tt:RecordingToken", recording_token), + element(:"tt:Mode", "Active"), + element(:"tt:Priority", "9") + ]) + ]) ]) end + def response(xml_response_body) do IO.puts xml_response_body end diff --git a/lib/replay/get_replay_uri.ex b/lib/replay/get_replay_uri.ex index df178f1..603db03 100644 --- a/lib/replay/get_replay_uri.ex +++ b/lib/replay/get_replay_uri.ex @@ -11,7 +11,7 @@ defmodule Onvif.Replay.GetReplayUri do Onvif.Replay.request(device, args, __MODULE__) end - def request_body(token, ss_stream \\ "RTP-Unicast", ss_protocol \\ "UDP") do + def request_body(token, ss_stream \\ "RTP-Unicast", ss_protocol \\ "TCP") do element(:"s:Body", [ element(:"trp:GetReplayUri", [ element(:"trp:StreamSetup", [ From c91e54e61ee2406ff03d6bfe932f791a3b908d74 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Wed, 20 Nov 2024 19:28:53 -0300 Subject: [PATCH 09/24] add more fixtures --- .../fixture/create_recording_job_success.xml | 64 ++++++++++++++ .../fixture/get_recording_jobs_empty.xml | 57 +++++++++++++ .../fixture/get_recording_jobs_success.xml | 84 +++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 test/recording/fixture/create_recording_job_success.xml create mode 100644 test/recording/fixture/get_recording_jobs_empty.xml create mode 100644 test/recording/fixture/get_recording_jobs_success.xml diff --git a/test/recording/fixture/create_recording_job_success.xml b/test/recording/fixture/create_recording_job_success.xml new file mode 100644 index 0000000..49505d6 --- /dev/null +++ b/test/recording/fixture/create_recording_job_success.xml @@ -0,0 +1,64 @@ + + + + + SD_DISK_20241120_211729_9C896594 + + SD_DISK_20241120_211729_9C896594 + Active + 9 + + + + \ No newline at end of file diff --git a/test/recording/fixture/get_recording_jobs_empty.xml b/test/recording/fixture/get_recording_jobs_empty.xml new file mode 100644 index 0000000..4c4bc0d --- /dev/null +++ b/test/recording/fixture/get_recording_jobs_empty.xml @@ -0,0 +1,57 @@ + + + + + + \ No newline at end of file diff --git a/test/recording/fixture/get_recording_jobs_success.xml b/test/recording/fixture/get_recording_jobs_success.xml new file mode 100644 index 0000000..5717531 --- /dev/null +++ b/test/recording/fixture/get_recording_jobs_success.xml @@ -0,0 +1,84 @@ + + + + + + SD_DISK_20241120_211729_9C896594 + + SD_DISK_20241120_211729_9C896594 + Active + 9 + + + profile_1_h264 + + false + + tag0 + VIDEO001 + + + tag1 + AUDIO001 + + + tag2 + META001 + + + + + + + \ No newline at end of file From bd3399a40fb18cb2e247aeff953a001013c6f035 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 25 Nov 2024 17:30:48 -0300 Subject: [PATCH 10/24] fix functions --- lib/factory.ex | 2 + lib/recording/create_recondig_job.ex | 21 +++- lib/recording/recording_jobs.ex | 159 +++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 lib/recording/recording_jobs.ex diff --git a/lib/factory.ex b/lib/factory.ex index 50545b8..2d6fd54 100644 --- a/lib/factory.ex +++ b/lib/factory.ex @@ -9,6 +9,8 @@ defmodule Onvif.Factory do manufacturer: "General", media_ver10_service_path: "/onvif/media_service", media_ver20_service_path: "/onvif/media2_service", + replay_service_path: "/onvif/replay_service", + recording_service_path: "/onvif/recording_service", model: "N864A6", ntp: "NTP", password: "admin", diff --git a/lib/recording/create_recondig_job.ex b/lib/recording/create_recondig_job.ex index bfe26da..1a36a4b 100644 --- a/lib/recording/create_recondig_job.ex +++ b/lib/recording/create_recondig_job.ex @@ -9,13 +9,13 @@ defmodule Onvif.Recording.CreateRecordingJob do Onvif.Recording.request(device, args, __MODULE__) end - def request_body(recording_token) do + def request_body(recording_token, priority \\ "0", mode \\ "Active") do element(:"s:Body", [ element(:"trc:CreateRecordingJob", [ element(:"trc:JobConfiguration", [ element(:"tt:RecordingToken", recording_token), - element(:"tt:Mode", "Active"), - element(:"tt:Priority", "9") + element(:"tt:Mode", mode), + element(:"tt:Priority", priority) ]) ]) ]) @@ -23,6 +23,19 @@ defmodule Onvif.Recording.CreateRecordingJob do def response(xml_response_body) do - IO.puts xml_response_body + parsed_result = + xml_response_body + |> parse(namespace_conformant: true, quiet: true) + |> xpath( + ~x"//s:Envelope/s:Body/trc:CreateRecordingJobResponse" + |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") + |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl"), + job_token: ~x"//trc:JobToken/text()"so, + recording_token: ~x"//trc:JobConfiguration/tt:RecordingToken/text()"so, + mode: ~x"//trc:JobConfiguration/tt:Mode/text()"so, + priority: ~x"//trc:JobConfiguration/tt:Priority/text()"so + ) + {:ok, parsed_result} end + end diff --git a/lib/recording/recording_jobs.ex b/lib/recording/recording_jobs.ex new file mode 100644 index 0000000..c2d9938 --- /dev/null +++ b/lib/recording/recording_jobs.ex @@ -0,0 +1,159 @@ +defmodule Onvif.Recording.RecordingJobs do + @moduledoc """ + Recordings. + """ + + use Ecto.Schema + import Ecto.Changeset + import SweetXml + + @primary_key false + @derive Jason.Encoder + @required [] + @optional [] + + embedded_schema do + embeds_many :job_item, JobItem, primary_key: false, on_replace: :delete do + @derive Jason.Encoder + field(:job_token, :string) + embeds_one :job_configuration, JobConfiguration, primary_key: false, on_replace: :update do + @derive Jason.Encoder + field(:recording_token, :string) + field(:mode, :string) + field(:priority, :string) + embeds_one :source, Source, primary_key: false, on_replace: :update do + @derive Jason.Encoder + field(:auto_create_receiver, :boolean) + embeds_one :source_token, SourceToken, primary_key: false, on_replace: :update do + @derive Jason.Encoder + field(:token, :string) + end + embeds_many :track, Track, primary_key: false, on_replace: :delete do + @derive Jason.Encoder + field(:source_tag, :string) + field(:destination, :string) + end + end + end + end + end + + def parse(nil), do: nil + def parse([]), do: nil + def parse(doc) do + xmap( + doc, + job_item: ~x"./tt:JobItem"elo |> transform_by(&parse_job_item/1), + ) + end + + def parse_job_item([]), do: nil + def parse_job_item(nil), do: nil + def parse_job_item(doc) do + xmap( + doc, + job_token: ~x"./tt:JobToken/text()"so, + job_configuration: ~x"./tt:JobConfiguration"eo |> transform_by(&parse_job_configuration/1), + ) + end + + def parse_job_configuration([]), do: nil + def parse_job_configuration(nil), do: nil + def parse_job_configuration(doc) do + xmap( + doc, + recording_token: ~x"./tt:RecordingToken/text()"so, + mode: ~x"./tt:Mode/text()"so, + priority: ~x"./tt:Priority/text()"so, + source: ~x"./tt:Source"eo |> transform_by(&parse_source/1), + ) + end + + def parse_source([]), do: nil + def parse_source(nil), do: nil + def parse_source(doc) do + xmap( + doc, + source_token: ~x"./tt:SourceToken"eo |> transform_by(&parse_source_token/1), + auto_create_receiver: ~x"./tt:AutoCreateReceiver/text()"so, + track: ~x"./tt:Track"elo |> transform_by(&parse_track/1), + ) + end + + def parse_source_token([]), do: nil + def parse_source_token(nil), do: nil + def parse_source_token(doc) do + xmap( + doc, + token: ~x"./tt:Token/text()"so, + ) + end + + def parse_track([]), do: nil + def parse_track(nil), do: nil + def parse_track(doc) do + xmap( + doc, + source_tag: ~x"./tt:SourceTag/text()"so, + destination: ~x"./tt:Destination/text()"so, + ) + end + + def to_struct(parsed) do + %__MODULE__{} + |> changeset(parsed) + |> apply_action(:validate) + end + + @spec to_json(%Onvif.Recording.RecordingJobs{}) :: + {: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(schema, params \\ %{}) do + schema + |> cast(params, []) + |> validate_required([]) + |> cast_embed(:job_item, with: &job_item_changeset/2) + end + + def job_item_changeset(schema, params) do + schema + |> cast(params, [:job_token]) + |> validate_required([:job_token]) + |> cast_embed(:job_configuration, with: &job_configuration_changeset/2) + end + + def job_configuration_changeset(schema, params) do + schema + |> cast(params, [:recording_token, :mode, :priority]) + |> validate_required([:recording_token, :mode, :priority]) + |> cast_embed(:source, with: &source_changeset/2) + end + + def source_changeset(schema, params) do + schema + |> cast(params, [:auto_create_receiver]) + |> cast_embed(:source_token, with: &source_token_changeset/2) + |> cast_embed(:track, with: &track_changeset/2) + end + + def source_token_changeset(schema, params) do + schema + |> cast(params, [:token]) + |> validate_required([:token]) + end + + def track_changeset(schema, params) do + schema + |> cast(params, [:source_tag, :destination]) + end + +end From 49ed1678c73421f7b726aeee5554bc0f6d8d3e3d Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 25 Nov 2024 17:31:29 -0300 Subject: [PATCH 11/24] add more tests --- test/recording/create_recording_job_test.exs | 21 ++ test/recording/create_recording_test.exs | 25 ++ .../fixture/create_recording_job_success.xml | 122 +++---- .../fixture/create_recording_success.xml | 112 +++--- .../fixture/get_recording_jobs_empty.xml | 108 +++--- .../fixture/get_recording_jobs_success.xml | 162 ++++----- .../fixture/get_recordings_success.xml | 332 +++++++++--------- .../get_replay_uri__recording_not_found.xml | 140 ++++---- .../fixtures/get_replay_uri__success.xml | 112 +++--- 9 files changed, 590 insertions(+), 544 deletions(-) create mode 100644 test/recording/create_recording_job_test.exs diff --git a/test/recording/create_recording_job_test.exs b/test/recording/create_recording_job_test.exs new file mode 100644 index 0000000..13fc134 --- /dev/null +++ b/test/recording/create_recording_job_test.exs @@ -0,0 +1,21 @@ +defmodule Onvif.Recording.CreateRecordingJobTest do + use ExUnit.Case, async: true + + @moduletag capture_log: true + + describe "CreateRecordingJob/2" do + test "create a recording" do + xml_response = File.read!("test/recording/fixture/create_recording_job_success.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response} = Onvif.Recording.CreateRecordingJob.request(device, ["SD_DISK_20241120_211729_9C896594", "9", "Active"]) + + assert response.job_token == "SD_DISK_20241120_211729_9C896594" + end + end +end diff --git a/test/recording/create_recording_test.exs b/test/recording/create_recording_test.exs index e69de29..8d09f2b 100644 --- a/test/recording/create_recording_test.exs +++ b/test/recording/create_recording_test.exs @@ -0,0 +1,25 @@ +defmodule Onvif.Recording.CreateRecordingTest do + use ExUnit.Case, async: true + + @moduletag capture_log: true + + describe "CreateRecording/2" do + test "create a recording" do + xml_response = File.read!("test/recording/fixture/create_recording_success.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response_uri} = Onvif.Recording.CreateRecording.request(device, %{ + name: "test", + content: "test", + max_retention: "PT1H" + }) + + assert response_uri == "SD_DISK_20200422_123501_A2388AB3" + end + end +end diff --git a/test/recording/fixture/create_recording_job_success.xml b/test/recording/fixture/create_recording_job_success.xml index 49505d6..82abbc4 100644 --- a/test/recording/fixture/create_recording_job_success.xml +++ b/test/recording/fixture/create_recording_job_success.xml @@ -1,64 +1,64 @@ - - - SD_DISK_20241120_211729_9C896594 - - SD_DISK_20241120_211729_9C896594 - Active - 9 - - - + 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:xmime="http://tempuri.org/xmime.xsd" + xmlns:xop="http://www.w3.org/2004/08/xop/include" + xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2" + xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" + xmlns:tt="http://www.onvif.org/ver10/schema" + xmlns:acert="http://www.axis.com/vapix/ws/cert" + xmlns:wsrfr="http://docs.oasis-open.org/wsrf/r-2" + xmlns:aa="http://www.axis.com/vapix/ws/action1" + xmlns:acertificates="http://www.axis.com/vapix/ws/certificates" + xmlns:aentry="http://www.axis.com/vapix/ws/entry" + xmlns:aev="http://www.axis.com/vapix/ws/event1" + xmlns:aeva="http://www.axis.com/vapix/ws/embeddedvideoanalytics1" + xmlns:ali1="http://www.axis.com/vapix/ws/light/CommonBinding" + xmlns:ali2="http://www.axis.com/vapix/ws/light/IntensityBinding" + xmlns:ali3="http://www.axis.com/vapix/ws/light/AngleOfIlluminationBinding" + xmlns:ali4="http://www.axis.com/vapix/ws/light/DayNightSynchronizeBinding" + xmlns:ali="http://www.axis.com/vapix/ws/light" + xmlns:apc="http://www.axis.com/vapix/ws/panopsiscalibration1" + xmlns:arth="http://www.axis.com/vapix/ws/recordedtour1" + xmlns:asd="http://www.axis.com/vapix/ws/shockdetection" + xmlns:aweb="http://www.axis.com/vapix/ws/webserver" + xmlns:tan1="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding" + xmlns:tan2="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding" + xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" + xmlns:tds="http://www.onvif.org/ver10/device/wsdl" + xmlns:tev1="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding" + xmlns:tev2="http://www.onvif.org/ver10/events/wsdl/EventBinding" + xmlns:tev3="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding" + xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" + xmlns:tev4="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding" + xmlns:tev="http://www.onvif.org/ver10/events/wsdl" + xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" + xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl" + xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" + xmlns:tr2="http://www.onvif.org/ver20/media/wsdl" + xmlns:trc="http://www.onvif.org/ver10/recording/wsdl" + xmlns:trp="http://www.onvif.org/ver10/replay/wsdl" + xmlns:trt="http://www.onvif.org/ver10/media/wsdl" + xmlns:tse="http://www.onvif.org/ver10/search/wsdl" + xmlns:ter="http://www.onvif.org/ver10/error" + xmlns:tns1="http://www.onvif.org/ver10/topics" + xmlns:tnsaxis="http://www.axis.com/2009/event/topics"> + + + SD_DISK_20241120_211729_9C896594 + + SD_DISK_20241120_211729_9C896594 + Active + 9 + + + \ No newline at end of file diff --git a/test/recording/fixture/create_recording_success.xml b/test/recording/fixture/create_recording_success.xml index 586028b..9ed4a7d 100644 --- a/test/recording/fixture/create_recording_success.xml +++ b/test/recording/fixture/create_recording_success.xml @@ -1,59 +1,59 @@ - - - SD_DISK_20200422_123501_A2388AB3 - - + 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:xmime="http://tempuri.org/xmime.xsd" + xmlns:xop="http://www.w3.org/2004/08/xop/include" + xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2" + xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" + xmlns:tt="http://www.onvif.org/ver10/schema" + xmlns:acert="http://www.axis.com/vapix/ws/cert" + xmlns:wsrfr="http://docs.oasis-open.org/wsrf/r-2" + xmlns:aa="http://www.axis.com/vapix/ws/action1" + xmlns:acertificates="http://www.axis.com/vapix/ws/certificates" + xmlns:aentry="http://www.axis.com/vapix/ws/entry" + xmlns:aev="http://www.axis.com/vapix/ws/event1" + xmlns:aeva="http://www.axis.com/vapix/ws/embeddedvideoanalytics1" + xmlns:ali1="http://www.axis.com/vapix/ws/light/CommonBinding" + xmlns:ali2="http://www.axis.com/vapix/ws/light/IntensityBinding" + xmlns:ali3="http://www.axis.com/vapix/ws/light/AngleOfIlluminationBinding" + xmlns:ali4="http://www.axis.com/vapix/ws/light/DayNightSynchronizeBinding" + xmlns:ali="http://www.axis.com/vapix/ws/light" + xmlns:apc="http://www.axis.com/vapix/ws/panopsiscalibration1" + xmlns:arth="http://www.axis.com/vapix/ws/recordedtour1" + xmlns:asd="http://www.axis.com/vapix/ws/shockdetection" + xmlns:aweb="http://www.axis.com/vapix/ws/webserver" + xmlns:tan1="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding" + xmlns:tan2="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding" + xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" + xmlns:tds="http://www.onvif.org/ver10/device/wsdl" + xmlns:tev1="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding" + xmlns:tev2="http://www.onvif.org/ver10/events/wsdl/EventBinding" + xmlns:tev3="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding" + xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" + xmlns:tev4="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding" + xmlns:tev="http://www.onvif.org/ver10/events/wsdl" + xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" + xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl" + xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" + xmlns:tr2="http://www.onvif.org/ver20/media/wsdl" + xmlns:trc="http://www.onvif.org/ver10/recording/wsdl" + xmlns:trp="http://www.onvif.org/ver10/replay/wsdl" + xmlns:trt="http://www.onvif.org/ver10/media/wsdl" + xmlns:tse="http://www.onvif.org/ver10/search/wsdl" + xmlns:ter="http://www.onvif.org/ver10/error" + xmlns:tns1="http://www.onvif.org/ver10/topics" + xmlns:tnsaxis="http://www.axis.com/2009/event/topics"> + + + SD_DISK_20200422_123501_A2388AB3 + + \ No newline at end of file diff --git a/test/recording/fixture/get_recording_jobs_empty.xml b/test/recording/fixture/get_recording_jobs_empty.xml index 4c4bc0d..b73e9b9 100644 --- a/test/recording/fixture/get_recording_jobs_empty.xml +++ b/test/recording/fixture/get_recording_jobs_empty.xml @@ -1,57 +1,57 @@ - - - + 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:xmime="http://tempuri.org/xmime.xsd" + xmlns:xop="http://www.w3.org/2004/08/xop/include" + xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2" + xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" + xmlns:tt="http://www.onvif.org/ver10/schema" + xmlns:acert="http://www.axis.com/vapix/ws/cert" + xmlns:wsrfr="http://docs.oasis-open.org/wsrf/r-2" + xmlns:aa="http://www.axis.com/vapix/ws/action1" + xmlns:acertificates="http://www.axis.com/vapix/ws/certificates" + xmlns:aentry="http://www.axis.com/vapix/ws/entry" + xmlns:aev="http://www.axis.com/vapix/ws/event1" + xmlns:aeva="http://www.axis.com/vapix/ws/embeddedvideoanalytics1" + xmlns:ali1="http://www.axis.com/vapix/ws/light/CommonBinding" + xmlns:ali2="http://www.axis.com/vapix/ws/light/IntensityBinding" + xmlns:ali3="http://www.axis.com/vapix/ws/light/AngleOfIlluminationBinding" + xmlns:ali4="http://www.axis.com/vapix/ws/light/DayNightSynchronizeBinding" + xmlns:ali="http://www.axis.com/vapix/ws/light" + xmlns:apc="http://www.axis.com/vapix/ws/panopsiscalibration1" + xmlns:arth="http://www.axis.com/vapix/ws/recordedtour1" + xmlns:asd="http://www.axis.com/vapix/ws/shockdetection" + xmlns:aweb="http://www.axis.com/vapix/ws/webserver" + xmlns:tan1="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding" + xmlns:tan2="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding" + xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" + xmlns:tds="http://www.onvif.org/ver10/device/wsdl" + xmlns:tev1="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding" + xmlns:tev2="http://www.onvif.org/ver10/events/wsdl/EventBinding" + xmlns:tev3="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding" + xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" + xmlns:tev4="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding" + xmlns:tev="http://www.onvif.org/ver10/events/wsdl" + xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" + xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl" + xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" + xmlns:tr2="http://www.onvif.org/ver20/media/wsdl" + xmlns:trc="http://www.onvif.org/ver10/recording/wsdl" + xmlns:trp="http://www.onvif.org/ver10/replay/wsdl" + xmlns:trt="http://www.onvif.org/ver10/media/wsdl" + xmlns:tse="http://www.onvif.org/ver10/search/wsdl" + xmlns:ter="http://www.onvif.org/ver10/error" + xmlns:tns1="http://www.onvif.org/ver10/topics" + xmlns:tnsaxis="http://www.axis.com/2009/event/topics"> + + + \ No newline at end of file diff --git a/test/recording/fixture/get_recording_jobs_success.xml b/test/recording/fixture/get_recording_jobs_success.xml index 5717531..aeba527 100644 --- a/test/recording/fixture/get_recording_jobs_success.xml +++ b/test/recording/fixture/get_recording_jobs_success.xml @@ -1,84 +1,84 @@ - - - - SD_DISK_20241120_211729_9C896594 - - SD_DISK_20241120_211729_9C896594 - Active - 9 - - - profile_1_h264 - - false - - tag0 - VIDEO001 - - - tag1 - AUDIO001 - - - tag2 - META001 - - - - - - + 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:xmime="http://tempuri.org/xmime.xsd" + xmlns:xop="http://www.w3.org/2004/08/xop/include" + xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2" + xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" + xmlns:tt="http://www.onvif.org/ver10/schema" + xmlns:acert="http://www.axis.com/vapix/ws/cert" + xmlns:wsrfr="http://docs.oasis-open.org/wsrf/r-2" + xmlns:aa="http://www.axis.com/vapix/ws/action1" + xmlns:acertificates="http://www.axis.com/vapix/ws/certificates" + xmlns:aentry="http://www.axis.com/vapix/ws/entry" + xmlns:aev="http://www.axis.com/vapix/ws/event1" + xmlns:aeva="http://www.axis.com/vapix/ws/embeddedvideoanalytics1" + xmlns:ali1="http://www.axis.com/vapix/ws/light/CommonBinding" + xmlns:ali2="http://www.axis.com/vapix/ws/light/IntensityBinding" + xmlns:ali3="http://www.axis.com/vapix/ws/light/AngleOfIlluminationBinding" + xmlns:ali4="http://www.axis.com/vapix/ws/light/DayNightSynchronizeBinding" + xmlns:ali="http://www.axis.com/vapix/ws/light" + xmlns:apc="http://www.axis.com/vapix/ws/panopsiscalibration1" + xmlns:arth="http://www.axis.com/vapix/ws/recordedtour1" + xmlns:asd="http://www.axis.com/vapix/ws/shockdetection" + xmlns:aweb="http://www.axis.com/vapix/ws/webserver" + xmlns:tan1="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding" + xmlns:tan2="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding" + xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" + xmlns:tds="http://www.onvif.org/ver10/device/wsdl" + xmlns:tev1="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding" + xmlns:tev2="http://www.onvif.org/ver10/events/wsdl/EventBinding" + xmlns:tev3="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding" + xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" + xmlns:tev4="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding" + xmlns:tev="http://www.onvif.org/ver10/events/wsdl" + xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" + xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl" + xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" + xmlns:tr2="http://www.onvif.org/ver20/media/wsdl" + xmlns:trc="http://www.onvif.org/ver10/recording/wsdl" + xmlns:trp="http://www.onvif.org/ver10/replay/wsdl" + xmlns:trt="http://www.onvif.org/ver10/media/wsdl" + xmlns:tse="http://www.onvif.org/ver10/search/wsdl" + xmlns:ter="http://www.onvif.org/ver10/error" + xmlns:tns1="http://www.onvif.org/ver10/topics" + xmlns:tnsaxis="http://www.axis.com/2009/event/topics"> + + + + SD_DISK_20241120_211729_9C896594 + + SD_DISK_20241120_211729_9C896594 + Active + 9 + + + profile_1_h264 + + false + + tag0 + VIDEO001 + + + tag1 + AUDIO001 + + + tag2 + META001 + + + + + + \ No newline at end of file diff --git a/test/recording/fixture/get_recordings_success.xml b/test/recording/fixture/get_recordings_success.xml index e035348..b014e53 100644 --- a/test/recording/fixture/get_recordings_success.xml +++ b/test/recording/fixture/get_recordings_success.xml @@ -1,169 +1,169 @@ - - - - SD_DISK_20200422_123501_A2388AB3 - - - - paolo - - - - - beta cam 1 - PT1H - - - - VIDEO001 - - Video - - - - - AUDIO001 - - Audio - - - - - META001 - - Metadata - - - - - - - SD_DISK_20200422_132613_45A883F5 - - - - paolo2 - - - - - beta cam 2 - PT1H - - - - VIDEO001 - - Video - - - - - AUDIO001 - - Audio - - - - - META001 - - Metadata - - - - - - - SD_DISK_20200422_132655_67086B52 - - - - paolo3 - - - - - beta cam 3 - PT1H - - - - VIDEO001 - - Video - - - - - AUDIO001 - - Audio - - - - - META001 - - Metadata - - - - - - - + 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:xmime="http://tempuri.org/xmime.xsd" + xmlns:xop="http://www.w3.org/2004/08/xop/include" + xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2" + xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" + xmlns:tt="http://www.onvif.org/ver10/schema" + xmlns:acert="http://www.axis.com/vapix/ws/cert" + xmlns:wsrfr="http://docs.oasis-open.org/wsrf/r-2" + xmlns:aa="http://www.axis.com/vapix/ws/action1" + xmlns:acertificates="http://www.axis.com/vapix/ws/certificates" + xmlns:aentry="http://www.axis.com/vapix/ws/entry" + xmlns:aev="http://www.axis.com/vapix/ws/event1" + xmlns:aeva="http://www.axis.com/vapix/ws/embeddedvideoanalytics1" + xmlns:ali1="http://www.axis.com/vapix/ws/light/CommonBinding" + xmlns:ali2="http://www.axis.com/vapix/ws/light/IntensityBinding" + xmlns:ali3="http://www.axis.com/vapix/ws/light/AngleOfIlluminationBinding" + xmlns:ali4="http://www.axis.com/vapix/ws/light/DayNightSynchronizeBinding" + xmlns:ali="http://www.axis.com/vapix/ws/light" + xmlns:apc="http://www.axis.com/vapix/ws/panopsiscalibration1" + xmlns:arth="http://www.axis.com/vapix/ws/recordedtour1" + xmlns:asd="http://www.axis.com/vapix/ws/shockdetection" + xmlns:aweb="http://www.axis.com/vapix/ws/webserver" + xmlns:tan1="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding" + xmlns:tan2="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding" + xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" + xmlns:tds="http://www.onvif.org/ver10/device/wsdl" + xmlns:tev1="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding" + xmlns:tev2="http://www.onvif.org/ver10/events/wsdl/EventBinding" + xmlns:tev3="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding" + xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" + xmlns:tev4="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding" + xmlns:tev="http://www.onvif.org/ver10/events/wsdl" + xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" + xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl" + xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" + xmlns:tr2="http://www.onvif.org/ver20/media/wsdl" + xmlns:trc="http://www.onvif.org/ver10/recording/wsdl" + xmlns:trp="http://www.onvif.org/ver10/replay/wsdl" + xmlns:trt="http://www.onvif.org/ver10/media/wsdl" + xmlns:tse="http://www.onvif.org/ver10/search/wsdl" + xmlns:ter="http://www.onvif.org/ver10/error" + xmlns:tns1="http://www.onvif.org/ver10/topics" + xmlns:tnsaxis="http://www.axis.com/2009/event/topics"> + + + + SD_DISK_20200422_123501_A2388AB3 + + + + paolo + + + + + beta cam 1 + PT1H + + + + VIDEO001 + + Video + + + + + AUDIO001 + + Audio + + + + + META001 + + Metadata + + + + + + + SD_DISK_20200422_132613_45A883F5 + + + + paolo2 + + + + + beta cam 2 + PT1H + + + + VIDEO001 + + Video + + + + + AUDIO001 + + Audio + + + + + META001 + + Metadata + + + + + + + SD_DISK_20200422_132655_67086B52 + + + + paolo3 + + + + + beta cam 3 + PT1H + + + + VIDEO001 + + Video + + + + + AUDIO001 + + Audio + + + + + META001 + + Metadata + + + + + + + \ No newline at end of file diff --git a/test/replay/fixtures/get_replay_uri__recording_not_found.xml b/test/replay/fixtures/get_replay_uri__recording_not_found.xml index 6f8e9bd..186a28e 100644 --- a/test/replay/fixtures/get_replay_uri__recording_not_found.xml +++ b/test/replay/fixtures/get_replay_uri__recording_not_found.xml @@ -1,73 +1,73 @@ - - - - SOAP-ENV:Sender - - ter:InvalidArgVal - - ter:NoRecording - - - - - Recording not found - - - The requested recording with the specified RecordingToken can not be found - - - + 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:xmime="http://tempuri.org/xmime.xsd" + xmlns:xop="http://www.w3.org/2004/08/xop/include" + xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2" + xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" + xmlns:tt="http://www.onvif.org/ver10/schema" + xmlns:acert="http://www.axis.com/vapix/ws/cert" + xmlns:wsrfr="http://docs.oasis-open.org/wsrf/r-2" + xmlns:aa="http://www.axis.com/vapix/ws/action1" + xmlns:acertificates="http://www.axis.com/vapix/ws/certificates" + xmlns:aentry="http://www.axis.com/vapix/ws/entry" + xmlns:aev="http://www.axis.com/vapix/ws/event1" + xmlns:aeva="http://www.axis.com/vapix/ws/embeddedvideoanalytics1" + xmlns:ali1="http://www.axis.com/vapix/ws/light/CommonBinding" + xmlns:ali2="http://www.axis.com/vapix/ws/light/IntensityBinding" + xmlns:ali3="http://www.axis.com/vapix/ws/light/AngleOfIlluminationBinding" + xmlns:ali4="http://www.axis.com/vapix/ws/light/DayNightSynchronizeBinding" + xmlns:ali="http://www.axis.com/vapix/ws/light" + xmlns:apc="http://www.axis.com/vapix/ws/panopsiscalibration1" + xmlns:arth="http://www.axis.com/vapix/ws/recordedtour1" + xmlns:asd="http://www.axis.com/vapix/ws/shockdetection" + xmlns:aweb="http://www.axis.com/vapix/ws/webserver" + xmlns:tan1="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding" + xmlns:tan2="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding" + xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" + xmlns:tds="http://www.onvif.org/ver10/device/wsdl" + xmlns:tev1="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding" + xmlns:tev2="http://www.onvif.org/ver10/events/wsdl/EventBinding" + xmlns:tev3="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding" + xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" + xmlns:tev4="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding" + xmlns:tev="http://www.onvif.org/ver10/events/wsdl" + xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" + xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl" + xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" + xmlns:tr2="http://www.onvif.org/ver20/media/wsdl" + xmlns:trc="http://www.onvif.org/ver10/recording/wsdl" + xmlns:trp="http://www.onvif.org/ver10/replay/wsdl" + xmlns:trt="http://www.onvif.org/ver10/media/wsdl" + xmlns:tse="http://www.onvif.org/ver10/search/wsdl" + xmlns:ter="http://www.onvif.org/ver10/error" + xmlns:tns1="http://www.onvif.org/ver10/topics" + xmlns:tnsaxis="http://www.axis.com/2009/event/topics"> + + + + SOAP-ENV:Sender + + ter:InvalidArgVal + + ter:NoRecording + + + + + Recording not found + + + The requested recording with the specified RecordingToken can not be found + + + \ No newline at end of file diff --git a/test/replay/fixtures/get_replay_uri__success.xml b/test/replay/fixtures/get_replay_uri__success.xml index 3bcacd5..46854b6 100644 --- a/test/replay/fixtures/get_replay_uri__success.xml +++ b/test/replay/fixtures/get_replay_uri__success.xml @@ -1,59 +1,59 @@ - - - rtsp://192.168.1.136/onvif-media/record/play.amp?onvifreplayid=SD_DISK_20200422_132655_67086B52&onvifreplayext=1&streamtype=unicast&session_timeout=30 - - + 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:xmime="http://tempuri.org/xmime.xsd" + xmlns:xop="http://www.w3.org/2004/08/xop/include" + xmlns:wsrfbf="http://docs.oasis-open.org/wsrf/bf-2" + xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" + xmlns:tt="http://www.onvif.org/ver10/schema" + xmlns:acert="http://www.axis.com/vapix/ws/cert" + xmlns:wsrfr="http://docs.oasis-open.org/wsrf/r-2" + xmlns:aa="http://www.axis.com/vapix/ws/action1" + xmlns:acertificates="http://www.axis.com/vapix/ws/certificates" + xmlns:aentry="http://www.axis.com/vapix/ws/entry" + xmlns:aev="http://www.axis.com/vapix/ws/event1" + xmlns:aeva="http://www.axis.com/vapix/ws/embeddedvideoanalytics1" + xmlns:ali1="http://www.axis.com/vapix/ws/light/CommonBinding" + xmlns:ali2="http://www.axis.com/vapix/ws/light/IntensityBinding" + xmlns:ali3="http://www.axis.com/vapix/ws/light/AngleOfIlluminationBinding" + xmlns:ali4="http://www.axis.com/vapix/ws/light/DayNightSynchronizeBinding" + xmlns:ali="http://www.axis.com/vapix/ws/light" + xmlns:apc="http://www.axis.com/vapix/ws/panopsiscalibration1" + xmlns:arth="http://www.axis.com/vapix/ws/recordedtour1" + xmlns:asd="http://www.axis.com/vapix/ws/shockdetection" + xmlns:aweb="http://www.axis.com/vapix/ws/webserver" + xmlns:tan1="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding" + xmlns:tan2="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding" + xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" + xmlns:tds="http://www.onvif.org/ver10/device/wsdl" + xmlns:tev1="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding" + xmlns:tev2="http://www.onvif.org/ver10/events/wsdl/EventBinding" + xmlns:tev3="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding" + xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" + xmlns:tev4="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding" + xmlns:tev="http://www.onvif.org/ver10/events/wsdl" + xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" + xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl" + xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" + xmlns:tr2="http://www.onvif.org/ver20/media/wsdl" + xmlns:trc="http://www.onvif.org/ver10/recording/wsdl" + xmlns:trp="http://www.onvif.org/ver10/replay/wsdl" + xmlns:trt="http://www.onvif.org/ver10/media/wsdl" + xmlns:tse="http://www.onvif.org/ver10/search/wsdl" + xmlns:ter="http://www.onvif.org/ver10/error" + xmlns:tns1="http://www.onvif.org/ver10/topics" + xmlns:tnsaxis="http://www.axis.com/2009/event/topics"> + + + rtsp://192.168.1.136/onvif-media/record/play.amp?onvifreplayid=SD_DISK_20200422_132655_67086B52&onvifreplayext=1&streamtype=unicast&session_timeout=30 + + \ No newline at end of file From 21e9c8ba3fc8961e14330f47f8e014298824abb7 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 25 Nov 2024 17:42:00 -0300 Subject: [PATCH 12/24] mix format --- lib/recording/create_recondig_job.ex | 3 +-- lib/recording/create_recording.ex | 1 + lib/recording/get_recording_jobs.ex | 3 +-- lib/recording/get_recordings.ex | 28 +++++++++++------------ lib/recording/get_replay_configuration.ex | 3 +-- lib/recording/recording_jobs.ex | 23 +++++++++++++------ lib/recording/recordings.ex | 25 ++++++++++++-------- lib/replay/get_replay_uri.ex | 1 - lib/replay/get_service_capabilities.ex | 3 ++- 9 files changed, 51 insertions(+), 39 deletions(-) diff --git a/lib/recording/create_recondig_job.ex b/lib/recording/create_recondig_job.ex index 1a36a4b..a13928f 100644 --- a/lib/recording/create_recondig_job.ex +++ b/lib/recording/create_recondig_job.ex @@ -21,7 +21,6 @@ defmodule Onvif.Recording.CreateRecordingJob do ]) end - def response(xml_response_body) do parsed_result = xml_response_body @@ -35,7 +34,7 @@ defmodule Onvif.Recording.CreateRecordingJob do mode: ~x"//trc:JobConfiguration/tt:Mode/text()"so, priority: ~x"//trc:JobConfiguration/tt:Priority/text()"so ) + {:ok, parsed_result} end - end diff --git a/lib/recording/create_recording.ex b/lib/recording/create_recording.ex index 8d5051c..69e0ccc 100644 --- a/lib/recording/create_recording.ex +++ b/lib/recording/create_recording.ex @@ -36,6 +36,7 @@ defmodule Onvif.Recording.CreateRecording do |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl") ) + {:ok, response_uri} end end diff --git a/lib/recording/get_recording_jobs.ex b/lib/recording/get_recording_jobs.ex index b701d29..6cbc60e 100644 --- a/lib/recording/get_recording_jobs.ex +++ b/lib/recording/get_recording_jobs.ex @@ -1,4 +1,3 @@ - defmodule Onvif.Recording.GetRecordingJobs do import SweetXml import XmlBuilder @@ -17,6 +16,6 @@ defmodule Onvif.Recording.GetRecordingJobs do end def response(xml_response_body) do - IO.puts xml_response_body + IO.puts(xml_response_body) end end diff --git a/lib/recording/get_recordings.ex b/lib/recording/get_recordings.ex index ac07a59..35d69bb 100644 --- a/lib/recording/get_recordings.ex +++ b/lib/recording/get_recordings.ex @@ -1,4 +1,3 @@ - defmodule Onvif.Recording.GetRecordings do import SweetXml import XmlBuilder @@ -19,7 +18,6 @@ defmodule Onvif.Recording.GetRecordings do end def response(xml_response_body) do - response = xml_response_body |> parse(namespace_conformant: true, quiet: true) @@ -29,18 +27,18 @@ defmodule Onvif.Recording.GetRecordings do |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl") |> add_namespace("tt", "http://www.onvif.org/ver10/schema") ) - |> Enum.map(&Recordings.parse/1) - |> Enum.reduce([], fn raw_recording, acc -> - case Recordings.to_struct(raw_recording) do - {:ok, recording} -> - [recording | acc] - - {:error, changeset} -> - Logger.error("Discarding invalid recording: #{inspect(changeset)}") - acc - end - end) - {:ok, response} - + |> Enum.map(&Recordings.parse/1) + |> Enum.reduce([], fn raw_recording, acc -> + case Recordings.to_struct(raw_recording) do + {:ok, recording} -> + [recording | acc] + + {:error, changeset} -> + Logger.error("Discarding invalid recording: #{inspect(changeset)}") + acc + end + end) + + {:ok, response} end end diff --git a/lib/recording/get_replay_configuration.ex b/lib/recording/get_replay_configuration.ex index 97c4aa0..78a77f1 100644 --- a/lib/recording/get_replay_configuration.ex +++ b/lib/recording/get_replay_configuration.ex @@ -1,4 +1,3 @@ - defmodule Onvif.Recording.GetServiceCapabilities do import SweetXml import XmlBuilder @@ -17,6 +16,6 @@ defmodule Onvif.Recording.GetServiceCapabilities do end def response(xml_response_body) do - IO.puts xml_response_body + IO.puts(xml_response_body) end end diff --git a/lib/recording/recording_jobs.ex b/lib/recording/recording_jobs.ex index c2d9938..df11b74 100644 --- a/lib/recording/recording_jobs.ex +++ b/lib/recording/recording_jobs.ex @@ -16,18 +16,22 @@ defmodule Onvif.Recording.RecordingJobs do embeds_many :job_item, JobItem, primary_key: false, on_replace: :delete do @derive Jason.Encoder field(:job_token, :string) + embeds_one :job_configuration, JobConfiguration, primary_key: false, on_replace: :update do @derive Jason.Encoder field(:recording_token, :string) field(:mode, :string) field(:priority, :string) + embeds_one :source, Source, primary_key: false, on_replace: :update do @derive Jason.Encoder field(:auto_create_receiver, :boolean) + embeds_one :source_token, SourceToken, primary_key: false, on_replace: :update do @derive Jason.Encoder field(:token, :string) end + embeds_many :track, Track, primary_key: false, on_replace: :delete do @derive Jason.Encoder field(:source_tag, :string) @@ -40,62 +44,68 @@ defmodule Onvif.Recording.RecordingJobs do def parse(nil), do: nil def parse([]), do: nil + def parse(doc) do xmap( doc, - job_item: ~x"./tt:JobItem"elo |> transform_by(&parse_job_item/1), + job_item: ~x"./tt:JobItem"elo |> transform_by(&parse_job_item/1) ) end def parse_job_item([]), do: nil def parse_job_item(nil), do: nil + def parse_job_item(doc) do xmap( doc, job_token: ~x"./tt:JobToken/text()"so, - job_configuration: ~x"./tt:JobConfiguration"eo |> transform_by(&parse_job_configuration/1), + job_configuration: ~x"./tt:JobConfiguration"eo |> transform_by(&parse_job_configuration/1) ) end def parse_job_configuration([]), do: nil def parse_job_configuration(nil), do: nil + def parse_job_configuration(doc) do xmap( doc, recording_token: ~x"./tt:RecordingToken/text()"so, mode: ~x"./tt:Mode/text()"so, priority: ~x"./tt:Priority/text()"so, - source: ~x"./tt:Source"eo |> transform_by(&parse_source/1), + source: ~x"./tt:Source"eo |> transform_by(&parse_source/1) ) end def parse_source([]), do: nil def parse_source(nil), do: nil + def parse_source(doc) do xmap( doc, source_token: ~x"./tt:SourceToken"eo |> transform_by(&parse_source_token/1), auto_create_receiver: ~x"./tt:AutoCreateReceiver/text()"so, - track: ~x"./tt:Track"elo |> transform_by(&parse_track/1), + track: ~x"./tt:Track"elo |> transform_by(&parse_track/1) ) end def parse_source_token([]), do: nil def parse_source_token(nil), do: nil + def parse_source_token(doc) do xmap( doc, - token: ~x"./tt:Token/text()"so, + token: ~x"./tt:Token/text()"so ) end def parse_track([]), do: nil def parse_track(nil), do: nil + def parse_track(doc) do xmap( doc, source_tag: ~x"./tt:SourceTag/text()"so, - destination: ~x"./tt:Destination/text()"so, + destination: ~x"./tt:Destination/text()"so ) end @@ -155,5 +165,4 @@ defmodule Onvif.Recording.RecordingJobs do schema |> cast(params, [:source_tag, :destination]) end - end diff --git a/lib/recording/recordings.ex b/lib/recording/recordings.ex index c56717a..002a4c2 100644 --- a/lib/recording/recordings.ex +++ b/lib/recording/recordings.ex @@ -19,6 +19,7 @@ defmodule Onvif.Recording.Recordings do @derive Jason.Encoder field(:content, :string) field(:maximum_retention_time, :string) + embeds_one(:source, Source, primary_key: false, on_replace: :update) do @derive Jason.Encoder field(:source_id, :string) @@ -34,6 +35,7 @@ defmodule Onvif.Recording.Recordings do embeds_many(:track, Track, primary_key: false, on_replace: :delete) do @derive Jason.Encoder field(:track_token, :string) + embeds_one(:configuration, Configuration, primary_key: false, on_replace: :update) do @derive Jason.Encoder field(:track_type, :string) @@ -45,17 +47,19 @@ defmodule Onvif.Recording.Recordings do def parse(nil), do: nil def parse([]), do: nil + def parse(doc) do xmap( doc, recording_token: ~x"./tt:RecordingToken/text()"so, configuration: ~x"./tt:Configuration"eo |> transform_by(&parse_configuration/1), - tracks: ~x"./tt:Tracks"eo |> transform_by(&parse_tracks/1), + tracks: ~x"./tt:Tracks"eo |> transform_by(&parse_tracks/1) ) end def parse_configuration([]), do: nil def parse_configuration(nil), do: nil + def parse_configuration(doc) do xmap( doc, @@ -67,6 +71,7 @@ defmodule Onvif.Recording.Recordings do def parse_source([]), do: nil def parse_source(nil), do: nil + def parse_source(doc) do xmap( doc, @@ -80,27 +85,30 @@ defmodule Onvif.Recording.Recordings do def parse_tracks([]), do: nil def parse_tracks(nil), do: nil + def parse_tracks(doc) do - xmap( + xmap( doc, track: ~x"./tt:Track"elo |> transform_by(&parse_track/1) - ) + ) end def parse_track([]), do: nil def parse_track(nil), do: nil + def parse_track(docs) do Enum.map(docs, fn doc -> - xmap( - doc, - track_token: ~x"./tt:TrackToken/text()"so, - configuration: ~x"./tt:Configuration"eo |> transform_by(&parse_track_configuration/1) - ) + xmap( + doc, + track_token: ~x"./tt:TrackToken/text()"so, + configuration: ~x"./tt:Configuration"eo |> transform_by(&parse_track_configuration/1) + ) end) end def parse_track_configuration([]), do: nil def parse_track_configuration(nil), do: nil + def parse_track_configuration(doc) do xmap( doc, @@ -157,5 +165,4 @@ defmodule Onvif.Recording.Recordings do def track_configuration_changeset(module, attrs) do cast(module, attrs, [:track_type, :description]) end - end diff --git a/lib/replay/get_replay_uri.ex b/lib/replay/get_replay_uri.ex index 603db03..bb5c0f9 100644 --- a/lib/replay/get_replay_uri.ex +++ b/lib/replay/get_replay_uri.ex @@ -37,5 +37,4 @@ defmodule Onvif.Replay.GetReplayUri do {:ok, parsed_result} end - end diff --git a/lib/replay/get_service_capabilities.ex b/lib/replay/get_service_capabilities.ex index b2d32e8..98ca6d9 100644 --- a/lib/replay/get_service_capabilities.ex +++ b/lib/replay/get_service_capabilities.ex @@ -31,6 +31,7 @@ defmodule Onvif.Replay.GetServiceCapabilities do session_timeout_range: ~x"//@SessionTimeoutRange"so, rtsp_web_socket_uri: ~x"//@RTSPWebSocketUri"so ) - {:ok, Map.merge(%Onvif.Replay.ServiceCapabilities{}, parsed_result)} + + {:ok, Map.merge(%Onvif.Replay.ServiceCapabilities{}, parsed_result)} end end From b58a256f3520aa3de283ba4a5593abd49dbcf65b Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 25 Nov 2024 17:52:52 -0300 Subject: [PATCH 13/24] test for get_replay_uri --- lib/replay/get_replay_uri.ex | 2 -- test/replay/get_replay_uri_test.exs | 37 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/replay/get_replay_uri_test.exs diff --git a/lib/replay/get_replay_uri.ex b/lib/replay/get_replay_uri.ex index bb5c0f9..1f57bcd 100644 --- a/lib/replay/get_replay_uri.ex +++ b/lib/replay/get_replay_uri.ex @@ -3,8 +3,6 @@ defmodule Onvif.Replay.GetReplayUri do import XmlBuilder require Logger - alias Onvif.Device - def soap_action, do: "http://www.onvif.org/ver10/replay/wsdl/GetReplayUri" def request(device, args) do diff --git a/test/replay/get_replay_uri_test.exs b/test/replay/get_replay_uri_test.exs new file mode 100644 index 0000000..e6a67d8 --- /dev/null +++ b/test/replay/get_replay_uri_test.exs @@ -0,0 +1,37 @@ +defmodule Onvif.Replay.GetReplayUriTest do + use ExUnit.Case, async: true + + @moduletag capture_log: true + + describe "GetReplayUri/2" do + test "get a replay uri" do + xml_response = File.read!("test/replay/fixtures/get_replay_uri__success.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response} = + Onvif.Replay.GetReplayUri.request(device, ["SD_DISK_20200422_132655_67086B52"]) + + assert response == + "rtsp://192.168.1.136/onvif-media/record/play.amp?onvifreplayid=SD_DISK_20200422_132655_67086B52&onvifreplayext=1&streamtype=unicast&session_timeout=30" + end + + test "get no uri" do + xml_response = File.read!("test/replay/fixtures/get_replay_uri__recording_not_found.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response} = Onvif.Replay.GetReplayUri.request(device, ["SD_DISK_000"]) + + assert response == "" + end + end +end From 3ea14e138a4a8153d1ea710485c85aaa2050e87c Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 25 Nov 2024 18:42:26 -0300 Subject: [PATCH 14/24] GetRecordingJobs --- lib/recording/get_recording_jobs.ex | 25 ++++++- lib/recording/recording_jobs.ex | 80 +++++++++------------- test/recording/get_recording_jobs_test.exs | 21 ++++++ 3 files changed, 76 insertions(+), 50 deletions(-) create mode 100644 test/recording/get_recording_jobs_test.exs diff --git a/lib/recording/get_recording_jobs.ex b/lib/recording/get_recording_jobs.ex index 6cbc60e..9530be1 100644 --- a/lib/recording/get_recording_jobs.ex +++ b/lib/recording/get_recording_jobs.ex @@ -3,6 +3,8 @@ defmodule Onvif.Recording.GetRecordingJobs do import XmlBuilder require Logger + alias Onvif.Recording.RecordingJobs + def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/GetRecordingJobs" def request(device) do @@ -16,6 +18,27 @@ defmodule Onvif.Recording.GetRecordingJobs do end def response(xml_response_body) do - IO.puts(xml_response_body) + response = + xml_response_body + |> parse(namespace_conformant: true, quiet: true) + |> xpath( + ~x"//s:Envelope/s:Body/trc:GetRecordingJobsResponse/trc:JobItem"el + |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") + |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl") + |> add_namespace("tt", "http://www.onvif.org/ver10/schema") + ) + |> Enum.map(&RecordingJobs.parse/1) + |> Enum.reduce([], fn raw_job, acc -> + case RecordingJobs.to_struct(raw_job) do + {:ok, job} -> + [job | acc] + + {:error, changeset} -> + Logger.error("Discarding invalid recording: #{inspect(changeset)}") + acc + end + end) + + {:ok, response} end end diff --git a/lib/recording/recording_jobs.ex b/lib/recording/recording_jobs.ex index df11b74..79e92e1 100644 --- a/lib/recording/recording_jobs.ex +++ b/lib/recording/recording_jobs.ex @@ -9,34 +9,31 @@ defmodule Onvif.Recording.RecordingJobs do @primary_key false @derive Jason.Encoder - @required [] + @required [:job_token] @optional [] embedded_schema do - embeds_many :job_item, JobItem, primary_key: false, on_replace: :delete do + field(:job_token, :string) + + embeds_one :job_configuration, JobConfiguration, primary_key: false, on_replace: :update do @derive Jason.Encoder - field(:job_token, :string) + field(:recording_token, :string) + field(:mode, :string) + field(:priority, :string) - embeds_one :job_configuration, JobConfiguration, primary_key: false, on_replace: :update do + embeds_one :source, Source, primary_key: false, on_replace: :update do @derive Jason.Encoder - field(:recording_token, :string) - field(:mode, :string) - field(:priority, :string) + field(:auto_create_receiver, :boolean) - embeds_one :source, Source, primary_key: false, on_replace: :update do + embeds_one :source_token, SourceToken, primary_key: false, on_replace: :update do @derive Jason.Encoder - field(:auto_create_receiver, :boolean) - - embeds_one :source_token, SourceToken, primary_key: false, on_replace: :update do - @derive Jason.Encoder - field(:token, :string) - end - - embeds_many :track, Track, primary_key: false, on_replace: :delete do - @derive Jason.Encoder - field(:source_tag, :string) - field(:destination, :string) - end + field(:token, :string) + end + + embeds_many :tracks, Tracks, primary_key: false, on_replace: :delete do + @derive Jason.Encoder + field(:source_tag, :string) + field(:destination, :string) end end end @@ -46,16 +43,6 @@ defmodule Onvif.Recording.RecordingJobs do def parse([]), do: nil def parse(doc) do - xmap( - doc, - job_item: ~x"./tt:JobItem"elo |> transform_by(&parse_job_item/1) - ) - end - - def parse_job_item([]), do: nil - def parse_job_item(nil), do: nil - - def parse_job_item(doc) do xmap( doc, job_token: ~x"./tt:JobToken/text()"so, @@ -84,7 +71,7 @@ defmodule Onvif.Recording.RecordingJobs do doc, source_token: ~x"./tt:SourceToken"eo |> transform_by(&parse_source_token/1), auto_create_receiver: ~x"./tt:AutoCreateReceiver/text()"so, - track: ~x"./tt:Track"elo |> transform_by(&parse_track/1) + tracks: ~x"./tt:Tracks"elo |> transform_by(&parse_track/1) ) end @@ -101,12 +88,14 @@ defmodule Onvif.Recording.RecordingJobs do def parse_track([]), do: nil def parse_track(nil), do: nil - def parse_track(doc) do - xmap( - doc, - source_tag: ~x"./tt:SourceTag/text()"so, - destination: ~x"./tt:Destination/text()"so - ) + def parse_track(docs) do + Enum.map(docs, fn doc -> + xmap( + doc, + source_tag: ~x"./tt:SourceTag/text()"so, + destination: ~x"./tt:Destination/text()"so + ) + end) end def to_struct(parsed) do @@ -127,17 +116,10 @@ defmodule Onvif.Recording.RecordingJobs do Jason.encode(schema) end - def changeset(schema, params \\ %{}) do - schema - |> cast(params, []) - |> validate_required([]) - |> cast_embed(:job_item, with: &job_item_changeset/2) - end - - def job_item_changeset(schema, params) do - schema - |> cast(params, [:job_token]) - |> validate_required([:job_token]) + def changeset(module, attrs) do + module + |> cast(attrs, @required ++ @optional) + |> validate_required(@required) |> cast_embed(:job_configuration, with: &job_configuration_changeset/2) end @@ -152,7 +134,7 @@ defmodule Onvif.Recording.RecordingJobs do schema |> cast(params, [:auto_create_receiver]) |> cast_embed(:source_token, with: &source_token_changeset/2) - |> cast_embed(:track, with: &track_changeset/2) + |> cast_embed(:tracks, with: &track_changeset/2) end def source_token_changeset(schema, params) do diff --git a/test/recording/get_recording_jobs_test.exs b/test/recording/get_recording_jobs_test.exs new file mode 100644 index 0000000..643c49e --- /dev/null +++ b/test/recording/get_recording_jobs_test.exs @@ -0,0 +1,21 @@ +defmodule Onvif.Recording.GetRecordingJobsTest do + use ExUnit.Case, async: true + + @moduletag capture_log: true + + describe "GetRecordingJobs/1" do + test "get recording jobs" do + xml_response = File.read!("test/recording/fixture/get_recording_jobs_success.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response} = Onvif.Recording.GetRecordingJobs.request(device) + + assert hd(response).job_token == "SD_DISK_20241120_211729_9C896594" + end + end +end From 4d67a05abde0871c953ff1f1244c150bff21a5c9 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 25 Nov 2024 18:44:36 -0300 Subject: [PATCH 15/24] GetRecordingJobs part 2 --- test/recording/get_recording_jobs_test.exs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/recording/get_recording_jobs_test.exs b/test/recording/get_recording_jobs_test.exs index 643c49e..469eef5 100644 --- a/test/recording/get_recording_jobs_test.exs +++ b/test/recording/get_recording_jobs_test.exs @@ -17,5 +17,19 @@ defmodule Onvif.Recording.GetRecordingJobsTest do assert hd(response).job_token == "SD_DISK_20241120_211729_9C896594" end + + test "empty recording job" do + xml_response = File.read!("test/recording/fixture/get_recording_jobs_empty.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response} = Onvif.Recording.GetRecordingJobs.request(device) + + assert response == [] + end end end From d00ba5cee8704b9708194f63a3a4756be01ab025 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 25 Nov 2024 19:09:50 -0300 Subject: [PATCH 16/24] GetRecordingsTest --- test/recording/get_recordings_test.exs | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/recording/get_recordings_test.exs diff --git a/test/recording/get_recordings_test.exs b/test/recording/get_recordings_test.exs new file mode 100644 index 0000000..78dae0c --- /dev/null +++ b/test/recording/get_recordings_test.exs @@ -0,0 +1,27 @@ +defmodule Onvif.Recording.GetRecordingsTest do + use ExUnit.Case, async: true + + @moduletag capture_log: true + + describe "GetRecordings/1" do + test "successfully get a list of recordings" do + xml_response = File.read!("test/recording/fixture/get_recordings_success.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response} = Onvif.Recording.GetRecordings.request(device) + + assert Enum.map(response, fn r -> + r.recording_token + end) == [ + "SD_DISK_20200422_132655_67086B52", + "SD_DISK_20200422_132613_45A883F5", + "SD_DISK_20200422_123501_A2388AB3" + ] + end + end +end From 5f3ce50c6f02512ea1ba45e24855b3856c31feeb Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 25 Nov 2024 20:57:45 -0300 Subject: [PATCH 17/24] Replay.GetServiceCapabilities --- lib/recording/get_replay_configuration.ex | 21 ------- lib/replay/get_service_capabilities.ex | 16 +++-- lib/replay/service_capabilities.ex | 45 ++++++++++++- .../get_service_capabilities_success.xml | 63 +++++++++++++++++++ test/replay/get_service_capabilities_test.exs | 31 +++++++++ 5 files changed, 148 insertions(+), 28 deletions(-) delete mode 100644 lib/recording/get_replay_configuration.ex create mode 100644 test/replay/fixtures/get_service_capabilities_success.xml create mode 100644 test/replay/get_service_capabilities_test.exs diff --git a/lib/recording/get_replay_configuration.ex b/lib/recording/get_replay_configuration.ex deleted file mode 100644 index 78a77f1..0000000 --- a/lib/recording/get_replay_configuration.ex +++ /dev/null @@ -1,21 +0,0 @@ -defmodule Onvif.Recording.GetServiceCapabilities do - import SweetXml - import XmlBuilder - require Logger - - def soap_action, do: "http://www.onvif.org/ver10/replay/wsdl/GetServiceCapabilities" - - def request(device) do - Onvif.Recording.request(device, __MODULE__) - end - - def request_body() do - element(:"s:Body", [ - element(:"trp:GetServiceCapabilities") - ]) - end - - def response(xml_response_body) do - IO.puts(xml_response_body) - end -end diff --git a/lib/replay/get_service_capabilities.ex b/lib/replay/get_service_capabilities.ex index 98ca6d9..b1ff809 100644 --- a/lib/replay/get_service_capabilities.ex +++ b/lib/replay/get_service_capabilities.ex @@ -3,8 +3,6 @@ defmodule Onvif.Replay.GetServiceCapabilities do import XmlBuilder require Logger - alias Onvif.Device - def soap_action, do: "http://www.onvif.org/ver10/replay/wsdl/GetServiceCapabilities" def request(device) do @@ -25,13 +23,21 @@ defmodule Onvif.Replay.GetServiceCapabilities do doc, ~x"//s:Envelope/s:Body/trp:GetServiceCapabilitiesResponse/trp:Capabilities" |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") - |> add_namespace("trp", "http://www.onvif.org/ver10/replay/wsdl"), + |> add_namespace("trp", "http://www.onvif.org/ver10/replay/wsdl") + |> add_namespace("tt", "http://www.onvif.org/ver10/schema"), rtp_rtsp_tcp: ~x"//@RTP_RTSP_TCP"so, reverse_playback: ~x"//@ReversePlayback"so, session_timeout_range: ~x"//@SessionTimeoutRange"so, - rtsp_web_socket_uri: ~x"//@RTSPWebSocketUri"so + rtsp_web_socket_uri: ~x"//@RTSPWebSocketUri"so, + receive_source: ~x"//tt:CapabilitiesExtension/RecordingCapabilities/@ReceiverSource"so, + media_profile_source: + ~x"//tt:CapabilitiesExtension/RecordingCapabilities/@MediaProfileSource"so, + dynamic_recordings: + ~x"//tt:CapabilitiesExtension/RecordingCapabilities/@DynamicRecordings"so, + dynamic_tracks: ~x"//tt:CapabilitiesExtension/RecordingCapabilities/@DynamicTracks"so, + max_string_length: ~x"//tt:CapabilitiesExtension/RecordingCapabilities/@MaxStringLength"so ) - {:ok, Map.merge(%Onvif.Replay.ServiceCapabilities{}, parsed_result)} + {:ok, Onvif.Replay.ServiceCapabilities.from_parsed(parsed_result)} end end diff --git a/lib/replay/service_capabilities.ex b/lib/replay/service_capabilities.ex index 2c6f154..78fa78d 100644 --- a/lib/replay/service_capabilities.ex +++ b/lib/replay/service_capabilities.ex @@ -1,4 +1,45 @@ defmodule Onvif.Replay.ServiceCapabilities do - @derive Jason.Encoder - defstruct [:rtp_rtsp_tcp, :reverse_playback, :session_timeout_range, :rtsp_web_socket_uri] + @fields [ + rtp_rtsp_tcp: false, + reverse_playback: false, + session_timeout_range: "0", + rtsp_web_socket_uri: false, + receive_source: false, + media_profile_source: false, + dynamic_recordings: false, + dynamic_tracks: false, + max_string_length: "0" + ] + + defstruct Keyword.keys(@fields) + + @type t() :: %__MODULE__{ + rtp_rtsp_tcp: boolean(), + reverse_playback: boolean(), + session_timeout_range: String.t(), + rtsp_web_socket_uri: boolean(), + receive_source: boolean(), + media_profile_source: boolean(), + dynamic_recordings: boolean(), + dynamic_tracks: boolean(), + max_string_length: integer() + } + + @doc """ + Converts a parsed map into a %Onvif.Replay.ServiceCapabilities{} struct with validated types. + """ + def from_parsed(parsed) do + # Ensure only valid keys and convert values + converted = + @fields + |> Enum.map(fn {key, _default} -> {key, convert_value(key, Map.get(parsed, key))} end) + |> Enum.into(%{}) + + struct(__MODULE__, converted) + end + + defp convert_value(_key, "true"), do: true + defp convert_value(_key, "false"), do: false + defp convert_value(_key, nil), do: nil + defp convert_value(_key, value), do: value end diff --git a/test/replay/fixtures/get_service_capabilities_success.xml b/test/replay/fixtures/get_service_capabilities_success.xml new file mode 100644 index 0000000..bc1cd74 --- /dev/null +++ b/test/replay/fixtures/get_service_capabilities_success.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/replay/get_service_capabilities_test.exs b/test/replay/get_service_capabilities_test.exs new file mode 100644 index 0000000..002752f --- /dev/null +++ b/test/replay/get_service_capabilities_test.exs @@ -0,0 +1,31 @@ +defmodule Onvif.Replay.GetServiceCapabilitiesTest do + use ExUnit.Case, async: true + + @moduletag capture_log: true + + describe "GetServiceCapabilities/1" do + test "get service capabilities" do + xml_response = File.read!("test/replay/fixtures/get_service_capabilities_success.xml") + + device = Onvif.Factory.device() + + Mimic.expect(Tesla, :request, fn _client, _opts -> + {:ok, %{status: 200, body: xml_response}} + end) + + {:ok, response} = Onvif.Replay.GetServiceCapabilities.request(device) + + assert response == %Onvif.Replay.ServiceCapabilities{ + dynamic_recordings: true, + dynamic_tracks: false, + max_string_length: "4096", + media_profile_source: true, + receive_source: false, + reverse_playback: false, + rtp_rtsp_tcp: true, + rtsp_web_socket_uri: "", + session_timeout_range: "0 4294967295" + } + end + end +end From 4cbf64bdcc4c40482c372d3a2d506dfdee50f921 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Tue, 26 Nov 2024 12:21:25 -0300 Subject: [PATCH 18/24] fix typo --- lib/recording/{create_recondig_job.ex => create_recordig_job.ex} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/recording/{create_recondig_job.ex => create_recordig_job.ex} (100%) diff --git a/lib/recording/create_recondig_job.ex b/lib/recording/create_recordig_job.ex similarity index 100% rename from lib/recording/create_recondig_job.ex rename to lib/recording/create_recordig_job.ex From a4bd3b313ca3e0f5ddc1a81564eeffea0c1f3b4e Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Tue, 26 Nov 2024 12:24:03 -0300 Subject: [PATCH 19/24] Update lib/replay.ex Co-authored-by: Yuri Oliveira --- lib/replay.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/replay.ex b/lib/replay.ex index 8ba725d..7f264e6 100644 --- a/lib/replay.ex +++ b/lib/replay.ex @@ -1,7 +1,7 @@ defmodule Onvif.Replay do @moduledoc """ - Interface for making requests to the Onvif recording service - http://www.onvif.org/onvif/ver10/recording.wsdl + Interface for making requests to the Onvif replay service + https://www.onvif.org/ver10/replay.wsdl """ require Logger alias Onvif.Device From ec5082626d3c110c6a44eb9e657effe49e709ea3 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Tue, 26 Nov 2024 15:58:38 -0300 Subject: [PATCH 20/24] fix typo in filename --- lib/recording/{create_recordig_job.ex => create_recording_job.ex} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/recording/{create_recordig_job.ex => create_recording_job.ex} (100%) diff --git a/lib/recording/create_recordig_job.ex b/lib/recording/create_recording_job.ex similarity index 100% rename from lib/recording/create_recordig_job.ex rename to lib/recording/create_recording_job.ex From b04ef6be76415dce3547945660730be6eda95505 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Tue, 26 Nov 2024 15:59:38 -0300 Subject: [PATCH 21/24] Update lib/recording/recording_jobs.ex Co-authored-by: Austin Hammer --- lib/recording/recording_jobs.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/recording/recording_jobs.ex b/lib/recording/recording_jobs.ex index 79e92e1..01e316f 100644 --- a/lib/recording/recording_jobs.ex +++ b/lib/recording/recording_jobs.ex @@ -1,4 +1,4 @@ -defmodule Onvif.Recording.RecordingJobs do +defmodule Onvif.Recording.RecordingJob do @moduledoc """ Recordings. """ From 21b9d7aa29ff92e19f73fb4f7ace4a0e9a31f059 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Tue, 26 Nov 2024 16:13:16 -0300 Subject: [PATCH 22/24] address review --- lib/recording/get_recording_jobs.ex | 6 +++--- lib/recording/{recording_jobs.ex => recording_job.ex} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename lib/recording/{recording_jobs.ex => recording_job.ex} (98%) diff --git a/lib/recording/get_recording_jobs.ex b/lib/recording/get_recording_jobs.ex index 9530be1..d271e3b 100644 --- a/lib/recording/get_recording_jobs.ex +++ b/lib/recording/get_recording_jobs.ex @@ -3,7 +3,7 @@ defmodule Onvif.Recording.GetRecordingJobs do import XmlBuilder require Logger - alias Onvif.Recording.RecordingJobs + alias Onvif.Recording.RecordingJob def soap_action, do: "http://www.onvif.org/ver10/recording/wsdl/GetRecordingJobs" @@ -27,9 +27,9 @@ defmodule Onvif.Recording.GetRecordingJobs do |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl") |> add_namespace("tt", "http://www.onvif.org/ver10/schema") ) - |> Enum.map(&RecordingJobs.parse/1) + |> Enum.map(&RecordingJob.parse/1) |> Enum.reduce([], fn raw_job, acc -> - case RecordingJobs.to_struct(raw_job) do + case RecordingJob.to_struct(raw_job) do {:ok, job} -> [job | acc] diff --git a/lib/recording/recording_jobs.ex b/lib/recording/recording_job.ex similarity index 98% rename from lib/recording/recording_jobs.ex rename to lib/recording/recording_job.ex index 01e316f..2e86530 100644 --- a/lib/recording/recording_jobs.ex +++ b/lib/recording/recording_job.ex @@ -104,7 +104,7 @@ defmodule Onvif.Recording.RecordingJob do |> apply_action(:validate) end - @spec to_json(%Onvif.Recording.RecordingJobs{}) :: + @spec to_json(%Onvif.Recording.RecordingJob{}) :: {:error, %{ :__exception__ => any, From 5d375ee644bf8f582dbbfcd194374cb2c6cfa1d4 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Tue, 26 Nov 2024 16:31:45 -0300 Subject: [PATCH 23/24] typo lib/recording/create_recording.ex Co-authored-by: Logeshwaran <9105503+waranlogesh@users.noreply.github.com> --- lib/recording/create_recording.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/recording/create_recording.ex b/lib/recording/create_recording.ex index 69e0ccc..7c6199c 100644 --- a/lib/recording/create_recording.ex +++ b/lib/recording/create_recording.ex @@ -28,7 +28,7 @@ defmodule Onvif.Recording.CreateRecording do end def response(xml_response_body) do - response_uri = + recording_token = xml_response_body |> parse(namespace_conformant: true, quiet: true) |> xpath( @@ -37,6 +37,6 @@ defmodule Onvif.Recording.CreateRecording do |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl") ) - {:ok, response_uri} + {:ok, recording_token} end end From 54fdea69487b83abad09014693a73df982614236 Mon Sep 17 00:00:00 2001 From: Paolo Oliveira Date: Mon, 2 Dec 2024 10:59:39 -0300 Subject: [PATCH 24/24] fixes from thanksgiving --- lib/recording/create_recording.ex | 45 ++++++++++++++++---- lib/recording/create_recording_job.ex | 10 ++--- lib/recording/recordings.ex | 2 +- test/recording/create_recording_job_test.exs | 2 +- test/recording/create_recording_test.exs | 14 +++--- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/lib/recording/create_recording.ex b/lib/recording/create_recording.ex index 7c6199c..2d1a26f 100644 --- a/lib/recording/create_recording.ex +++ b/lib/recording/create_recording.ex @@ -9,24 +9,53 @@ defmodule Onvif.Recording.CreateRecording do Onvif.Recording.request(device, args, __MODULE__) end - def request_body(%{name: name, content: content, max_retention: max_retention}) do + def request_body(config: %Onvif.Recording.Recordings.Configuration{} = config) do element(:"s:Body", [ element(:"trc:CreateRecording", [ element(:"trc:RecordingConfiguration", [ element(:"tt:Source", [ - element(:"tt:SourceId", ""), - element(:"tt:Name", name), - element(:"tt:Location", ""), - element(:"tt:Description", ""), - element(:"tt:Address", "") + gen_source_id(config.source.source_id), + gen_name(config.source.name), + gen_location(config.source.location), + gen_description(config.source.description), + gen_address(config.source.address) ]), - element(:"tt:Content", content), - element(:"tt:MaximumRetentionTime", max_retention) + gen_content(config.content), + gen_maximum_retention_time(config.maximum_retention_time) ]) ]) ]) end + def gen_source_id(nil), do: [] + def gen_source_id(""), do: [] + def gen_source_id(source_id), do: element(:"tt:SourceId", source_id) + + def gen_name(nil), do: [] + def gen_name(""), do: [] + def gen_name(name), do: element(:"tt:Name", name) + + def gen_location(nil), do: [] + def gen_location(""), do: [] + def gen_location(location), do: element(:"tt:Location", location) + + def gen_description(nil), do: [] + def gen_description(""), do: [] + def gen_description(description), do: element(:"tt:Description", description) + + def gen_address(nil), do: [] + def gen_address(""), do: [] + def gen_address(address), do: element(:"tt:Address", address) + + def gen_content(nil), do: [] + def gen_content(""), do: [] + def gen_content(content), do: element(:"tt:Content", content) + + def gen_maximum_retention_time(nil), do: [] + def gen_maximum_retention_time(""), do: [] + def gen_maximum_retention_time(maximum_retention_time), do: element(:"tt:MaximumRetentionTime", maximum_retention_time) + + def response(xml_response_body) do recording_token = xml_response_body diff --git a/lib/recording/create_recording_job.ex b/lib/recording/create_recording_job.ex index a13928f..da1ca52 100644 --- a/lib/recording/create_recording_job.ex +++ b/lib/recording/create_recording_job.ex @@ -26,15 +26,11 @@ defmodule Onvif.Recording.CreateRecordingJob do xml_response_body |> parse(namespace_conformant: true, quiet: true) |> xpath( - ~x"//s:Envelope/s:Body/trc:CreateRecordingJobResponse" + ~x"//s:Envelope/s:Body/trc:CreateRecordingJobResponse/trc:JobToken/text()"s0 |> add_namespace("s", "http://www.w3.org/2003/05/soap-envelope") - |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl"), - job_token: ~x"//trc:JobToken/text()"so, - recording_token: ~x"//trc:JobConfiguration/tt:RecordingToken/text()"so, - mode: ~x"//trc:JobConfiguration/tt:Mode/text()"so, - priority: ~x"//trc:JobConfiguration/tt:Priority/text()"so + |> add_namespace("trc", "http://www.onvif.org/ver10/recording/wsdl") + |> add_namespace("tt", "http://www.onvif.org/ver10/schema") ) - {:ok, parsed_result} end end diff --git a/lib/recording/recordings.ex b/lib/recording/recordings.ex index 002a4c2..7ef8eb9 100644 --- a/lib/recording/recordings.ex +++ b/lib/recording/recordings.ex @@ -1,6 +1,6 @@ defmodule Onvif.Recording.Recordings do @moduledoc """ - Recordings. + Onvif.Recording.Recordings schema. """ use Ecto.Schema diff --git a/test/recording/create_recording_job_test.exs b/test/recording/create_recording_job_test.exs index 13fc134..2437cb9 100644 --- a/test/recording/create_recording_job_test.exs +++ b/test/recording/create_recording_job_test.exs @@ -15,7 +15,7 @@ defmodule Onvif.Recording.CreateRecordingJobTest do {:ok, response} = Onvif.Recording.CreateRecordingJob.request(device, ["SD_DISK_20241120_211729_9C896594", "9", "Active"]) - assert response.job_token == "SD_DISK_20241120_211729_9C896594" + assert response == "SD_DISK_20241120_211729_9C896594" end end end diff --git a/test/recording/create_recording_test.exs b/test/recording/create_recording_test.exs index 8d09f2b..eff84bf 100644 --- a/test/recording/create_recording_test.exs +++ b/test/recording/create_recording_test.exs @@ -13,11 +13,15 @@ defmodule Onvif.Recording.CreateRecordingTest do {:ok, %{status: 200, body: xml_response}} end) - {:ok, response_uri} = Onvif.Recording.CreateRecording.request(device, %{ - name: "test", - content: "test", - max_retention: "PT1H" - }) + {:ok, response_uri} = Onvif.Recording.CreateRecording.request( + device, + config: %Onvif.Recording.Recordings.Configuration{ + content: "test", + maximum_retention_time: "PT1H", + source: %Onvif.Recording.Recordings.Configuration.Source{ + name: "test", + } + }) assert response_uri == "SD_DISK_20200422_123501_A2388AB3" end