~ubuntu-branches/ubuntu/saucy/gnash/saucy-proposed

« back to all changes in this revision

Viewing changes to utilities/rtmpget.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Alexander Sack
  • Date: 2008-10-13 14:29:49 UTC
  • mfrom: (1.1.9 upstream)
  • Revision ID: james.westby@ubuntu.com-20081013142949-f6qdvnu4mn05ltdc
Tags: 0.8.4~~bzr9980-0ubuntu1
* new upstream release 0.8.4 (LP: #240325)
* ship new lib usr/lib/gnash/libmozsdk.so.* in mozilla-plugin-gnash
  - update debian/mozilla-plugin-gnash.install
* ship new lib usr/lib/gnash/libgnashnet.so.* in gnash-common
  - update debian/gnash-common.install
* add basic debian/build_head script to build latest CVS head packages.
  - add debian/build_head
* new sound architecture requires build depend on libsdl1.2-dev
  - update debian/control
* head build script now has been completely migrated to bzr (upstream +
  ubuntu)
  - update debian/build_head
* disable kde gui until klash/qt4 has been fixed; keep kde packages as empty
  packages for now.
  - update debian/rules
  - debian/klash.install
  - debian/klash.links
  - debian/klash.manpages
  - debian/konqueror-plugin-gnash.install
* drop libkonq5-dev build dependency accordingly
  - update debian/control
* don't install headers manually anymore. gnash doesnt provide a -dev
  package after all
  - update debian/rules
* update libs installed in gnash-common; libgnashserver-*.so is not available
  anymore (removed); in turn we add the new libgnashcore-*.so
  - update debian/gnash-common.install
* use -Os for optimization and properly pass CXXFLAGS=$(CFLAGS) to configure
  - update debian/rules
* touch firefox .autoreg in postinst of mozilla plugin
  - update debian/mozilla-plugin-gnash.postinst
* link gnash in ubufox plugins directory for the plugin alternative switcher
  - add debian/mozilla-plugin-gnash.links
* suggest ubufox accordingly
  - update debian/control
* add new required build-depends on libgif-dev
  - update debian/control
* add Xb-Npp-Description and Xb-Npp-File as new plugin database meta data
  - update debian/control

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// rtmpget.cpp:  RTMP file downloader utility
 
2
// 
 
3
//   Copyright (C) 2008 Free Software Foundation, Inc.
 
4
// 
 
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.
 
9
// 
 
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.
 
14
// 
 
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
 
18
// 
 
19
 
 
20
 
 
21
#ifdef HAVE_CONFIG_H
 
22
#include "gnashconfig.h"
 
23
#endif
 
24
 
 
25
#include <string>
 
26
#include <iostream>
 
27
#include <sstream>
 
28
#include <csignal>
 
29
#include <vector>
 
30
#include <sys/mman.h>
 
31
#include <cerrno>
 
32
#include <sys/types.h>
 
33
#include <sys/stat.h>
 
34
#include <fcntl.h>
 
35
#include "gettext.h"
 
36
 
 
37
// classes internal to Gnash
 
38
#include "gnash.h"
 
39
#include "network.h"
 
40
#include "log.h"
 
41
#include "http.h"
 
42
#include "limits.h"
 
43
#include "netstats.h"
 
44
#include "statistics.h"
 
45
#include "gmemory.h"
 
46
#include "arg_parser.h"
 
47
#include "amf.h"
 
48
#include "rtmp.h"
 
49
#include "rtmp_client.h"
 
50
#include "rtmp_msg.h"
 
51
#include "buffer.h"
 
52
#include "network.h"
 
53
#include "element.h"
 
54
#include "URL.h"
 
55
 
 
56
// classes internal to Cygnal
 
57
#include "buffer.h"
 
58
#include "handler.h"
 
59
 
 
60
#ifdef ENABLE_NLS
 
61
#include <locale.h>
 
62
#endif
 
63
 
 
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>
 
69
 
 
70
using gnash::log_debug;
 
71
using namespace std;
 
72
using namespace gnash;
 
73
using namespace amf;
 
74
 
 
75
static void usage();
 
76
static void version_and_copyright();
 
77
static void cntrlc_handler(int sig);
 
78
 
 
79
void connection_handler(Handler::thread_params_t *args);
 
80
void admin_handler(Handler::thread_params_t *args);
 
81
 
 
82
LogFile& dbglogfile = LogFile::getDefaultInstance();
 
83
 
 
84
// The rcfile is loaded and parsed here:
 
85
RcInitFile& rcfile = RcInitFile::getDefaultInstance();
 
86
 
 
87
// Toggles very verbose debugging info from the network Network class
 
88
static bool netdebug = false;
 
89
 
 
90
static struct sigaction  act;
 
91
 
 
92
std::vector<std::string> infiles;
 
93
 
 
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.
 
99
 
 
100
// end of globals
 
101
 
 
102
int
 
103
main(int argc, char *argv[])
 
104
{
 
105
    // Initialize national language support
 
106
#ifdef ENABLE_NLS
 
107
    setlocale (LC_ALL, "");
 
108
    bindtextdomain (PACKAGE, LOCALEDIR);
 
109
    textdomain (PACKAGE);
 
110
#endif
 
111
    
 
112
    // If no command line arguments have been supplied, do nothing but
 
113
    // print the  usage message.
 
114
    if (argc < 2) {
 
115
        usage();
 
116
        exit(0);
 
117
    }
 
118
 
 
119
   const Arg_parser::Option opts[] =
 
120
        {
 
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  }
 
133
        };
 
134
 
 
135
    Arg_parser parser(argc, argv, opts);
 
136
    if( ! parser.error().empty() )      
 
137
    {
 
138
        cout << parser.error() << endl;
 
139
        exit(EXIT_FAILURE);
 
140
    }
 
141
 
 
142
    // Set the log file name before trying to write to
 
143
    // it, or we might get two.
 
144
    dbglogfile.setLogFilename("rtmpget-dbg.log");
 
145
    
 
146
    if (rcfile.verbosityLevel() > 0) {
 
147
        dbglogfile.setVerbosity(rcfile.verbosityLevel());
 
148
    }    
 
149
 
 
150
    RTMPClient client;    
 
151
    short port = 0;
 
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
 
161
        
 
162
    // Handle command line arguments
 
163
    for( int i = 0; i < parser.arguments(); ++i ) {
 
164
        const int code = parser.code(i);
 
165
        try {
 
166
            switch( code ) {
 
167
              case 'h':
 
168
                  version_and_copyright();
 
169
                  usage();
 
170
                  exit(0);
 
171
              case 'V':
 
172
                  version_and_copyright();
 
173
                  exit(0);
 
174
              case 'v':
 
175
                  dbglogfile.setVerbosity();
 
176
                  log_debug (_("Verbose output turned on"));
 
177
                  break;
 
178
              case 'a':
 
179
                  app = parser.argument(i);
 
180
                  break;
 
181
              case 'p':
 
182
                  path = parser.argument(i);
 
183
                  break;
 
184
              case 't':
 
185
                  tcUrl = parser.argument(i);
 
186
                  break;
 
187
              case 's':
 
188
                  swfUrl = parser.argument(i);
 
189
                  break;
 
190
              case 'f':
 
191
                  filename = parser.argument(i);
 
192
                  break;
 
193
              case 'n':
 
194
                  netdebug = true;
 
195
                  break;
 
196
              case 'd':
 
197
                  rcfile.dump();
 
198
                  exit(0);
 
199
                  break;
 
200
              case 0:
 
201
                  infiles.push_back(parser.argument(i));
 
202
                  break;
 
203
              default:
 
204
                  log_error (_("Extraneous argument: %s"), parser.argument(i).c_str());
 
205
            }
 
206
        }
 
207
        
 
208
        catch (Arg_parser::ArgParserException &e) {
 
209
            cerr << _("Error parsing command line options: ") << e.what() << endl;
 
210
            cerr << _("This is a Gnash bug.") << endl;
 
211
        }
 
212
    }
 
213
    
 
214
    if (infiles.empty()) {
 
215
        cerr << _("Error: no input file was specified. Exiting.") << endl;
 
216
        usage();
 
217
        return EXIT_FAILURE;
 
218
    }
 
219
    
 
220
    string url = infiles[0];
 
221
    string portstr;
 
222
    
 
223
    // Trap ^C (SIGINT) so we can kill all the threads
 
224
    act.sa_handler = cntrlc_handler;
 
225
    sigaction (SIGINT, &act, NULL);
 
226
 
 
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
 
232
    }
 
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;
 
