File Coverage

lib/Video/NRK/Cache/Store.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   8 use v5.37.9;
  1         3  
2 1     1   3 use feature 'class';
  1         2  
  1         98  
3 1     1   5 no warnings 'experimental::class';
  1         1  
  1         32  
4 1     1   660 use open qw( :utf8 :std );
  1         990  
  1         5  
5              
6             package Video::NRK::Cache::Store 3.02; # Dist::Zilla doesn't know about class yet
7              
8             class Video::NRK::Cache::Store;
9             # ABSTRACT: Store NRK Video on Demand cache on disk (abstract base class)
10              
11              
12 1     1   113 use Carp qw( croak );
  1         1  
  1         39  
13 1     1   17 use Cwd qw( cwd );
  1         1  
  1         39  
14 1     1   870 use Path::Tiny 0.125 qw( path );
  1         14583  
  1         77  
15 1     1   8 use List::Util qw( max );
  1         1  
  1         1255  
16              
17              
18             our $RATE = 1600; # kilo-bytes per second
19             our $NICE = 1;
20             our $DRY_RUN = 0;
21              
22              
23             field $program_id :param;
24             field $url :param;
25             field $meta_title :param = $program_id;
26             field $meta_desc :param = '';
27             field $options :param = {};
28              
29             field $dir;
30             field $file;
31             field $dir_mp4;
32             field %dir_sub;
33             field $nice = $NICE;
34             field $quality = 3;
35              
36             # :reader
37             method program_id () { $program_id }
38             method url () { $url }
39             method meta_title () { $meta_title }
40             method meta_desc () { $meta_desc }
41             method options () { $options }
42              
43             method dir () { $dir }
44             method file () { $file }
45             method dir_mp4 () { $dir_mp4 }
46             method dir_sub () { %dir_sub }
47             method nice () { $nice }
48             method quality () { $quality }
49              
50              
51             ADJUST {
52             $quality = $options->{quality} if defined $options->{quality};
53             $nice = $options->{nice} if defined $options->{nice};
54            
55             $dir = path(cwd)->child("$meta_title");
56             $file = path(cwd)->child("$meta_title.mp4");
57             $dir_mp4 = $dir->child("$program_id.mp4");
58             $dir_sub{nb_ttv} = $dir->child("$program_id.nb-ttv.vtt");
59             $dir_sub{nb_nor} = $dir->child("$program_id.nb-nor.vtt");
60             $dir_sub{nn_ttv} = $dir->child("$program_id.nn-ttv.vtt");
61             $dir_sub{nn_nor} = $dir->child("$program_id.nn-nor.vtt");
62             $dir_sub{ffmpeg} = $dir->child("$program_id.ffmpeg.vtt");
63             }
64              
65              
66             method create () {
67             $self->prep;
68             $self->download;
69             $self->ffmpeg;
70             $self->post;
71             }
72              
73              
74             method rate () {
75             return unless $nice;
76             return max 1, int $RATE / 2 ** ($nice - 1);
77             }
78              
79              
80             method prep () {
81             croak "File exists: $file" if $file->exists;
82             $dir->mkdir;
83             }
84              
85              
86             method ffmpeg () {
87             my @codecs = (
88             '-c:v' => $options->{vcodec} ? split ' ', $options->{vcodec} : 'copy',
89             '-c:a' => $options->{acodec} ? split ' ', $options->{acodec} : 'copy',
90             );
91             croak "acodec/vcodec must have uneven number of items" if @codecs & 1; # won't catch the mess if both are wrong
92             my $dir_sub = $dir_sub{nb_ttv}->exists ? $dir_sub{nb_ttv} :
93             $dir_sub{nn_ttv}->exists ? $dir_sub{nn_ttv} :
94             $dir_sub{nb_nor}->exists ? $dir_sub{nb_nor} :
95             $dir_sub{nn_nor}->exists ? $dir_sub{nn_nor} :
96             undef;
97             if ($dir_sub) {
98            
99             # As of 2025, many NRK subs lack a space after . It's a problem
100             # with the original data: The NRK web player is affected, too.
101             my $sub = path($dir_sub)->slurp_raw;
102             $sub =~ s{(?=\w)}{ }g;
103             path($dir_sub{ffmpeg})->spew_raw($sub);
104            
105             @codecs = (
106             -f => 'srt', -i => "$dir_sub{ffmpeg}",
107             qw( -map 0:0 -map 0:1 -map 1:0 ), @codecs, qw( -c:s mov_text ),
108             );
109             # https://trac.ffmpeg.org/wiki/Map
110             }
111             $self->system( 'ffmpeg',
112             -i => "$dir_mp4",
113             @codecs,
114             -metadata => "description=$meta_desc",
115             -metadata => "comment=$url",
116             -metadata => "copyright=NRK",
117             -metadata => "episode_id=$program_id",
118             "$file",
119             );
120             }
121              
122              
123             method post () {
124             $dir->remove_tree;
125             }
126              
127              
128             method system ($cmd, @args) {
129             say join " ", $cmd, @args;
130             system $cmd, @args unless $DRY_RUN;
131             $self->_ipc_error_check($!, $?, $cmd);
132             }
133              
134              
135             method _ipc_error_check ($os_err, $code, $cmd) {
136             utf8::decode $os_err;
137             croak "$cmd failed to execute: $os_err" if $code == -1;
138             croak "$cmd died with signal " . ($code & 0x7f) if $code & 0x7f;
139             croak "$cmd exited with status " . ($code >> 8) if $code;
140             }
141              
142              
143             1;
144              
145             __END__