| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package WWW::YouTube::Download; |
|
2
|
|
|
|
|
|
|
|
|
3
|
7
|
|
|
7
|
|
488157
|
use strict; |
|
|
7
|
|
|
|
|
79
|
|
|
|
7
|
|
|
|
|
216
|
|
|
4
|
7
|
|
|
7
|
|
39
|
use warnings; |
|
|
7
|
|
|
|
|
14
|
|
|
|
7
|
|
|
|
|
178
|
|
|
5
|
7
|
|
|
7
|
|
153
|
use 5.008001; |
|
|
7
|
|
|
|
|
24
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.63'; |
|
8
|
|
|
|
|
|
|
|
|
9
|
7
|
|
|
7
|
|
51
|
use Carp qw(croak); |
|
|
7
|
|
|
|
|
14
|
|
|
|
7
|
|
|
|
|
356
|
|
|
10
|
7
|
|
|
7
|
|
4086
|
use URI (); |
|
|
7
|
|
|
|
|
49160
|
|
|
|
7
|
|
|
|
|
174
|
|
|
11
|
7
|
|
|
7
|
|
4850
|
use LWP::UserAgent; |
|
|
7
|
|
|
|
|
278858
|
|
|
|
7
|
|
|
|
|
291
|
|
|
12
|
7
|
|
|
7
|
|
3582
|
use JSON::MaybeXS 'JSON'; |
|
|
7
|
|
|
|
|
41523
|
|
|
|
7
|
|
|
|
|
494
|
|
|
13
|
7
|
|
|
7
|
|
3551
|
use HTML::Entities qw/decode_entities/; |
|
|
7
|
|
|
|
|
40981
|
|
|
|
7
|
|
|
|
|
491
|
|
|
14
|
7
|
|
|
7
|
|
53
|
use HTTP::Request; |
|
|
7
|
|
|
|
|
14
|
|
|
|
7
|
|
|
|
|
215
|
|
|
15
|
7
|
|
|
7
|
|
3170
|
use MIME::Type; |
|
|
7
|
|
|
|
|
8712
|
|
|
|
7
|
|
|
|
|
313
|
|
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
$Carp::Internal{ (__PACKAGE__) }++; |
|
18
|
|
|
|
|
|
|
|
|
19
|
7
|
|
|
7
|
|
51
|
use constant DEFAULT_FMT => 18; |
|
|
7
|
|
|
|
|
17
|
|
|
|
7
|
|
|
|
|
1073
|
|
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
my $base_url = 'http://www.youtube.com/watch?v='; |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
sub new { |
|
24
|
32
|
|
|
32
|
1
|
16097
|
my $class = shift; |
|
25
|
32
|
|
|
|
|
73
|
my %args = @_; |
|
26
|
|
|
|
|
|
|
$args{ua} = LWP::UserAgent->new( |
|
27
|
|
|
|
|
|
|
agent => __PACKAGE__.'/'.$VERSION, |
|
28
|
|
|
|
|
|
|
parse_head => 0, |
|
29
|
32
|
50
|
|
|
|
212
|
) unless exists $args{ua}; |
|
30
|
32
|
|
|
|
|
6242
|
bless \%args, $class; |
|
31
|
|
|
|
|
|
|
} |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
for my $name (qw[video_id video_url title user fmt fmt_list suffix]) { |
|
34
|
|
|
|
|
|
|
## no critic (TestingAndDebugging::ProhibitNoStrict) |
|
35
|
7
|
|
|
7
|
|
52
|
no strict 'refs'; |
|
|
7
|
|
|
|
|
17
|
|
|
|
7
|
|
|
|
|
318
|
|
|
36
|
|
|
|
|
|
|
*{"get_$name"} = sub { |
|
37
|
7
|
|
|
7
|
|
41
|
use strict 'refs'; |
|
|
7
|
|
|
|
|
15
|
|
|
|
7
|
|
|
|
|
30854
|
|
|
38
|
0
|
|
|
0
|
|
0
|
my ($self, $video_id) = @_; |
|
39
|
0
|
0
|
|
|
|
0
|
croak "Usage: $self->get_$name(\$video_id|\$watch_url)" unless $video_id; |
|
40
|
0
|
|
|
|
|
0
|
my $data = $self->prepare_download($video_id); |
|
41
|
0
|
|
|
|
|
0
|
return $data->{$name}; |
|
42
|
|
|
|
|
|
|
}; |
|
43
|
|
|
|
|
|
|
} |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub playback_url { |
|
46
|
0
|
|
|
0
|
1
|
0
|
my ($self, $video_id, $args) = @_; |
|
47
|
0
|
0
|
|
|
|
0
|
croak "Usage: $self->playback_url('[video_id|video_url]')" unless $video_id; |
|
48
|
0
|
|
0
|
|
|
0
|
$args ||= {}; |
|
49
|
|
|
|
|
|
|
|
|
50
|
0
|
|
|
|
|
0
|
my $data = $self->prepare_download($video_id); |
|
51
|
0
|
|
0
|
|
|
0
|
my $fmt = $args->{fmt} || $data->{fmt} || DEFAULT_FMT; |
|
52
|
0
|
|
0
|
|
|
0
|
my $video_url = $data->{video_url_map}{$fmt}{url} || croak "this video does not offer format (fmt) $fmt"; |
|
53
|
|
|
|
|
|
|
|
|
54
|
0
|
|
|
|
|
0
|
return $video_url; |
|
55
|
|
|
|
|
|
|
} |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
sub download { |
|
58
|
0
|
|
|
0
|
1
|
0
|
my ($self, $video_id, $args) = @_; |
|
59
|
0
|
0
|
|
|
|
0
|
croak "Usage: $self->download('[video_id|video_url]')" unless $video_id; |
|
60
|
0
|
|
0
|
|
|
0
|
$args ||= {}; |
|
61
|
|
|
|
|
|
|
|
|
62
|
0
|
|
|
|
|
0
|
my $data = $self->prepare_download($video_id); |
|
63
|
|
|
|
|
|
|
|
|
64
|
0
|
|
0
|
|
|
0
|
my $fmt = $args->{fmt} || $data->{fmt} || DEFAULT_FMT; |
|
65
|
|
|
|
|
|
|
|
|
66
|
0
|
|
0
|
|
|
0
|
my $video_url = $data->{video_url_map}{$fmt}{url} || croak "this video has not supported fmt: $fmt"; |
|
67
|
0
|
|
0
|
|
|
0
|
$args->{filename} ||= $args->{file_name}; |
|
68
|
|
|
|
|
|
|
my $filename = $self->_format_filename($args->{filename}, { |
|
69
|
|
|
|
|
|
|
video_id => $data->{video_id}, |
|
70
|
|
|
|
|
|
|
title => $data->{title}, |
|
71
|
|
|
|
|
|
|
user => $data->{user}, |
|
72
|
|
|
|
|
|
|
fmt => $fmt, |
|
73
|
|
|
|
|
|
|
suffix => $data->{video_url_map}{$fmt}{suffix} || _suffix($fmt), |
|
74
|
0
|
|
0
|
|
|
0
|
resolution => $data->{video_url_map}{$fmt}{resolution} || '0x0', |
|
|
|
|
0
|
|
|
|
|
|
75
|
|
|
|
|
|
|
}); |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
$args->{cb} = $self->_default_cb({ |
|
78
|
|
|
|
|
|
|
filename => $filename, |
|
79
|
|
|
|
|
|
|
verbose => $args->{verbose}, |
|
80
|
|
|
|
|
|
|
progress => $args->{progress}, |
|
81
|
|
|
|
|
|
|
overwrite => defined $args->{overwrite} ? $args->{overwrite} : 1, |
|
82
|
0
|
0
|
|
|
|
0
|
}) unless ref $args->{cb} eq 'CODE'; |
|
|
|
0
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
|
|
84
|
0
|
|
|
|
|
0
|
my $res = $self->ua->get($video_url, ':content_cb' => $args->{cb}); |
|
85
|
0
|
0
|
|
|
|
0
|
croak "!! $video_id download failed: ", $res->status_line if $res->is_error; |
|
86
|
|
|
|
|
|
|
} |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
sub _format_filename { |
|
89
|
0
|
|
|
0
|
|
0
|
my ($self, $filename, $data) = @_; |
|
90
|
0
|
0
|
|
|
|
0
|
return "$data->{video_id}.$data->{suffix}" unless defined $filename; |
|
91
|
0
|
0
|
|
|
|
0
|
$filename =~ s#{([^}]+)}#$data->{$1} || "{$1}"#eg; |
|
|
0
|
|
|
|
|
0
|
|
|
92
|
0
|
|
|
|
|
0
|
return $filename; |
|
93
|
|
|
|
|
|
|
} |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
sub _is_supported_fmt { |
|
96
|
0
|
|
|
0
|
|
0
|
my ($self, $video_id, $fmt) = @_; |
|
97
|
0
|
|
|
|
|
0
|
my $data = $self->prepare_download($video_id); |
|
98
|
0
|
0
|
|
|
|
0
|
defined($data->{video_url_map}{$fmt}{url}) ? 1 : 0; |
|
99
|
|
|
|
|
|
|
} |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub _progress { |
|
102
|
0
|
|
|
0
|
|
0
|
my ($self, $args, $total) = @_; |
|
103
|
|
|
|
|
|
|
|
|
104
|
0
|
0
|
|
|
|
0
|
if (not defined $args->{_progress}) { |
|
105
|
0
|
0
|
|
|
|
0
|
eval "require Term::ProgressBar" or return; ## no critic |
|
106
|
0
|
|
|
|
|
0
|
$args->{_progress} = Term::ProgressBar->new( { count => $total, ETA => 'linear', remove => 0, fh => \*STDOUT } ); |
|
107
|
0
|
0
|
|
|
|
0
|
$args->{_progress}->minor( $total > 50_000_000 ? 1 : 0 ); |
|
108
|
0
|
|
|
|
|
0
|
$args->{_progress}->max_update_rate(1); |
|
109
|
|
|
|
|
|
|
|
|
110
|
0
|
|
|
|
|
0
|
$args->{_progress}->message("Total $total"); |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
0
|
|
|
|
|
0
|
return $args->{_progress}; |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
sub _default_cb { |
|
117
|
0
|
|
|
0
|
|
0
|
my ($self, $args) = @_; |
|
118
|
0
|
|
|
|
|
0
|
my ($file, $verbose, $overwrite, $progress) = @$args{qw/filename verbose overwrite progress/}; |
|
119
|
|
|
|
|
|
|
|
|
120
|
0
|
0
|
0
|
|
|
0
|
croak "file exists! $file" if -f $file and !$overwrite; |
|
121
|
0
|
0
|
|
|
|
0
|
open my $wfh, '>', $file or croak $file, " $!"; |
|
122
|
0
|
|
|
|
|
0
|
binmode $wfh; |
|
123
|
|
|
|
|
|
|
|
|
124
|
0
|
0
|
|
|
|
0
|
print "Downloading `$file`\n" if $verbose; |
|
125
|
|
|
|
|
|
|
return sub { |
|
126
|
0
|
|
|
0
|
|
0
|
my ($chunk, $res, $proto) = @_; |
|
127
|
0
|
|
|
|
|
0
|
print $wfh $chunk; # write file |
|
128
|
|
|
|
|
|
|
|
|
129
|
0
|
0
|
0
|
|
|
0
|
if ($verbose || $self->{verbose}) { |
|
130
|
0
|
|
|
|
|
0
|
my $size = tell $wfh; |
|
131
|
0
|
|
|
|
|
0
|
my $total = $res->header('Content-Length'); |
|
132
|
|
|
|
|
|
|
|
|
133
|
0
|
0
|
|
|
|
0
|
if ($progress) { |
|
134
|
0
|
0
|
|
|
|
0
|
if (my $p = $self->_progress($args, $total)){ |
|
135
|
0
|
|
|
|
|
0
|
$p->update($size); |
|
136
|
0
|
|
|
|
|
0
|
return; |
|
137
|
|
|
|
|
|
|
} |
|
138
|
|
|
|
|
|
|
else{ |
|
139
|
0
|
|
|
|
|
0
|
print "(You need Term::ProgressBar module to show progress bar with -P switch)\n"; |
|
140
|
0
|
|
|
|
|
0
|
$progress = 0; |
|
141
|
|
|
|
|
|
|
} |
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
|
|
144
|
0
|
|
|
|
|
0
|
printf "%d/%d (%.2f%%)\r", $size, $total, $size / $total * 100; |
|
145
|
0
|
0
|
|
|
|
0
|
print "\n" if $total == $size; |
|
146
|
|
|
|
|
|
|
} |
|
147
|
0
|
|
|
|
|
0
|
}; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
sub prepare_download { |
|
151
|
1
|
|
|
1
|
1
|
9
|
my ($self, $video_id) = @_; |
|
152
|
1
|
50
|
|
|
|
5
|
croak "Usage: $self->prepare_download('[video_id|watch_url]')" unless $video_id; |
|
153
|
1
|
|
|
|
|
3
|
$video_id = $self->video_id($video_id); |
|
154
|
|
|
|
|
|
|
|
|
155
|
1
|
50
|
|
|
|
7
|
return $self->{cache}{$video_id} if ref $self->{cache}{$video_id} eq 'HASH'; |
|
156
|
|
|
|
|
|
|
|
|
157
|
1
|
|
|
|
|
4
|
my ($title, $user, $video_url_map); |
|
158
|
1
|
|
|
|
|
3
|
my $content = $self->_get_content($video_id); |
|
159
|
1
|
50
|
|
|
|
250
|
if ($self->_is_new($content)) { |
|
160
|
1
|
|
|
|
|
5
|
my $args = $self->_get_args($content); |
|
161
|
1
|
|
|
|
|
4
|
my $player_resp = JSON()->new->decode($args->{player_response}); |
|
162
|
1
|
|
|
|
|
607
|
$video_url_map = $self->_decode_player_response($player_resp); |
|
163
|
1
|
|
|
|
|
12
|
$title = decode_entities $player_resp->{videoDetails}{title}; |
|
164
|
1
|
|
|
|
|
113
|
$user = decode_entities $player_resp->{videoDetails}{author}; |
|
165
|
|
|
|
|
|
|
} else { |
|
166
|
0
|
|
|
|
|
0
|
$title = $self->_fetch_title($content); |
|
167
|
0
|
|
|
|
|
0
|
$user = $self->_fetch_user($content); |
|
168
|
0
|
|
|
|
|
0
|
$video_url_map = $self->_fetch_video_url_map($content); |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
|
|
171
|
1
|
|
|
|
|
5
|
my $fmt_list = []; |
|
172
|
|
|
|
|
|
|
my $sorted = [ |
|
173
|
|
|
|
|
|
|
map { |
|
174
|
2
|
|
|
|
|
6
|
push @$fmt_list, $_->[0]->{fmt}; |
|
175
|
2
|
|
|
|
|
4
|
$_->[0] |
|
176
|
|
|
|
|
|
|
} sort { |
|
177
|
1
|
|
|
|
|
6
|
$b->[1] <=> $a->[1] |
|
178
|
|
|
|
|
|
|
} map { |
|
179
|
1
|
|
|
|
|
5
|
my $resolution = $_->{resolution}; |
|
|
2
|
|
|
|
|
5
|
|
|
180
|
2
|
|
|
|
|
14
|
$resolution =~ s/(\d+)x(\d+)/$1 * $2/e; |
|
|
2
|
|
|
|
|
10
|
|
|
181
|
2
|
|
|
|
|
9
|
[ $_, $resolution ] |
|
182
|
|
|
|
|
|
|
} values %$video_url_map, |
|
183
|
|
|
|
|
|
|
]; |
|
184
|
|
|
|
|
|
|
|
|
185
|
1
|
|
|
|
|
4
|
my $hq_data = $sorted->[0]; |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
return $self->{cache}{$video_id} = { |
|
188
|
|
|
|
|
|
|
video_id => $video_id, |
|
189
|
|
|
|
|
|
|
video_url => $hq_data->{url}, |
|
190
|
|
|
|
|
|
|
title => $title, |
|
191
|
|
|
|
|
|
|
user => $user, |
|
192
|
|
|
|
|
|
|
video_url_map => $video_url_map, |
|
193
|
|
|
|
|
|
|
fmt => $hq_data->{fmt}, |
|
194
|
|
|
|
|
|
|
fmt_list => $fmt_list, |
|
195
|
|
|
|
|
|
|
suffix => $hq_data->{suffix}, |
|
196
|
|
|
|
|
|
|
resolution => $hq_data->{resolution}, |
|
197
|
1
|
|
|
|
|
29
|
}; |
|
198
|
|
|
|
|
|
|
} |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
sub _mime_to_suffix { |
|
201
|
2
|
|
|
2
|
|
15
|
(my $mime = shift) =~ s{^([^;]+);.*}{$1}; |
|
202
|
2
|
|
|
|
|
16
|
my $stype = MIME::Type->new(type => $mime)->subType; |
|
203
|
2
|
0
|
|
|
|
147
|
return $stype eq 'webm' ? 'webm' |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
: $stype eq 'mp4' ? 'mp4' |
|
205
|
|
|
|
|
|
|
: $stype eq '3gp' ? '3gp' |
|
206
|
|
|
|
|
|
|
: 'flv' |
|
207
|
|
|
|
|
|
|
; |
|
208
|
|
|
|
|
|
|
} |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub _decode_player_response { |
|
211
|
1
|
|
|
1
|
|
5
|
my ($self, $player_data) = @_; |
|
212
|
|
|
|
|
|
|
# need to decode $player_data->{streamingData}{formats}; |
|
213
|
1
|
|
|
|
|
3
|
my $fmt_map = { map { $_->{mimeType} => join 'x', $_->{width}, $_->{height} } @{$player_data->{streamingData}{formats}} }; |
|
|
2
|
|
|
|
|
12
|
|
|
|
1
|
|
|
|
|
5
|
|
|
214
|
1
|
|
|
|
|
2
|
my $fmt_url_map = { map { $_->{mimeType} => $_->{url} } @{$player_data->{streamingData}{formats}} }; |
|
|
2
|
|
|
|
|
7
|
|
|
|
1
|
|
|
|
|
11
|
|
|
215
|
|
|
|
|
|
|
# more formats in $player_data->{streamingData}{adaptiveFormats}; |
|
216
|
|
|
|
|
|
|
return +{ |
|
217
|
|
|
|
|
|
|
map { |
|
218
|
2
|
|
|
|
|
8
|
$_->{fmt} => $_, |
|
219
|
|
|
|
|
|
|
} map +{ |
|
220
|
|
|
|
|
|
|
fmt => $_, |
|
221
|
|
|
|
|
|
|
resolution => $fmt_map->{$_}, |
|
222
|
1
|
|
|
|
|
8
|
url => $fmt_url_map->{$_}, |
|
223
|
|
|
|
|
|
|
suffix => _mime_to_suffix($_), |
|
224
|
|
|
|
|
|
|
}, keys %$fmt_map |
|
225
|
|
|
|
|
|
|
}; |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
sub _fetch_title { |
|
229
|
0
|
|
|
0
|
|
0
|
my ($self, $content) = @_; |
|
230
|
|
|
|
|
|
|
|
|
231
|
0
|
0
|
|
|
|
0
|
my ($title) = $content =~ // or return; |
|
232
|
0
|
|
|
|
|
0
|
return decode_entities($title); |
|
233
|
|
|
|
|
|
|
} |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
sub _fetch_user { |
|
236
|
1
|
|
|
1
|
|
12
|
my ($self, $content) = @_; |
|
237
|
|
|
|
|
|
|
|
|
238
|
1
|
50
|
|
|
|
11
|
if( $content =~ /
|
|
0
|
|
|
|
|
|
|
239
|
1
|
|
|
|
|
11
|
return decode_entities($1); |
|
240
|
|
|
|
|
|
|
}elsif( $content =~ /","author":"([^"]+)","/ ){ |
|
241
|
0
|
|
|
|
|
0
|
return decode_entities($1); |
|
242
|
|
|
|
|
|
|
}else{ |
|
243
|
0
|
|
|
|
|
0
|
return; |
|
244
|
|
|
|
|
|
|
} |
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub _fetch_video_url_map { |
|
248
|
0
|
|
|
0
|
|
0
|
my ($self, $content) = @_; |
|
249
|
|
|
|
|
|
|
|
|
250
|
0
|
|
|
|
|
0
|
my $args = $self->_get_args($content); |
|
251
|
0
|
0
|
0
|
|
|
0
|
unless ($args->{fmt_list} and $args->{url_encoded_fmt_stream_map}) { |
|
252
|
0
|
|
|
|
|
0
|
croak 'failed to find video urls'; |
|
253
|
|
|
|
|
|
|
} |
|
254
|
|
|
|
|
|
|
|
|
255
|
0
|
|
|
|
|
0
|
my $fmt_map = _parse_fmt_map($args->{fmt_list}); |
|
256
|
0
|
|
|
|
|
0
|
my $fmt_url_map = _parse_stream_map($args->{url_encoded_fmt_stream_map}); |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
my $video_url_map = +{ |
|
259
|
|
|
|
|
|
|
map { |
|
260
|
0
|
|
|
|
|
0
|
$_->{fmt} => $_, |
|
261
|
|
|
|
|
|
|
} map +{ |
|
262
|
|
|
|
|
|
|
fmt => $_, |
|
263
|
|
|
|
|
|
|
resolution => $fmt_map->{$_}, |
|
264
|
0
|
|
|
|
|
0
|
url => $fmt_url_map->{$_}, |
|
265
|
|
|
|
|
|
|
suffix => _suffix($_), |
|
266
|
|
|
|
|
|
|
}, keys %$fmt_map |
|
267
|
|
|
|
|
|
|
}; |
|
268
|
|
|
|
|
|
|
|
|
269
|
0
|
|
|
|
|
0
|
return $video_url_map; |
|
270
|
|
|
|
|
|
|
} |
|
271
|
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub _get_content { |
|
273
|
0
|
|
|
0
|
|
0
|
my ($self, $video_id) = @_; |
|
274
|
|
|
|
|
|
|
|
|
275
|
0
|
|
|
|
|
0
|
my $url = "$base_url$video_id"; |
|
276
|
|
|
|
|
|
|
|
|
277
|
0
|
|
|
|
|
0
|
my $req = HTTP::Request->new; |
|
278
|
0
|
|
|
|
|
0
|
$req->method('GET'); |
|
279
|
0
|
|
|
|
|
0
|
$req->uri($url); |
|
280
|
0
|
|
|
|
|
0
|
$req->header('Accept-Language' => 'en-US'); |
|
281
|
|
|
|
|
|
|
|
|
282
|
0
|
|
|
|
|
0
|
my $res = $self->ua->request($req); |
|
283
|
0
|
0
|
|
|
|
0
|
croak "GET $url failed. status: ", $res->status_line if $res->is_error; |
|
284
|
|
|
|
|
|
|
|
|
285
|
0
|
|
|
|
|
0
|
return $res->content; |
|
286
|
|
|
|
|
|
|
} |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
sub _get_args { |
|
289
|
2
|
|
|
2
|
|
6
|
my ($self, $content) = @_; |
|
290
|
|
|
|
|
|
|
|
|
291
|
2
|
|
|
|
|
2
|
my $data; |
|
292
|
2
|
|
|
|
|
246
|
for my $line (split "\n", $content) { |
|
293
|
2
|
50
|
|
|
|
8
|
next unless $line; |
|
294
|
2
|
50
|
|
|
|
1613
|
if ($line =~ /the uploader has not made this video available in your country/i) { |
|
|
|
50
|
|
|
|
|
|
|
295
|
0
|
|
|
|
|
0
|
croak 'Video not available in your country'; |
|
296
|
|
|
|
|
|
|
} |
|
297
|
|
|
|
|
|
|
elsif ($line =~ /^.+ytplayer\.config\s*=\s*(\{.*})/) { |
|
298
|
2
|
|
|
|
|
14
|
($data, undef) = JSON->new->utf8(1)->decode_prefix($1); |
|
299
|
2
|
|
|
|
|
872
|
last; |
|
300
|
|
|
|
|
|
|
} |
|
301
|
|
|
|
|
|
|
} |
|
302
|
|
|
|
|
|
|
|
|
303
|
2
|
50
|
|
|
|
12
|
croak 'failed to extract JSON data' unless $data->{args}; |
|
304
|
|
|
|
|
|
|
|
|
305
|
2
|
|
|
|
|
9
|
return $data->{args}; |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
sub _is_new { |
|
309
|
1
|
|
|
1
|
|
5
|
my ($self, $content) = @_; |
|
310
|
1
|
|
|
|
|
4
|
my $args = $self->_get_args($content); |
|
311
|
1
|
50
|
33
|
|
|
31
|
return 1 unless ($args->{fmt_list} and $args->{url_encoded_fmt_stream_map}); |
|
312
|
0
|
|
|
|
|
0
|
return 0; |
|
313
|
|
|
|
|
|
|
} |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub _parse_fmt_map { |
|
316
|
0
|
|
|
0
|
|
0
|
my $param = shift; |
|
317
|
0
|
|
|
|
|
0
|
my $fmt_map = {}; |
|
318
|
0
|
|
|
|
|
0
|
for my $stuff (split ',', $param) { |
|
319
|
0
|
|
|
|
|
0
|
my ($fmt, $resolution) = split '/', $stuff; |
|
320
|
0
|
|
|
|
|
0
|
$fmt_map->{$fmt} = $resolution; |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
0
|
|
|
|
|
0
|
return $fmt_map; |
|
324
|
|
|
|
|
|
|
} |
|
325
|
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
sub _sigdecode { |
|
327
|
0
|
|
|
0
|
|
0
|
my @s = @_; |
|
328
|
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
# based on youtube_dl/extractor/youtube.py from yt-dl.org |
|
330
|
0
|
0
|
|
|
|
0
|
if (@s == 93) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
331
|
0
|
|
|
|
|
0
|
return (reverse(@s[30..86]), $s[88], reverse(@s[6..28])); |
|
332
|
|
|
|
|
|
|
} elsif (@s == 92) { |
|
333
|
0
|
|
|
|
|
0
|
return ($s[25], @s[3..24], $s[0], @s[26..41], $s[79], @s[43..78], $s[91], @s[80..82]); |
|
334
|
|
|
|
|
|
|
} elsif (@s == 91) { |
|
335
|
0
|
|
|
|
|
0
|
return (reverse(@s[28..84]), $s[86], reverse(@s[6..26])); |
|
336
|
|
|
|
|
|
|
} elsif (@s == 90) { |
|
337
|
0
|
|
|
|
|
0
|
return ($s[25], @s[3..24], $s[2], @s[26..39], $s[77], @s[41..76], $s[89], @s[78..80]); |
|
338
|
|
|
|
|
|
|
} elsif (@s == 89) { |
|
339
|
0
|
|
|
|
|
0
|
return (reverse(@s[79..84]), $s[87], reverse(@s[61..77]), $s[0], reverse(@s[4..59])); |
|
340
|
|
|
|
|
|
|
} elsif (@s == 88) { |
|
341
|
0
|
|
|
|
|
0
|
return (@s[7..27], $s[87], @s[29..44], $s[55], @s[46..54], $s[2], @s[56..86], $s[28]); |
|
342
|
|
|
|
|
|
|
} elsif (@s == 87) { |
|
343
|
0
|
|
|
|
|
0
|
return (@s[6..26], $s[4], @s[28..38], $s[27], @s[40..58], $s[2], @s[60..86]); |
|
344
|
|
|
|
|
|
|
} elsif (@s == 86) { |
|
345
|
0
|
|
|
|
|
0
|
return (@s[4..30], $s[3], @s[32..84]); |
|
346
|
|
|
|
|
|
|
} elsif (@s == 85) { |
|
347
|
0
|
|
|
|
|
0
|
return (@s[3..10], $s[0], @s[12..54], $s[84], @s[56..83]); |
|
348
|
|
|
|
|
|
|
} elsif (@s == 84) { |
|
349
|
0
|
|
|
|
|
0
|
return (reverse(@s[71..78]), $s[14], reverse(@s[38..69]), $s[70], reverse(@s[15..36]), $s[80], reverse(@s[0..13])); |
|
350
|
|
|
|
|
|
|
} elsif (@s == 83) { |
|
351
|
0
|
|
|
|
|
0
|
return (reverse(@s[64..80]), $s[0], reverse(@s[1..62]), $s[63]); |
|
352
|
|
|
|
|
|
|
} elsif (@s == 82) { |
|
353
|
0
|
|
|
|
|
0
|
return (reverse(@s[38..80]), $s[7], reverse(@s[8..36]), $s[0], reverse(@s[1..6]), $s[37]); |
|
354
|
|
|
|
|
|
|
} elsif (@s == 81) { |
|
355
|
0
|
|
|
|
|
0
|
return ($s[56], reverse(@s[57..79]), $s[41], reverse(@s[42..55]), $s[80], reverse(@s[35..40]), $s[0], reverse(@s[30..33]), $s[34], reverse(@s[10..28]), $s[29], reverse(@s[1..8]), $s[9]); |
|
356
|
|
|
|
|
|
|
} elsif (@s == 80) { |
|
357
|
0
|
|
|
|
|
0
|
return (@s[1..18], $s[0], @s[20..67], $s[19], @s[69..79]); |
|
358
|
|
|
|
|
|
|
} elsif (@s == 79) { |
|
359
|
0
|
|
|
|
|
0
|
return ($s[54], reverse(@s[55..77]), $s[39], reverse(@s[40..53]), $s[78], reverse(@s[35..38]), $s[0], reverse(@s[30..33]), $s[34], reverse(@s[10..28]), $s[29], reverse(@s[1..8]), $s[9]); |
|
360
|
|
|
|
|
|
|
} |
|
361
|
|
|
|
|
|
|
|
|
362
|
0
|
|
|
|
|
0
|
return (); # fail |
|
363
|
|
|
|
|
|
|
} |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
sub _getsig { |
|
366
|
0
|
|
|
0
|
|
0
|
my $sig = shift; |
|
367
|
0
|
0
|
|
|
|
0
|
croak 'Unable to find signature' unless $sig; |
|
368
|
0
|
|
|
|
|
0
|
my @sig = _sigdecode(split(//, $sig)); |
|
369
|
0
|
0
|
|
|
|
0
|
croak "Unable to decode signature $sig of length " . length($sig) unless @sig; |
|
370
|
0
|
|
|
|
|
0
|
return join('', @sig); |
|
371
|
|
|
|
|
|
|
} |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub _parse_stream_map { |
|
374
|
0
|
|
|
0
|
|
0
|
my $param = shift; |
|
375
|
0
|
|
|
|
|
0
|
my $fmt_url_map = {}; |
|
376
|
0
|
|
|
|
|
0
|
for my $stuff (split ',', $param) { |
|
377
|
0
|
|
|
|
|
0
|
my $uri = URI->new; |
|
378
|
0
|
|
|
|
|
0
|
$uri->query($stuff); |
|
379
|
0
|
|
|
|
|
0
|
my $query = +{ $uri->query_form }; |
|
380
|
0
|
|
0
|
|
|
0
|
my $sig = $query->{sig} || ($query->{s} ? _getsig($query->{s}) : undef); |
|
381
|
0
|
|
|
|
|
0
|
my $url = $query->{url}; |
|
382
|
0
|
0
|
|
|
|
0
|
$fmt_url_map->{$query->{itag}} = $url . ($sig ? '&signature='.$sig : ''); |
|
383
|
|
|
|
|
|
|
} |
|
384
|
|
|
|
|
|
|
|
|
385
|
0
|
|
|
|
|
0
|
return $fmt_url_map; |
|
386
|
|
|
|
|
|
|
} |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
sub ua { |
|
389
|
0
|
|
|
0
|
1
|
0
|
my ($self, $ua) = @_; |
|
390
|
0
|
0
|
|
|
|
0
|
return $self->{ua} unless $ua; |
|
391
|
0
|
0
|
|
|
|
0
|
croak "Usage: $self->ua(\$LWP_LIKE_OBJECT)" unless eval { $ua->isa('LWP::UserAgent') }; |
|
|
0
|
|
|
|
|
0
|
|
|
392
|
0
|
|
|
|
|
0
|
$self->{ua} = $ua; |
|
393
|
|
|
|
|
|
|
} |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
sub _suffix { |
|
396
|
0
|
|
|
0
|
|
0
|
my $fmt = shift; |
|
397
|
0
|
0
|
|
|
|
0
|
return $fmt =~ /43|44|45|46/ ? 'webm' |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
: $fmt =~ /18|22|37|38/ ? 'mp4' |
|
399
|
|
|
|
|
|
|
: $fmt =~ /13|17/ ? '3gp' |
|
400
|
|
|
|
|
|
|
: 'flv' |
|
401
|
|
|
|
|
|
|
; |
|
402
|
|
|
|
|
|
|
} |
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
sub video_id { |
|
405
|
17
|
|
|
17
|
1
|
40
|
my ($self, $stuff) = @_; |
|
406
|
17
|
50
|
|
|
|
44
|
return unless $stuff; |
|
407
|
17
|
100
|
|
|
|
175
|
if ($stuff =~ m{/.*?[?&;!](?:v|video_id)=([^?=/;]+)}) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
408
|
8
|
|
|
|
|
40
|
return $1; |
|
409
|
|
|
|
|
|
|
} |
|
410
|
|
|
|
|
|
|
elsif ($stuff =~ m{/(?:e|v|embed)/([^?=/;]+)}) { |
|
411
|
4
|
|
|
|
|
20
|
return $1; |
|
412
|
|
|
|
|
|
|
} |
|
413
|
|
|
|
|
|
|
elsif ($stuff =~ m{#p/(?:u|search)/\d+/([^&?/]+)}) { |
|
414
|
1
|
|
|
|
|
5
|
return $1; |
|
415
|
|
|
|
|
|
|
} |
|
416
|
|
|
|
|
|
|
elsif ($stuff =~ m{youtu.be/([^?=/;]+)}) { |
|
417
|
1
|
|
|
|
|
6
|
return $1; |
|
418
|
|
|
|
|
|
|
} |
|
419
|
|
|
|
|
|
|
else { |
|
420
|
3
|
|
|
|
|
11
|
return $stuff; |
|
421
|
|
|
|
|
|
|
} |
|
422
|
|
|
|
|
|
|
} |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
sub playlist_id { |
|
425
|
10
|
|
|
10
|
1
|
24
|
my ($self, $stuff) = @_; |
|
426
|
10
|
50
|
|
|
|
25
|
return unless $stuff; |
|
427
|
10
|
100
|
|
|
|
73
|
if ($stuff =~ m{/.*?[?&;!]list=([^?=/;]+)}) { |
|
|
|
100
|
|
|
|
|
|
|
428
|
4
|
|
|
|
|
23
|
return $1; |
|
429
|
|
|
|
|
|
|
} |
|
430
|
|
|
|
|
|
|
elsif ($stuff =~ m{^\s*([FP]L[\w\-]+)\s*$}) { |
|
431
|
3
|
|
|
|
|
14
|
return $1; |
|
432
|
|
|
|
|
|
|
} |
|
433
|
3
|
|
|
|
|
10
|
return $stuff; |
|
434
|
|
|
|
|
|
|
} |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
sub user_id { |
|
437
|
4
|
|
|
4
|
1
|
10
|
my ($self, $stuff) = @_; |
|
438
|
4
|
50
|
|
|
|
11
|
return unless $stuff; |
|
439
|
4
|
100
|
|
|
|
23
|
if ($stuff =~ m{/user/([^?=/;]+)}) { |
|
440
|
3
|
|
|
|
|
17
|
return $1; |
|
441
|
|
|
|
|
|
|
} |
|
442
|
1
|
|
|
|
|
6
|
return $stuff; |
|
443
|
|
|
|
|
|
|
} |
|
444
|
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
1; |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
=pod |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=encoding UTF-8 |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
=head1 NAME |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
WWW::YouTube::Download - WWW::YouTube::Download - Very simple YouTube video download interface |
|
454
|
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
=head1 VERSION |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
version 0.63 |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
use WWW::YouTube::Download; |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
my $client = WWW::YouTube::Download->new; |
|
464
|
|
|
|
|
|
|
$client->download($video_id); |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
my $video_url = $client->get_video_url($video_id); |
|
467
|
|
|
|
|
|
|
my $title = $client->get_title($video_id); # maybe encoded utf8 string. |
|
468
|
|
|
|
|
|
|
my $fmt = $client->get_fmt($video_id); # maybe highest quality. |
|
469
|
|
|
|
|
|
|
my $suffix = $client->get_suffix($video_id); # maybe highest quality file suffix |
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
WWW::YouTube::Download is a library to download videos from YouTube. It relies entirely on |
|
474
|
|
|
|
|
|
|
scraping a video's webpage and does not use YT's /get_video_info URL space. |
|
475
|
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
=head1 METHODS |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=over |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
=item B |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
$client = WWW::YouTube::Download->new; |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
Creates a WWW::YouTube::Download instance. |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
=item B |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
$client->download($video_id); |
|
489
|
|
|
|
|
|
|
$client->download($video_id, { |
|
490
|
|
|
|
|
|
|
fmt => 37, |
|
491
|
|
|
|
|
|
|
filename => 'sample.mp4', # save file name |
|
492
|
|
|
|
|
|
|
}); |
|
493
|
|
|
|
|
|
|
$client->download($video_id, { |
|
494
|
|
|
|
|
|
|
filename => '{title}.{suffix}', # maybe `video_title.mp4` |
|
495
|
|
|
|
|
|
|
}); |
|
496
|
|
|
|
|
|
|
$client->download($video_id, { |
|
497
|
|
|
|
|
|
|
cb => \&callback, |
|
498
|
|
|
|
|
|
|
}); |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
Download the video file. |
|
501
|
|
|
|
|
|
|
The first parameter is passed to YouTube video url. |
|
502
|
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
Allowed arguments: |
|
504
|
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=over |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=item C |
|
508
|
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
Set a callback subroutine, SEE L ':content_cb' |
|
510
|
|
|
|
|
|
|
for details. |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=item C |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
Set the filename, possibly using placeholders to be filled with |
|
515
|
|
|
|
|
|
|
information gathered about the video. |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
C<< filename >> supported format placeholders: |
|
518
|
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
{video_id} |
|
520
|
|
|
|
|
|
|
{title} |
|
521
|
|
|
|
|
|
|
{user} |
|
522
|
|
|
|
|
|
|
{fmt} |
|
523
|
|
|
|
|
|
|
{suffix} |
|
524
|
|
|
|
|
|
|
{resolution} |
|
525
|
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
Output filename is set to C<{video_id}.{suffix}> by default. |
|
527
|
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=item C |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
B<< DEPRECATED >> alternative for C. |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
=item C |
|
533
|
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
set the format to download. Defaults to the best video quality |
|
535
|
|
|
|
|
|
|
(inferred by the available resolutions). |
|
536
|
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
=back |
|
538
|
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
=item B |
|
540
|
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
$client->playback_url($video_id); |
|
542
|
|
|
|
|
|
|
$client->playback_url($video_id, { fmt => 37 }); |
|
543
|
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
Return playback URL of the video. This is direct link to the movie file. |
|
545
|
|
|
|
|
|
|
Function supports only "fmt" option. |
|
546
|
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
=item B |
|
548
|
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
Gather data about the video. A hash reference is returned, with the following |
|
550
|
|
|
|
|
|
|
keys: |
|
551
|
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
=over |
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
=item C |
|
555
|
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
the default, suggested format. It is inferred by selecting the |
|
557
|
|
|
|
|
|
|
alternative with the highest resolution. |
|
558
|
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
=item C |
|
560
|
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
the list of available formats, as an array reference. |
|
562
|
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=item C |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
the filename extension associated to the default format (see C |
|
566
|
|
|
|
|
|
|
above). |
|
567
|
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
=item C |
|
569
|
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
the title of the video |
|
571
|
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
=item C |
|
573
|
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
the YouTube user owning the video |
|
575
|
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
=item C |
|
577
|
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
the video identifier |
|
579
|
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
=item C |
|
581
|
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
the URL of the video associated to the default format (see C |
|
583
|
|
|
|
|
|
|
above). |
|
584
|
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
=item C |
|
586
|
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
an hash reference containing details about all available formats. |
|
588
|
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
=back |
|
590
|
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
The C has one key/value pair for each available format, |
|
592
|
|
|
|
|
|
|
where the key is the format identifier (can be used as C parameter |
|
593
|
|
|
|
|
|
|
for L, for example) and the value is a hash reference with |
|
594
|
|
|
|
|
|
|
the following data: |
|
595
|
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=over |
|
597
|
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
=item C |
|
599
|
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
the format specifier, that can be passed to L |
|
601
|
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
=item C |
|
603
|
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
the resolution as IxI |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=item C |
|
607
|
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
the suffix, providing a hint about the video format (e.g. webm, flv, ...) |
|
609
|
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
=item C |
|
611
|
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
the URL where the video can be found |
|
613
|
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
=back |
|
615
|
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
=item B |
|
617
|
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
$self->ua->agent(); |
|
619
|
|
|
|
|
|
|
$self->ua($LWP_LIKE_OBJECT); |
|
620
|
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
Sets and gets LWP::UserAgent object. |
|
622
|
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
=item B |
|
624
|
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
Parses given URL and returns video ID. |
|
626
|
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
=item B |
|
628
|
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
Parses given URL and returns playlist ID. |
|
630
|
|
|
|
|
|
|
|
|
631
|
|
|
|
|
|
|
=item B |
|
632
|
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
Parses given URL and returns YouTube username. |
|
634
|
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
=item B |
|
636
|
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
=item B |
|
638
|
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
=item B |
|
640
|
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
=item B |
|
642
|
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
=item B |
|
644
|
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
=item B |
|
646
|
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
=item B |
|
648
|
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
=back |
|
650
|
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
=head1 CONTRIBUTORS |
|
652
|
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
yusukebe |
|
654
|
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
=head1 BUG REPORTING |
|
656
|
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
Please use github issues: L<< https://github.com/xaicron/p5-www-youtube-download/issues >>. |
|
658
|
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
660
|
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
L and L. |
|
662
|
|
|
|
|
|
|
L |
|
663
|
|
|
|
|
|
|
L |
|
664
|
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
=head1 AUTHOR |
|
666
|
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
xaicron |
|
668
|
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
670
|
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
This software is copyright (c) 2013 by Yuji Shimada. |
|
672
|
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
|
674
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
|
675
|
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
=cut |
|
677
|
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
__END__ |