1
// rtmpget.cpp: RTMP file downloader utility
3
// Copyright (C) 2008 Free Software Foundation, Inc.
5
// This program is free software; you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation; either version 3 of the License, or
8
// (at your option) any later version.
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
// GNU General Public License for more details.
15
// You should have received a copy of the GNU General Public License
16
// along with this program; if not, write to the Free Software
17
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
#include "gnashconfig.h"
32
#include <sys/types.h>
37
// classes internal to Gnash
44
#include "statistics.h"
46
#include "arg_parser.h"
49
#include "rtmp_client.h"
56
// classes internal to Cygnal
64
#include <boost/date_time/gregorian/gregorian.hpp>
65
#include <boost/date_time/time_zone_base.hpp>
66
#include <boost/date_time/posix_time/posix_time.hpp>
67
#include <boost/thread/thread.hpp>
68
#include <boost/bind.hpp>
70
using gnash::log_debug;
72
using namespace gnash;
76
static void version_and_copyright();
77
static void cntrlc_handler(int sig);
79
void connection_handler(Handler::thread_params_t *args);
80
void admin_handler(Handler::thread_params_t *args);
82
LogFile& dbglogfile = LogFile::getDefaultInstance();
84
// The rcfile is loaded and parsed here:
85
RcInitFile& rcfile = RcInitFile::getDefaultInstance();
87
// Toggles very verbose debugging info from the network Network class
88
static bool netdebug = false;
90
static struct sigaction act;
92
std::vector<std::string> infiles;
94
// The next few global variables have to be global because Boost
95
// threads don't take arguments. Since these are set in main() before
96
// any of the threads are started, and it's value should never change,
97
// it's safe to use these without a mutex, as all threads share the
98
// same read-only value.
103
main(int argc, char *argv[])
105
// Initialize national language support
107
setlocale (LC_ALL, "");
108
bindtextdomain (PACKAGE, LOCALEDIR);
109
textdomain (PACKAGE);
112
// If no command line arguments have been supplied, do nothing but
113
// print the usage message.
119
const Arg_parser::Option opts[] =
121
{ 'h', "help", Arg_parser::no },
122
{ 'V', "version", Arg_parser::no },
123
{ 'p', "port-offset", Arg_parser::yes },
124
{ 'v', "verbose", Arg_parser::no },
125
{ 'd', "dump", Arg_parser::no },
126
{ 'a', "app", Arg_parser::yes },
127
{ 'p', "path", Arg_parser::yes },
128
{ 'f', "filename", Arg_parser::yes },
129
{ 't', "tcurl", Arg_parser::yes },
130
{ 's', "swfurl", Arg_parser::yes },
131
{ 'u', "url", Arg_parser::yes },
132
{ 'n', "netdebug", Arg_parser::no }
135
Arg_parser parser(argc, argv, opts);
136
if( ! parser.error().empty() )
138
cout << parser.error() << endl;
142
// Set the log file name before trying to write to
143
// it, or we might get two.
144
dbglogfile.setLogFilename("rtmpget-dbg.log");
146
if (rcfile.verbosityLevel() > 0) {
147
dbglogfile.setVerbosity(rcfile.verbosityLevel());
152
string protocol; // the network protocol, rtmp or http
153
string app; // the application name
154
string path; // the path to the file on the server
155
string query; // any queries for the host
156
string filename; // the filename to play
157
string tcUrl; // the tcUrl field
158
string swfUrl; // the swfUrl field
159
string pageUrl; // the pageUrl field
160
string hostname; // the hostname of the server
162
// Handle command line arguments
163
for( int i = 0; i < parser.arguments(); ++i ) {
164
const int code = parser.code(i);
168
version_and_copyright();
172
version_and_copyright();
175
dbglogfile.setVerbosity();
176
log_debug (_("Verbose output turned on"));
179
app = parser.argument(i);
182
path = parser.argument(i);
185
tcUrl = parser.argument(i);
188
swfUrl = parser.argument(i);
191
filename = parser.argument(i);
201
infiles.push_back(parser.argument(i));
204
log_error (_("Extraneous argument: %s"), parser.argument(i).c_str());
208
catch (Arg_parser::ArgParserException &e) {
209
cerr << _("Error parsing command line options: ") << e.what() << endl;
210
cerr << _("This is a Gnash bug.") << endl;
214
if (infiles.empty()) {
215
cerr << _("Error: no input file was specified. Exiting.") << endl;
220
string url = infiles[0];
223
// Trap ^C (SIGINT) so we can kill all the threads
224
act.sa_handler = cntrlc_handler;
225
sigaction (SIGINT, &act, NULL);
227
// Take a standard URL apart.
228
string::size_type start = url.find(':', 0);
229
if (start != string::npos) {
230
protocol = url.substr(0, start);
231
start += 3; // skip past the "://" part after the protocol
233
string::size_type end = url.find('/', start);
234
if (end != string::npos) {
235
string::size_type pos = url.find(':', start);
236
if (pos != string::npos) {
237
hostname = url.substr(start, pos - start);
238
portstr = url.substr(pos + 1, (end - pos) - 1);
239
port = strtol(portstr.c_str(), NULL, 0) & 0xffff;
241
hostname = url.substr(start, end - start);
242
if ((protocol == "http") || (protocol == "rtmpt")) {
245
if (protocol == "rtmp") {
251
end = url.rfind('/');
252
if (end != string::npos) {
253
path = url.substr(start + 1, end - start - 1);
255
filename = url.substr(end + 1);
258
start = path.find('?', 0);
259
if (start != string::npos) {
260
end = path.find('/', 0);
261
query = path.substr(0, end);
263
path = path.substr(end, path.size());
269
tcUrl = protocol + "://" + hostname;
270
if (!portstr.empty()) {
271
tcUrl += ":" + portstr;
273
if (!query.empty()) {
274
tcUrl += "/" + query;
281
// Get the application name
284
if (!query.empty()) {
290
if (swfUrl.empty()) {
291
swfUrl = "mediaplayer.swf";
293
if (pageUrl.empty()) {
294
pageUrl = "http://gnashdev.org";
298
cerr << "URL is " << url << endl;
299
cerr << "Protocol is " << protocol << endl;
300
cerr << "Host is " << hostname << endl;
301
cerr << "Port is " << port << endl;
302
cerr << "Path is " << path << endl;
303
cerr << "Filename is " << filename << endl;
304
cerr << "App is " << app << endl;
305
cerr << "Query is " << query << endl;
306
cerr << "tcUrl is " << tcUrl << endl;
307
cerr << "swfUrl is " << swfUrl << endl;
308
cerr << "pageUrl is " << pageUrl << endl;
311
client.toggleDebug(netdebug);
312
if (client.createClient(hostname, port) == false) {
313
log_error("Can't connect to RTMP server %s", hostname);
317
client.handShakeRequest();
319
client.clientFinish();
321
// Make a buffer to hold the handshake data.
323
RTMP::rtmp_head_t *rthead = 0;
325
log_debug("Sending NetConnection Connect message,");
326
Buffer *buf2 = client.encodeConnect(app.c_str(), swfUrl.c_str(), tcUrl.c_str(), 615, 124, 1, pageUrl.c_str());
327
// Buffer *buf2 = client.encodeConnect("video/2006/sekt/gate06/tablan_valentin", "mediaplayer.swf", "rtmp://velblod.videolectures.net/video/2006/sekt/gate06/tablan_valentin", 615, 124, 1, "http://gnashdev.org");
328
// Buffer *buf2 = client.encodeConnect("oflaDemo", "http://192.168.1.70/software/gnash/tests/ofla_demo.swf", "rtmp://localhost/oflaDemo/stream", 615, 124, 1, "http://192.168.1.70/software/gnash/tests/index.html");
329
buf2->resize(buf2->size() - 6); // FIXME: encodeConnect returns the wrong size for the buffer!
330
size_t total_size = buf2->size();
331
RTMPMsg *msg1 = client.sendRecvMsg(0x3, RTMP::HEADER_12, total_size, RTMP::INVOKE, RTMPMsg::FROM_CLIENT, buf2);
335
if (msg1->getStatus() == RTMPMsg::NC_CONNECT_SUCCESS) {
336
log_debug("Sent NetConnection Connect message sucessfully");
338
log_error("Couldn't send NetConnection Connect message,");
343
// make the createStream for ID 3 encoded object
344
log_debug("Sending NetStream::createStream message,");
345
Buffer *buf3 = client.encodeStream(0x2);
347
total_size = buf3->size();
348
RTMPMsg *msg2 = client.sendRecvMsg(0x3, RTMP::HEADER_12, total_size, RTMP::INVOKE, RTMPMsg::FROM_CLIENT, buf3);
349
double streamID = 0.0;
352
log_debug("Sent NetStream::createStream message successfully.");
353
std::vector<amf::Element *> hell = msg2->getElements();
354
if (hell.size() > 0) {
355
streamID = hell[0]->to_number();
357
if (msg2->getMethodName() == "close") {
358
log_debug("Got close packet!!! Exiting...");
364
log_error("Couldn't send NetStream::createStream message,");
367
int id = int(streamID);
368
log_debug("Stream ID returned from createStream is: %d", id);
370
// make the NetStream::play() operations for ID 2 encoded object
371
// log_debug("Sending NetStream play message,");
372
Buffer *buf4 = client.encodeStreamOp(0, RTMP::STREAM_PLAY, false, filename.c_str());
373
// Buffer *buf4 = client.encodeStreamOp(0, RTMP::STREAM_PLAY, false, "gate06_tablan_bcueu_01");
374
// log_debug("TRACE: buf4: %s", hexify(buf4->reference(), buf4->size(), true));
375
total_size = buf4->size();
376
RTMPMsg *msg3 = client.sendRecvMsg(0x8, RTMP::HEADER_12, total_size, RTMP::INVOKE, RTMPMsg::FROM_CLIENT, buf4);
379
if (msg3->getStatus() == RTMPMsg::NS_PLAY_START) {
380
log_debug("Sent NetStream::play message sucessfully.");
382
log_error("Couldn't send NetStream::play message,");
389
Buffer *msgs = client.recvMsg(1); // use a 1 second timeout
391
log_error("Never got any data!");
394
RTMP::queues_t *que = client.split(msgs);
396
log_error("Never got any messages!");
401
deque<CQue *>::iterator it;
402
for (it = que->begin(); it != que->end(); it++) {
407
while (que->size()) {
408
cerr << "QUE SIZE: " << que->size() << endl;
409
Buffer *ptr = que->front()->pop();
410
if ((ptr->size() >= 0) && (ptr->size() <= 0xffff)) {
411
que->pop_front(); // delete the item from the queue
412
RTMP::rtmp_head_t *rthead = client.decodeHeader(ptr);
413
msg2 = client.decodeMsgBody(ptr);
415
// log_error("Couldn't process the RTMP message!");
419
log_error("Buffer size (%d) out of range at %d", ptr->size(), __LINE__);
425
// std::vector<amf::Element *> hell = msg2->getElements();
426
// std::vector<amf::Element *> props = hell[0]->getProperties();
428
// cerr << "HELL Elements: " << hell.size() << endl;
429
// cerr << "HELL Properties: " << props.size() << endl;
431
// // cerr << props[0]->getName() << endl;
432
// // cerr << props[0]->to_string() << endl;
433
// cerr << props[0]->getName() << endl;
434
// // cerr << props[0]->to_number() << endl;
435
// cerr << props[1]->getName() << ": " << props[1]->to_string() << endl;
436
// cerr << props[2]->getName() << ": " << props[3]->to_string() << endl;
437
// cerr << props[3]->getName() << ": " << props[3]->to_string() << endl;
439
// Element *eell = hell[0]->findProperty("level");
443
// *eell = hell[0]->findProperty("code");
449
// Write the packet to disk so we can anaylze it with other tools
450
int fd = open("outbuf.raw",O_WRONLY|O_CREAT, S_IRWXU);
454
cout << "Writing packet to disk: \"outbuf.raw\"" << endl;
455
// write(fd, out, 12);
456
// write(fd, outbuf.begin(), amf_obj.totalsize());
457
write(fd, buf2->reference(), buf2->size());
458
write(fd, buf3->reference(), buf3->size());
463
// Trap Control-C so we can cleanly exit
465
cntrlc_handler (int /*sig*/)
467
log_debug(_("Got an interrupt"));
473
version_and_copyright()
475
cout << "rtmpget " << VERSION << endl
477
<< _("Copyright (C) 2008 Free Software Foundation, Inc.\n"
478
"Cygnal comes with NO WARRANTY, to the extent permitted by law.\n"
479
"You may redistribute copies of Cygnal under the terms of the GNU General\n"
480
"Public License. For more information, see the file named COPYING.\n")
488
cout << _("rtmpget -- a file downloaded that uses RTMP.") << endl
490
<< _("Usage: rtmpget [options...]") << endl
491
<< _(" -h, --help Print this help and exit") << endl
492
<< _(" -V, --version Print version information and exit") << endl
493
<< _(" -v, --verbose Output verbose debug info") << endl
494
<< _(" -n, --netdebug Verbose networking debug info") << endl
495
<< _(" -d, --dump display init file to terminal") << endl
501
// indent-tabs-mode: t