From 269b300bfaa19aa4e3f4144c6410fd9921cb19b9 Mon Sep 17 00:00:00 2001 From: JDierkse Date: Wed, 28 Jul 2021 15:58:51 +0200 Subject: [PATCH] Add RTSP Camera Type --- API/WebAPI.cpp | 151 ++++++++++++++++++++++---------------- Libraries/cppcodec | 1 + Makefile.conf | 1 + Recorder/RTSPRecorder.cpp | 99 +++++++++++++++++++++++++ Recorder/RTSPRecorder.h | 41 +++++++++++ Recorder/Settings.h | 1 + 6 files changed, 229 insertions(+), 65 deletions(-) create mode 120000 Libraries/cppcodec create mode 100644 Recorder/RTSPRecorder.cpp create mode 100644 Recorder/RTSPRecorder.h diff --git a/API/WebAPI.cpp b/API/WebAPI.cpp index b1f3034..3ee2e00 100644 --- a/API/WebAPI.cpp +++ b/API/WebAPI.cpp @@ -2,10 +2,13 @@ #include "Recorder/Recorder.h" #include "Recorder/DigooRecorder.h" #include "Recorder/FoscamRecorder.h" +#include "Recorder/RTSPRecorder.h" #include "Recorder/VStarCamRecorder.h" #include "Recorder/WatchBotRecorder.h" #include "Recorder/ZModoRecorder.h" #include +#include +#include #include @@ -17,7 +20,7 @@ namespace API { // Status Query // Goal: Provide status information based on POST data // Use: DomoticaSite status update -// URL: http:///API////// +// URL: http:///API/////// WebAPI::WebAPI() { @@ -40,78 +43,96 @@ Http::HttpServer::HttpReply WebAPI::ProcessQuery(ctpl::thread_pool* pThreadPool, reply.status = Http::HttpServer::HttpReply::Status::InternalServerError; reply.content = "{\"result\": \"error\"}\r\n"; - std::string apiURI = uri.substr(4); + try { + std::string apiURI = uri.substr(4); - std::vector uriTokens = StringAlgorithm::split(apiURI, '/'); + std::vector uriTokens = StringAlgorithm::split(apiURI, '/'); - if (uriTokens.size() < 7) + if (uriTokens.size() < 7) return reply; - std::string action = uriTokens[1]; - std::string brand = uriTokens[2]; + std::string action = uriTokens[1]; + std::string brand = uriTokens[2]; - Recorder::Settings settings; - settings.Path = path; - settings.IpAddress = uriTokens[3]; - settings.Port = uriTokens[4]; - settings.Username = uriTokens[5]; - settings.Password = uriTokens[6]; + Recorder::Settings settings; + settings.Path = path; + settings.IpAddress = uriTokens[3]; + settings.Port = uriTokens[4]; + settings.URL = cppcodec::base64_url::decode(uriTokens[5].c_str(), uriTokens[5].length()); + settings.Username = uriTokens[6]; + settings.Password = uriTokens[7]; - int numberOfImages = 5; - if (uriTokens.size() > 7) - numberOfImages = stoi(uriTokens[7]); + int numberOfImages = 5; + if (uriTokens.size() > 8) + numberOfImages = stoi(uriTokens[8]); - reply.status = Http::HttpServer::HttpReply::Status::Ok; - if (StringAlgorithm::iequals("Digoo", brand)) - { - auto recorder = Recorder::DigooRecorder(pHttpClient, settings); - if (StringAlgorithm::iequals("Snapshot", action)) - reply.content = recorder.Snapshot(pThreadPool, ffmpeg); - else if (StringAlgorithm::iequals("MultiSnapshot", action)) - reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); - else if (StringAlgorithm::iequals("Video", action)) - reply.content = recorder.Video(pThreadPool, ffmpeg); - } - else if (StringAlgorithm::iequals("Foscam", brand)) - { - auto recorder = Recorder::FoscamRecorder(pHttpClient, settings); - if (StringAlgorithm::iequals("Snapshot", action)) - reply.content = recorder.Snapshot(pThreadPool, ffmpeg); - else if (StringAlgorithm::iequals("MultiSnapshot", action)) - reply.content = recorder.MultiSnapshot(pThreadPool, numberOfImages); - else if (StringAlgorithm::iequals("Video", action)) - reply.content = recorder.Video(pThreadPool, ffmpeg); - } - else if (StringAlgorithm::iequals("VStarCam", brand)) - { - auto recorder = Recorder::VStarCamRecorder(pHttpClient, settings); - if (StringAlgorithm::iequals("Snapshot", action)) - reply.content = recorder.Snapshot(pThreadPool, ffmpeg); - else if (StringAlgorithm::iequals("MultiSnapshot", action)) - reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); - else if (StringAlgorithm::iequals("Video", action)) - reply.content = recorder.Video(pThreadPool, ffmpeg); - } - else if (StringAlgorithm::iequals("WatchBot", brand)) - { - auto recorder = Recorder::WatchBotRecorder(pHttpClient, settings); - if (StringAlgorithm::iequals("Snapshot", action)) - reply.content = recorder.Snapshot(pThreadPool, ffmpeg); - else if (StringAlgorithm::iequals("MultiSnapshot", action)) - reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); - else if (StringAlgorithm::iequals("Video", action)) - reply.content = recorder.Video(pThreadPool, ffmpeg); - } - else if (StringAlgorithm::iequals("ZModo", brand)) - { - auto recorder = Recorder::ZModoRecorder(pHttpClient, settings); - if (StringAlgorithm::iequals("Snapshot", action)) - reply.content = recorder.Snapshot(pThreadPool, ffmpeg); - else if (StringAlgorithm::iequals("MultiSnapshot", action)) - reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); - else if (StringAlgorithm::iequals("Video", action)) - reply.content = recorder.Video(pThreadPool, ffmpeg); + reply.status = Http::HttpServer::HttpReply::Status::Ok; + if (StringAlgorithm::iequals("Digoo", brand)) + { + auto recorder = Recorder::DigooRecorder(pHttpClient, settings); + if (StringAlgorithm::iequals("Snapshot", action)) + reply.content = recorder.Snapshot(pThreadPool, ffmpeg); + else if (StringAlgorithm::iequals("MultiSnapshot", action)) + reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); + else if (StringAlgorithm::iequals("Video", action)) + reply.content = recorder.Video(pThreadPool, ffmpeg); + } + else if (StringAlgorithm::iequals("Foscam", brand)) + { + auto recorder = Recorder::FoscamRecorder(pHttpClient, settings); + if (StringAlgorithm::iequals("Snapshot", action)) + reply.content = recorder.Snapshot(pThreadPool, ffmpeg); + else if (StringAlgorithm::iequals("MultiSnapshot", action)) + reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); + else if (StringAlgorithm::iequals("Video", action)) + reply.content = recorder.Video(pThreadPool, ffmpeg); + } + else if (StringAlgorithm::iequals("RTSP", brand)) + { + auto recorder = Recorder::RTSPRecorder(pHttpClient, settings); + if (StringAlgorithm::iequals("Snapshot", action)) + reply.content = recorder.Snapshot(pThreadPool, ffmpeg); + else if (StringAlgorithm::iequals("MultiSnapshot", action)) + reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); + else if (StringAlgorithm::iequals("Video", action)) + reply.content = recorder.Video(pThreadPool, ffmpeg); + } + else if (StringAlgorithm::iequals("VStarCam", brand)) + { + auto recorder = Recorder::VStarCamRecorder(pHttpClient, settings); + if (StringAlgorithm::iequals("Snapshot", action)) + reply.content = recorder.Snapshot(pThreadPool, ffmpeg); + else if (StringAlgorithm::iequals("MultiSnapshot", action)) + reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); + else if (StringAlgorithm::iequals("Video", action)) + reply.content = recorder.Video(pThreadPool, ffmpeg); + } + else if (StringAlgorithm::iequals("WatchBot", brand)) + { + auto recorder = Recorder::WatchBotRecorder(pHttpClient, settings); + if (StringAlgorithm::iequals("Snapshot", action)) + reply.content = recorder.Snapshot(pThreadPool, ffmpeg); + else if (StringAlgorithm::iequals("MultiSnapshot", action)) + reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); + else if (StringAlgorithm::iequals("Video", action)) + reply.content = recorder.Video(pThreadPool, ffmpeg); + } + else if (StringAlgorithm::iequals("ZModo", brand)) + { + auto recorder = Recorder::ZModoRecorder(pHttpClient, settings); + if (StringAlgorithm::iequals("Snapshot", action)) + reply.content = recorder.Snapshot(pThreadPool, ffmpeg); + else if (StringAlgorithm::iequals("MultiSnapshot", action)) + reply.content = recorder.MultiSnapshot(pThreadPool, ffmpeg, numberOfImages); + else if (StringAlgorithm::iequals("Video", action)) + reply.content = recorder.Video(pThreadPool, ffmpeg); + } } + catch (const std::exception& e) + { + std::cout << "WebAPI::ProcessQuery() - Error: " << e.what() << std::endl; + return reply; + } return reply; } diff --git a/Libraries/cppcodec b/Libraries/cppcodec new file mode 120000 index 0000000..320f97e --- /dev/null +++ b/Libraries/cppcodec @@ -0,0 +1 @@ +../../Libraries/cppcodec \ No newline at end of file diff --git a/Makefile.conf b/Makefile.conf index edb69ca..8614c86 100644 --- a/Makefile.conf +++ b/Makefile.conf @@ -7,6 +7,7 @@ LIBRARIES += Logging LIBRARIES += Utilities LIBRARIES += Image +CFLAGS += -I$(ROOTPATH)/Libraries/cppcodec/cppcodec CFLAGS += -I$(ROOTPATH)/Libraries/CTPL CFLAGS += -I$(ROOTPATH)/Libraries/filesystem CFLAGS += -I$(ROOTPATH)/Libraries/inih diff --git a/Recorder/RTSPRecorder.cpp b/Recorder/RTSPRecorder.cpp new file mode 100644 index 0000000..6ae8c6c --- /dev/null +++ b/Recorder/RTSPRecorder.cpp @@ -0,0 +1,99 @@ +#include "RTSPRecorder.h" +#include "Util.h" +#include +#include + + +namespace CameraRecorder { +namespace Recorder { + +RTSPRecorder::RTSPRecorder(Http::HttpClient* pHttpClient, const Settings& settings) : + m_pHttpClient(pHttpClient), + m_settings(settings) +{ +} + +RTSPRecorder::~RTSPRecorder() +{ +} + +std::string RTSPRecorder::Snapshot(ctpl::thread_pool* pThreadPool, const std::string& ffmpeg) +{ + Logging::Log(Logging::Severity::Debug, "RTSPRecorder Snapshot"); + + std::string dateTimeString = GetDateTimeString(); + std::string fileName = GetFileName(m_settings, "jpg", dateTimeString, 0); + + Http::HttpClient* pHttpClient = m_pHttpClient; + Settings settings = m_settings; + RTSPRecorder::Snapshots(ffmpeg, settings, dateTimeString, 1); + + return fileName; +} + +std::string RTSPRecorder::MultiSnapshot(ctpl::thread_pool* pThreadPool, const std::string& ffmpeg, int numberOfImages) +{ + Logging::Log(Logging::Severity::Debug, "RTSPRecorder MultiSnapshot"); + + std::string dateTimeString = GetDateTimeString(); + std::string fileName = GetFileName(m_settings, "jpg", dateTimeString, 0); + + Http::HttpClient* pHttpClient = m_pHttpClient; + Settings settings = m_settings; + pThreadPool->push( [ffmpeg, settings, dateTimeString, numberOfImages](int) { RTSPRecorder::Snapshots(ffmpeg, settings, dateTimeString, numberOfImages); } ); + + return fileName; +} + +std::string RTSPRecorder::Video(ctpl::thread_pool* pThreadPool, const std::string& ffmpeg) +{ + Logging::Log(Logging::Severity::Debug, "RTSPRecorder Video"); + + std::stringstream url; + url << "rtsp://" << m_settings.Username << ":" << m_settings.Password << "@" << m_settings.IpAddress << ":" << m_settings.Port << m_settings.URL; + + std::string dateTimeString = GetDateTimeString(); + std::string fileName = GetFileName(m_settings, "mp4", dateTimeString, 0); + + std::stringstream command; + command << ffmpeg << " -rtsp_transport tcp -i \"" << url.str() << "\" -t 30 -c:v copy -an -strict experimental " << fileName << " > /dev/null 2>&1 ; " << ffmpeg << " -i " << fileName << " -vf \"select=eq(n\\,0)\" -q:v 3 " << fileName << ".jpg > /dev/null 2>&1"; + + std::string cmd(command.str()); + Logging::Log(Logging::Severity::Debug, cmd); + pThreadPool->push( [cmd](int) { int retval = std::system(cmd.c_str()); } ); + + return fileName; +} + +std::string RTSPRecorder::GetFileName(const Settings& settings, const std::string& extension, const std::string& dateTimeString, int imageIndex) +{ + filesystem::path fileLocation(settings.Path); + fileLocation = fileLocation / filesystem::path(settings.IpAddress); + + std::string fileName = std::string(dateTimeString).append(".").append(extension); + filesystem::path fullFileName = fileLocation / filesystem::path(fileName); + + return fullFileName.str(); +} + +void RTSPRecorder::Snapshots(const std::string& ffmpeg, const Settings& settings, std::string dateTimeString, int numberOfImages) +{ + for (int i = 0; i < numberOfImages; ++i) + RTSPRecorder::Snapshot(ffmpeg, settings, RTSPRecorder::GetFileName(settings, "jpg", dateTimeString, i)); +} + +void RTSPRecorder::Snapshot(const std::string& ffmpeg, const Settings& settings, const filesystem::path& fileName) +{ + std::stringstream url; + url << "rtsp://" << settings.Username << ":" << settings.Password << "@" << settings.IpAddress << ":" << settings.Port << settings.URL; + + std::stringstream command; + command << ffmpeg << " -rtsp_transport tcp -i \"" << url.str() << "\" -vframes 1 -qscale:v 2 " << fileName << " > /dev/null 2>&1"; + + std::string cmd(command.str()); + Logging::Log(Logging::Severity::Debug, cmd); + int retval = std::system(cmd.c_str()); +} + +} // namespace Recorder +} // namespace CameraRecorder diff --git a/Recorder/RTSPRecorder.h b/Recorder/RTSPRecorder.h new file mode 100644 index 0000000..52e4d2b --- /dev/null +++ b/Recorder/RTSPRecorder.h @@ -0,0 +1,41 @@ +#ifndef RECORDER_RTSPRECORDER_H +#define RECORDER_RTSPRECORDER_H + +#include "Recorder.h" +#include "Settings.h" +#include + + +namespace Http { + +class HttpClient; + +} // namespace Http + +namespace CameraRecorder { +namespace Recorder { + +class RTSPRecorder : public virtual Recorder +{ +public: + RTSPRecorder(Http::HttpClient* pHttpClient, const Settings& settings); + ~RTSPRecorder(); + + virtual std::string Snapshot(ctpl::thread_pool* pThreadPool, const std::string& ffmpeg) override; + virtual std::string MultiSnapshot(ctpl::thread_pool* pThreadPool, const std::string& ffmpeg, int numberOfImages) override; + virtual std::string Video(ctpl::thread_pool* pThreadPool, const std::string& ffmpeg) override; + +private: + static std::string GetFileName(const Settings& settings, const std::string& extension, const std::string& dateTimeString, int imageIndex); + static void Snapshots(const std::string& ffmpeg, const Settings& settings, std::string dateTimeString, int numberOfImages); + static void Snapshot(const std::string& ffmpeg, const Settings& settings, const filesystem::path& fileName); + +private: + Http::HttpClient* m_pHttpClient; + Settings m_settings; +}; + +} // namespace Recorder +} // namespace CameraRecorder + +#endif // RECORDER_RTSPRECORDER_H diff --git a/Recorder/Settings.h b/Recorder/Settings.h index 1684f00..f225606 100644 --- a/Recorder/Settings.h +++ b/Recorder/Settings.h @@ -12,6 +12,7 @@ struct Settings std::string Path; std::string IpAddress; std::string Port; + std::string URL; std::string Username; std::string Password; };