File Coverage

blib/lib/FlashVideo/RTMPDownloader.pm
Criterion Covered Total %
statement 27 116 23.2
branch 4 60 6.6
condition 2 33 6.0
subroutine 8 12 66.6
pod 0 4 0.0
total 41 225 18.2


line stmt bran cond sub pod time code
1             # Part of get-flash-videos. See get_flash_videos for copyright.
2             package FlashVideo::RTMPDownloader;
3              
4 2     2   29479 use strict;
  2         4  
  2         66  
5 2     2   10 use base 'FlashVideo::Downloader';
  2         4  
  2         742  
6 2     2   1929 use IPC::Open3;
  2         11769  
  2         120  
7 2     2   18 use Fcntl ();
  2         4  
  2         41  
8 2     2   14 use Symbol qw(gensym);
  2         3  
  2         89  
9 2     2   11 use FlashVideo::Utils;
  2         6  
  2         278  
10              
11 2     2   11 use constant LATEST_RTMPDUMP => 2.2;
  2         4  
  2         5981  
12              
13             sub download {
14 0     0 0 0 my ($self, $rtmp_data) = @_;
15              
16 0         0 $self->{printable_filename} = $rtmp_data->{flv};
17              
18 0         0 my $file = $rtmp_data->{flv} = $self->get_filename($rtmp_data->{flv});
19              
20 0 0 0     0 if (-s $file && !$rtmp_data->{live}) {
21 0         0 info "RTMP output filename '$self->{printable_filename}' already " .
22             "exists, asking to resume...";
23 0         0 $rtmp_data->{resume} = '';
24             }
25              
26 0 0       0 if(my $socks = FlashVideo::Mechanize->new->get_socks_proxy) {
27 0         0 $rtmp_data->{socks} = $socks;
28             }
29              
30 0         0 my($r_fh, $w_fh); # So Perl doesn't close them behind our back..
31              
32 0 0 0     0 if ($rtmp_data->{live} && $self->action eq 'play') {
33             # Playing live stream, we pipe this straight to the player, rather than
34             # saving on disk.
35             # XXX: The use of /dev/fd could go away now rtmpdump supports streaming to
36             # STDOUT.
37              
38 0         0 pipe($r_fh, $w_fh);
39              
40 0         0 my $pid = fork;
41 0 0       0 die "Fork failed" unless defined $pid;
42 0 0       0 if(!$pid) {
43 0         0 fcntl $r_fh, Fcntl::F_SETFD(), ~Fcntl::FD_CLOEXEC();
44 0         0 exec $self->replace_filename($self->player, "/dev/fd/" . fileno $r_fh);
45 0         0 die "Exec failed\n";
46             }
47              
48 0         0 fcntl $w_fh, Fcntl::F_SETFD(), ~Fcntl::FD_CLOEXEC();
49 0         0 $rtmp_data->{flv} = "/dev/fd/" . fileno $w_fh;
50              
51 0         0 $self->{stream} = undef;
52             }
53              
54 0         0 my $prog = $self->get_rtmp_program;
55              
56 0 0 0     0 if($prog eq 'flvstreamer' && ($rtmp_data->{rtmp} =~ /^rtmpe:/ || $rtmp_data->{swfhash})) {
      0        
57 0 0       0 error "FLVStreamer does not support "
58             . ($rtmp_data->{swfhash} ? "SWF hashing" : "RTMPE streams")
59             . ", please install rtmpdump.";
60 0         0 exit 1;
61             }
62              
63 0 0       0 if($self->debug) {
64 0         0 $rtmp_data->{verbose} = undef;
65             }
66              
67 0         0 my($return, @errors) = $self->run($prog, $rtmp_data);
68              
69 0 0 0     0 if($return != 0 && "@errors" =~ /failed to connect/i) {
70             # Try port 443 as an alternative
71 0         0 info "Couldn't connect on RTMP port, trying port 443 instead";
72 0         0 $rtmp_data->{port} = 443;
73 0         0 ($return, @errors) = $self->run($prog, $rtmp_data);
74             }
75              
76 0 0 0     0 if($file ne '-' && (-s $file < 100 || !$self->check_file($file))) {
      0        
77             # This avoids trying to resume an invalid file
78 0         0 error "Download failed, no valid file downloaded";
79 0         0 unlink $rtmp_data->{flv};
80 0         0 return 0;
81             }
82              
83 0 0       0 if($return == 2) {
    0          
84 0         0 info "\nDownload incomplete -- try running again to resume.";
85 0         0 return 0;
86             } elsif($return) {
87 0         0 info "\nDownload failed.";
88 0         0 return 0;
89             }
90              
91 0         0 return -s $file;
92             }
93              
94             sub get_rtmp_program {
95 0 0   0 0 0 if(is_program_on_path("rtmpdump")) {
    0          
96 0         0 return "rtmpdump";
97             } elsif(is_program_on_path("flvstreamer")) {
98 0         0 return "flvstreamer";
99             }
100              
101             # Default to rtmpdump
102 0         0 return "rtmpdump";
103             }
104              
105             sub get_command {
106 2     2 0 80 my($self, $rtmp_data, $debug) = @_;
107              
108 6         107 return map {
109 2         12 my $arg = $_;
110              
111 4 100       38 (ref $rtmp_data->{$arg} eq 'ARRAY'
112             # Arrayref means multiple options of the same type
113             ? (map {
114 2         20 ("--$arg" => $debug
115             ? $self->shell_escape($_)
116 6 100 66     24 : $_) } @{$rtmp_data->{$arg}})
117             # Single argument
118             : ("--$arg" => (($debug && $rtmp_data->{$arg})
119             ? $self->shell_escape($rtmp_data->{$arg})
120             : $rtmp_data->{$arg}) || ()))
121             } keys %$rtmp_data;
122             }
123              
124             sub run {
125 0     0 0   my($self, $prog, $rtmp_data) = @_;
126              
127 0           debug "Running $prog", join(" ", $self->get_command($rtmp_data, 1));
128              
129 0           my($in, $out, $err);
130 0           $err = gensym;
131 0           my $pid = open3($in, $out, $err, $prog, $self->get_command($rtmp_data));
132              
133             # Windows doesn't send signals to child processes, so we need to do it
134             # manually to ensure that we don't have stray rtmpdump processes.
135 0           local $SIG{INT};
136 0 0         if ($^O =~ /mswin/i) {
137             $SIG{INT} = sub {
138 0     0     kill 'TERM', $pid;
139 0           exit;
140 0           };
141             }
142              
143 0           my $complete = 0;
144 0           my $buf = "";
145 0           my @error;
146              
147 0           while(sysread($err, $buf, 128, length $buf) > 0) {
148 0           $buf =~ s/\015\012/\012/g;
149              
150 0           my @parts = split /\015/, $buf;
151 0           $buf = "";
152              
153 0           for(@parts) {
154             # Hide almost everything from rtmpdump, it's less confusing this way.
155 0 0         if(/^((?:DEBUG:|WARNING:|Closing connection|ERROR: No playpath found).*)\n/) {
    0          
    0          
    0          
156 0           debug "$prog: $1";
157             } elsif(/^(ERROR: .*)\012/) {
158 0           push @error, $1;
159 0           info "$prog: $1";
160             } elsif(/^([0-9.]+) kB(?:\s+\/ \S+ sec)?(?: \(([0-9.]+)%\))?/i) {
161 0           $self->{downloaded} = $1 * 1024;
162 0           my $percent = $2;
163              
164 0 0 0       if($self->{downloaded} && $percent != 0) {
165             # An approximation, but should be reasonable if we don't have the size.
166 0           $self->{content_length} = $self->{downloaded} / ($percent / 100);
167             }
168              
169 0           $self->progress;
170             } elsif(/\012$/) {
171 0           for my $l(split /\012/) {
172 0 0         if($l =~ /^[A-F0-9]{,2}(?:\s+[A-F0-9]{2})*\s*$/) {
    0          
    0          
    0          
173 0           debug $l;
174             } elsif($l =~ /Download complete/) {
175 0           $complete = 1;
176             } elsif($l =~ /\s+filesize\s+(\d+)/) {
177 0           $self->{content_length} = $1;
178             } elsif($l =~ /\w/) {
179 0 0         print STDERR "\r" if $self->{downloaded};
180 0           info $l;
181              
182 0 0 0       if($l =~ /^RTMPDump v([0-9.]+)/ && $1 < LATEST_RTMPDUMP) {
183 0           error "==== Using the latest version of RTMPDump (version "
184             . LATEST_RTMPDUMP . ") is recommended. ====";
185             }
186             }
187             }
188              
189 0 0         if(/open3/) {
190 0           error "\nMake sure you have 'rtmpdump' or 'flvstreamer' installed and available on your PATH.";
191 0           return 0;
192             }
193             } else {
194             # Hack; assume lack of newline means it was an incomplete read..
195 0           $buf = $_;
196             }
197             }
198              
199             # Should be about enough..
200 0 0 0       if(defined $self->{stream} && $self->{downloaded} > 300_000) {
201 0           $self->{stream}->();
202             }
203             }
204              
205 0           waitpid $pid, 0;
206 0           return $? >> 8, @error;
207             }
208              
209             1;