Skip to content

Commit

Permalink
Adds TrunkPlayerNG Yo
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxwellDPS committed Feb 9, 2024
1 parent d700b4d commit 34e661b
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 119 deletions.
27 changes: 27 additions & 0 deletions docs/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,33 @@ The matching simplestream config to send audio from talkgroup 58918 to TCP port
}
```

##### Trunk Player Next Gen Plugin

**Name:** tpng_uploader
**Library:** libtpng_uploader.so

This plugin makes it easy to connect Trunk Recorder with [Trunk-PlayerNG](https://github.com/Trunk-Player) an Enterprise Grade Trunking API with ACLs, notifications, User management, a filterable API, Mertics, and more... WebUI not guaranteed™ (yee ik grovl grovel). Since [Trunk-PlayerNG](https://github.com/Trunk-Player) is ACL based you dont have to worry about multiple, the server will take what you are allowed to send it. **So you only need one!** Its time to make radio analysis easy!



| Key | Required | Default Value | Type | Description |
| ------- | :------: | ------------- | ------ | ------------------------------------------------------------ |
| url || | string | The base API URL for your TPNG. You need this duh 🙄|
| token || | array | This is your recorder token, since its ACL based it can be used for any systems Trunk-Recorder can hear; TLDR ya only need one 🔥 \*\*mic drop\*\* --- END N3RD FLEX --- |

###### Example Plugin Object:

```json
{ // Yeah its hard IK
"name": "tpng_uploader",
"library": "libtpng_uploader.so",
"url": "http://trunkplayer.io/api/v1",
"token": "96eae4bc-8e31-46a4-9756-66eeb2af3440"
}
```



## talkgroupsFile

This file provides info on the different talkgroups in a trunking system. A lot of this info can be found on the [Radio Reference](http://www.radioreference.com/) website. You need to be a Radio Reference member to download the table for your system preformatted as a CSV file. You can also try clicking on the "List All in one table" link, selecting everything in the table and copying it into a spreadsheet program, and then exporting or saving as a CSV file.
Expand Down
1 change: 1 addition & 0 deletions docs/Playback.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ sidebar_position: 5
By default, Trunk Recorder just dumps a lot of recorded files into a directory. Here are a couple of options to make it easier to browse through recordings and share them on the Internet.
* [OpenMHz](https://github.com/robotastic/trunk-recorder/wiki/Uploading-to-OpenMHz): This is my free hosted platform for sharing recordings
* [Trunk Player](https://github.com/ScanOC/trunk-player): A great Python based server, if you want to you want to run your own
* [Trunk-PlayerNG](https://github.com/Trunk-Player) an Enterprise Grade Trunking API with ACLs, notifications, User management, a filterable API, Mertics, and more... WebUI not guaranteed™ (yee ik grovl grovel). Bur since TP-NG is ACL based you dont have to worry about multiple configs. Its time to make radio analysis easy!
* [Rdio Scanner](https://github.com/chuot/rdio-scanner): Provide a good looking, scanner style interface for listening to Trunk Recorder
* Broadcastify Calls (API): see Radio Reference [forum thread](https://forums.radioreference.com/threads/405236/) and [wiki page](https://wiki.radioreference.com/index.php/Broadcastify-Calls-Trunk-Recorder)
* [Broadcastify via Liquidsoap](https://github.com/robotastic/trunk-recorder/wiki/Streaming-online-to-Broadcastify-with-Liquid-Soap)
Expand Down
8 changes: 3 additions & 5 deletions docs/Plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ The matching simplestream config to send audio from talkgroup 58918 to TCP port
##### Trunk Player Next Gen Plugin

**Name:** tpng_uploader
**Library:** tpng_uploader.so
**Library:** libtpng_uploader.so

This plugin makes it easy to connect Trunk Recorder with [Trunk-PlayerNG](https://github.com/Trunk-Player) an Enterprise Grade Trunking API with ACLs, notifications, User management, a filterable API, Mertics, and more... WebUI not guaranteed™ (yee ik grovl grovel). Since [Trunk-PlayerNG](https://github.com/Trunk-Player) is ACL based you dont have to worry about multiple, the server will take what you are allowed to send it. **So you only need one!** Its time to make radio analysis easy!

Expand All @@ -200,14 +200,12 @@ This plugin makes it easy to connect Trunk Recorder with [Trunk-PlayerNG](https:
| url || | string | The base API URL for your TPNG. You need this duh 🙄|
| token || | array | This is your recorder token, since its ACL based it can be used for any systems Trunk-Recorder can hear; TLDR ya only need one 🔥 \*\*mic drop\*\* --- END N3RD FLEX --- |



##### Example Plugin Object:
###### Example Plugin Object:

```json
{ // Yeah its hard IK
"name": "tpng_uploader",
"library": "tpng_uploader.so",
"library": "libtpng_uploader.so",
"url": "http://trunkplayer.io/api/v1",
"token": "96eae4bc-8e31-46a4-9756-66eeb2af3440"
}
Expand Down
233 changes: 119 additions & 114 deletions plugins/tpng_uploader/tpng_uploader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
#include <time.h>
#include <vector>
#include <fstream>

