Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Redesign onvif + media streaming management #471

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 59 additions & 5 deletions apps/ex_nvr/lib/ex_nvr/devices.ex
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ defmodule ExNVR.Devices do
end
end

def create_stream_profile(device, params) do
with {:ok, module} <- camera_module(Device.vendor(device)),
{:ok, {url, opts}} <- url_and_opts(device) do
module.create_stream_profile(url, params, opts)
end
end

def fetch_lpr_event(device, last_event_timestamp \\ nil) do
with {:ok, {url, opts}} <- url_and_opts(device) do
opts = opts ++ [last_event_timestamp: last_event_timestamp, timezone: device.timezone]
Expand Down Expand Up @@ -195,7 +202,7 @@ defmodule ExNVR.Devices do

{:error, reason} ->
Logger.error("""
Onvif: could not get date and time from '#{camera.url}'
Onvif: could not get device information from '#{camera.url}'
due to: #{inspect(reason)}
""")

Expand All @@ -213,7 +220,7 @@ defmodule ExNVR.Devices do

{:error, reason} ->
Logger.error("""
Onvif: could not get date and time from '#{camera.url}'
Onvif: could not get network interfaces from '#{camera.url}'
due to: #{inspect(reason)}
""")

Expand All @@ -225,14 +232,14 @@ defmodule ExNVR.Devices do

defp get_media_profiles(camera, opts) do
with {:ok, %{media: media}} <- ExNVR.Onvif.get_capabilities(camera.url, opts),
{:ok, media_profiles} <- ExNVR.Onvif.get_media_profiles(media.x_addr, opts) do
{:ok, media_profiles} <- get_media_profiles(camera, media, opts) do
media_profiles
|> Enum.map(fn profile ->
uri = ExNVR.Onvif.get_media_stream_uri!(media.x_addr, profile.token, opts)
uri = get_media_stream_uri!(camera, media, profile, opts)
Map.put(profile, :stream_uri, uri)
end)
|> Enum.map(fn profile ->
uri = ExNVR.Onvif.get_media_snapshot_uri!(media.x_addr, profile.token, opts)
uri = get_media_snapshot_uri!(camera, media, profile, opts)
Map.put(profile, :snapshot_uri, uri)
end)
|> then(&Map.put(camera, :media_profiles, &1))
Expand All @@ -241,6 +248,53 @@ defmodule ExNVR.Devices do
end
end

defp get_media_profiles(camera, media, opts) do
case camera.device_information.manufacturer do
"AXIS" ->
device = %Device{
vendor: camera.device_information.manufacturer,
url: camera.url,
credentials: %Device.Credentials{
username: opts[:username],
password: opts[:password]
}
}

stream_profiles(device)

_other ->
ExNVR.Onvif.get_media_profiles(media.x_addr, opts)
end
end

defp get_media_stream_uri!(camera, media, profile, opts) do
case camera.device_information.manufacturer do
"AXIS" ->
"rtsp://#{URI.parse(camera.url).host}/axis-media/media.amp?streamprofile=#{profile.name}"

_other ->
ExNVR.Onvif.get_media_stream_uri!(
media.x_addr,
profile.token,
opts
)
end
end

defp get_media_snapshot_uri!(camera, media, profile, opts) do
case camera.device_information.manufacturer do
"AXIS" ->
"http://#{URI.parse(camera.url).host}/axis-cgi/jpg/image.cgi"

_other ->
ExNVR.Onvif.get_media_snapshot_uri!(
media.x_addr,
profile.token,
opts
)
end
end

defp url_and_opts(%{url: nil}), do: {:error, :url_not_configured}

