File Coverage

blib/lib/WWW/SVT/Play/Video.pm
Criterion Covered Total %
statement 100 138 72.4
branch 13 26 50.0
condition 0 6 0.0
subroutine 23 31 74.1
pod 13 13 100.0
total 149 214 69.6


line stmt bran cond sub pod time code
1             package WWW::SVT::Play::Video;
2              
3             # Copyright (c) 2012, 2013 - Olof Johansson
4             # All rights reserved.
5             #
6             # This program is free software; you can redistribute it and/or
7             # modify it under the same terms as Perl itself.
8              
9             =head1 NAME
10              
11             WWW::SVT::Play::Video, extract information about videos on SVT Play
12              
13             =head1 SYNOPSIS
14              
15             use WWW::SVT::Play::Video;
16              
17             my $uri = 'http://www.svtplay.se/video/1014238/del-8';
18             my $svtp = WWW::SVT::Play::Video->new($uri);
19             say $svtp->title;
20              
21             if ($svtp->has_hls) {
22             say $svtp->stream(protocol => 'HLS')->url;
23             }
24              
25             =head1 DESCRIPTION
26              
27             =cut
28              
29 1     1   563014 use warnings FATAL => 'all';
  1         3  
  1         60  
30 1     1   6 use strict;
  1         2  
  1         54  
31              
32             our $VERSION = 0.12;
33 1     1   5 use Carp;
  1         2  
  1         80  
34              
35 1     1   725 use WWW::SVT::Play::Video::Stream;
  1         3  
  1         40  
36 1     1   7 use WWW::SVT::Play::Utils qw(playertype_map);
  1         2  
  1         57  
37              
38 1     1   450 use LWP::UserAgent;
  1         278374  
  1         40  
39 1     1   10 use List::Util qw/max/;
  1         2  
  1         81  
40 1     1   5 use Encode;
  1         2  
  1         77  
41 1     1   6 use JSON;
  1         2  
  1         6  
42 1     1   115 use URI;
  1         2  
  1         23  
43 1     1   6992 use URI::QueryParam;
  1         838  
  1         30  
44 1     1   8 use URI::Escape;
  1         2  
  1         77  
45              
46 1     1   7 use Data::Dumper;
  1         2  
  1         1430  
