diff --git a/lib/config.cpp b/lib/config.cpp index eeafd84c..75c7dda1 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -70,8 +70,6 @@ Util::Config::Config(std::string cmd) { /// { /// "short":"o", //The short option letter /// "long":"onName", //The long option -/// "short_off":"n", //The short option-off letter -/// "long_off":"offName", //The long option-off /// "arg":"integer", //The type of argument, if required. /// "value":[], //The default value(s) for this option if it is not given on the commandline. /// "arg_num":1, //The count this value has on the commandline, after all the options have been processed. @@ -89,9 +87,6 @@ void Util::Config::addOption(std::string optname, JSON::Value option) { if (it->isMember("long")) { long_count++; } - if (it->isMember("long_off")) { - long_count++; - } } } @@ -111,12 +106,6 @@ void Util::Config::printHelp(std::ostream & output) { longest = current; } current = 0; - if (it->isMember("long_off")) { - current += (*it)["long_off"].asString().size() + 4; - } - if (it->isMember("short_off")) { - current += (*it)["short_off"].asString().size() + 3; - } if (current > longest) { longest = current; } @@ -159,26 +148,6 @@ void Util::Config::printHelp(std::ostream & output) { output << f << (*it)["help"].asString() << std::endl; } } - if (it->isMember("long_off") || it->isMember("short_off")) { - if (it->isMember("long_off") && it->isMember("short_off")) { - f = "--" + (*it)["long_off"].asString() + ", -" + (*it)["short_off"].asString(); - } else { - if (it->isMember("long_off")) { - f = "--" + (*it)["long_off"].asString(); - } - if (it->isMember("short_off")) { - f = "-" + (*it)["short_off"].asString(); - } - } - while (f.size() < longest) { - f.append(" "); - } - if (it->isMember("arg")) { - output << f << "(" << (*it)["arg"].asString() << ") " << (*it)["help"].asString() << std::endl; - } else { - output << f << (*it)["help"].asString() << std::endl; - } - } if (it->isMember("arg_num")) { f = it.key(); while (f.size() < longest) { @@ -205,12 +174,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) { shortopts += ":"; } } - if (it->isMember("short_off")) { - shortopts += (*it)["short_off"].asString(); - if (it->isMember("arg")) { - shortopts += ":"; - } - } if (it->isMember("long")) { longOpts[long_i].name = (*it)["long"].asStringRef().c_str(); longOpts[long_i].val = (*it)["short"].asString()[0]; @@ -219,14 +182,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) { } long_i++; } - if (it->isMember("long_off")) { - longOpts[long_i].name = (*it)["long_off"].asStringRef().c_str(); - longOpts[long_i].val = (*it)["short_off"].asString()[0]; - if (it->isMember("arg")) { - longOpts[long_i].has_arg = 1; - } - long_i++; - } if (it->isMember("arg_num") && !(it->isMember("value") && (*it)["value"].size())) { if ((*it)["arg_num"].asInt() > arg_count) { arg_count = (*it)["arg_num"].asInt(); @@ -286,9 +241,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) { } break; } - if (it->isMember("short_off") && (*it)["short_off"].asString()[0] == opt) { - (*it)["value"].append((long long int)0); - } } break; } @@ -321,12 +273,18 @@ JSON::Value & Util::Config::getOption(std::string optname, bool asArray) { } if (!vals[optname].isMember("value") || !vals[optname]["value"].isArray()) { vals[optname]["value"].append(JSON::Value()); + vals[optname]["value"].shrink(0); } if (asArray) { return vals[optname]["value"]; } else { int n = vals[optname]["value"].size(); - return vals[optname]["value"][n - 1]; + if (!n){ + static JSON::Value empty = ""; + return empty; + }else{ + return vals[optname]["value"][n - 1]; + } } } @@ -412,8 +370,8 @@ int Util::Config::serveThreadedSocket(int (*callback)(Socket::Connection &)) { if (vals.isMember("socket")) { server_socket = Socket::Server(Util::getTmpFolder() + getString("socket")); } - if (vals.isMember("listen_port") && vals.isMember("listen_interface")) { - server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false); + if (vals.isMember("port") && vals.isMember("interface")) { + server_socket = Socket::Server(getInteger("port"), getString("interface"), false); } if (!server_socket.connected()) { DEBUG_MSG(DLVL_DEVEL, "Failure to open socket"); @@ -429,8 +387,8 @@ int Util::Config::serveForkedSocket(int (*callback)(Socket::Connection & S)) { if (vals.isMember("socket")) { server_socket = Socket::Server(Util::getTmpFolder() + getString("socket")); } - if (vals.isMember("listen_port") && vals.isMember("listen_interface")) { - server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false); + if (vals.isMember("port") && vals.isMember("interface")) { + server_socket = Socket::Server(getInteger("port"), getString("interface"), false); } if (!server_socket.connected()) { DEBUG_MSG(DLVL_DEVEL, "Failure to open socket"); @@ -443,7 +401,6 @@ int Util::Config::serveForkedSocket(int (*callback)(Socket::Connection & S)) { /// Activated the stored config. This will: /// - Drop permissions to the stored "username", if any. -/// - Daemonize the process if "daemonize" exists and is true. /// - Set is_active to true. /// - Set up a signal handler to set is_active to false for the SIGINT, SIGHUP and SIGTERM signals. void Util::Config::activate() { @@ -451,14 +408,6 @@ void Util::Config::activate() { setUser(getString("username")); vals.removeMember("username"); } - if (vals.isMember("daemonize") && getBool("daemonize")) { - if (vals.isMember("logfile") && getString("logfile") != "") { - Daemonize(true); - } else { - Daemonize(false); - } - vals.removeMember("daemonize"); - } struct sigaction new_action; struct sigaction cur_action; new_action.sa_sigaction = signal_handler; @@ -503,33 +452,78 @@ void Util::Config::signal_handler(int signum, siginfo_t * sigInfo, void * ignore } } //signal_handler + +/// Adds the options from the given JSON capabilities structure. +/// Recurses into optional and required, added options as needed. +void Util::Config::addOptionsFromCapabilities(const JSON::Value & capa){ + //First add the required options. + if (capa.isMember("required") && capa["required"].size()){ + jsonForEachConst(capa["required"], it){ + if (!it->isMember("short") || !it->isMember("option") || !it->isMember("type")){ + FAIL_MSG("Incomplete required option: %s", it.key().c_str()); + continue; + } + JSON::Value opt; + opt["short"] = (*it)["short"]; + opt["long"] = (*it)["option"].asStringRef().substr(2); + if (it->isMember("type")){ + //int, uint, debug, select, str + if ((*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "uint"){ + opt["arg"] = "integer"; + }else{ + opt["arg"] = "string"; + } + } + if (it->isMember("default")){ + opt["value"].append((*it)["default"]); + } + opt["help"] = (*it)["help"]; + addOption(it.key(), opt); + } + } + //Then, the optionals. + if (capa.isMember("optional") && capa["optional"].size()){ + jsonForEachConst(capa["optional"], it){ + if (it.key() == "debug"){continue;} + if (!it->isMember("short") || !it->isMember("option") || !it->isMember("default")){ + FAIL_MSG("Incomplete optional option: %s", it.key().c_str()); + continue; + } + JSON::Value opt; + opt["short"] = (*it)["short"]; + opt["long"] = (*it)["option"].asStringRef().substr(2); + if (it->isMember("type")){ + //int, uint, debug, select, str + if ((*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "uint"){ + opt["arg"] = "integer"; + }else{ + opt["arg"] = "string"; + } + } + if (it->isMember("default")){ + opt["value"].append((*it)["default"]); + } + opt["help"] = (*it)["help"]; + addOption(it.key(), opt); + } + } +} + /// Adds the default connector options. Also updates the capabilities structure with the default options. /// Besides the options addBasicConnectorOptions adds, this function also adds port and interface options. void Util::Config::addConnectorOptions(int port, JSON::Value & capabilities) { - JSON::Value option; - option.null(); - option["long"] = "port"; - option["short"] = "p"; - option["arg"] = "integer"; - option["help"] = "TCP port to listen on"; - option["value"].append((long long)port); - addOption("listen_port", option); capabilities["optional"]["port"]["name"] = "TCP port"; - capabilities["optional"]["port"]["help"] = "TCP port to listen on - default if unprovided is " + option["value"][0u].asString(); + capabilities["optional"]["port"]["help"] = "TCP port to listen on"; capabilities["optional"]["port"]["type"] = "uint"; + capabilities["optional"]["port"]["short"] = "p"; capabilities["optional"]["port"]["option"] = "--port"; - capabilities["optional"]["port"]["default"] = option["value"][0u]; + capabilities["optional"]["port"]["default"] = (long long)port; - option.null(); - option["long"] = "interface"; - option["short"] = "i"; - option["arg"] = "string"; - option["help"] = "Interface address to listen on, or 0.0.0.0 for all available interfaces."; - option["value"].append("0.0.0.0"); - addOption("listen_interface", option); capabilities["optional"]["interface"]["name"] = "Interface"; - capabilities["optional"]["interface"]["help"] = "Address of the interface to listen on - default if unprovided is all interfaces"; + capabilities["optional"]["interface"]["help"] = "Address of the interface to listen on"; + capabilities["optional"]["interface"]["default"] = "0.0.0.0"; capabilities["optional"]["interface"]["option"] = "--interface"; + capabilities["optional"]["interface"]["short"] = "i"; capabilities["optional"]["interface"]["type"] = "str"; addBasicConnectorOptions(capabilities); @@ -537,38 +531,16 @@ void Util::Config::addConnectorOptions(int port, JSON::Value & capabilities) { /// Adds the default connector options. Also updates the capabilities structure with the default options. void Util::Config::addBasicConnectorOptions(JSON::Value & capabilities) { - JSON::Value option; - option.null(); - option["long"] = "username"; - option["short"] = "u"; - option["arg"] = "string"; - option["help"] = "Username to drop privileges to, or root to not drop provileges."; - option["value"].append("root"); - addOption("username", option); capabilities["optional"]["username"]["name"] = "Username"; capabilities["optional"]["username"]["help"] = "Username to drop privileges to - default if unprovided means do not drop privileges"; capabilities["optional"]["username"]["option"] = "--username"; + capabilities["optional"]["username"]["short"] = "u"; + capabilities["optional"]["username"]["default"] = "root"; capabilities["optional"]["username"]["type"] = "str"; + addOptionsFromCapabilities(capabilities); - if (capabilities.isMember("socket")) { - option.null(); - option["arg"] = "string"; - option["help"] = "Socket name that can be connected to for this connector."; - option["value"].append(capabilities["socket"]); - addOption("socket", option); - } - - option.null(); - option["long"] = "daemon"; - option["short"] = "d"; - option["long_off"] = "nodaemon"; - option["short_off"] = "n"; - option["help"] = "Whether or not to daemonize the process after starting."; - option["value"].append(0ll); - addOption("daemonize", option); - - option.null(); + JSON::Value option; option["long"] = "json"; option["short"] = "j"; option["help"] = "Output connector info in JSON format, then exit."; @@ -658,17 +630,3 @@ void Util::setUser(std::string username) { } } -/// Will turn the current process into a daemon. -/// Works by calling daemon(1,0): -/// Does not change directory to root. -/// Does redirect output to /dev/null -void Util::Daemonize(bool notClose) { - DEBUG_MSG(DLVL_DEVEL, "Going into background mode..."); - int noClose = 0; - if (notClose) { - noClose = 1; - } - if (daemon(1, noClose) < 0) { - DEBUG_MSG(DLVL_ERROR, "Failed to daemonize: %s", strerror(errno)); - } -} diff --git a/lib/config.h b/lib/config.h index af3d2c26..dcd3bad7 100644 --- a/lib/config.h +++ b/lib/config.h @@ -40,6 +40,7 @@ namespace Util { int serveThreadedSocket(int (*callback)(Socket::Connection & S)); int serveForkedSocket(int (*callback)(Socket::Connection & S)); int servePlainSocket(int (*callback)(Socket::Connection & S)); + void addOptionsFromCapabilities(const JSON::Value & capabilities); void addBasicConnectorOptions(JSON::Value & capabilities); void addConnectorOptions(int port, JSON::Value & capabilities); }; @@ -53,7 +54,4 @@ namespace Util { /// Will set the active user to the named username. void setUser(std::string user); - /// Will turn the current process into a daemon. - void Daemonize(bool notClose = false); - } diff --git a/src/controller/controller.cpp b/src/controller/controller.cpp index a79791ac..69c6dfa4 100644 --- a/src/controller/controller.cpp +++ b/src/controller/controller.cpp @@ -163,10 +163,9 @@ int main(int argc, char ** argv){ stored_user["default"] = "root"; } Controller::conf = Util::Config(argv[0]); - Controller::conf.addOption("listen_port", stored_port); - Controller::conf.addOption("listen_interface", stored_interface); + Controller::conf.addOption("port", stored_port); + Controller::conf.addOption("interface", stored_interface); Controller::conf.addOption("username", stored_user); - Controller::conf.addOption("daemonize", JSON::fromString("{\"long\":\"daemon\", \"short\":\"d\", \"default\":0, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Turns deamon mode on (-d) or off (-n). -d runs quietly in background, -n (default) enables verbose in foreground.\"}")); Controller::conf.addOption("account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}")); Controller::conf.addOption("logfile", JSON::fromString("{\"long\":\"logfile\", \"short\":\"L\", \"arg\":\"string\" \"default\":\"\",\"help\":\"Redirect all standard output to a log file, provided with an argument\"}")); Controller::conf.addOption("configFile", JSON::fromString("{\"long\":\"config\", \"short\":\"c\", \"arg\":\"string\" \"default\":\"config.json\", \"help\":\"Specify a config file other than default.\"}")); @@ -219,10 +218,10 @@ int main(int argc, char ** argv){ //check for port, interface and username in arguments //if they are not there, take them from config file, if there if (Controller::Storage["config"]["controller"]["port"]){ - Controller::conf.getOption("listen_port", true)[0u] = Controller::Storage["config"]["controller"]["port"]; + Controller::conf.getOption("port", true)[0u] = Controller::Storage["config"]["controller"]["port"]; } if (Controller::Storage["config"]["controller"]["interface"]){ - Controller::conf.getOption("listen_interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"]; + Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"]; } if (Controller::Storage["config"]["controller"]["username"]){ Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"]; @@ -285,16 +284,16 @@ int main(int argc, char ** argv){ }else{//logfile is enabled //check for username if ( !Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){ - std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("listen_port") << " and follow the instructions." << std::endl; + std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("port") << " and follow the instructions." << std::endl; } //check for protocols if ( !Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || Controller::Storage["config"]["protocols"].size() < 1){ - std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl; + std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl; } } //check for streams - regardless of logfile setting if ( !Controller::Storage.isMember("streams") || Controller::Storage["streams"].size() < 1){ - std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl; + std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl; } }//connected to a terminal diff --git a/src/output/output.cpp b/src/output/output.cpp index 17a794bb..855d570c 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -98,15 +98,15 @@ namespace Mist { capa["optional"]["startpos"]["name"] = "Starting position in live buffer"; capa["optional"]["startpos"]["help"] = "For live, where in the buffer the stream starts playback by default. 0 = beginning, 1000 = end"; capa["optional"]["startpos"]["option"] = "--startPos"; + capa["optional"]["startpos"]["short"] = "P"; + capa["optional"]["startpos"]["default"] = (long long)500; capa["optional"]["startpos"]["type"] = "uint"; - cfg->addOption("startpos", JSON::fromString("{\"arg\":\"uint\",\"default\":500,\"short\":\"P\",\"long\":\"startPos\",\"help\":\"For live, where in the buffer the stream starts playback by default. 0 = beginning, 1000 = end\"}")); - /* begin-roxlu */ - capa["optional"]["outputfilename"]["type"] = "string"; + capa["optional"]["outputfilename"]["type"] = "str"; capa["optional"]["outputfilename"]["name"] = "outputfilename"; capa["optional"]["outputfilename"]["help"] = "Name of the file into which we write the recording."; capa["optional"]["outputfilename"]["option"] = "--outputFilename"; - cfg->addOption("outputfilename", JSON::fromString("{\"arg\":\"string\",\"default\":\"\",\"short\":\"O\",\"long\":\"outputFilename\",\"help\":\"The name of the file that is used to record a stream.\"}")); - /* end-roxlu */ + capa["optional"]["outputfilename"]["short"] = "O"; + capa["optional"]["outputfilename"]["default"] = ""; } Output::Output(Socket::Connection & conn) : myConn(conn) { diff --git a/src/output/output_push.cpp b/src/output/output_push.cpp index e1a05226..8fed2405 100644 --- a/src/output/output_push.cpp +++ b/src/output/output_push.cpp @@ -332,13 +332,13 @@ namespace Mist { capa["required"]["pushlist"]["name"] = "URL location of the pushing list"; capa["required"]["pushlist"]["help"] = "This is the location that will be checked for pushable data."; capa["required"]["pushlist"]["option"] = "--pushlist"; + capa["required"]["pushlist"]["short"] = "p"; capa["required"]["pushlist"]["type"] = "str"; - cfg->addOption("pushlist", JSON::fromString("{\"arg\":\"string\",\"short\":\"p\",\"long\":\"pushlist\",\"help\":\"This is the location that will be checked for pushable data.\"}")); capa["required"]["destination"]["name"] = "URL location of the destination"; capa["required"]["destination"]["help"] = "This is the location that the date will be pushed to."; capa["required"]["destination"]["option"] = "--destination"; + capa["required"]["destination"]["short"] = "D"; capa["required"]["destination"]["type"] = "str"; - cfg->addOption("destination", JSON::fromString("{\"arg\":\"string\",\"short\":\"D\",\"long\":\"destination\",\"help\":\"This is the location that the data will be checked for pushed to.\"}")); cfg->addBasicConnectorOptions(capa); pConf = cfg; config = cfg; diff --git a/src/output/output_rtsp.cpp b/src/output/output_rtsp.cpp index 57446dc8..52818546 100644 --- a/src/output/output_rtsp.cpp +++ b/src/output/output_rtsp.cpp @@ -53,18 +53,12 @@ namespace Mist { capa["methods"][0u]["type"] = "rtsp"; capa["methods"][0u]["priority"] = 2ll; - JSON::Value maxsend_opt; - maxsend_opt["arg"] = "integer"; - maxsend_opt["default"] = (long long)RTP::MAX_SEND; - maxsend_opt["short"] = "m"; - maxsend_opt["long"] = "max-packet-size"; - maxsend_opt["help"] = "Maximum size of RTP packets in bytes"; - cfg->addOption("maxsend", maxsend_opt); capa["optional"]["maxsend"]["name"] = "Max RTP packet size"; capa["optional"]["maxsend"]["help"] = "Maximum size of RTP packets in bytes"; capa["optional"]["maxsend"]["default"] = (long long)RTP::MAX_SEND; capa["optional"]["maxsend"]["type"] = "uint"; capa["optional"]["maxsend"]["option"] = "--max-packet-size"; + capa["optional"]["maxsend"]["short"] = "m"; cfg->addConnectorOptions(554, capa); config = cfg; diff --git a/src/output/output_ts.cpp b/src/output/output_ts.cpp index 4f74bc65..dc547df0 100644 --- a/src/output/output_ts.cpp +++ b/src/output/output_ts.cpp @@ -40,19 +40,18 @@ namespace Mist { capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add this protocol multiple times using different ports."; capa["required"]["streamname"]["type"] = "str"; capa["required"]["streamname"]["option"] = "--stream"; + capa["required"]["streamname"]["short"] = "s"; capa["optional"]["tracks"]["name"] = "Tracks"; capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces"; capa["optional"]["tracks"]["type"] = "str"; capa["optional"]["tracks"]["option"] = "--tracks"; + capa["optional"]["tracks"]["short"] = "t"; + capa["optional"]["tracks"]["default"] = ""; capa["codecs"][0u][0u].append("HEVC"); capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("AC3"); - cfg->addOption("streamname", - JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); - cfg->addOption("tracks", - JSON::fromString("{\"arg\":\"string\",\"value\":[\"\"],\"short\": \"t\",\"long\":\"tracks\",\"help\":\"The track IDs of the stream that this connector will transmit separated by spaces.\"}")); cfg->addConnectorOptions(8888, capa); config = cfg; } diff --git a/src/output/output_ts_push.cpp b/src/output/output_ts_push.cpp index 41ace0e4..d00f346e 100644 --- a/src/output/output_ts_push.cpp +++ b/src/output/output_ts_push.cpp @@ -51,32 +51,29 @@ namespace Mist { capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add this protocol multiple times using different ports."; capa["required"]["streamname"]["type"] = "str"; capa["required"]["streamname"]["option"] = "--stream"; + capa["required"]["streamname"]["short"] = "s"; capa["required"]["destination"]["name"] = "Destination"; capa["required"]["destination"]["help"] = "Where to push to, in the format protocol://hostname:port. Ie: udp://127.0.0.1:9876"; capa["required"]["destination"]["type"] = "str"; capa["required"]["destination"]["option"] = "--destination"; + capa["required"]["destination"]["short"] = "D"; capa["required"]["udpsize"]["name"] = "UDP Size"; capa["required"]["udpsize"]["help"] = "The number of TS packets to push in a single UDP datagram"; capa["required"]["udpsize"]["type"] = "uint"; capa["required"]["udpsize"]["default"] = 5; capa["required"]["udpsize"]["option"] = "--udpsize"; + capa["required"]["udpsize"]["short"] = "u"; capa["optional"]["tracks"]["name"] = "Tracks"; capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces"; capa["optional"]["tracks"]["type"] = "str"; capa["optional"]["tracks"]["option"] = "--tracks"; + capa["optional"]["tracks"]["short"] = "t"; + capa["optional"]["tracks"]["default"] = ""; capa["codecs"][0u][0u].append("HEVC"); capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("MP3"); cfg->addBasicConnectorOptions(capa); - cfg->addOption("streamname", - JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); - cfg->addOption("destination", - JSON::fromString("{\"arg\":\"string\",\"short\":\"D\",\"long\":\"destination\",\"help\":\"Where to push to, in the format protocol://hostname:port. Ie: udp://127.0.0.1:9876\"}")); - cfg->addOption("tracks", - JSON::fromString("{\"arg\":\"string\",\"value\":[\"\"],\"short\": \"t\",\"long\":\"tracks\",\"help\":\"The track IDs of the stream that this connector will transmit separated by spaces.\"}")); - cfg->addOption("udpsize", - JSON::fromString("{\"arg\":\"integer\",\"value\":5,\"short\": \"u\",\"long\":\"udpsize\",\"help\":\"The number of TS packets to push in a single UDP datagram.\"}")); config = cfg; }