#include <iostream>

#include "../../trunk-recorder/call_concluder/call_concluder.h"
#include "../../trunk-recorder/plugin_manager/plugin_api.h"
#include "../../trunk-recorder/plugin_manager/plugin_api.h"
#include "../../lib/json.hpp"
#include "../../lib/base64.h"
#include <boost/dll/alias.hpp> // for BOOST_DLL_ALIAS
#include <boost/dll/alias.hpp>
#include <boost/foreach.hpp>
#include <sys/stat.h>

Expand All @@ -22,9 +21,12 @@ struct TPNG_Uploader_Data {
std::string tpng_server;
};

static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
((std::string *)userp)->append((char *)contents, size * nmemb);
return size * nmemb;
};

class TPNG_Uploader : public Plugin_Api {
// float aggr_;
// my_plugin_aggregator() : aggr_(0) {}
TPNG_Uploader_Data data;

public:
Expand Down Expand Up @@ -90,124 +92,146 @@ class TPNG_Uploader : public Plugin_Api {
return json;
}

int upload(Call_Data_t call_info) {
std::string base64_encode_m4a(const std::string& path) {
std::vector<char> temp;

std::string token = this->data.token;
if (token.size() == 0) {
// BOOST_LOG_TRIVIAL(error) << "[" << call_info.short_name << "]\tTG: " << talkgroup_display << "\t " << std::put_time(std::localtime(&start_time), "%c %Z") << "\tOpenMHz Upload failed, API Key not found in config for shortName";
return 0;
std::ifstream infile;
infile.open(path, std::ios::binary); // Open file in binary mode
if (infile.is_open()) {
while (!infile.eof()) {
char c = (char)infile.get();
temp.push_back(c);
}
infile.close();
}
else return "File could not be opened";
std::string ret(temp.begin(), temp.end() - 1);
ret = macaron::Base64::Encode(ret);

return ret;
}

int upload(Call_Data_t call_info) {

std::string token = this->data.token;
std::stringstream json_buffer = create_call_json(call_info);
nlohmann::json call_json = nlohmann::json::parse(json_buffer.str());
std::string base64_audio = base64_encode_m4a(call_info.converted);

std::string base64_audio = macaron::Base64::Encode(call_info.converted);
if (token.size() == 0) {
return 0;
}

nlohmann::json payload = {
{"recorder", this->data.token},
{"name", call_info.status_filename},
{"recorder", token},
{"name", call_info.filename},
{"json", call_json},
{"audio_file", base64_audio}
};
std::string post_data = payload.dump();

CURLM *multi_handle;
CURL *curl;
curl = curl_easy_init();
multi_handle = curl_multi_init();
std::string response_buffer;

std::string url = data.tpng_server + "/radio/transmission/create";
if (curl && multi_handle) {
std::string url = data.tpng_server + "/radio/transmission/create";

/* what URL that receives this POST */
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str());

std::string post_data = payload.dump();
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str());

int still_running = 0;
curl_multi_add_handle(multi_handle, curl);
curl_multi_perform(multi_handle, &still_running);

while (still_running) {
struct timeval timeout;
int rc; /* select() return code */
CURLMcode mc; /* curl_multi_fdset() return code */

fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd = -1;

long curl_timeo = -1;

FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);

/* set a suitable timeout to play around with */
timeout.tv_sec = 1;
timeout.tv_usec = 0;

curl_multi_timeout(multi_handle, &curl_timeo);
if (curl_timeo >= 0) {
timeout.tv_sec = curl_timeo / 1000;
if (timeout.tv_sec > 1)
timeout.tv_sec = 1;
else
timeout.tv_usec = (curl_timeo % 1000) * 1000;
}
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer);

/* get file descriptors from the transfers */
mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
int still_running = 0;
curl_multi_add_handle(multi_handle, curl);
curl_multi_perform(multi_handle, &still_running);

if (mc != CURLM_OK) {
fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc);
break;
}
while (still_running) {
struct timeval timeout;
int rc; /* select() return code */
CURLMcode mc; /* curl_multi_fdset() return code */

/* On success the value of maxfd is guaranteed to be >= -1. We call
select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
to sleep 100ms, which is the minimum suggested value in the
curl_multi_fdset() doc. */

if (maxfd == -1) {
/* Portable sleep for platforms other than Windows. */
struct timeval wait = {0, 100 * 1000}; /* 100ms */
rc = select(0, NULL, NULL, NULL, &wait);
} else {
/* Note that on some platforms 'timeout' may be modified by select().
If you need access to the original value save a copy beforehand. */
rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
}
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd = -1;

long curl_timeo = -1;

FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);

/* set a suitable timeout to play around with */
timeout.tv_sec = 1;
timeout.tv_usec = 0;

switch (rc) {
case -1:
/* select error */
break;
case 0:
default:
/* timeout or readable/writable sockets */
curl_multi_perform(multi_handle, &still_running);
break;
curl_multi_timeout(multi_handle, &curl_timeo);
if (curl_timeo >= 0) {
timeout.tv_sec = curl_timeo / 1000;
if (timeout.tv_sec > 1)
timeout.tv_sec = 1;
else
timeout.tv_usec = (curl_timeo % 1000) * 1000;
}

/* get file descriptors from the transfers */
mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

if (mc != CURLM_OK) {
fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc);
break;
}

/* On success the value of maxfd is guaranteed to be >= -1. We call
select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
to sleep 100ms, which is the minimum suggested value in the
curl_multi_fdset() doc. */

if (maxfd == -1) {
/* Portable sleep for platforms other than Windows. */
struct timeval wait = {0, 100 * 1000}; /* 100ms */
rc = select(0, NULL, NULL, NULL, &wait);
} else {
/* Note that on some platforms 'timeout' may be modified by select().
If you need access to the original value save a copy beforehand. */
rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
}

switch (rc) {
case -1:
/* select error */
break;
case 0:
default:
/* timeout or readable/writable sockets */
curl_multi_perform(multi_handle, &still_running);
break;
}
}
}