47             $Data::Dumper::Indent = 1;
48              
49             =head1 CONSTRUCTOR
50              
51             =head2 WWW::SVT::Play::Video->new($uri)
52              
53             Construct a WWW::SVT::Play::Video object by passing the URL to
54             the video you're interested in. A second argument consisting of a
55             hashref of options is reserved for future use.
56              
57             =cut
58              
59             sub new {
60 5     5 1 2807 my $class = shift;
61 5         14 my $url = shift;
62 5         20 my $self = bless {}, $class;
63              
64 5         46 my $uri = URI->new($url);
65 5         10093 $uri->query_form('output', 'json');
66              
67 5         577 my $json = _get("$uri");
68 5         3799 $self->{_json} = _get_json($json);
69              
70 5         10 my %streams;
71             my %has; # what kind of streams does this video have?
72              
73 5         9 for my $stream (@{$self->{_json}->{video}->{videoReferences}}) {
  5         20  
74 10         62 my $obj = WWW::SVT::Play::Video::Stream->from_json($stream);
75 10 50       25 next unless defined $obj;
76              
77 10 50       37 if ($obj->is_rtmp) {
78 0         0 $has{rtmp} = 1;
79 0         0 $streams{rtmp}->{$obj->bitrate} = $obj;
80             } else {
81 10         41 $has{$obj->type} = 1;
82 10         30 $streams{$obj->type} = $obj;
83             }
84             }
85              
86 3         12 my @subtitles = map {
87 5         21 $_->{url}
88 5         11 } grep { $_->{url} } @{$self->{_json}->{video}->{subtitleReferences}};
  5         48  
89              
90 5         13 $self->{url} = $url;
91 5         11 $self->{streams} = \%streams;
92 5         19 $self->{filename} = $self->_gen_filename;
93 5         153 $self->{subtitles} = \@subtitles;
94 5         15 $self->{duration} = $self->{_json}->{video}->{materialLength};
95 5         17 $self->{title} = $self->{_json}->{context}->{title};
96              
97 5         15 $self->{has} = \%has;
98              
99 5         24 return $self;
100             }
101              
102             =head2 url
103              
104             $svtp->url
105              
106             Returns the URL to the video's web page after it has been,
107             postprocessed somewhat.
108              
109             =cut
110              
111             sub url {
112 5     5 1 2024 my $self = shift;
113 5         26 return $self->{url};
114             }
115              
116             =head2 stream
117              
118             $svtp->stream( protocol => 'HLS' )
119             $svtp->stream( internal => 'ios' )
120             $svtp->stream( protocol => 'RTMP', bitrate => '1400')
121              
122             my $url = $svtp->stream( protocol => 'HLS' )->url
123             if $svtp->has_hls;
124              
125             Returns the stream object matching the given requirement (or
126             undef if video does not have a matching stream). Takes either SVT
127             Play internal playerType name (named parameter: internal), or the
128             protocol name (named parameter: protocol).
129              
130             Currently supported protocols: HLS, HDS and RTMP. If extracting
131             RTMP, an optional bitrate parameter can be supplied. If this
132             isn't supplied, a hash of bitrate url pairs is returned.
133              
134             RTMP is deprecated and no longer in use by SVT Play. Support for
135             this may be dropped in the future.
136              
137             =cut
138              
139             sub stream {
140 0     0 1 0 my $self = shift;
141 0         0 my %args = @_;
142              
143 0         0 my $type = lc $args{protocol};
144 0   0     0 $type //= playertype_map($args{internal});
145              
146 0         0 my $bitrate = $args{bitrate};
147              
148 0 0 0     0 if ($bitrate and $type eq 'rtmp') {
149 0         0 return $self->{streams}->{rtmp}->{$bitrate};
150             }
151              
152 0 0       0 return $self->{streams}->{$type} if
153             exists $args{protocol};
154             }
155              
156             =head2 title
157              
158             Returns a human readable title for the video.
159              
160             =cut
161              
162             sub title {
163 5     5 1 10 my $self = shift;
164 5         23 return $self->{title};
165             }
166              
167             =head2 $svtp->filename($type)
168              
169             Returns a filename suggestion for the video. If you give the
170             optional type argument, you also get a file extension.
171              
172             =cut
173              
174             sub filename {
175 20     20 1 7674 my $self = shift;
176 20         30 my $type = shift;
177 20         32 my $filename = $self->{filename};
178 20 100       69 my $ext = $self->_ext_by_type($type) if $type;
179 20 100       64 return $self->{filename} unless $ext;
180 15         122 return sprintf "%s.%s", $filename, $ext;
181             }
182              
183             =head2 $svtp->rtmp_bitrates
184              
185             In list context, returns a list of available RTMP stream bitrates
186             for the video. In scalar context, the highest available bitrate
187             is returned.
188              
189             B Currently, we only support listing bitrates for RTMP
190             streams, since they are given to us directly in the JSON blob.
191              
192             =cut
193              
194             sub rtmp_bitrates {
195 0     0 1 0 my $self = shift;
196 0         0 my @streams;
197              
198 0 0       0 return unless $self->has_rtmp;
199 0 0       0 return max keys %{$self->{streams}->{rtmp}} if not wantarray;
  0         0  
200 0         0 return keys %{$self->{streams}->{rtmp}};
  0         0  
201             }
202              
203             =head2 $svtp->format($bitrate)
204              
205             Returns a "guess" of what the format is, by trying to extract a
206             file extension from the stream URL. Of course, the format depends
207             on what bitrate you want, so you have to supply that.
208              
209             =cut
210              
211             sub format {
212 0     0 1 0 my $self = shift;
213 0         0 my $bitrate = shift;
214              
215 0         0 my ($ext) = $self->{streams}->{$bitrate} =~ m#\.(\w+)$#;
216 0         0 return $ext;
217             }
218              
219             =head2 $svtp->subtitles
220              
221             In list context, returns a list of URLs to subtitles. In scalar
222             context, returns the first URL in that list. If there are no
223             subtitles available for this video, returns an empty list (in
224             list context) or undef (in scalar context).
225              
226             =cut
227              
228             sub subtitles {
229 10     10 1 19 my $self = shift;
230 10         15 my @subtitles;
231 10         16 push @subtitles, @{$self->{subtitles}};
  10         32  
232              
233 10 100       57 return @subtitles if wantarray;
234 5         27 return $subtitles[0];
235             }
236              
237             =head2 $svtp->duration
238              
239             Returns the length of the video in seconds.
240              
241             =cut
242              
243             sub duration {
244 10     10 1 1988 my $self = shift;
245 10         49 return $self->{duration};
246             }
247              
248             =head2 $svtp->has_hls
249              
250             =cut
251              
252             sub has_hls {
253 0     0 1 0 my $self = shift;
254 0         0 return $self->{has}->{hls};
255             }
256              
257             =head2 $svtp->has_hds
258              
259             =cut
260              
261             sub has_hds {
262 0     0 1 0 my $self = shift;
263 0         0 return $self->{has}->{hds};
264             }
265              
266             =head2 $svtp->has_rtmp
267              
268             =cut
269              
270             sub has_rtmp {
271 0     0 1 0 my $self = shift;
272 0         0 return $self->{has}->{rtmp};
273             }
274              
275             =head2 $svtp->has_http
276              
277             =cut
278              
279             sub has_http {
280 0     0 1 0 my $self = shift;
281 0         0 return $self->{has}->{http};
282             }
283              
284             ## INTERNAL SUBROUTINES
285             ## These are *not* easter eggs or something like that. Yes, I'm
286             ## looking at you, Woldrich!
287              
288             sub _get {
289 5     5   44 my $uri = shift;
290 5         137 my $ua = LWP::UserAgent->new(
291             agent => "WWW::SVT::Play/$VERSION",
292             );
293 5         56 $ua->env_proxy;
294 5         49 my $resp = $ua->get($uri);
295              
296 5 50       3633 return $resp->decoded_content if $resp->is_success;
297 0         0 die "Failed to fetch $uri: ", $resp->status_line;
298             }
299              
300             sub _get_json {
301 5     5   15 my $json_blob = shift;
302              
303             # I have no idea what I'm doing and why I have to
304             # encode $json_blob as UTF-8... I should probably
305             # go read some perluniintro... :-(
306 5         18 $json_blob = encode('UTF-8', $json_blob);
307 5         452 return decode_json($json_blob);
308             }
309              
310             sub _get_stream_by_protocol {
311 0     0   0 my $self = shift;
312 0         0 my $proto = lc(shift);
313              
314 0         0 my %type_map = (
315             hds => 'flash',
316             hls => 'ios',
317             );
318              
319 0         0 my $internal = $type_map{$proto};
320 0 0       0 if (not defined $internal) {
321 0         0 carp "Unknown protocol $proto";
322 0         0 return;
323             }
324              
325 0         0 return $self->{streams}->{$internal};
326             }
327              
328             sub _gen_filename {
329 5     5   8 my $self = shift;
330              
331 5         24 my $stats_url = URI->new($self->{_json}->{statistics}->{statisticsUrl});
332 5         277 return uri_unescape($stats_url->query);
333             }
334              
335             sub _ext_by_type {
336 15     15   19 my $self = shift;
337 15         18 my $type = shift;
338              
339 15 100       42 return 'mp4' if $type eq 'hls';
340 10 100       26 return 'flv' if $type eq 'hds';
341 5         12 return $type; # better than nothing, i guess...
342             }
343              
344             =head1 COPYRIGHT
345              
346             Copyright (c) 2012, 2013 - Olof Johansson
347              
348             All rights reserved.
349              
350             This program is free software; you can redistribute it and/or
351             modify it under the same terms as Perl itself.
352              
353             =cut
354              
355             1;