240
        } else {
 
241
            hostname = url.substr(start, end - start);
 
242
            if ((protocol == "http") || (protocol == "rtmpt")) {
 
243
                port = RTMPT_PORT;
 
244
            }
 
245
            if (protocol == "rtmp") {
 
246
                port = RTMP_PORT;
 
247
            }
 
248
        }
 
249
        start = end;
 
250
    }
 
251
    end = url.rfind('/');
 
252
    if (end != string::npos) {
 
253
        path = url.substr(start + 1, end - start - 1);
 
254
        start = end;
 
255
        filename = url.substr(end + 1);
 
256
    }
 
257
 
 
258
    start = path.find('?', 0);
 
259
    if (start != string::npos) {
 
260
        end = path.find('/', 0);
 
261
        query = path.substr(0, end);
 
262
        app = query;
 
263
        path = path.substr(end, path.size());
 
264
    } else {
 
265
        app = path;
 
266
    }
 
267
 
 
268
    if (tcUrl.empty()) {
 
269
        tcUrl = protocol + "://" + hostname;
 
270
        if (!portstr.empty()) {
 
271
            tcUrl += ":" + portstr;
 
272
        }
 
273
        if (!query.empty()) {
 
274
            tcUrl += "/" + query;
 
275
        } else {
 
276
            tcUrl += "/" + path;
 
277
        }
 
278
    }
 