defp url_and_opts(device) do
Expand Down
18 changes: 16 additions & 2 deletions apps/ex_nvr/lib/ex_nvr/devices/cameras/http_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ defmodule ExNVR.Devices.Cameras.HttpClient do
"""
@callback stream_profiles(url(), camera_opts()) :: {:ok, [StreamProfile.t()]} | error()

@optional_callbacks fetch_lpr_event: 2, device_info: 2, stream_profiles: 2
@doc """
Create a new stream profile for camera.
"""
@callback create_stream_profile(url(), map(), camera_opts()) :: :ok | :error

@optional_callbacks fetch_lpr_event: 2,
device_info: 2,
stream_profiles: 2,
create_stream_profile: 3

defmacro __using__(_opts) do
quote location: :keep do
Expand All @@ -37,7 +45,13 @@ defmodule ExNVR.Devices.Cameras.HttpClient do
@impl true
def stream_profiles(_url, _opts), do: {:error, :not_implemented}

defoverridable fetch_lpr_event: 2, device_info: 2, stream_profiles: 2
@impl true
def create_stream_profile(_url, _params, _opts), do: {:error, :not_implemented}

defoverridable fetch_lpr_event: 2,
device_info: 2,
stream_profiles: 2,
create_stream_profile: 3
end
end
end
30 changes: 29 additions & 1 deletion apps/ex_nvr/lib/ex_nvr/devices/cameras/http_client/axis.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,34 @@ defmodule ExNVR.Devices.Cameras.HttpClient.Axis do
|> parse_http_response(&parse_stream_profile_response/1)
end

@impl true
def create_stream_profile(url, params, opts) do
url = url <> @stream_profiles

parameters =
params
|> Map.delete("name")
|> Enum.map(fn {key, value} -> "#{key}=#{value}" end)
|> Enum.join("&")

body = %{
"apiVersion" => "1.0",
"method" => "create",
"params" => %{
"streamProfile" => [
%{"name" => params["name"], "description" => params["name"], "parameters" => parameters}
]
}
}

url
|> HTTP.post(body, opts)
|> case do
{:ok, %{body: %{"data" => _data}}} -> :ok
_else -> :error
end
end

defp parse_response(body, timezone) do
SweetXml.xpath(
body,
Expand Down Expand Up @@ -131,7 +159,7 @@ defmodule ExNVR.Devices.Cameras.HttpClient.Axis do
width: width,
height: height,
gop: Map.get(params, "videokeyframeinterval", "32") |> String.to_integer(),
bitrate: 0,
bitrate: Map.get(params, "videobitrate", "0") |> String.to_integer(),
bitrate_mode: Map.get(params, "videobitratemode", "abr"),
smart_codec: false
}
Expand Down
29 changes: 27 additions & 2 deletions apps/ex_nvr/lib/ex_nvr/onvif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule ExNVR.Onvif do

import ExNVR.Onvif.Utils, only: [delete_namespaces: 1]

alias ExNVR.Devices.Cameras.StreamProfile
alias ExNVR.Onvif.{Discovery, Http}

@default_timeout 3_000
Expand Down Expand Up @@ -96,8 +97,32 @@ defmodule ExNVR.Onvif do
result =
Keyword.values(result.get_profiles_response)
|> Enum.map(fn media_profile ->
configurations = Map.drop(media_profile.configurations, [:analytics])
%{media_profile | configurations: configurations}
%StreamProfile{
id: media_profile.name,
enabled: true,
name: media_profile.name,
codec: get_in(media_profile, [:configurations, :video_encoder, :encoding]),
profile: get_in(media_profile, [:configurations, :video_encoder, :profile]),
width: get_in(media_profile, [:configurations, :video_encoder, :resolution, :width]),
height:
get_in(media_profile, [:configurations, :video_encoder, :resolution, :height]),
frame_rate:
get_in(media_profile, [
:configurations,
:video_encoder,
:rate_control,
:frame_rate_limit
]),
bitrate:
get_in(media_profile, [
:configurations,
:video_encoder,
:rate_control,
:bitrate_limit
]),
gop: get_in(media_profile, [:configurations, :video_encoder, :gov_length])
}
|> Map.put(:token, media_profile.token)
end)

{:ok, result}
Expand Down
Loading