1 /*
2  * SRT - Secure, Reliable, Transport
3  * Copyright (c) 2018 Haivision Systems Inc.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  */
10 
11 /*****************************************************************************
12 written by
13    Haivision Systems Inc.
14  *****************************************************************************/
15 #ifdef _WIN32
16 #include <direct.h>
17 #endif
18 #include <iostream>
19 #include <iterator>
20 #include <vector>
21 #include <map>
22 #include <stdexcept>
23 #include <string>
24 #include <csignal>
25 #include <thread>
26 #include <chrono>
27 #include <cassert>
28 #include <sys/stat.h>
29 #include <srt.h>
30 #include <udt.h>
31 #include <common.h>
32 
33 #include "apputil.hpp"
34 #include "uriparser.hpp"
35 #include "logsupport.hpp"
36 #include "socketoptions.hpp"
37 #include "transmitmedia.hpp"
38 #include "verbose.hpp"
39 
40 
41 #ifndef S_ISDIR
42 #define S_ISDIR(mode)  (((mode) & S_IFMT) == S_IFDIR)
43 #endif
44 
45 
46 using namespace std;
47 
48 static bool interrupt = false;
OnINT_ForceExit(int)49 void OnINT_ForceExit(int)
50 {
51     Verb() << "\n-------- REQUESTED INTERRUPT!\n";
52     interrupt = true;
53 }
54 
55 
56 struct FileTransmitConfig
57 {
58     unsigned long chunk_size;
59     bool skip_flushing;
60     bool quiet = false;
61     srt_logging::LogLevel::type loglevel = srt_logging::LogLevel::error;
62     set<srt_logging::LogFA> logfas;
63     string logfile;
64     int bw_report = 0;
65     int stats_report = 0;
66     string stats_out;
67     SrtStatsPrintFormat stats_pf = SRTSTATS_PROFMAT_2COLS;
68     bool full_stats = false;
69 
70     string source;
71     string target;
72 };
73 
74 
PrintOptionHelp(const set<string> & opt_names,const string & value,const string & desc)75 void PrintOptionHelp(const set<string> &opt_names, const string &value, const string &desc)
76 {
77     cerr << "\t";
78     int i = 0;
79     for (auto opt : opt_names)
80     {
81         if (i++) cerr << ", ";
82         cerr << "-" << opt;
83     }
84 
85     if (!value.empty())
86         cerr << ":" << value;
87     cerr << "\t- " << desc << "\n";
88 }
89 
90 
parse_args(FileTransmitConfig & cfg,int argc,char ** argv)91 int parse_args(FileTransmitConfig &cfg, int argc, char** argv)
92 {
93     const OptionName
94         o_chunk     = { "c", "chunk" },
95         o_no_flush  = { "sf", "skipflush" },
96         o_bwreport  = { "r", "bwreport", "report", "bandwidth-report", "bitrate-report" },
97         o_statsrep  = { "s", "stats", "stats-report-frequency" },
98         o_statsout  = { "statsout" },
99         o_statspf   = { "pf", "statspf" },
100         o_statsfull = { "f", "fullstats" },
101         o_loglevel  = { "ll", "loglevel" },
102         o_logfa     = { "logfa" },
103         o_logfile   = { "logfile" },
104         o_quiet     = { "q", "quiet" },
105         o_verbose   = { "v", "verbose" },
106         o_help      = { "h", "help" },
107         o_version   = { "version" };
108 
109     const vector<OptionScheme> optargs = {
110         { o_chunk,        OptionScheme::ARG_ONE },
111         { o_no_flush,     OptionScheme::ARG_NONE },
112         { o_bwreport,     OptionScheme::ARG_ONE },
113         { o_statsrep,     OptionScheme::ARG_ONE },
114         { o_statsout,     OptionScheme::ARG_ONE },
115         { o_statspf,      OptionScheme::ARG_ONE },
116         { o_statsfull,    OptionScheme::ARG_NONE },
117         { o_loglevel,     OptionScheme::ARG_ONE },
118         { o_logfa,        OptionScheme::ARG_ONE },
119         { o_logfile,      OptionScheme::ARG_ONE },
120         { o_quiet,        OptionScheme::ARG_NONE },
121         { o_verbose,      OptionScheme::ARG_NONE },
122         { o_help,         OptionScheme::ARG_NONE },
123         { o_version,      OptionScheme::ARG_NONE }
124     };
125 
126     options_t params = ProcessOptions(argv, argc, optargs);
127 
128           bool print_help    = Option<OutBool>(params, false, o_help);
129     const bool print_version = Option<OutBool>(params, false, o_version);
130 
131     if (params[""].size() != 2 && !print_help && !print_version)
132     {
133         cerr << "ERROR. Invalid syntax. Specify source and target URIs.\n";
134         if (params[""].size() > 0)
135         {
136             cerr << "The following options are passed without a key: ";
137             copy(params[""].begin(), params[""].end(), ostream_iterator<string>(cerr, ", "));
138             cerr << endl;
139         }
140         print_help = true; // Enable help to print it further
141     }
142 
143 
144     if (print_help)
145     {
146         cout << "SRT sample application to transmit files.\n";
147         cerr << "Built with SRT Library version: " << SRT_VERSION << endl;
148         const uint32_t srtver = srt_getversion();
149         const int major = srtver / 0x10000;
150         const int minor = (srtver / 0x100) % 0x100;
151         const int patch = srtver % 0x100;
152         cerr << "SRT Library version: " << major << "." << minor << "." << patch << endl;
153         cerr << "Usage: srt-file-transmit [options] <input-uri> <output-uri>\n";
154         cerr << "\n";
155 
156         PrintOptionHelp(o_chunk, "<chunk=1456>", "max size of data read in one step");
157         PrintOptionHelp(o_no_flush, "", "skip output file flushing");
158         PrintOptionHelp(o_bwreport, "<every_n_packets=0>", "bandwidth report frequency");
159         PrintOptionHelp(o_statsrep, "<every_n_packets=0>", "frequency of status report");
160         PrintOptionHelp(o_statsout, "<filename>", "output stats to file");
161         PrintOptionHelp(o_statspf, "<format=default>", "stats printing format [json|csv|default]");
162         PrintOptionHelp(o_statsfull, "", "full counters in stats-report (prints total statistics)");
163         PrintOptionHelp(o_loglevel, "<level=error>", "log level [fatal,error,info,note,warning]");
164         PrintOptionHelp(o_logfa, "<fas=general,...>", "log functional area [all,general,bstats,control,data,tsbpd,rexmit]");
165         PrintOptionHelp(o_logfile, "<filename="">", "write logs to file");
166         PrintOptionHelp(o_quiet, "", "quiet mode (default off)");
167         PrintOptionHelp(o_verbose, "", "verbose mode (default off)");
168         cerr << "\n";
169         cerr << "\t-h,-help - show this help\n";
170         cerr << "\t-version - print SRT library version\n";
171         cerr << "\n";
172         cerr << "\t<input-uri>  - URI specifying a medium to read from\n";
173         cerr << "\t<output-uri> - URI specifying a medium to write to\n";
174         cerr << "URI syntax: SCHEME://HOST:PORT/PATH?PARAM1=VALUE&PARAM2=VALUE...\n";
175         cerr << "Supported schemes:\n";
176         cerr << "\tsrt: use HOST, PORT, and PARAM for setting socket options\n";
177         cerr << "\tudp: use HOST, PORT and PARAM for some UDP specific settings\n";
178         cerr << "\tfile: file URI or file://con to use stdin or stdout\n";
179 
180         return 2;
181     }
182 
183     if (Option<OutBool>(params, false, o_version))
184     {
185         cerr << "SRT Library version: " << SRT_VERSION << endl;
186         return 2;
187     }
188 
189     cfg.chunk_size    = stoul(Option<OutString>(params, "1456", o_chunk));
190     cfg.skip_flushing = Option<OutBool>(params, false, o_no_flush);
191     cfg.bw_report     = stoi(Option<OutString>(params, "0", o_bwreport));
192     cfg.stats_report  = stoi(Option<OutString>(params, "0", o_statsrep));
193     cfg.stats_out     = Option<OutString>(params, "", o_statsout);
194     const string pf   = Option<OutString>(params, "default", o_statspf);
195     if (pf == "default")
196     {
197         cfg.stats_pf = SRTSTATS_PROFMAT_2COLS;
198     }
199     else if (pf == "json")
200     {
201         cfg.stats_pf = SRTSTATS_PROFMAT_JSON;
202     }
203     else if (pf == "csv")
204     {
205         cfg.stats_pf = SRTSTATS_PROFMAT_CSV;
206     }
207     else
208     {
209         cfg.stats_pf = SRTSTATS_PROFMAT_2COLS;
210         cerr << "ERROR: Unsupported print format: " << pf << endl;
211         return 1;
212     }
213 
214     cfg.full_stats = Option<OutBool>(params, false, o_statsfull);
215     cfg.loglevel   = SrtParseLogLevel(Option<OutString>(params, "error", o_loglevel));
216     cfg.logfas     = SrtParseLogFA(Option<OutString>(params, "", o_logfa));
217     cfg.logfile    = Option<OutString>(params, "", o_logfile);
218     cfg.quiet      = Option<OutBool>(params, false, o_quiet);
219 
220     if (Option<OutBool>(params, false, o_verbose))
221         Verbose::on = !cfg.quiet;
222 
223     cfg.source = params[""].at(0);
224     cfg.target = params[""].at(1);
225 
226     return 0;
227 }
228 
229 
ExtractPath(string path,string & w_dir,string & w_fname)230 void ExtractPath(string path, string& w_dir, string& w_fname)
231 {
232     string directory = path;
233     string filename = "";
234 
235     struct stat state;
236     stat(path.c_str(), &state);
237 
238     if (!S_ISDIR(state.st_mode))
239     {
240         // Extract directory as a butlast part of path
241         size_t pos = path.find_last_of("/");
242         if ( pos == string::npos )
243         {
244             filename = path;
245             directory = ".";
246         }
247         else
248         {
249             directory = path.substr(0, pos);
250             filename = path.substr(pos+1);
251         }
252     }
253 
254     if (directory[0] != '/')
255     {
256         // Glue in the absolute prefix of the current directory
257         // to make it absolute. This is needed to properly interpret
258         // the fixed uri.
259         static const size_t s_max_path = 4096; // don't care how proper this is
260         char tmppath[s_max_path];
261 #ifdef _WIN32
262         const char* gwd = _getcwd(tmppath, s_max_path);
263 #else
264         const char* gwd = getcwd(tmppath, s_max_path);
265 #endif
266         if ( !gwd )
267         {
268             // Don't bother with that now. We need something better for
269             // that anyway.
270             throw std::invalid_argument("Path too long");
271         }
272         const string wd = gwd;
273 
274         directory = wd + "/" + directory;
275     }
276 
277     w_dir = directory;
278     w_fname = filename;
279 }
280 
DoUpload(UriParser & ut,string path,string filename,const FileTransmitConfig & cfg,std::ostream & out_stats)281 bool DoUpload(UriParser& ut, string path, string filename,
282               const FileTransmitConfig &cfg, std::ostream &out_stats)
283 {
284     bool result = false;
285     unique_ptr<Target> tar;
286     SRTSOCKET s = SRT_INVALID_SOCK;
287     bool connected = false;
288     int pollid = -1;
289 
290     ifstream ifile(path, ios::binary);
291     if ( !ifile )
292     {
293         cerr << "Error opening file: '" << path << "'";
294         goto exit;
295     }
296 
297     pollid = srt_epoll_create();
298     if ( pollid < 0 )
299     {
300         cerr << "Can't initialize epoll";
301         goto exit;
302     }
303 
304 
305     while (!interrupt)
306     {
307         if (!tar.get())
308         {
309             tar = Target::Create(ut.makeUri());
310             if (!tar.get())
311             {
312                 cerr << "Unsupported target type: " << ut.uri() << endl;
313                 goto exit;
314             }
315 
316             int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR;
317             if (srt_epoll_add_usock(pollid,
318                     tar->GetSRTSocket(), &events))
319             {
320                 cerr << "Failed to add SRT destination to poll, "
321                     << tar->GetSRTSocket() << endl;
322                 goto exit;
323             }
324             srt::setstreamid(tar->GetSRTSocket(), filename);
325         }
326 
327         s = tar->GetSRTSocket();
328         assert(s != SRT_INVALID_SOCK);
329 
330         SRTSOCKET efd;
331         int efdlen = 1;
332         if (srt_epoll_wait(pollid,
333             0, 0, &efd, &efdlen,
334             100, nullptr, nullptr, 0, 0) < 0)
335         {
336             continue;
337         }
338 
339         assert(efd == s);
340         assert(efdlen == 1);
341 
342         SRT_SOCKSTATUS status = srt_getsockstate(s);
343 
344         switch (status)
345         {
346             case SRTS_LISTENING:
347             {
348                 if (!tar->AcceptNewClient())
349                 {
350                     cerr << "Failed to accept SRT connection" << endl;
351                     goto exit;
352                 }
353 
354                 srt_epoll_remove_usock(pollid, s);
355 
356                 s = tar->GetSRTSocket();
357                 int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR;
358                 if (srt_epoll_add_usock(pollid, s, &events))
359                 {
360                     cerr << "Failed to add SRT client to poll" << endl;
361                     goto exit;
362                 }
363                 cerr << "Target connected (listener)" << endl;
364                 connected = true;
365             }
366             break;
367             case SRTS_CONNECTED:
368             {
369                 if (!connected)
370                 {
371                     cerr << "Target connected (caller)" << endl;
372                     connected = true;
373                 }
374             }
375             break;
376             case SRTS_BROKEN:
377             case SRTS_NONEXIST:
378             case SRTS_CLOSED:
379             {
380                 cerr << "Target disconnected" << endl;
381                 goto exit;
382             }
383             default:
384             {
385                 // No-Op
386             }
387             break;
388         }
389 
390         if (connected)
391         {
392             vector<char> buf(cfg.chunk_size);
393             size_t n = ifile.read(buf.data(), cfg.chunk_size).gcount();
394             size_t shift = 0;
395             while (n > 0)
396             {
397                 int st = tar->Write(buf.data() + shift, n, 0, out_stats);
398                 Verb() << "Upload: " << n << " --> " << st
399                     << (!shift ? string() : "+" + Sprint(shift));
400                 if (st == SRT_ERROR)
401                 {
402                     cerr << "Upload: SRT error: " << srt_getlasterror_str()
403                         << endl;
404                     goto exit;
405                 }
406 
407                 n -= st;
408                 shift += st;
409             }
410 
411             if (ifile.eof())
412             {
413                 cerr << "File sent" << endl;
414                 result = true;
415                 break;
416             }
417 
418             if ( !ifile.good() )
419             {
420                 cerr << "ERROR while reading file\n";
421                 goto exit;
422             }
423 
424         }
425     }
426 
427     if (result && !cfg.skip_flushing)
428     {
429         assert(s != SRT_INVALID_SOCK);
430 
431         // send-flush-loop
432         result = false;
433         while (!interrupt)
434         {
435             size_t bytes;
436             size_t blocks;
437             int st = srt_getsndbuffer(s, &blocks, &bytes);
438             if (st == SRT_ERROR)
439             {
440                 cerr << "Error in srt_getsndbuffer: " << srt_getlasterror_str()
441                     << endl;
442                 goto exit;
443             }
444             if (bytes == 0)
445             {
446                 cerr << "Buffers flushed" << endl;
447                 result = true;
448                 break;
449             }
450             Verb() << "Sending buffer still: bytes=" << bytes << " blocks="
451                 << blocks;
452             srt::sync::this_thread::sleep_for(srt::sync::milliseconds_from(250));
453         }
454     }
455 
456 exit:
457     if (pollid >= 0)
458     {
459         srt_epoll_release(pollid);
460     }
461 
462     return result;
463 }
464 
DoDownload(UriParser & us,string directory,string filename,const FileTransmitConfig & cfg,std::ostream & out_stats)465 bool DoDownload(UriParser& us, string directory, string filename,
466                 const FileTransmitConfig &cfg, std::ostream &out_stats)
467 {
468     bool result = false;
469     unique_ptr<Source> src;
470     SRTSOCKET s = SRT_INVALID_SOCK;
471     bool connected = false;
472     int pollid = -1;
473     string id;
474     ofstream ofile;
475     SRT_SOCKSTATUS status;
476     SRTSOCKET efd;
477     int efdlen = 1;
478 
479     pollid = srt_epoll_create();
480     if ( pollid < 0 )
481     {
482         cerr << "Can't initialize epoll";
483         goto exit;
484     }
485 
486     while (!interrupt)
487     {
488         if (!src.get())
489         {
490             src = Source::Create(us.makeUri());
491             if (!src.get())
492             {
493                 cerr << "Unsupported source type: " << us.uri() << endl;
494                 goto exit;
495             }
496 
497             int events = SRT_EPOLL_IN | SRT_EPOLL_ERR;
498             if (srt_epoll_add_usock(pollid,
499                     src->GetSRTSocket(), &events))
500             {
501                 cerr << "Failed to add SRT source to poll, "
502                     << src->GetSRTSocket() << endl;
503                 goto exit;
504             }
505         }
506 
507         s = src->GetSRTSocket();
508         assert(s != SRT_INVALID_SOCK);
509 
510         if (srt_epoll_wait(pollid,
511             &efd, &efdlen, 0, 0,
512             100, nullptr, nullptr, 0, 0) < 0)
513         {
514             continue;
515         }
516 
517         assert(efd == s);
518         assert(efdlen == 1);
519 
520         status = srt_getsockstate(s);
521         Verb() << "Event with status " << status << "\n";
522 
523         switch (status)
524         {
525             case SRTS_LISTENING:
526             {
527                 if (!src->AcceptNewClient())
528                 {
529                     cerr << "Failed to accept SRT connection" << endl;
530                     goto exit;
531                 }
532 
533                 srt_epoll_remove_usock(pollid, s);
534 
535                 s = src->GetSRTSocket();
536                 int events = SRT_EPOLL_IN | SRT_EPOLL_ERR;
537                 if (srt_epoll_add_usock(pollid, s, &events))
538                 {
539                     cerr << "Failed to add SRT client to poll" << endl;
540                     goto exit;
541                 }
542                 id = srt::getstreamid(s);
543                 cerr << "Source connected (listener), id ["
544                     << id << "]" << endl;
545                 connected = true;
546                 continue;
547             }
548             break;
549             case SRTS_CONNECTED:
550             {
551                 if (!connected)
552                 {
553                     id = srt::getstreamid(s);
554                     cerr << "Source connected (caller), id ["
555                         << id << "]" << endl;
556                     connected = true;
557                 }
558             }
559             break;
560 
561             // No need to do any special action in case of broken.
562             // The app will just try to read and in worst case it will
563             // get an error.
564             case SRTS_BROKEN:
565             cerr << "Connection closed, reading buffer remains\n";
566             break;
567 
568             case SRTS_NONEXIST:
569             case SRTS_CLOSED:
570             {
571                 cerr << "Source disconnected" << endl;
572                 goto exit;
573             }
574             break;
575             default:
576             {
577                 // No-Op
578             }
579             break;
580         }
581 
582         if (connected)
583         {
584             MediaPacket packet(cfg.chunk_size);
585 
586             if (!ofile.is_open())
587             {
588                 const char * fn = id.empty() ? filename.c_str() : id.c_str();
589                 directory.append("/");
590                 directory.append(fn);
591                 ofile.open(directory.c_str(), ios::out | ios::trunc | ios::binary);
592 
593                 if (!ofile.is_open())
594                 {
595                     cerr << "Error opening file [" << directory << "]" << endl;
596                     goto exit;
597                 }
598                 cerr << "Writing output to [" << directory << "]" << endl;
599             }
600 
601             int n = src->Read(cfg.chunk_size, packet, out_stats);
602             if (n == SRT_ERROR)
603             {
604                 cerr << "Download: SRT error: " << srt_getlasterror_str() << endl;
605                 goto exit;
606             }
607 
608             if (n == 0)
609             {
610                 result = true;
611                 cerr << "Download COMPLETE.\n";
612                 break;
613             }
614 
615             // Write to file any amount of data received
616             Verb() << "Download: --> " << n;
617             ofile.write(packet.payload.data(), n);
618             if (!ofile.good())
619             {
620                 cerr << "Error writing file" << endl;
621                 goto exit;
622             }
623 
624         }
625     }
626 
627 exit:
628     if (pollid >= 0)
629     {
630         srt_epoll_release(pollid);
631     }
632 
633     return result;
634 }
635 
Upload(UriParser & srt_target_uri,UriParser & fileuri,const FileTransmitConfig & cfg,std::ostream & out_stats)636 bool Upload(UriParser& srt_target_uri, UriParser& fileuri,
637             const FileTransmitConfig &cfg, std::ostream &out_stats)
638 {
639     if ( fileuri.scheme() != "file" )
640     {
641         cerr << "Upload: source accepted only as a file\n";
642         return false;
643     }
644     // fileuri is source-reading file
645     // srt_target_uri is SRT target
646 
647     string path = fileuri.path();
648     string directory, filename;
649     ExtractPath(path, (directory), (filename));
650     Verb() << "Extract path '" << path << "': directory=" << directory << " filename=" << filename;
651     // Set ID to the filename.
652     // Directory will be preserved.
653 
654     // Add some extra parameters.
655     srt_target_uri["transtype"] = "file";
656 
657     return DoUpload(srt_target_uri, path, filename, cfg, out_stats);
658 }
659 
Download(UriParser & srt_source_uri,UriParser & fileuri,const FileTransmitConfig & cfg,std::ostream & out_stats)660 bool Download(UriParser& srt_source_uri, UriParser& fileuri,
661               const FileTransmitConfig &cfg, std::ostream &out_stats)
662 {
663     if (fileuri.scheme() != "file" )
664     {
665         cerr << "Download: target accepted only as a file\n";
666         return false;
667     }
668 
669     string path = fileuri.path(), directory, filename;
670     ExtractPath(path, (directory), (filename));
671     Verb() << "Extract path '" << path << "': directory=" << directory << " filename=" << filename;
672 
673     // Add some extra parameters.
674     srt_source_uri["transtype"] = "file";
675 
676     return DoDownload(srt_source_uri, directory, filename, cfg, out_stats);
677 }
678 
679 
main(int argc,char ** argv)680 int main(int argc, char** argv)
681 {
682     FileTransmitConfig cfg;
683     const int parse_ret = parse_args(cfg, argc, argv);
684     if (parse_ret != 0)
685         return parse_ret == 1 ? EXIT_FAILURE : 0;
686 
687     //
688     // Set global config variables
689     //
690     if (cfg.chunk_size != SRT_LIVE_MAX_PLSIZE)
691         transmit_chunk_size = cfg.chunk_size;
692     transmit_stats_writer = SrtStatsWriterFactory(cfg.stats_pf);
693     transmit_bw_report = cfg.bw_report;
694     transmit_stats_report = cfg.stats_report;
695     transmit_total_stats = cfg.full_stats;
696 
697     //
698     // Set SRT log levels and functional areas
699     //
700     srt_setloglevel(cfg.loglevel);
701     for (set<srt_logging::LogFA>::iterator i = cfg.logfas.begin(); i != cfg.logfas.end(); ++i)
702         srt_addlogfa(*i);
703 
704     //
705     // SRT log handler
706     //
707     std::ofstream logfile_stream; // leave unused if not set
708     if (!cfg.logfile.empty())
709     {
710         logfile_stream.open(cfg.logfile.c_str());
711         if (!logfile_stream)
712         {
713             cerr << "ERROR: Can't open '" << cfg.logfile.c_str() << "' for writing - fallback to cerr\n";
714         }
715         else
716         {
717             srt::setlogstream(logfile_stream);
718         }
719     }
720 
721     //
722     // SRT stats output
723     //
724     std::ofstream logfile_stats; // leave unused if not set
725     if (cfg.stats_out != "" && cfg.stats_out != "stdout")
726     {
727         logfile_stats.open(cfg.stats_out.c_str());
728         if (!logfile_stats)
729         {
730             cerr << "ERROR: Can't open '" << cfg.stats_out << "' for writing stats. Fallback to stdout.\n";
731             return 1;
732         }
733     }
734     else if (cfg.bw_report != 0 || cfg.stats_report != 0)
735     {
736         g_stats_are_printed_to_stdout = true;
737     }
738 
739     ostream &out_stats = logfile_stats.is_open() ? logfile_stats : cout;
740 
741     // File transmission code
742 
743     UriParser us(cfg.source), ut(cfg.target);
744 
745     Verb() << "SOURCE type=" << us.scheme() << ", TARGET type=" << ut.scheme();
746 
747     signal(SIGINT, OnINT_ForceExit);
748     signal(SIGTERM, OnINT_ForceExit);
749 
750     try
751     {
752         if (us.scheme() == "srt")
753         {
754             if (ut.scheme() != "file")
755             {
756                 cerr << "SRT to FILE should be specified\n";
757                 return 1;
758             }
759             Download(us, ut, cfg, out_stats);
760         }
761         else if (ut.scheme() == "srt")
762         {
763             if (us.scheme() != "file")
764             {
765                 cerr << "FILE to SRT should be specified\n";
766                 return 1;
767             }
768             Upload(ut, us, cfg, out_stats);
769         }
770         else
771         {
772             cerr << "SRT URI must be one of given media.\n";
773             return 1;
774         }
775     }
776     catch (std::exception& x)
777     {
778         cerr << "ERROR: " << x.what() << endl;
779         return 1;
780     }
781 
782 
783     return 0;
784 }
785