279
    
 
280
    if (app.empty()) {
 
281
        // Get the application name
 
282
        app = path;
 
283
        
 
284
        if (!query.empty()) {
 
285
            app = path;
 
286
            app += "?" + query;
 
287
        }
 
288
    }
 
289
 
 
290
    if (swfUrl.empty()) {
 
291
        swfUrl = "mediaplayer.swf";
 
292
    }
 
293
    if (pageUrl.empty()) {
 
294
        pageUrl = "http://gnashdev.org";
 
295
    }
 
296
    
 
297
    if (netdebug) {
 
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;
 
309
    }
 
310
 
 
311
    client.toggleDebug(netdebug);
 
312
    if (client.createClient(hostname, port) == false) {
 
313
        log_error("Can't connect to RTMP server %s", hostname);
 
314
        exit(-1);
 
315
    }
 
316
    
 
317
    client.handShakeRequest();
 
318
    
 
319
    client.clientFinish();
 
320
    
 
321
    // Make a buffer to hold the handshake data.
 
322
    Buffer buf(1537);
 
323
    RTMP::rtmp_head_t *rthead = 0;
 
324
    int ret = 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);
 
332
    
 
333
    if (msg1) {
 
334
                msg1->dump();
 
335
                if (msg1->getStatus() ==  RTMPMsg::NC_CONNECT_SUCCESS) {
 
336
                log_debug("Sent NetConnection Connect message sucessfully");
 
337
                } else {
 
338
            log_error("Couldn't send NetConnection Connect message,");
 
339
        //          exit(-1);
 
340
                }
 
341
    }
 
342
    
 
343
    // make the createStream for ID 3 encoded object
 
344
    log_debug("Sending NetStream::createStream message,");
 
