Multithreading & UX update

This commit is contained in:
Brendan LE GLAUNEC
2016-10-28 09:50:37 +02:00
committed by Brendan Le Glaunec
parent de757e848d
commit 509d68f023
39 changed files with 572 additions and 407 deletions
+52 -11
View File
@@ -56,7 +56,7 @@ configuration::load_ids() {
"the default one "
"instead.",
"configuration");
content = read_file(default_ids_file_path_).second;
content = read_file(default_rtsp_ids_file).second;
}
if (content.size()) {
auto root = Json::Value();
@@ -101,7 +101,7 @@ configuration::load_url() {
"the default one "
"instead.",
"configuration");
content = read_file(default_urls_file_path_).second;
content = read_file(default_rtsp_url_file).second;
}
if (content.size()) {
auto root = Json::Value();
@@ -131,18 +131,47 @@ serialize(const Json::Value& root) {
std::pair<bool, configuration> ret;
try {
ret.second.ports = root["ports"].asString();
ret.second.subnets = root["subnets"].asString();
ret.second.rtsp_ids_file = root["rtsp_ids_file"].asString();
ret.second.rtsp_url_file = root["rtsp_url_file"].asString();
ret.second.thumbnail_storage_path = root["thumbnail_storage_path"].asString();
ret.second.cache_manager_path = root["cache_manager_path"].asString();
ret.second.cache_manager_name = root["cache_manager_name"].asString();
if (!root["ports"].isNull())
ret.second.ports = root["ports"].asString();
else
ret.second.ports = default_ports;
if (!root["subnets"].isNull())
ret.second.subnets = root["subnets"].asString();
else
ret.second.subnets = default_subnets;
if (!root["rtsp_ids_file"].isNull())
ret.second.rtsp_ids_file = root["rtsp_ids_file"].asString();
else
ret.second.rtsp_ids_file = default_rtsp_ids_file;
if (!root["rtsp_url_file"].isNull())
ret.second.rtsp_url_file = root["rtsp_url_file"].asString();
else
ret.second.rtsp_url_file = default_rtsp_url_file;
if (!root["thumbnail_storage_path"].isNull())
ret.second.thumbnail_storage_path = root["thumbnail_storage_path"].asString();
else
ret.second.thumbnail_storage_path = default_thumbnail_storage_path;
if (!root["cache_manager_path"].isNull())
ret.second.cache_manager_path = root["cache_manager_path"].asString();
else
ret.second.cache_manager_path = default_cache_manager_path;
if (!root["cache_manager_name"].isNull())
ret.second.cache_manager_name = root["cache_manager_name"].asString();
else
ret.second.cache_manager_name = default_cache_manager_name;
ret.first = true;
} catch (std::exception& e) {
} catch (const std::exception& e) {
LOG_ERR_("Configuration failed : " + std::string(e.what()), "configuration");
ret.first = false;
}
return ret;
}
@@ -156,7 +185,16 @@ configuration::get_raw() const {
// Will return true & valid configuration if success
// Otherwise false & empty configuration
std::pair<bool, configuration>
load(const std::string& path) {
load(const std::pair<bool, etix::tool::opt_parse>& args) {
std::string path;
if (not args.second.exist("-c")) {
path = etix::cameradar::default_configuration_path;
LOG_WARN_("No custom path set, trying to use default path: " + path, "main");
} else {
path = args.second["-c"];
}
// Check if the file exists at the given path
if (access(path.c_str(), F_OK) == -1) {
LOG_ERR_("Can't access: " + path, "configuration");
@@ -191,6 +229,9 @@ load(const std::string& path) {
conf.first &= conf.second.load_url();
conf.first &= conf.second.load_ids();
if (args.second.exist("-s")) conf.second.subnets = args.second["-s"];
if (args.second.exist("-p")) conf.second.ports = args.second["-p"];
return conf;
}
}
+21 -14
View File
@@ -17,9 +17,19 @@
namespace etix {
namespace cameradar {
//! Sends a request to the camera using the OPTION method,
//! then a DESCRIBE to check for valid IDs
//! then another DESCIBE with IDs if an authentication is needed
// Ugly workaround
size_t
write_data(void* buffer, size_t size, size_t nmemb, void* userp) {
// I'm sorry for this
// Forget you ever saw it
(void)buffer;
(void)userp;
return size * nmemb;
}
// Sends a request to the camera using the OPTION method,
// then a DESCRIBE to check for valid IDs
// then another DESCIBE with IDs if an authentication is needed
bool
curl_describe(const std::string& path, bool logs) {
CURL* csession;
@@ -27,23 +37,21 @@ curl_describe(const std::string& path, bool logs) {
struct curl_slist* custom_msg = NULL;
char URL[256];
long rc;
FILE* protofile = NULL;
protofile = fopen("/dev/null", "wb");
csession = curl_easy_init();
if (csession == NULL) return -1;
sprintf(URL, "%s", path.c_str());
// These are the options for all following cURL requests
// Activate verbose if debug is needed
curl_easy_setopt(csession, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(csession, CURLOPT_TIMEOUT, 1);
curl_easy_setopt(csession, CURLOPT_NOBODY, 1);
curl_easy_setopt(csession, CURLOPT_URL, URL);
curl_easy_setopt(csession, CURLOPT_RTSP_STREAM_URI, URL);
curl_easy_setopt(csession, CURLOPT_FOLLOWLOCATION, 0);
curl_easy_setopt(csession, CURLOPT_HEADER, 0);
curl_easy_setopt(csession, CURLOPT_INTERLEAVEDATA, protofile);
curl_easy_setopt(csession, CURLOPT_VERBOSE, 0);
curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS);
curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
curl_easy_setopt(csession, CURLOPT_WRITEFUNCTION, write_data);
// This request will handshake the stream's server, it should always return 200 OK
curl_easy_perform(csession);
curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
@@ -51,11 +59,7 @@ curl_describe(const std::string& path, bool logs) {
custom_msg, "Accept: application/x-rtsp-mh, application/rtsl, application/sdp");
curl_easy_setopt(csession, CURLOPT_RTSPHEADER, custom_msg);
curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE);
curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
// This request will check if the given path is right without the need of encrypted ids
curl_easy_perform(
csession); // will return 404 if no ids and bad route, 401 if ids, 200 is all ok
res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
unsigned long pos = path.find("@");
if (pos != std::string::npos) {
std::string encoded = etix::tool::encode::encode64(path.substr(7, pos - 7));
@@ -63,14 +67,17 @@ curl_describe(const std::string& path, bool logs) {
curl_slist_append(custom_msg, std::string("Authorization: Basic " + encoded).c_str());
curl_easy_setopt(csession, CURLOPT_RTSPHEADER, custom_msg);
curl_easy_setopt(csession, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE);
curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
// curl_easy_setopt(csession, CURLOPT_WRITEDATA, protofile);
// This request will check if the given ids are good
curl_easy_perform(csession); // will return 404 if good ids, 401 if bad ids
res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
} else {
curl_easy_perform(
csession); // will return 404 if no ids and bad route, 401 if ids, 200 is all ok
res = curl_easy_getinfo(csession, CURLINFO_RESPONSE_CODE, &rc);
}
curl_easy_cleanup(csession);
fclose(protofile);
curl_global_cleanup();
LOG_DEBUG_("Response code : " + std::to_string(rc), "describe");
if (logs) {
// Some cameras return 400 instead of 401, don't know why.
+2 -2
View File
@@ -54,8 +54,8 @@ dispatcher::run() {
worker.join();
}
//! This loop is used to add all the tasks specified in the command line
//! And then run them successively
// This loop is used to add all the tasks specified in the command line
// And then run them successively
void
dispatcher::do_stuff() {
if (opts.second.exist("-d")) {
+2 -2
View File
@@ -88,12 +88,12 @@ create_recursive_folder(const std::string& folder) {
std::string
get_file_folder(std::string full_file_path) {
//! remove ending slash
// remove ending slash
if (full_file_path.back() == '/') full_file_path.pop_back();
size_t last_slash_position = full_file_path.find_last_of('/');
//! it there is no slash, there is no folder to return
// it there is no slash, there is no folder to return
if (last_slash_position == std::string::npos) return "";
return std::string(full_file_path, 0, last_slash_position);
+1 -1
View File
@@ -17,7 +17,7 @@
namespace etix {
namespace cameradar {
//! Launches a command and checks for the return value
// Launches a command and checks for the return value
bool
launch_command(const std::string& cmd) {
int status = system(cmd.c_str());
+5 -12
View File
@@ -24,7 +24,6 @@ void
print_version() {
std::cout << "Cameradar version " << CAMERADAR_VERSION << std::endl;
std::cout << "Build " << CAMERADAR_VERSION_BUILD << std::endl;
std::cout << "Git commit " << CAMERADAR_VERSION_GIT_SHA1 << std::endl;
}
// Command line parsing
@@ -32,6 +31,8 @@ std::pair<bool, etix::tool::opt_parse>
parse_cmdline(int argc, char* argv[]) {
auto opt_parse = etix::tool::opt_parse{ argc, argv };
opt_parse.optional("-s", "Set subnets (e.g.: `172.16.0.0/24`)", true);
opt_parse.optional("-p", "Set ports (e.g.: `554,8554`)", true);
opt_parse.optional("-c", "Path to the configuration file (-c /path/to/conf)", true);
opt_parse.optional("-l", "Set log level (-l 4 will only show warnings and errors)", true);
opt_parse.optional("-d", "Launch the discovery tool on the given subnet", false);
@@ -86,18 +87,10 @@ main(int argc, char* argv[]) {
if (not args.first) return EXIT_FAILURE;
print_version();
// configure file configuration path
auto conf_path = std::string{};
if (not args.second.exist("-c")) {
conf_path = etix::cameradar::default_configuration_path;
LOG_WARN_("No custom path set, trying to use default path: " + conf_path, "main");
} else {
conf_path = args.second["-c"];
}
if (not args.second.exist("-l")) {
etix::tool::logger::get_instance("cameradar").set_level(etix::tool::loglevel::INFO);
LOG_INFO_("No log level set, using log level 2 (ignoring DEBUG)", "main");
etix::tool::logger::get_instance("cameradar").set_level(etix::tool::loglevel::DEBUG);
LOG_INFO_("No log level set, using log level 1", "main");
} else {
try {
int level = std::stoi(args.second["-l"]);
@@ -110,7 +103,7 @@ main(int argc, char* argv[]) {
}
// Try to load the configuration
auto conf = cmrdr::load(conf_path);
auto conf = cmrdr::load(args);
if (not conf.first) { return EXIT_FAILURE; }
LOG_INFO_("Configuration successfully loaded", "main");
+44 -33
View File
@@ -25,9 +25,9 @@ static const std::string no_ids_warning_ =
"default routes. "
"Path bruteforce is impossible without the IDs.";
//! Tries to match the detected combination of Username / Password
//! with the camera stream. Creates a resource in the DB upon
//! valid discovery
// Tries to match the detected combination of Username / Password
// with the camera stream. Creates a resource in the DB upon
// valid discovery
bool
brutelogs::test_ids(const etix::cameradar::stream_model& stream,
const std::string& password,
@@ -36,22 +36,24 @@ brutelogs::test_ids(const etix::cameradar::stream_model& stream,
std::string path = stream.service_name + "://";
if (username != "" || password != "") { path += username + ":" + password + "@"; }
path += stream.address + ":" + std::to_string(stream.port);
LOG_DEBUG_("Testing ids : " + path, "brutelogs");
LOG_INFO_("Testing ids : " + path, "brutelogs");
try {
if (curl_describe(path, true)) {
LOG_DEBUG_("[FOUND IDS] : " + path, "brutelogs");
LOG_INFO_("[FOUND IDS] : " + path, "brutelogs");
found = true;
stream_model newstream{
stream.address, stream.port, username, password,
stream.route, stream.service_name, stream.product, stream.protocol,
stream.state, true, stream.path_found, stream.thumbnail_path
stream.address, stream.port, username, password,
stream.route, stream.service_name, stream.product, stream.protocol,
stream.state, stream.path_found, true, stream.thumbnail_path
};
if ((*cache)->has_changed(stream)) return true;
(*cache)->update_stream(newstream);
} else {
stream_model newstream{ stream.address, stream.port, username,
password, stream.route, stream.service_name,
stream.product, stream.protocol, stream.state,
false, stream.path_found, stream.thumbnail_path };
stream_model newstream{ stream.address, stream.port, username,
password, stream.route, stream.service_name,
stream.product, stream.protocol, stream.state,
stream.path_found, false, stream.thumbnail_path };
if ((*cache)->has_changed(stream)) return true;
(*cache)->update_stream(newstream);
}
} catch (const std::runtime_error& e) {
@@ -63,25 +65,45 @@ brutelogs::test_ids(const etix::cameradar::stream_model& stream,
bool
ids_already_found(std::vector<stream_model> streams, stream_model stream) {
for (const auto& it : streams) {
if ((stream.address == it.address) && (stream.port == it.port) && it.ids_found) return true;
if ((stream.address == it.address) && (stream.port == it.port) && it.ids_found) {
LOG_DEBUG_(">>>>>> PATH " + std::to_string(stream.path_found) + " IDS " +
std::to_string(stream.ids_found),
"wtf");
return true;
}
}
return false;
}
//! Tries to discover the right IDs on all RTSP streams in DB
//! Uses the ids.json file to try different combinations
bool
brutelogs::bruteforce_camera(const stream_model& stream) const {
for (const auto& username : conf.usernames) {
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break;
for (const auto& password : conf.passwords) {
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break;
if ((*cache)->has_changed(stream)) return true;
if (test_ids(stream, password, username)) return true;
}
}
return false;
}
// Tries to discover the right IDs on all RTSP streams in DB
// Uses the ids.json file to try different combinations
bool
brutelogs::run() const {
std::vector<std::future<bool>> futures;
LOG_INFO_(
"Beginning bruteforce of the usernames and passwords task, it may "
"take a while.",
"brutelogs");
std::vector<etix::cameradar::stream_model> streams = (*cache)->get_streams();
LOG_DEBUG_("Found " + std::to_string(streams.size()) + " streams in the cache", "brutelogs");
bool doubleskip;
size_t found = 0;
for (const auto& stream : streams) {
doubleskip = false;
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break;
if ((found < streams.size()) && ids_already_found(streams, stream)) {
@@ -92,24 +114,13 @@ brutelogs::run() const {
"brutelogs");
++found;
} else {
for (const auto& username : conf.usernames) {
if (doubleskip ||
signal_handler::instance().should_stop() !=
etix::cameradar::stop_priority::running)
break;
for (const auto& password : conf.passwords) {
if (doubleskip ||
signal_handler::instance().should_stop() !=
etix::cameradar::stop_priority::running)
break;
if (test_ids(stream, password, username)) {
++found;
doubleskip = true;
}
}
}
futures.push_back(
std::async(std::launch::async, &brutelogs::bruteforce_camera, this, stream));
}
}
for (auto& fit : futures) {
if (fit.get()) { ++found; }
}
if (!found) {
LOG_WARN_(no_ids_warning_, "brutelogs");
return false;
+36 -18
View File
@@ -19,9 +19,9 @@ static const std::string no_route_found_ =
"default "
"routes. Thumbnail generation is impossible without the path.";
//! Tries to match the detected combination of Username / Password
//! with a route for the camera stream. Creates a resource in the DB upon
//! valid discovery
// Tries to match the detected combination of Username / Password
// with a route for the camera stream. Creates a resource in the DB upon
// valid discovery
bool
brutepath::test_path(const stream_model& stream, const std::string& route) const {
bool found = false;
@@ -29,7 +29,7 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
stream.address + ":" + std::to_string(stream.port);
if (route.front() != '/') { path += "/"; }
path += route;
LOG_DEBUG_("Testing path : " + path, "brutepath");
LOG_INFO_("Testing path : " + path, "brutepath");
try {
if (curl_describe(path, false)) {
// insert in DB and go to the next port, print a cool message
@@ -40,6 +40,7 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
stream.service_name, stream.product, stream.protocol, stream.state, true,
stream.ids_found, stream.thumbnail_path
};
if ((*cache)->has_changed(stream)) return true;
(*cache)->update_stream(newstream);
} else {
stream_model newstream{
@@ -47,6 +48,7 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
stream.service_name, stream.product, stream.protocol, stream.state, false,
stream.ids_found, stream.thumbnail_path
};
if ((*cache)->has_changed(stream)) return true;
(*cache)->update_stream(newstream);
}
} catch (const std::runtime_error& e) { LOG_INFO_(e.what(), "brutepath"); }
@@ -56,16 +58,36 @@ brutepath::test_path(const stream_model& stream, const std::string& route) const
bool
path_already_found(std::vector<stream_model> streams, stream_model model) {
for (const auto& stream : streams) {
if ((model.address == stream.address) && (model.port == stream.port) && stream.path_found)
if ((model.address == stream.address) && (model.port == stream.port) && stream.path_found) {
LOG_DEBUG_(">>>>>> PATH " + std::to_string(stream.path_found) + " IDS " +
std::to_string(stream.ids_found),
"wtf");
return true;
}
}
return false;
}
//! Tries to discover a route on all RTSP streams in DB
//! Uses the url.json file to try different routes
bool
brutepath::bruteforce_camera(const stream_model& stream) const {
for (const auto& route : conf.paths) {
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break;
if ((*cache)->has_changed(stream)) return true;
if (test_path(stream, route)) {
return true;
break;
}
}
return false;
}
// Tries to discover a route on all RTSP streams in DB
// Uses the url.json file to try different routes
bool
brutepath::run() const {
std::vector<std::future<bool>> futures;
LOG_INFO_("Beginning bruteforce of the camera paths task, it may take a while.", "bruteforce");
std::vector<stream_model> streams = (*cache)->get_streams();
int found = 0;
@@ -74,22 +96,18 @@ brutepath::run() const {
break;
if (path_already_found(streams, stream)) {
LOG_INFO_(stream.address +
" : This camera's path was already discovered in the database."
"Skipping to the next camera.",
" : This camera's path was already discovered in the database. Skipping "
"to the next camera.",
"brutepath");
++found;
} else {
for (const auto& route : conf.paths) {
if (signal_handler::instance().should_stop() !=
etix::cameradar::stop_priority::running)
break;
if (test_path(stream, route)) {
found++;
break;
}
}
futures.push_back(
std::async(std::launch::async, &brutepath::bruteforce_camera, this, stream));
}
}
for (auto& fit : futures) {
if (fit.get()) { ++found; }
}
if (!found) {
LOG_WARN_(no_route_found_, "brutepath");
+11 -11
View File
@@ -17,15 +17,15 @@
namespace etix {
namespace cameradar {
//! The first command checks if dpkg finds nmap in the system by cutting the
//! result and grepping
//! nmap from it.
//!
//! The second command checks the version of nmap, right now it needs to be the
//! 6.47 but this could
//! be changed to 6 or greater depending on the needs. In a docker container
//! this should not be a
//! problem.
// The first command checks if dpkg finds nmap in the system by cutting the
// result and grepping
// nmap from it.
//
// The second command checks the version of nmap, right now it needs to be the
// 6.47 but this could
// be changed to 6 or greater depending on the needs. In a docker container
// this should not be a
// problem.
bool
nmap_is_ok() {
return (
@@ -33,8 +33,8 @@ nmap_is_ok() {
&& launch_command("mkdir -p /tmp/scans")); // Creates the directory in which the scans will be stored
}
//! Launches and checks the return of the nmap command
//! Uses the subnets specified in the conf file to launch nmap
// Launches and checks the return of the nmap command
// Uses the subnets specified in the conf file to launch nmap
bool
mapping::run() const {
if (nmap_is_ok()) {
+8 -8
View File
@@ -24,7 +24,7 @@ static const std::string no_hosts_found_ =
"were "
"accessible";
//! Avoids segfaults on unknown xml structure
// Avoids segfaults on unknown xml structure
std::string
xml_safe_get(const TiXmlElement* elem, const std::string& attr) {
if (elem == nullptr) return "closed";
@@ -32,8 +32,8 @@ xml_safe_get(const TiXmlElement* elem, const std::string& attr) {
return "closed";
}
//! Parse a single host node (generally containing only one camera)
//! Pushes it back to the data structure
// Parse a single host node (generally containing only one camera)
// Pushes it back to the data structure
void
parsing::parse_camera(TiXmlElement* xml_host, std::vector<stream_model>& data) const {
TiXmlElement* xml_streams = xml_host->FirstChild("ports")->ToElement();
@@ -58,8 +58,8 @@ parsing::parse_camera(TiXmlElement* xml_host, std::vector<stream_model>& data) c
}
}
//! Prints all detected cameras into the data structure and stops the program if
//! no open RTSP streams were found
// Prints all detected cameras into the data structure and stops the program if
// no open RTSP streams were found
bool
parsing::print_detected_cameras(const std::vector<stream_model>& data) const {
int added = 0;
@@ -90,8 +90,8 @@ parsing::print_detected_cameras(const std::vector<stream_model>& data) const {
return true;
}
//! Opens the nmap output file, parses the data of each discovered port
//! Adds the RTSP ports only into the DB
// Opens the nmap output file, parses the data of each discovered port
// Adds the RTSP ports only into the DB
bool
parsing::run() const {
std::vector<stream_model> data;
@@ -113,7 +113,7 @@ parsing::run() const {
LOG_WARN_(no_hosts_found_, "parsing");
if (data.size() == 0) { LOG_WARN_("No cameras were discovered", "parsing"); }
return print_detected_cameras(data);
} catch (std::exception& e) {
} catch (const std::exception& e) {
LOG_ERR_("Error during parsing. brutepath aborted : " + std::string(e.what()), "parsing");
return false;
}
+2 -2
View File
@@ -17,8 +17,8 @@
namespace etix {
namespace cameradar {
//! Launches and checks the return of the nmap command
//! Uses the subnets specified in the conf file to launch nmap
// Launches and checks the return of the nmap command
// Uses the subnets specified in the conf file to launch nmap
bool
print::run() const {
std::vector<stream_model> results = (*cache)->get_valid_streams();
@@ -17,9 +17,9 @@
namespace etix {
namespace cameradar {
//! Gets all the discovered streams with good routes and logs
//! And launches an ffmpeg command to generate a thumbnail
//! In order to check for the stream validity
// Gets all the discovered streams with good routes and logs
// And launches an ffmpeg command to generate a thumbnail
// In order to check for the stream validity
bool
stream_check::run() const {
GstElement* pipeline;
@@ -30,8 +30,8 @@ stream_check::run() const {
std::vector<stream_model> streams = (*cache)->get_valid_streams();
if (not streams.size()) {
LOG_WARN_("There were no valid streams to check. Cameradar will stop.", "stream_check");
return false;
LOG_WARN_("There were no valid streams to check. Cameradar will stop.", "stream_check");
return false;
}
for (const auto& stream : streams) {
GError* error = NULL;
@@ -40,13 +40,17 @@ stream_check::run() const {
gst_parse_launch("rtspsrc name=source ! rtph264depay ! h264parse ! fakesink", &error);
std::string location = "rtsp://";
location += stream.username + ":" + stream.password + "@" + stream.address + ":" + std::to_string(stream.port);
location += stream.username + ":" + stream.password + "@" + stream.address + ":" +
std::to_string(stream.port);
if (pipeline == NULL) {
LOG_ERR_("[" + stream.address + "] Can't configure pipeline", "stream_check");
return false;
} else {
elem = gst_bin_get_by_name(GST_BIN(pipeline), "source");
LOG_DEBUG_("Launching gstreamer check on rtsp://" + stream.username + ":" + stream.password + "@" + stream.address + ":" + std::to_string(stream.port), "gstreamer check");
LOG_DEBUG_("Launching gstreamer check on rtsp://" + stream.username + ":" +
stream.password + "@" + stream.address + ":" +
std::to_string(stream.port),
"gstreamer check");
g_object_set(G_OBJECT(elem), "location", location.c_str(), "latency", 20, NULL);
if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
@@ -63,7 +67,9 @@ stream_check::run() const {
(*cache)->update_stream(invalidstream);
return false;
}
LOG_INFO_("[" + stream.address + "] Set pipeline to playing", "stream_check");
LOG_INFO_("[" + stream.address +
"] This stream is accessible and seems to be functional",
"stream_check");
}
}
LOG_INFO_("All streams could be accessed with GStreamer", "stream_check");
+60 -47
View File
@@ -41,61 +41,74 @@ thumbnail::build_output_file_path(const std::string& path) const {
return ss.str();
}
//! Gets all the discovered streams with good routes and logs
//! And launches an ffmpeg command to generate a thumbnail
//! In order to check for the stream validity
bool
thumbnail::generate_thumbnail(const stream_model& stream) const {
LOG_INFO_("Generating thumbnail for " + stream.address, "thumbnail_generation");
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
return false;
std::string ffmpeg_cmd =
"mkdir -p %s ; "
"ffmpeg "
"-rtsp_transport tcp "
"-y "
"-nostdin "
"-loglevel quiet " // no logs
"-i '%s' " // input
"-vcodec mjpeg " // jpeg codec
"-vframes 1 " // only take one frame
"-an " // disable audio
"-f image2 " // force image
"-s 240x180 " // force size
"'%s'";
std::string fullpath = make_path(stream);
std::string output = build_output_file_path(stream.address);
ffmpeg_cmd = tool::fmt(ffmpeg_cmd.c_str(),
output.substr(0, output.find_last_of("/")).c_str(),
fullpath.c_str(),
output.c_str());
if (!launch_command(ffmpeg_cmd)) {
LOG_WARN_("The following command [" + ffmpeg_cmd +
"] didn't work. That can either mean that the stream is "
"not valid or "
"that there is a problem with the camera.",
"thumbnail_generation");
return false;
} else {
LOG_DEBUG_("Generated thumbnail : " + ffmpeg_cmd, "thumbnail_generation");
try {
stream_model result{ stream.address, stream.port, stream.username,
stream.password, stream.route, stream.service_name,
stream.product, stream.protocol, stream.state,
stream.path_found, stream.ids_found, output };
(*cache)->update_stream(result);
} catch (const std::exception& e) { LOG_DEBUG_(e.what(), "thumbnail_generation"); }
}
return true;
}
// Gets all the discovered streams with good routes and logs
// And launches an ffmpeg command to generate a thumbnail
// In order to check for the stream validity
bool
thumbnail::run() const {
std::vector<std::future<bool>> futures;
std::vector<stream_model> streams = (*cache)->get_valid_streams();
LOG_INFO_("Started thumbnail generation, it may take a while", "thumbnail");
if (not streams.size()) {
LOG_WARN_("There were no valid streams to generate thumbnails from. Cameradar will stop.", "thumbnail_generation");
return false;
LOG_WARN_("There were no valid streams to generate thumbnails from. Cameradar will stop.",
"thumbnail_generation");
return false;
}
int done = 0;
for (const auto& stream : streams) {
LOG_DEBUG_("Generating thumbnail for " + stream.address, "thumbnail_generation");
if (signal_handler::instance().should_stop() != etix::cameradar::stop_priority::running)
break;
std::string ffmpeg_cmd =
"mkdir -p %s ; "
"ffmpeg "
"-y "
"-nostdin "
"-loglevel quiet "
"-i '%s' "
"-vcodec mjpeg "
"-vframes 1 "
"-an "
"-f image2 "
"-s 320x240 "
"'%s'";
std::string fullpath = make_path(stream);
std::string output = build_output_file_path(stream.address);
ffmpeg_cmd = tool::fmt(ffmpeg_cmd.c_str(),
output.substr(0, output.find_last_of("/")).c_str(),
fullpath.c_str(),
output.c_str());
if (!launch_command(ffmpeg_cmd)) {
LOG_WARN_("The following command [" + ffmpeg_cmd +
"] didn't work. That can either mean that the stream is "
"not valid or "
"that there is a problem with the camera.",
"thumbnail_generation");
} else {
LOG_DEBUG_("Generated thumbnail : " + ffmpeg_cmd, "thumbnail_generation");
try {
stream_model result{ stream.address, stream.port, stream.username,
stream.password, stream.route, stream.service_name,
stream.product, stream.protocol, stream.state,
stream.path_found, stream.ids_found, output };
(*cache)->update_stream(result);
} catch (std::exception& e) { LOG_DEBUG_(e.what(), "thumbnail_generation"); }
}
futures.push_back(
std::async(std::launch::async, &thumbnail::generate_thumbnail, this, stream));
}
for (auto& fit : futures) {
if (fit.get()) { ++done; }
}
LOG_INFO_("All thumbnails have been successfully generated in " +
this->conf.thumbnail_storage_path,
"thumbnail_generation");
return true;
}
}