File Coverage

blib/lib/Linux/DVB/DVBT/Utils.pm
Criterion Covered Total %
statement 100 143 69.9
branch 42 72 58.3
condition 19 26 73.0
subroutine 8 15 53.3
pod 13 13 100.0
total 182 269 67.6


line stmt bran cond sub pod time code
1             package Linux::DVB::DVBT::Utils ;
2              
3             =head1 NAME
4              
5             Linux::DVB::DVBT::Utils - DVBT utilities
6              
7             =head1 SYNOPSIS
8              
9             use Linux::DVB::DVBT::Utils ;
10            
11              
12             =head1 DESCRIPTION
13              
14             Module provides a set of useful miscellaneous utility routines used by the DVBT module. You may use them in your own scripts
15             if you wish to (I mainly use the time coversion functions in my scripts).
16              
17             =cut
18              
19              
20 11     11   26548 use strict ;
  11         24  
  11         4808  
21              
22             our $VERSION = '2.07' ;
23             our $DEBUG = 0 ;
24              
25             our %CONTENT_DESC = (
26             0x10 => "Film|movie/drama (general)",
27             0x11 => "Film|detective/thriller",
28             0x12 => "Film|adventure/western/war",
29             0x13 => "Film|science fiction/fantasy/horror",
30             0x14 => "Film|comedy",
31             0x15 => "Film|soap/melodrama/folkloric",
32             0x16 => "Film|romance",
33             0x17 => "Film|serious/classical/religious/historical movie/drama",
34             0x18 => "Film|adult movie/drama",
35              
36             0x20 => "News|news/current affairs (general)",
37             0x21 => "News|news/weather report",
38             0x22 => "News|news magazine",
39             0x23 => "News|documentary",
40             0x24 => "News|discussion/interview/debate",
41              
42             0x30 => "Show|show/game show (general)",
43             0x31 => "Show|game show/quiz/contest",
44             0x32 => "Show|variety show",
45             0x33 => "Show|talk show",
46              
47             0x40 => "Sports|sports (general)",
48             0x41 => "Sports|special events (Olympic Games, World Cup etc.)",
49             0x42 => "Sports|sports magazines",
50             0x43 => "Sports|football/soccer",
51             0x44 => "Sports|tennis/squash",
52             0x45 => "Sports|team sports (excluding football)",
53             0x46 => "Sports|athletics",
54             0x47 => "Sports|motor sport",
55             0x48 => "Sports|water sport",
56             0x49 => "Sports|winter sports",
57             0x4A => "Sports|equestrian",
58             0x4B => "Sports|martial sports",
59              
60             0x50 => "Children|children's/youth programmes (general)",
61             0x51 => "Children|pre-school children's programmes",
62             0x52 => "Children|entertainment programmes for 6 to 14",
63             0x53 => "Children|entertainment programmes for 10 to 16",
64             0x54 => "Children|informational/educational/school programmes",
65             0x55 => "Children|cartoons/puppets",
66              
67             0x60 => "Music|music/ballet/dance (general)",
68             0x61 => "Music|rock/pop",
69             0x62 => "Music|serious music/classical music",
70             0x63 => "Music|folk/traditional music",
71             0x64 => "Music|jazz",
72             0x65 => "Music|musical/opera",
73             0x66 => "Music|ballet",
74              
75             0x70 => "Arts|arts/culture (without music, general)",
76             0x71 => "Arts|performing arts",
77             0x72 => "Arts|fine arts",
78             0x73 => "Arts|religion",
79             0x74 => "Arts|popular culture/traditional arts",
80             0x75 => "Arts|literature",
81             0x76 => "Arts|film/cinema",
82             0x77 => "Arts|experimental film/video",
83             0x78 => "Arts|broadcasting/press",
84             0x79 => "Arts|new media",
85             0x7A => "Arts|arts/culture magazines",
86             0x7B => "Arts|fashion",
87              
88             0x80 => "Social|social/political issues/economics (general)",
89             0x81 => "Social|magazines/reports/documentary",
90             0x82 => "Social|economics/social advisory",
91             0x83 => "Social|remarkable people",
92              
93             0x90 => "Education|education/science/factual topics (general)",
94             0x91 => "Education|nature/animals/environment",
95             0x92 => "Education|technology/natural sciences",
96             0x93 => "Education|medicine/physiology/psychology",
97             0x94 => "Education|foreign countries/expeditions",
98             0x95 => "Education|social/spiritual sciences",
99             0x96 => "Education|further education",
100             0x97 => "Education|languages",
101              
102             0xA0 => "Leisure|leisure hobbies (general)",
103             0xA1 => "Leisure|tourism/travel",
104             0xA2 => "Leisure|handicraft",
105             0xA3 => "Leisure|motoring",
106             0xA4 => "Leisure|fitness & health",
107             0xA5 => "Leisure|cooking",
108             0xA6 => "Leisure|advertizement/shopping",
109             0xA7 => "Leisure|gardening",
110              
111             0xB0 => "Special|original language",
112             0xB1 => "Special|black & white",
113             0xB2 => "Special|unpublished",
114             0xB3 => "Special|live broadcast",
115             );
116              
117             our %AUDIO_FLAGS = (
118             'AD' => 'is_audio_described',
119             'S' => 'is_subtitled',
120             'SL' => 'is_deaf_signed',
121             );
122              
123             our %CHAR_TRANSLATE ;
124              
125             #============================================================================================
126             BEGIN {
127            
128 11     11   53 foreach my $cc (0..255)
129             {
130 2816         3387 my $chr = chr $cc ;
131 2816         2937 my $xlt = $chr ;
132 2816 100 100     9607 if (($cc < ord(' ')) || ($cc > ord('~') ))
133             {
134 1771         1976 $xlt = '' ;
135             }
136 2816         7911 $CHAR_TRANSLATE{$chr} = $xlt ;
137             }
138            
139 11         39792 $CHAR_TRANSLATE{"\n"} = ' ' ;
140             }
141              
142              
143             #============================================================================================
144              
145             =head2 Functions
146              
147             =over 4
148              
149             =cut
150              
151              
152              
153              
154             #-----------------------------------------------------------------------------
155              
156             =item B
157              
158             Convert time (in HH:MM format) into minutes
159              
160             =cut
161              
162             sub time2mins
163             {
164 0     0 1 0 my ($time) = @_ ;
165 0         0 my $mins=0;
166 0 0       0 if ($time =~ m/(\d+)\:(\d+)/)
167             {
168 0         0 $mins = 60*$1 + $2 ;
169             }
170 0         0 return $mins ;
171             }
172              
173             #-----------------------------------------------------------------------------
174              
175             =item B
176              
177             Convert minutes into time (in HH:MM format)
178              
179             =cut
180              
181             sub mins2time
182             {
183 0     0 1 0 my ($mins) = @_ ;
184 0         0 my $hours = int($mins/60) ;
185 0         0 $mins = $mins % 60 ;
186 0         0 my $time = sprintf "%02d:%02d", $hours, $mins ;
187 0         0 return $time ;
188             }
189              
190             #-----------------------------------------------------------------------------
191              
192             =item B
193              
194             Convert seconds into time (in HH:MM:SS format)
195              
196             =cut
197              
198             sub secs2time
199             {
200 0     0 1 0 my ($secs) = @_ ;
201            
202 0         0 my $mins = int($secs/60) ;
203 0         0 $secs = $secs % 60 ;
204            
205 0         0 my $hours = int($mins/60) ;
206 0         0 $mins = $mins % 60 ;
207            
208 0         0 my $time = sprintf "%02d:%02d:%02d", $hours, $mins, $secs ;
209 0         0 return $time ;
210             }
211              
212              
213              
214             #-----------------------------------------------------------------------------
215              
216             =item B
217              
218             Calculate duration in minutes between start and end times (in HH:MM format)
219              
220             =cut
221              
222             sub duration
223             {
224 0     0 1 0 my ($start, $end) = @_ ;
225 0         0 my $start_mins = time2mins($start) ;
226 0         0 my $end_mins = time2mins($end) ;
227 0 0       0 $end_mins += 24*60 if ($end_mins < $start_mins) ;
228 0         0 my $duration_mins = $end_mins - $start_mins ;
229 0         0 my $duration = mins2time($duration_mins) ;
230              
231             #print STDERR "duration($start ($start_mins), $end ($end_mins)) = $duration ($duration_mins)\n" if $this->debug() ;
232              
233 0         0 return $duration ;
234             }
235              
236             #-----------------------------------------------------------------------------
237              
238             =item B
239              
240             Convert time (in HH:MM, HH:MM:SS, or MM format) into seconds
241              
242             =cut
243              
244             sub time2secs
245             {
246 0     0 1 0 my ($time) = @_ ;
247            
248             # Default to 30mins
249 0         0 my $seconds = 30*60 ;
250            
251             # Convert duration to seconds
252 0 0       0 if ($time =~ m/^(\d+)$/)
    0          
    0          
253             {
254 0         0 $seconds = 60 * $1 ;
255             }
256             elsif ($time =~ m/^(\d+):(\d+):(\d+)$/)
257             {
258 0         0 $seconds = (60*60 * $1) + (60 * $2) + $3 ;
259             }
260             elsif ($time =~ m/^(\d+):(\d+)$/)
261             {
262 0         0 $seconds = (60*60 * $1) + (60 * $2) ;
263             }
264 0         0 return $seconds ;
265             }
266              
267             #-----------------------------------------------------------------------------
268              
269             =item B
270              
271             Convert time (in HH:MM, HH:MM:SS, or SS format) into seconds
272              
273             (i.e. if integer value is specified, treat it as a time in seconds rather than minutes)
274              
275             =cut
276              
277             sub timesec2secs
278             {
279 0     0 1 0 my ($time) = @_ ;
280            
281             # Default to 30mins
282 0         0 my $seconds = 30*60 ;
283            
284             # Convert duration to seconds
285 0 0       0 if ($time =~ m/^(\d+)$/)
    0          
    0          
286             {
287 0         0 $seconds = $1 ;
288             }
289             elsif ($time =~ m/^(\d+):(\d+):(\d+)$/)
290             {
291 0         0 $seconds = (60*60 * $1) + (60 * $2) + $3 ;
292             }
293             elsif ($time =~ m/^(\d+):(\d+)$/)
294             {
295 0         0 $seconds = (60*60 * $1) + (60 * $2) ;
296             }
297 0         0 return $seconds ;
298             }
299              
300             #============================================================================================
301              
302             =back
303              
304             =head2 EPG Functions
305              
306             =over 4
307              
308             =cut
309              
310              
311             #-----------------------------------------------------------------------------
312              
313             =item B
314              
315             (Used by EPG function)
316              
317             Ensure text returned by epg() is formatted as text.
318              
319             =cut
320              
321             sub text
322             {
323 46     46 1 11630 my ($text) = @_ ;
324              
325 46 50       110 if ($text)
326             {
327             ## if text starts with non-ascii then assume it is encrypted and skip it
328 46 100       119 if ($text =~ /^\\x([\da-fA-F]{2})/)
329             {
330 3         6 my $cc = hex $1 ;
331 3 100 100     20 if ( ($cc < 0x20) || ($cc > 0x7e) )
332             {
333 2         10 return "" ;
334             }
335             }
336            
337 44         80 $text =~ s/\\x([\da-fA-F]{2})/$CHAR_TRANSLATE{chr hex $1}/ge ;
  13         61  
338              
339             # remove newlines
340 44         120 $text =~ s/[\r\n]/ /g ;
341             # replace multiple whitespace
342 44         699 $text =~ s/\s+/ /g ;
343             # remove leading space
344 44         90 $text =~ s/^\s+//g ;
345             # remove trailing space
346 44         198 $text =~ s/\s+$//g ;
347             }
348 44         284 return $text ;
349             }
350              
351             #-----------------------------------------------------------------------------
352              
353             =item B
354              
355             (Used by EPG function)
356              
357             Convert category code into genre string.
358              
359             =cut
360              
361             sub genre
362             {
363 0     0 1 0 my ($cat) = @_ ;
364              
365 0         0 my $genre = "" ;
366 0 0 0     0 if ($cat && exists($CONTENT_DESC{$cat}))
367             {
368 0         0 $genre = $CONTENT_DESC{$cat} ;
369             }
370            
371 0         0 return $genre ;
372             }
373              
374              
375              
376             #-----------------------------------------------------------------------------
377             # Usually run in the order:
378             #
379             # fix_title(\$title, \$synopsis) ;
380             # fix_synopsis(\$title, \$synopsis, \$new_program) ;
381             # fix_episodes(\$title, \$synopsis, \$episode, \$num_episodes) ;
382             # fix_audio(\$title, \$synopsis, \%flags) ;
383             # subtitle(\$synopsis, \$subtitle) ;
384              
385              
386             =item B
387              
388             (Used by EPG function)
389              
390             Fix title when title is truncated by ellipses and the synopsis continues the title.
391              
392             For example:
393              
394             title = Julian Fellowes Investigates...
395             synopsis = ...a Most Mysterious Murder. The Case of xxxx
396            
397             Returns:
398              
399             title = Julian Fellowes Investigates a Most Mysterious Murder.
400             synopsis = The Case of xxxx
401              
402             =cut
403              
404             sub fix_title
405             {
406 23     23 1 139 my ($title_ref, $synopsis_ref) = @_ ;
407              
408 23 100 66     125 return unless ($$title_ref && $$synopsis_ref) ;
409              
410 22 50       68 print STDERR "fix_title(title=\"$$title_ref\", synopsis=\"$$synopsis_ref\")\n" if $DEBUG ;
411              
412             # fix title when title is 'Julian Fellowes Investigates...'
413             # and synopsis is '...a Most Mysterious Murder. The Case of xxxxx'
414 22 100       61 if ($$synopsis_ref =~ s/^\.\.\.\s?//)
415             {
416             # remove trailing ... from title
417 1         5 $$title_ref =~ s/\.\.\.//;
418            
419             # synopsis = 'a Most Mysterious Murder. The Case of xxxxx'
420             # match = 'a Most Mysterious Murder'
421             # new synopsis = 'The Case of xxxxx'
422 1         7 $$synopsis_ref =~ s/^(.+?)\. //;
423 1 50       6 if ($1)
424             {
425             # new title = 'Julian Fellowes Investigates a Most Mysterious Murder'
426 1         4 $$title_ref .= ' ' . $1;
427            
428             # remove duplicate spaces
429 1         4 $$title_ref =~ s/ {2,}/ /;
430             }
431             }
432              
433             # Followed by ...
434 22         62 $$synopsis_ref =~ s/Followed by .*// ;
435            
436             # Strip leading/trailing space
437 22         49 $$title_ref =~ s/^\s+// ;
438 22         45 $$title_ref =~ s/\s+$// ;
439 22         43 $$synopsis_ref =~ s/^\s+// ;
440 22         213 $$synopsis_ref =~ s/\s+$// ;
441            
442            
443 22 50       79 print STDERR "fix_title() - END title=\"$$title_ref\", synopsis=\"$$synopsis_ref\"\n" if $DEBUG ;
444             }
445              
446             #-----------------------------------------------------------------------------
447              
448             =item B
449              
450             (Used by EPG function)
451              
452             Checks the synopsis for any indication that this is a new program/series.
453              
454             Examples of supported new program indication are:
455              
456             New.
457             Brand new ****.
458             All new ****.
459              
460             Also removes extraneous information like:
461              
462             Also in HD.
463              
464             =cut
465              
466             sub fix_synopsis
467             {
468 23     23 1 113 my ($title_ref, $synopsis_ref, $new_prog_ref) = @_ ;
469              
470 23   100     144 $$synopsis_ref ||= "" ;
471 23   50     86 $$new_prog_ref ||= 0 ;
472              
473 23 50       60 print STDERR "fix_synopsis(title=\"$$title_ref\", synopsis=\"$$synopsis_ref\")\n" if $DEBUG ;
474              
475             # Examples:
476            
477             # All New!
478             # Brand new series.
479             # New.
480             # New:
481 23 100       231 if ($$synopsis_ref =~ s%^\s*(all\s+|brand\s+){0,1}new(\s+\S+){0,1}\s*([\.\!\:\-]+\s*)%%i)
482             {
483 6         10 $$new_prog_ref = 1 ;
484             }
485            
486             # Also in HD.
487 23         273 $$synopsis_ref =~ s%\s*Also in HD[\.\s]*%%i ;
488              
489             # Strip leading/trailing space
490 23         46 $$synopsis_ref =~ s/^\s+// ;
491 23         228 $$synopsis_ref =~ s/\s+$// ;
492              
493 23 50       72 print STDERR "fix_synopsis() - END title=\"$$title_ref\", synopsis=\"$$synopsis_ref\", newprog=$$new_prog_ref\n" if $DEBUG ;
494             }
495              
496              
497              
498              
499             #-----------------------------------------------------------------------------
500              
501             =item B
502              
503             (Used by EPG function)
504              
505             Checks the synopsis for mention of episodes and removes this information if found (setting
506             the $episode_ref and $num_episodes_ref refs accordingly).
507              
508             Examples of supported episode descriptions are:
509              
510             (part 1 of 7)
511             1/7
512             Episode 1 of 7
513              
514             =cut
515              
516             sub fix_episodes
517             {
518 23     23 1 120 my ($title_ref, $synopsis_ref, $episode_ref, $num_episodes_ref) = @_ ;
519              
520 23   100     50 $$synopsis_ref ||= "" ;
521              
522 23 50       52 print STDERR "fix_episodes(title=\"$$title_ref\", synopsis=\"$$synopsis_ref\")\n" if $DEBUG ;
523              
524             # optional ()
525             # optional ending .
526             #
527             # "(XXX 1 of 7)"
528             # "1/7"
529             # "Episode 1 of 7."
530             # "Part 1 of 7."
531             # "(1/7)."
532            
533             # (* word* dig /|\|of dig :.)*
534 23 100       7111 if ($$synopsis_ref =~ s%\(*\s*\w*\s*(\d+)\s*(?:/|\\|of)\s*(\d+)[\:\.\s\)]*%%i)
535             {
536 11         31 $$episode_ref = $1;
537 11         25 $$num_episodes_ref = $2;
538             }
539              
540             # Strip leading/trailing space
541 23         47 $$synopsis_ref =~ s/^\s+// ;
542 23         178 $$synopsis_ref =~ s/\s+$// ;
543            
544 23 50       86 print STDERR "fix_episodes() - END title=\"$$title_ref\", synopsis=\"$$synopsis_ref\", episode=$$episode_ref, num_episodes_ref=$$num_episodes_ref\n" if $DEBUG ;
545             }
546              
547             #-----------------------------------------------------------------------------
548              
549             =item B
550              
551             (Used by EPG function)
552              
553             Searches the synopsis string and removes any audio information, adding the information
554             to the $flags HASH reference.
555              
556             The flags supported are:
557              
558             'AD' => 'is_audio_described',
559             'S' => 'is_subtitled',
560             'SL' => 'is_deaf_signed',
561              
562             =cut
563              
564             sub fix_audio
565             {
566 23     23 1 111 my ($title_ref, $synopsis_ref, $flags_href) = @_ ;
567              
568 23 50       66 print STDERR "fix_audio(title=\"$$title_ref\", synopsis=\"$$synopsis_ref\")\n" if $DEBUG ;
569              
570             # extract audio described / subtitled / deaf_signed from synopsis
571 23   100     54 $$synopsis_ref ||= "" ;
572 23 100       94 return unless $$synopsis_ref =~ s/\[([A-Z,]+)\][\.\s]*//;
573            
574 2         26 my $flags = $1;
575 2         8 foreach my $flag (split ",", $flags)
576             {
577 4   50     13 my $method = $AUDIO_FLAGS{$flag} || next; # bad data
578 4         13 $flags_href->{$method} = 1;
579             }
580 2 50       10 print STDERR "fix_audio() - END title=\"$$title_ref\", synopsis=\"$$synopsis_ref\"\n" if $DEBUG ;
581             }
582              
583             #-----------------------------------------------------------------------------
584              
585             =item B
586              
587             Extracts a sub-title from the synopsis. Looks for text of the format:
588              
589             Some sort of subtitle: the rest of the synopsis....
590            
591             And returns the sentence before the ':' i.e.
592            
593             Some sort of subtitle
594              
595             Returns empty string if not found.
596              
597             NOTE: Not to be confused with subtitling for the hard of hearing!
598              
599             =cut
600              
601             =item B
602              
603             Same as L but supports old-style
604             interface.
605              
606             =cut
607              
608             sub subtitle
609             {
610 46     46 1 65901 my ($synopsis_ref, $subtitle_ref, $genre_ref) = @_ ;
611              
612             ## Allow for old-style interface
613 46 100       257 if (!ref($synopsis_ref))
614             {
615 23         42 my $synopsis = $synopsis_ref ;
616 23         43 $synopsis_ref = \$synopsis ;
617             }
618 46         77 my $subtitle = "" ;
619 46   100     232 $subtitle_ref ||= \$subtitle ;
620 46         72 my $genre = "" ;
621 46   50     164 $genre_ref ||= \$genre ;
622              
623              
624             ## Defaults
625 46         78 $$genre_ref = "" ;
626 46         57 $$subtitle_ref = "" ;
627              
628 46 50       1172 print STDERR "subtitle(synopsis=\"$$synopsis_ref\")\n" if $DEBUG ;
629              
630 46         51 my $restore_synopsis ;
631              
632             # Strip out "* series." from start (e.g. Drama series, Crime drama series, etc)
633             ## Check what's left of synopsis to remove any genre info
634 46 100       1205 if ($$synopsis_ref =~ s/^\s*(\w+\s+){1,2}series\.\s*//i)
635             {
636 1         4 $$genre_ref = $1 ;
637             }
638            
639             ## Don't treat time(s) as start of subtitle
640             ## e.g. 4:50 from paddington
641             # "Blood Wedding (Part 1): ...."
642 46 100       257 if ($$synopsis_ref =~ s/^\s*(.+?)\:(?!\d\d)\s*//)
643             {
644 32         191 $$subtitle_ref = $1;
645 32         52 $restore_synopsis = ':' ;
646             }
647            
648             # If none found then see if we can use a sort sentence from the start of the synopsis
649 46 100       113 if (!$$subtitle_ref)
650             {
651 14 100       93 if ($$synopsis_ref =~ s/^\s*([^\.]+)\.\s*//)
652             {
653 12         26 $$subtitle_ref = $1;
654 12         22 $restore_synopsis = '.' ;
655             }
656             else
657             {
658             # get a limited subset
659 2         5 $$subtitle_ref = $$synopsis_ref ;
660 2         5 $$subtitle_ref =~ s/^\s+// ;
661 2         6 $$subtitle_ref = substr $$subtitle_ref, 0, 32 ;
662             }
663             }
664              
665             ## Check what's left of synopsis to remove any genre info
666             # Drama series.
667 46 100       239 if ($$synopsis_ref =~ s/^\s*(\w+\s+){1,2}series\.\s*//i)
668             {
669 3         8 $$genre_ref = $1 ;
670             }
671            
672            
673             ## Glue subtitle back onto front of synopsis
674 46 100       102 if ($restore_synopsis)
675             {
676 44         192 $$synopsis_ref = "$$subtitle_ref$restore_synopsis $$synopsis_ref" ;
677             }
678              
679             # Strip leading/trailing space
680 46         93 $$synopsis_ref =~ s/^\s+// ;
681 46         286 $$synopsis_ref =~ s/\s+$// ;
682 46         84 $$subtitle_ref =~ s/^\s+// ;
683 46         122 $$subtitle_ref =~ s/\s+$// ;
684              
685 46 50       99 print STDERR "subtitle() - END synopsis=\"$$synopsis_ref\", subtitle=\"$$subtitle_ref\"\n" if $DEBUG ;
686              
687             # return subtitle
688 46         226 return $$subtitle_ref ;
689             }
690              
691              
692              
693              
694              
695             # ============================================================================================
696             # END OF PACKAGE
697              
698             =back
699              
700             =cut
701              
702             1;
703