345
    Buffer *buf3 = client.encodeStream(0x2);
 
346
//    buf3->dump();
 
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;
 
350
    if (msg2) {
 
351
        msg2->dump();
 
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();
 
356
        } else {
 
357
            if (msg2->getMethodName() == "close") {     
 
358
                log_debug("Got close packet!!! Exiting...");
 
359
                exit(0);
 
360
            }
 
361
            streamID = 0.0;
 
362
        }
 
363
    } else {
 
364
        log_error("Couldn't send NetStream::createStream message,");
 
365
//      exit(-1);
 
366
    }
 
367
    int id = int(streamID);
 
368
    log_debug("Stream ID returned from createStream is: %d", id);
 
369
    
 
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);
 
377
    if (msg3) {
 
378
        msg3->dump();
 
379
        if (msg3->getStatus() ==  RTMPMsg::NS_PLAY_START) {
 
380
            log_debug("Sent NetStream::play message sucessfully.");
 
381
        } else {
 
382
            log_error("Couldn't send NetStream::play message,");
 
383
//          exit(-1);
 
384
        }
 
385
    }
 
386
 
 
387
    int loop = 20;
 
388
    do {
 
389
        Buffer *msgs = client.recvMsg(1);       // use a 1 second timeout
 
390
        if (msgs == 0) {
 
391
            log_error("Never got any data!");
 
392
            exit(-1);
 
393
        }
 
394
        RTMP::queues_t *que = client.split(msgs);
 
395
        if (que == 0) {
 
396
            log_error("Never got any messages!");
 
397
            exit(-1);
 
398
        }
 
399
 
 
400
#if 0
 
401
        deque<CQue *>::iterator it;
 
402
        for (it = que->begin(); it != que->end(); it++) {
 
403
            CQue *q = *(it);
 
404
            q->dump();
 
405
        }
 
406
#endif
 
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);
 
414
                if (msg2 == 0) {
 
415
//                  log_error("Couldn't process the RTMP message!");
 
416
                    continue;
 
417
                }
 
418
            } else {
 
419
                log_error("Buffer size (%d) out of range at %d", ptr->size(), __LINE__);
 
420
                break;
 
421
            }
 
422
        }
 
423
    } while(loop--);
 
424
 
 
425
//     std::vector<amf::Element *> hell = msg2->getElements();
 
426
//     std::vector<amf::Element *> props = hell[0]->getProperties();
 
427
 
 
428
//     cerr << "HELL Elements: " << hell.size() << endl;
 
429
//     cerr << "HELL Properties: " << props.size() << endl;
 
430
 
 
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;
 
438
 
 
439
//     Element *eell = hell[0]->findProperty("level");
 
440
//     if (eell) {
 
441
//      eell->dump();
 
442
//     }
 
443
//     *eell = hell[0]->findProperty("code");
 
444
//     if (eell) {
 
445
//      eell->dump();
 
446
//     }
 
447
 
 
448
#if 0
 
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);
 
451
    if (fd < 0) {
 
452
        perror("open");
 
453
    }
 
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());
 
459
    close(fd);
 
460
#endif    
 
461
}
 
462
 
 
463
// Trap Control-C so we can cleanly exit
 
464
static void
 
465
cntrlc_handler (int /*sig*/)
 
466
{
 
467
    log_debug(_("Got an interrupt"));
 
468
 
 
469
    exit(-1);
 
470
}
 
471
 
 
472
static void
 
473
version_and_copyright()
 
474
{
 
475
    cout << "rtmpget " << VERSION << endl
 
476
        << 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")
 
481
    << endl;
 
482
}
 
483
 
 
484
 
 
485
static void
 
486
usage()
 
487
{
 
488
        cout << _("rtmpget -- a file downloaded that uses RTMP.") << endl
 
489
        << 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
 
496
        << endl;
 
497
}
 
498
 
 
499
// local Variables:
 
500
// mode: C++
 
501
// indent-tabs-mode: t
 
502
// End: