File Coverage

lib/Video/NRK/Cache.pm
Criterion Covered Total %
statement 23 23 100.0
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 31 31 100.0


line stmt bran cond sub pod time code
1 1     1   140933 use v5.37.9;
  1         4  
2 1     1   6 use feature 'class';
  1         3  
  1         297  
3 1     1   10 no warnings 'experimental::class';
  1         2  
  1         123  
4              
5             package Video::NRK::Cache 3.02; # Dist::Zilla doesn't know about class yet
6              
7             class Video::NRK::Cache;
8             # ABSTRACT: Cache NRK Video on Demand broadcasts for offline viewing
9              
10              
11 1     1   25 use Carp qw( croak );
  1         2  
  1         77  
12 1     1   857 use HTTP::Tiny ();
  1         74226  
  1         63  
13 1     1   972 use JSON::PP qw( decode_json );
  1         31166  
  1         97  
14              
15 1     1   2497 use Video::NRK::Cache::ProgramId;
  1         3  
  1         40  
16 1     1   424 use Video::NRK::Cache::Ytdlp;
  1         3  
  1         693  
17              
18              
19             my $version = $Video::NRK::Cache::VERSION ? "/$Video::NRK::Cache::VERSION" : " (DEV)";
20             our %UA_CONFIG = ( agent => "nrkcache$version ", verify_SSL => 1 );
21              
22              
23             field $program_id;
24             field $url :param;
25             field $psapi_base :param //= undef;
26             field $meta :param ||= {};
27             field $options :param ||= {};
28             field $store_class :param //= 'Video::NRK::Cache::Ytdlp';
29             field $store;
30             field $ua = HTTP::Tiny->new( %UA_CONFIG );
31              
32             # :reader
33             method program_id () { $program_id }
34             method url () { $url }
35             method store () { $store }
36              
37              
38             ADJUST {
39             my $prf = Video::NRK::Cache::ProgramId->new(
40             ua => $ua,
41             parse => $url,
42             psapi_base => $psapi_base,
43             );
44             $program_id = $prf->id or die;
45             $psapi_base = $prf->psapi_base;
46             $self->get_metadata() unless defined $meta->{title} && defined $meta->{desc};
47            
48             $store = $store_class->new(
49             program_id => $program_id,
50             url => $url = $prf->url,
51             meta_title => $meta->{title},
52             meta_desc => $meta->{desc},
53             options => $options,
54             );
55             }
56              
57              
58             method get_json ($endpoint) {
59             my $json_url = "$psapi_base$endpoint" =~ s/\{id\}/$program_id/r;
60             my $res = $ua->get($json_url, {headers => { Accept => 'application/json' }});
61             my $error = $res->{status} == 599 ? ": $res->{content}" : "";
62             croak "HTTP error $res->{status} $res->{reason} on $res->{url}$error" unless $res->{success};
63             return decode_json $res->{content};
64             }
65              
66              
67             method get_metadata () {
68             my $json = $self->get_json("/playback/metadata/program/{id}");
69            
70             my $title = $json->{preplay}{titles}{title} // '';
71             if (my $subtitle = $json->{preplay}{titles}{subtitle}) {
72             $title .= " $subtitle" if length $subtitle < 30;
73             # The "subtitle" sometimes contains the full-length description,
74             # which we don't want in the file name.
75             }
76             $title =~ s/$/-$program_id/ unless $title =~ m/$program_id$/;
77             $meta->{title} //= $title;
78            
79             my $description = $json->{preplay}{description} // '';
80             $description .= " ($program_id)";
81             $meta->{desc} //= $description;
82            
83             return $meta;
84             }
85              
86              
87             1;
88              
89             __END__