File Coverage

blib/lib/Video/Subtitle/SBV.pm
Criterion Covered Total %
statement 51 101 50.5
branch 8 40 20.0
condition 0 6 0.0
subroutine 9 13 69.2
pod 8 9 88.8
total 76 169 44.9


line stmt bran cond sub pod time code
1             =head1 Video::Subtitle::SBV
2              
3             =head2 NAME
4              
5             Video::Subtitle::SBV - read and write SBV format (YouTube) subtitle files
6              
7             =head2 SYNOPSIS
8              
9             my $subtitles = Video::Subtitle::SBV->new ();
10             $subtitles->parse_file ('subtitles.txt');
11             $subtitles->add ({start => '00:00:22.010',
12             end => '00:00:26.020',
13             text => 'Bad city, bad bad city, fat city bad'});
14             $subtitles->write_file ('subtitles.sbv');
15              
16             =over
17              
18             =item UTF-8
19              
20             Input subtitle text files must be either ASCII or text encoded using
21             UTF-8. Output is in UTF-8.
22              
23             =back
24              
25             =cut
26              
27             package Video::Subtitle::SBV;
28             require Exporter;
29             @ISA = qw(Exporter);
30             @EXPORT_OK = qw/validate_time validate_subtitle time_to_milliseconds/;
31 1     1   94681 use warnings;
  1         2  
  1         69  
32 1     1   6 use strict;
  1         2  
  1         52  
33 1     1   5 use Carp;
  1         7  
  1         97  
34 1     1   6 use autodie;
  1         3  
  1         9  
35             our $VERSION = 0.04;
36              
37             =head1 FUNCTIONS
38              
39             =head2 validate_time
40              
41             use Video::Subtitle::SBV 'validate_time';
42             if (validate_time ('00:00:01.010')) {
43             print "Time is valid.\n";
44             }
45              
46             Check whether a subtitle contains a valid time string or not.
47              
48             =cut
49              
50             # This regular expression is for parsing times
51              
52             my $time_re = qr/(\d{1,2}):(\d{2}):(\d{2})\.(\d{0,3})/;
53              
54             sub validate_time
55             {
56 0     0 1 0 my ($time) = @_;
57 0         0 my $status;
58 0 0       0 if ($time =~ $time_re) {
59 0         0 my ($minutes, $seconds) = ($2, $3);
60 0 0 0     0 if ($minutes < 60 && $seconds < 60) {
61 0         0 $status = 1;
62             }
63             }
64 0         0 return $status;
65             }
66              
67             =head2 time_to_milliseconds
68              
69             use Video::Subtitles::SBV 'time_to_milliseconds';
70             my $ms_time = time_to_milliseconds ('00:00:01.010');
71             # $ms_time = 1010
72              
73             If the input is not a valid time, it returns the undefined value.
74              
75             =cut
76              
77             sub time_to_milliseconds
78             {
79 0     0 1 0 my ($time) = @_;
80 0         0 my $ms;
81 0 0       0 if ($time =~ $time_re) {
82 0         0 my ($hours, $minutes, $seconds, $milliseconds) = ($1, $2, $3, $4);
83 0 0       0 if (! defined $milliseconds) {
84 0         0 $milliseconds = 0;
85             }
86 0         0 $minutes += $hours * 60;
87 0         0 $seconds += $minutes * 60;
88 0         0 $milliseconds += $seconds * 1000;
89 0         0 $ms = $milliseconds;
90             }
91 0         0 return $ms;
92             }
93              
94             =head2 validate_subtitle
95              
96             use Video::Subtitle::SBV 'validate_subtitle';
97             if (validate_subtitle ($my_title)) {
98             print "Subtitle is valid.\n";
99             }
100              
101             This routine checks whether the hash reference stored in C<$my_title>
102             is a valid entry which can be given to the L method. The L
103             method uses this to validate its input, and it is also available as a
104             standalone routine exported on request.
105              
106             You can also use a second argument to make it print out the reason why
107             the subtitle is invalid:
108              
109             validate_subtitle ($my_title, 1);
110              
111             Any "true" value will make it print out the reason.
112              
113             =cut
114              
115             sub validate_subtitle
116             {
117 0     0 1 0 my ($subtitle, $verbose) = @_;
118 0         0 my $validity;
119             my $location;
120 0 0       0 if ($verbose) {
121 0 0 0     0 if ($subtitle->{file} && $subtitle->{line}) {
122 0         0 $location = "$subtitle->{file}:$subtitle->{line}: ";
123             }
124             else {
125 0         0 $location = '';
126             }
127             }
128 0 0       0 if (ref $subtitle ne 'HASH') {
129 0 0       0 if ($verbose) {
130 0         0 carp "$location\$subtitle is not a hash reference";
131             }
132 0         0 goto invalid;
133             }
134 0         0 for my $key (qw/start end text/) {
135 0 0       0 if (! $subtitle->{$key}) {
136 0 0       0 if ($verbose) {
137 0         0 carp "$location\$subtitle does not have required information '$key'";
138             }
139 0         0 goto invalid;
140             }
141             }
142 0         0 for my $key (qw/start end/) {
143 0         0 my $time = $subtitle->{$key};
144 0 0       0 if ($time !~ $time_re) {
145 0 0       0 if ($verbose) {
146 0         0 carp "$location\$subtitle $key time '$time' is not a valid time";
147             }
148 0         0 goto invalid;
149             }
150             }
151 0         0 $validity = 1;
152              
153 0         0 invalid:
154             return $validity;
155             }
156              
157             =head1 METHODS
158              
159             =head2 new
160              
161             my $subtitles = Video::Subtitle::SBV->new ();
162              
163             Create an object which will contain the subtitles you create.
164              
165             =cut
166              
167             sub new
168             {
169 1     1 1 13 my $subtitles = {};
170              
171             # "list" is the list of subtitles. This is accessed by using
172             # "parse_file" or "add".
173              
174 1         3 $subtitles->{list} = [];
175              
176             # "verbosity" controls whether to print error messages on
177             # encountering errors. This is accessed by using "set_verbosity".
178              
179 1         3 $subtitles->{verbosity} = undef;
180 1         3 bless $subtitles;
181 1         3 return $subtitles;
182             }
183              
184             =head2 set_verbosity
185              
186             $subtitles->set_verbosity ('yes');
187              
188             Give this function any true value to make it print error messages. Set
189             to any false value to stop printing the error messages.
190              
191             If this is not switched on, the routine will silently ignore
192             ill-formated inputs.
193              
194             =cut
195              
196             sub set_verbosity
197             {
198 1     1 1 8 my ($subtitles, $verbosity) = @_;
199 1         9 $subtitles->{verbosity} = $verbosity;
200             }
201              
202             # Add a new subtitle to the list of subtitles. This is a private
203             # method.
204              
205             sub add_subtitle
206             {
207 96     96 0 105 my ($subtitles) = @_;
208 96         153 my $subtitle = {};
209 96         179 push @{$subtitles->{list}}, $subtitle;
  96         260  
210 96         177 return $subtitle;
211             }
212              
213             =head2 parse_file
214              
215             $subtitles->parse_file ('subtitles.txt');
216              
217             Read in a file of subtitles in the SBV format.
218              
219             =cut
220              
221             sub parse_file
222             {
223 1     1 1 8 my ($subtitles, $file_name) = @_;
224 1         7 open my $input, "<:encoding(utf8)", $file_name;
225 1         20087 my $subtitle;
226 1         4 my $text = '';
227 1         34 while (<$input>) {
228 295 100       1966 if (/^($time_re),($time_re)\s*$/) {
    100          
229 96         178 $subtitle = add_subtitle ($subtitles);
230 96         276 $subtitle->{start} = $1;
231 96         212 $subtitle->{end} = $6;
232 96         154 $subtitle->{file} = $file_name;
233 96         472 $subtitle->{line} = $.;
234             }
235             elsif (/\S/) {
236 103 50       199 if ($subtitle->{finished}) {
237 0 0       0 if ($subtitles->{verbosity}) {
238 0         0 carp "$file_name:$.: subtitle text without a valid start/end time\n";
239             }
240             }
241             else {
242 103         465 $subtitle->{text} .= $_;
243             }
244             }
245             # Otherwise it is a blank line, which means the end of the subtitle.
246             else {
247 96         363 $subtitle->{finished} = 1;
248             }
249             }
250 1         7 close $input;
251             }
252              
253             =head2 add
254              
255             $subtitles->add ({start => '00:00:22.010',
256             end => '00:00:26.020',
257             text => 'Bad city, bad bad city, fat city bad'});
258              
259             Add a subtitle to the file. You can have more than one subtitle in the
260             list.
261              
262             =cut
263              
264             sub add
265             {
266 0     0 1 0 my ($subtitles, @title_list) = @_;
267 0         0 for my $subtitle (@title_list) {
268 0 0       0 if (validate_subtitle ($subtitle, $subtitles->{verbosity})) {
269 0         0 push @{$subtitles->{list}}, $subtitle;
  0         0  
270             }
271             }
272             }
273              
274             =head2 write_file
275              
276             $subtitles->write_file ('subtitles.sbv');
277              
278             Write the stored subtitles in C<$subtitles> to the specified file.
279              
280             If this method is called without an argument, it prints the subtitles
281             to standard output:
282              
283             $subtitles->write_file ();
284              
285             =cut
286              
287             sub write_file
288             {
289 1     1 1 1135 my ($subtitles, $file_name) = @_;
290 1         3 my $output;
291             my $old_fh;
292 1 50       4 if (! $file_name) {
293 0         0 binmode STDOUT, ":utf8";
294             }
295             else {
296 1         5 open $output, ">:encoding(utf8)", $file_name;
297 1         246 $old_fh = select $output;
298             }
299 1         2 for my $subtitle (@{$subtitles->{list}}) {
  1         10  
300 96         481 print <
301             $subtitle->{start},$subtitle->{end}
302             $subtitle->{text}
303             EOF
304             # Sometimes $subtitle->{text} may not have an ending newline
305             # if it is added via the "add" method, so we need to print
306             # another one to get the required blank line.
307 96 50       428 if ($subtitle->{text} !~ /\n\h*$/) {
308 0         0 print "\n";
309             }
310             }
311 1 50       5 if ($file_name) {
312 1         5 close $output;
313 1         268 select $old_fh;
314             }
315             }
316              
317             1;
318              
319             =head1 BUGS
320              
321             =over
322              
323             =item SBV format specification
324              
325             I'm not too sure where the SBV format is actually specified, so the
326             methods in this module are based on looking at examples of the format.
327             That means that some details, such as whether it is compulsory to have
328             a milliseconds field in the times, or whether it is necessary to have
329             a blank line at the end of each subtitle, are just guesses.
330              
331             =item Speaker field
332              
333             SBV allows for a "speaker" field, specified by ">>", but this module
334             doesn't do anything special with that field.
335              
336             =item Video::Subtitle
337              
338             I named this "Video::Subtitle::SBV" because there is an existing
339             module called L, just to be consistent. However,
340             "Video::SubtitleB" with an S would be a better name.
341              
342             =back
343              
344             =head1 AUTHOR
345              
346             Ben Bullock, bkb@cpan.org
347              
348             =head1 LICENCE
349              
350             You can use, modify and redistribute this software library under the
351             standard Perl licences (Gnu General Public Licence or Artistic
352             licence).
353