long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);

CURLMcode res = curl_multi_cleanup(multi_handle);
CURLMcode res = curl_multi_cleanup(multi_handle);

/* always cleanup */
curl_easy_cleanup(curl);
/* always cleanup */
curl_easy_cleanup(curl);

if (res == CURLM_OK && response_code == 200) {
struct stat file_info;
stat(call_info.converted, &file_info);
if (res == CURLM_OK) {
struct stat file_info;
stat(call_info.converted, &file_info);

BOOST_LOG_TRIVIAL(info) << "[" << call_info.short_name << "]\t\033[0;34m" << call_info.call_num << "C\033[0m\tTG: " << call_info.talkgroup_display << "\tFreq: " << format_freq(call_info.freq) << "\tOpenMHz Upload Success - file size: " << file_info.st_size;
;
return 0;
BOOST_LOG_TRIVIAL(info) << "[" << call_info.short_name << "]\t\033[0;34m" << call_info.call_num << "C\033[0m\tTG: " << call_info.talkgroup_display << "\tFreq: " << format_freq(call_info.freq) << "\tTrunk-PlayerNG Upload Success - file size: " << file_info.st_size;
;
return 0;
}
}
BOOST_LOG_TRIVIAL(error) << "[" << call_info.short_name << "]\t\033[0;34m" << call_info.call_num << "C\033[0m\tTG: " << call_info.talkgroup_display << "\tFreq: " << format_freq(call_info.freq) << "\tTrunk-PlayerNG Upload Error: " << response_buffer;
return 1;
}

Expand All @@ -217,7 +241,7 @@ class TPNG_Uploader : public Plugin_Api {

int parse_config(json config_data) {

// Tests to see if the uploadServer value exists in the config file
// Tests to see if the url value exists in the config file
bool upload_server_exists = config_data.contains("url");
if (!upload_server_exists) {
return 1;
Expand All @@ -227,7 +251,6 @@ class TPNG_Uploader : public Plugin_Api {
this->data.token = config_data.value("token", "");
BOOST_LOG_TRIVIAL(info) << "Trunk-PlayerNG Server: " << this->data.tpng_server;

// from: http://www.zedwood.com/article/cpp-boost-url-regex
boost::regex ex("(http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
boost::cmatch what;

Expand All @@ -240,34 +263,16 @@ class TPNG_Uploader : public Plugin_Api {
BOOST_LOG_TRIVIAL(error) << "Trunk-PlayerNG Server set, but no token is setted\n";
return 1;
}

return 0;
}

/*
int init(Config *config, std::vector<Source *> sources, std::vector<System *> systems) { return 0; }
int start() { return 0; }
int stop() { return 0; }
int poll_one() { return 0; }
int signal(long unitId, const char *signaling_type, gr::blocks::SignalType sig_type, Call *call, System *system, Recorder *recorder) { return 0; }
int audio_stream(Recorder *recorder, float *samples, int sampleCount) { return 0; }
int call_start(Call *call) { return 0; }
int calls_active(std::vector<Call *> calls) { return 0; }
int setup_recorder(Recorder *recorder) { return 0; }
int setup_system(System *system) { return 0; }
int setup_systems(std::vector<System *> systems) { return 0; }
int setup_sources(std::vector<Source *> sources) { return 0; }
int setup_config(std::vector<Source *> sources, std::vector<System *> systems) { return 0; }
int system_rates(std::vector<System *> systems, float timeDiff) { return 0; }
*/
// Factory method
static boost::shared_ptr<TPNG_Uploader> create() {
return boost::shared_ptr<TPNG_Uploader>(
new TPNG_Uploader());
}
};

BOOST_DLL_ALIAS(
TPNG_Uploader::create, // <-- this function is exported with...
create_plugin // <-- ...this alias name
TPNG_Uploader::create,
create_plugin
)
1 change: 1 addition & 0 deletions trunk-recorder/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ bool load_config(string config_file, Config &config, gr::top_block_sptr &tb, std
BOOST_LOG_TRIVIAL(info) << "\n\n-------------------------------------\nPLUGINS\n-------------------------------------\n";
add_internal_plugin("openmhz_uploader", "libopenmhz_uploader.so", data);
add_internal_plugin("broadcastify_uploader", "libbroadcastify_uploader.so", data);
add_internal_plugin("tpng_uploader", "libtpng_uploader.so", data);
add_internal_plugin("unit_script", "libunit_script.so", data);
add_internal_plugin("stat_socket", "libstat_socket.so", data);
initialize_plugins(data, &config, sources, systems);
Expand Down

0 comments on commit 34e661b

Please sign in to comment.