File Coverage

blib/lib/Linux/DVB/DVBT/Advert/Config.pm
Criterion Covered Total %
statement 140 387 36.1
branch 52 222 23.4
condition 13 88 14.7
subroutine 17 28 60.7
pod 7 16 43.7
total 229 741 30.9


line stmt bran cond sub pod time code
1             package Linux::DVB::DVBT::Advert::Config ;
2            
3             =head1 NAME
4            
5             Linux::DVB::DVBT::Advert::Config - Advert detection config file
6            
7             =head1 SYNOPSIS
8            
9             use Linux::DVB::DVBT::Advert::Config ;
10            
11            
12             =head1 DESCRIPTION
13            
14             This module provides the configuration file utilities for the advert detection and removal utilities.
15            
16             =head2 Settings
17            
18             Settings are passed into the routines via a HASH ref. Settings also come from the default set, and
19             from any config file parameters. The order of priority for these settings is:
20            
21             =over 4
22            
23             Default settings I
24            
25             Config file (Generic section)
26            
27             Config file (Channel-specific section)
28            
29             Settings used for detection
30            
31             Settings HASH ref I
32            
33             =back
34            
35             Note that the "settings used for detection" only apply if the results from detection are saved in a file,
36             and then this file is read in for analysis (see L)
37            
38             Settings are split between those that control the detection phase, and those that control the
39             analysis phase:
40            
41             =head3 Detection Settings
42            
43             Most of the detection settings (other than detection_method) can safely be left to their default values.
44            
45            
46             =over 4
47            
48             =item B
49            
50             Normally leave this set to the default (all methods used). Sometimes you may want to disable all advert detection (for non-commercial
51             channels), or possibly disable logo detection for channels that display a logo at all times.
52            
53             This variable is actually a bitmap of flags: setting a bit enables the corresponding method. If you know what you want, you can specify
54             the variable as a decimal or hex value. It's recommended, however, to use the following symbols:
55            
56             =over 4
57            
58             =item I
59            
60             Use this symbol on it's own to disable advert detection
61            
62             =item I
63            
64             Use this symbol to specify the built-in default detection method (usually means use all methods)
65            
66             =item I
67            
68             Black frame detection. This is where "black" (or dark) frames are expected to appear before and after advert
69             breaks.
70            
71             =item I
72            
73             Logo detection. This is where the channel logo is expected to be present during programs, but absent during adverts.
74            
75             =item I
76            
77             Silence (audio volume) detection. Quiet (or silent) periods are expected before and after adverts.
78            
79             =back
80            
81             The symbols above can be combined to specify the complete detection method. Use '+' or '-' before the
82             symbol to enable or disable the method respectively. Using a '+' or '-' before the first symbol specified,
83             results in the symbols being added/subtracted from the default set. Otherwise the set specified are used
84             to fully define the methods to use.
85            
86             Example: "detection_method = logo + black" means to use only black frame and logo detection.
87            
88             Example: "detection_method = -logo" means to use default detection methods but disable logo detection.
89            
90             Example: "detection_method = disable" means to disable advert detection.
91            
92            
93             =item B
94            
95             Black frame detection: sets the threshold for the step difference of scene score between frames for a scene change
96             to be detected.
97            
98             =item B
99            
100             Black frame detection: scene change detection percentage above which is deemed a scene change frame
101            
102             =item B
103            
104             Black frame detection: maximum pixel value for the pixel to be determined as "black"
105            
106             =item B
107            
108             Black frame detection: maximum brightness percentage under which is treated as black
109            
110             =item B
111            
112             Black frame detection: step difference of the brightness score between frames used to detect a black frame
113            
114             =item B
115            
116             Black frame detection: percentage of frame to use for black frame detection (for example, setting this to 90% results
117             in 5% of the edges around the frame to be ignored)
118            
119             =item B
120            
121             Black frame detection: pixel value used for brightness detection
122            
123             =item B
124            
125             Black frame detection: noise level used for black frame uniformity detection
126            
127            
128            
129             =item B
130            
131             Logo detection: Step size used for moving between pixels in all logo detection functions. For example, setting this to 2 skips
132             every other pixel, resulting in halving the amount of dtaa to process.
133            
134             =item B
135            
136             Logo detection: Once a logo has been possibly detected, this is a check to ensure that the area of the screen used by the logo
137             is no greater than this value. Otherwise the detected region cannot be a valid logo and is discarded.
138            
139             =item B
140            
141             Logo detection: Number of image frames stored in a rolling detection buffer. The number of frames skipped between each stored frame is set by L
142            
143             =item B
144            
145             Logo detection: Number of frames to skip between used frames. This creates a bigger discrepancy between images and makes the logo
146             area easier to detect.
147            
148             =item B
149            
150             Logo detection: level used to decided whether this is a logo edge
151            
152             =item B
153            
154             Logo detection: The logo detection score is averaged over this number of frames.
155            
156             =item B
157            
158             Logo detection: maximum period (in frames) to use for detecting a logo. If a logo has not been found when we reach this number of frames
159             from the start of the video, then logo detection is cancelled.
160            
161             =item B
162            
163             Logo detection: perecntage of total frame to use for logo detection
164            
165             =item B
166            
167             Logo detection: number of times a logo result is re-checked. Once a logo area is detected, the process is re-started this many times
168             to ensure we don't have a false detection.
169            
170             =item B
171            
172             Logo detection: the logo detection score % must be above this value before a frame is flagged as containing a logo.
173            
174             =item B
175            
176             Logo detection: number of pixels to use in logo edge detection
177            
178            
179             =item B
180            
181             Silence detection: adds "fuzziness" to detection of silence frames
182            
183             =back
184            
185            
186             =head3 Analysis Settings
187            
188             The analysis settings consist of a global set of settings that are used in all cases. Also, each detection mode (black frame, logo etc)
189             has it's own set of settings that may be set to over-ride the global set. In the same manner as the detection settings, each set if prefixed
190             by it's own namespace (e.g. for black frame detection use 'frame.' etc).
191            
192             =over 4
193            
194             =item B
195            
196             The maximum length of a single advert (in frames). Multiple adverts may be joined to form the total advert/commercial break between
197             program sections.
198            
199             =item B
200            
201             The minimum length of a single advert (in frames).
202            
203             =item B
204            
205             The minimum length (in frames) of a section of program.
206            
207             =item B
208            
209             (UNUSED) Expected amount of padding (in frames) before the start of a program recording.
210            
211             =item B
212            
213             (UNUSED) Expected amount of padding (in frames) at the start of a program recording.
214            
215             =item B
216            
217             Minimum number of frames to be contracted into a block.
218            
219             =item B
220            
221             fuzziness window when contracting frames into a block
222            
223             =item B
224            
225             widest gap (no valid frames) over which to span when contracting frames into a block
226            
227             =item B
228            
229             window (in frames) in which to reduce the end of the program to the nearest gap
230            
231             =item B
232            
233             frame gap used for reducing program end point
234            
235            
236             =back
237            
238             Black frame settings (see above to descriptions):
239            
240             =over 4
241            
242             =item B
243            
244             =item B
245            
246             =item B
247            
248             =item B
249            
250             =item B
251            
252             =item B
253            
254             =item B
255            
256             =item B
257            
258             =item B
259            
260             =item B
261            
262             =item B
263            
264             =item B
265            
266             =back
267            
268             Logo frame settings (see above to descriptions):
269            
270             =over 4
271            
272             =item B
273            
274             =item B
275            
276             =item B
277            
278             =item B
279            
280             =item B
281            
282             =item B
283            
284             =item B
285            
286             =item B
287            
288             =item B
289            
290             =item B
291            
292             =item B
293            
294             =item B
295            
296             =item B
297            
298             =item B
299            
300             =back
301            
302             Silence frame settings (see above to descriptions):
303            
304             =over 4
305            
306             =item B
307            
308             =item B
309            
310             =item B
311            
312             =item B
313            
314             =item B
315            
316             =item B
317            
318             =item B
319            
320             =item B
321            
322             =item B
323            
324             =item B
325            
326             =item B
327            
328             =item B
329            
330             =back
331            
332             =head2 Config File
333            
334             The configuration file is of the form:
335            
336             # global settings
337             detection_method = 15
338             frame.max_black = 48
339             frame.window_percent = 95
340             frame.max_brightness = 60
341             frame.test_brightness = 40
342             frame.brightness_jump = 200
343             frame.schange_cutlevel = 85
344             frame.schange_jump = 30
345             frame.noise_level = 5
346             logo.window_percent = 95
347             logo.logo_window = 50
348             logo.logo_edge_radius = 2
349             logo.logo_edge_step = 1
350             logo.logo_edge_threshold = 5
351             logo.logo_checking_period = 30000
352             logo.logo_skip_frames = 25
353             logo.logo_num_checks = 5
354             logo.logo_ok_percent = 80
355             logo.logo_max_percentage_of_screen = 10
356             logo.logo_ave_points = 250
357             audio.scale = 1
358             audio.silence_threshold = -80
359            
360             # Channel-specific settings
361             [Dave]
362             logo.logo_skip_frames = 30
363             logo.logo_num_checks = 2
364             logo.logo_ok_percent = 85
365            
366            
367             =head2 Config File Search Path
368            
369             Some of the functions (for example L) accept an optional search path. If this is specified then the same
370             search path will be used from that point on (until a different search path is specified).
371            
372             By default, the search path is set to attempt to match with L on those platforms that support that path; otherwise
373             the user's home directory is used.
374            
375             Setting the search path allows the module to attempt to read/write the configuration file from multiple directories. This allows there to
376             be a common global file used by all users, but each user may then create their own configuration file to over ride the global one however
377             they choose.
378            
379             The format for the search path is an ARRAY ref list of directories, for example:
380            
381             [ '/etc/dvb', '~/.tv' ]
382            
383             or
384            
385             [ 'c:\tv', 'd:\profiles\user\tv' ]
386            
387            
388             =cut
389            
390            
391 12     12   67 use strict ;
  12         23  
  12         443  
392 12     12   62 use Carp ;
  12         21  
  12         654  
393            
394 12     12   65 use File::Spec ;
  12         18  
  12         248  
395 12     12   1619 use Data::Dumper ;
  12         11635  
  12         687  
396            
397 12     12   11752 use Linux::DVB::DVBT::Advert::Constants ;
  12         28  
  12         11530  
398            
399             our $VERSION = '1.03' ;
400             our $DEBUG = 0 ;
401            
402             our $DEFAULT_CONFIG_PATH ;
403             our $FILENAME = 'dvb-adv' ;
404            
405             my %NUMERALS = (
406             'one' => 1,
407             'two' => 2,
408             'three' => 3,
409             'four' => 4,
410             'five' => 5,
411             'six' => 6,
412             'seven' => 7,
413             'eight' => 8,
414             'nine' => 9,
415             ) ;
416            
417            
418             #============================================================================================
419             our $ADVERT_GLOBAL_SECTION = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA__GLOBAL__" ;
420             our $METHOD_VAR = "detection_method" ;
421             our $METHOD_DISABLE = "disable" ;
422             our $METHOD_DISABLE_REGEXP = "none|$METHOD_DISABLE" ;
423            
424             my %SETTINGS_COMMENTS = (
425            
426             'max_advert' => 'maximum length of a single advert (in frames)',
427             'min_advert' => 'minimum length of advert period (cut period) excludes prog change (in frames)',
428             'min_program' => 'minimum length of a program (in frames)',
429             'start_pad' => 'padding at start of recording (in frames)',
430             'end_pad' => 'padding at end of recording (in frames)',
431             'min_frames' => 'minimum number of frames to be contracted into a block',
432             'frame_window' => 'fuzziness window when contracting frames into a block',
433             'max_gap' => 'widest gap (no valid frames) over which to span when contracting frames into a block',
434             'reduce_end' => 'window (in frames) in which to reduce the end of the program to the nearest gap',
435             'reduce_min_gap' => 'frame gap used for reducing program end point',
436             'detection_method' => 'advert detection method specified numerically or in symbols e.g. logo+black',
437            
438             'logo.logo_edge_step' => 'pixel step size for logo detection',
439             'logo.logo_window' => 'number of frames stored in the logo detection buffer',
440             'logo.logo_max_percentage_of_screen' => 'maximum size of a logo (anything larger is discarded)',
441             'logo.logo_skip_frames' => 'number of frames to skip between logo detection',
442             'logo.logo_edge_threshold' => 'level used to decided whether this is a logo edge',
443             'logo.logo_ave_points' => 'logo averaging buffer size',
444             'logo.logo_checking_period' => 'maximum period (in frames) to use for detecting a logo',
445             'logo.window_percent' => 'percentage of frame to use for detection',
446             'logo.logo_num_checks' => 'number of logo re-checks',
447             'logo.logo_ok_percent' => 'percentage over which logo detection is deemed a match',
448             'logo.logo_edge_radius' => 'number of pixels to use in logo edge detection',
449             'logo.logo_rise_threshold' => 'percentage over which logo detection is deemed a match : going from non-logo to logo frames',
450             'logo.logo_fall_threshold' => 'percentage over which logo detection is deemed a match : going from logo to non-logo frames',
451            
452             'frame.schange_jump' => 'sceen change detection level step',
453             'frame.schange_cutlevel' => 'scene change detection percentage above which is deemed a scene change frame',
454             'frame.max_black' => 'maximum pixel value under which pixel is treated as black',
455             'frame.max_brightness' => 'maximum brightness percentage under which is treated as black',
456             'frame.brightness_jump' => 'difference level between frames used to detect a black frame',
457             'frame.window_percent' => 'percentage of frame to use for detection',
458             'frame.test_brightness' => 'pixel value used for brightness detection',
459             'frame.noise_level' => 'noise level used for black frame uniformity detection',
460            
461             'audio.silence_window' => 'adds "fuzziness" to detection of silence frames',
462            
463             ) ;
464            
465            
466             my @SETTINGS_REGIONS = (
467             'global', 'frame', 'logo', 'audio',
468             ) ;
469             my %SETTINGS_TEMPLATE = (
470            
471             'global' => [
472             '# ------------------------------------------------',
473             '# Global settings.',
474             '#',
475             '# Any settings here propagate down any unset ',
476             '# detection-specific settings',
477             '# ------------------------------------------------',
478             '',
479             '# -- Settings used by detection algorithms (XS) --',
480             '',
481             'detection_method',
482             '',
483             '# -- Settings used by analysis algorithms (Perl) --',
484             '',
485             'max_advert',
486             'min_advert',
487             'min_program',
488             'start_pad',
489             'end_pad',
490             'min_frames',
491             'frame_window',
492             'max_gap',
493             'reduce_end',
494             'reduce_min_gap',
495             'increase_start',
496             'increase_min_gap',
497             '',
498             ],
499            
500             'frame' => [
501             '# ------------------------------------------------',
502             '# Frame detection specific settings.',
503             '# ------------------------------------------------',
504             '',
505             '# -- Settings used by detection algorithms (XS) --',
506             '',
507             'frame.schange_jump',
508             'frame.schange_cutlevel',
509             'frame.max_black',
510             'frame.max_brightness',
511             'frame.brightness_jump',
512             'frame.window_percent',
513             'frame.test_brightness',
514             'frame.noise_level',
515             '',
516             '# -- Settings used by analysis algorithms (Perl) --',
517             '',
518             'frame.max_advert',
519             'frame.min_advert',
520             'frame.min_program',
521             'frame.start_pad',
522             'frame.end_pad',
523             'frame.min_frames',
524             'frame.frame_window',
525             'frame.max_gap',
526             'frame.reduce_end',
527             'frame.reduce_min_gap',
528             'frame.increase_start',
529             'frame.increase_min_gap',
530             '',
531             ],
532            
533             'logo' => [
534             '# ------------------------------------------------',
535             '# Logo detection specific settings.',
536             '# ------------------------------------------------',
537             '',
538             '# -- Settings used by detection algorithms (XS) --',
539             '',
540             'logo.logo_edge_step',
541             'logo.logo_max_percentage_of_screen',
542             'logo.logo_window',
543             'logo.logo_skip_frames',
544             'logo.logo_edge_threshold',
545             'logo.logo_ave_points',
546             'logo.logo_checking_period',
547             'logo.window_percent',
548             'logo.logo_num_checks',
549             'logo.logo_ok_percent',
550             'logo.logo_edge_radius',
551             '',
552             '# -- Settings used by analysis algorithms (Perl) --',
553             '',
554             'logo.max_advert',
555             'logo.min_advert',
556             'logo.min_program',
557             'logo.start_pad',
558             'logo.end_pad',
559             'logo.min_frames',
560             'logo.frame_window',
561             'logo.max_gap',
562             'logo.reduce_end',
563             'logo.reduce_min_gap',
564             'logo.increase_start',
565             'logo.increase_min_gap',
566             'logo.logo_rise_threshold',
567             'logo.logo_fall_threshold',
568             '',
569             ],
570            
571             'audio' => [
572             '# ------------------------------------------------',
573             '# Audio detection specific settings.',
574             '# ------------------------------------------------',
575             '',
576             '# -- Settings used by detection algorithms (XS) --',
577             '',
578             'audio.silence_window',
579             '',
580             '# -- Settings used by analysis algorithms (Perl) --',
581             '',
582             'audio.max_advert',
583             'audio.min_advert',
584             'audio.min_program',
585             'audio.start_pad',
586             'audio.end_pad',
587             'audio.min_frames',
588             'audio.frame_window',
589             'audio.max_gap',
590             'audio.reduce_end',
591             'audio.reduce_min_gap',
592             'audio.increase_start',
593             'audio.increase_min_gap',
594             '',
595             ],
596             ) ;
597            
598            
599             #============================================================================================
600             BEGIN {
601            
602             ## Default for Linux::DVB::DVBT
603 12     12   52 $DEFAULT_CONFIG_PATH = [ qw(/etc/dvb ~/.tv) ] ;
604            
605 12         23 my $home ;
606 12 50 33     174 if ( exists $ENV{HOME} and defined $ENV{HOME} )
607             {
608 12         49 $home = $ENV{HOME};
609             }
610            
611             ## Check OS
612 12 50 33     63364 if ( $^O eq 'MSWin32' )
    50          
    50          
    50          
613             {
614             # All versions of Windows
615            
616             # Do we have a user profile?
617 0 0 0     0 if ( !$home && exists $ENV{USERPROFILE} && $ENV{USERPROFILE} )
      0        
618             {
619 0         0 $home = $ENV{USERPROFILE};
620             }
621            
622             # Some Windows use something like $ENV{HOME}
623 0 0 0     0 if ( !$home && exists $ENV{HOMEDRIVE} && exists $ENV{HOMEPATH} && $ENV{HOMEDRIVE} && $ENV{HOMEPATH} )
      0        
      0        
      0        
624             {
625 0         0 $home = File::Spec->catpath($ENV{HOMEDRIVE}, $ENV{HOMEPATH}, '');
626             }
627            
628 0         0 $DEFAULT_CONFIG_PATH = [] ;
629 0 0 0     0 if ($home && -d $home)
630             {
631 0         0 $home =~ s%\\%/%g ;
632 0         0 push @$DEFAULT_CONFIG_PATH, "$home/tv" ;
633             }
634            
635             # add current dir
636 0         0 push @$DEFAULT_CONFIG_PATH, "." ;
637             }
638             elsif ( $^O eq 'darwin')
639             {
640 0 0       0 if (!$home)
641             {
642 0         0 $home = (getpwuid($<))[7];
643             }
644            
645 0         0 $DEFAULT_CONFIG_PATH = [] ;
646 0 0 0     0 if ($home && -d $home)
647             {
648 0         0 push @$DEFAULT_CONFIG_PATH, "$home/tv" ;
649             }
650            
651             # add current dir
652 0         0 push @$DEFAULT_CONFIG_PATH, "." ;
653             }
654             elsif ( $^O eq 'MacOS' )
655             {
656 0 0       0 if (!$home)
657             {
658             # On some platforms getpwuid dies if called at all
659 0         0 local $SIG{'__DIE__'} = '';
660 0         0 $home = (getpwuid($<))[7];
661             }
662            
663 0         0 $DEFAULT_CONFIG_PATH = [] ;
664 0 0 0     0 if ($home && -d $home)
665             {
666 0         0 push @$DEFAULT_CONFIG_PATH, "$home/tv" ;
667             }
668            
669             # add current dir
670 0         0 push @$DEFAULT_CONFIG_PATH, "." ;
671             }
672             elsif ( ($^O eq 'linux') || ($^O eq 'cygwin') )
673             {
674             # Default to Linux::DVB::DVBT
675             }
676             else
677             {
678             # Default to Unix semantics
679 0         0 $DEFAULT_CONFIG_PATH = [ qw(/etc/dvb ~/.tv .) ] ;
680             }
681            
682            
683            
684             #print "Search Path:\n" ;
685             #foreach (@$DEFAULT_CONFIG_PATH)
686             #{
687             # print " $_\n" ;
688             #}
689            
690            
691             }
692            
693            
694            
695             #============================================================================================
696            
697             =head2 Functions
698            
699             =over 4
700            
701             =cut
702            
703            
704             #----------------------------------------------------------------------
705            
706             =item B
707            
708             Read the advert settings file and return a HASH ref containing the settings.
709            
710             Optionally set the search path (see L)
711            
712             See L
713            
714             =cut
715            
716             sub read_dvb_adv
717             {
718 5     5 1 9 my ($search_path) = @_ ;
719            
720 5   33     17 $search_path ||= $DEFAULT_CONFIG_PATH ;
721 5         12 $DEFAULT_CONFIG_PATH = $search_path ;
722            
723 5         23 my $adv_settings_href = {
724             "$ADVERT_GLOBAL_SECTION" => {},
725             } ;
726            
727             ## Optional file so allow for it to be not present
728 5         27 my @fnames = read_filenames($search_path) ;
729 5         12 foreach my $fname (@fnames)
730             {
731 6         24 my %dvb_adv = (
732             "$ADVERT_GLOBAL_SECTION" => {},
733             ) ;
734 6 100       375 if (open my $fh, "<$fname")
735             {
736 5         8 my $line ;
737 5         12 my $channel = $ADVERT_GLOBAL_SECTION ;
738 5         293 while(defined($line=<$fh>))
739             {
740 1074         1263 chomp $line ;
741 1074 100       4669 next if $line =~ /^\s*#/ ; # skip comments
742            
743 468 100       700 if ($line =~ /\[([^]]+)\]/)
744             {
745 16         48 $channel=$1;
746             }
747             else
748             {
749 452   100     1042 $dvb_adv{$channel} ||= {} ;
750 452         858 parse_assignment($line, $dvb_adv{$channel}) ;
751             }
752             }
753 5         78 close $fh ;
754            
755             # combine
756 5         21 $adv_settings_href = Linux::DVB::DVBT::Advert::Config::merge_settings(
757             $adv_settings_href,
758             \%dvb_adv,
759             ) ;
760             }
761            
762             }
763            
764 5         25 return $adv_settings_href ;
765             }
766            
767            
768             #----------------------------------------------------------------------
769            
770             =item B
771            
772             Write advert config information
773            
774             Optionally set the search path (see L)
775            
776             =cut
777            
778             sub write_dvb_adv
779             {
780 0     0 1 0 my ($href, $search_path) = @_ ;
781            
782 0   0     0 $search_path ||= $DEFAULT_CONFIG_PATH ;
783 0         0 $DEFAULT_CONFIG_PATH = $search_path ;
784            
785             ## write settings
786 0         0 do_write_dvb_adv(write_filename($search_path), $href) ;
787             }
788            
789             #----------------------------------------------------------------------
790            
791             =item B
792            
793             Returns the advert config file writeable filename path
794            
795             Optionally set the search path (see L)
796            
797             =cut
798            
799             sub write_filename
800             {
801 0     0 1 0 my ($search_path) = @_ ;
802            
803 0   0     0 $search_path ||= $DEFAULT_CONFIG_PATH ;
804 0         0 $DEFAULT_CONFIG_PATH = $search_path ;
805            
806 0         0 my $dir = write_dir($search_path, $FILENAME) ;
807            
808 0         0 return "$dir/$FILENAME" ;
809             }
810            
811             #----------------------------------------------------------------------
812            
813             =item B
814            
815             Returns an array of found file paths for all readable advert config files found in search path
816            
817             Optionally set the search path (see L)
818            
819             =cut
820            
821             sub read_filenames
822             {
823 5     5 1 869 my ($search_path) = @_ ;
824            
825 5   33     16 $search_path ||= $DEFAULT_CONFIG_PATH ;
826 5         10 $DEFAULT_CONFIG_PATH = $search_path ;
827            
828 5         20 my @files = read_dir($search_path, $FILENAME) ;
829 5 50       527 print "read_filenames() dirs=@files\n" if $DEBUG ;
830 5         16 foreach my $file (@files)
831             {
832 6         31 $file .= "/$FILENAME" ;
833             }
834            
835 5 50       520 print "read_filenames() = @files\n" if $DEBUG ;
836            
837 5         31 return @files ;
838             }
839            
840             #----------------------------------------------------------------------
841            
842             =item B
843            
844             Write a default advert config information file
845            
846             Optionally set the search path (see L)
847            
848             =cut
849            
850             sub write_default_dvb_adv
851             {
852 0     0 1 0 my ($href, $search_path) = @_ ;
853            
854 0   0     0 $search_path ||= $DEFAULT_CONFIG_PATH ;
855 0         0 $DEFAULT_CONFIG_PATH = $search_path ;
856            
857             # Add some example settings
858 0         0 my $settings_href = { %$href } ;
859 0         0 $settings_href->{'Dave'} = {
860             'reduce_end' => 900,
861             'reduce_min_gap' => 50,
862             } ;
863 0         0 $settings_href->{'BBC1'} = {
864             'detection_method' => 'disable',
865             } ;
866 0         0 $settings_href->{'BBC2'} = {
867             'detection_method' => 'disable',
868             } ;
869             # $settings_href->{'BBC3'} = {
870             # 'detection_method' => 'disable',
871             # } ;
872             # $settings_href->{'BBC4'} = {
873             # 'detection_method' => 'disable',
874             # } ;
875 0         0 $settings_href->{'Virgin1'} = {
876             'detection_method' => 'disable',
877             } ;
878            
879 0 0       0 print Data::Dumper->Dump(["write_default_dvb_adv() settings:", $href]) if $DEBUG ;
880            
881             # comment everything EXCEPT detection_method
882 0         0 my $commented_href = {} ;
883            
884 0         0 foreach my $section (keys %$settings_href)
885             {
886 0         0 foreach my $field (sort keys %{$href->{$section}})
  0         0  
887             {
888 0         0 my $val = $href->{$section}{$field} ;
889 0 0       0 if (ref($val) eq 'HASH')
890             {
891 0         0 foreach my $subvar (sort keys %{$val})
  0         0  
892             {
893 0 0       0 next if $subvar eq $METHOD_VAR ;
894 0         0 $commented_href->{$section}{$field}{$subvar} = 1 ;
895             }
896             }
897             else
898             {
899 0 0       0 next if $field eq $METHOD_VAR ;
900 0         0 $commented_href->{$section}{$field} = 1 ;
901             }
902             }
903             }
904            
905             ## write example settings
906 0         0 do_write_dvb_adv(write_filename($search_path), $settings_href, $commented_href) ;
907             }
908            
909            
910             # ============================================================================================
911             # PROTECTED
912            
913             #----------------------------------------------------------------------
914             # Uses optional $commented_href which comments out any matching elements
915             # This is only used for writing default config files (i.e. allows for
916             # commented examples)
917             #
918             sub do_write_dvb_adv
919             {
920 0     0 0 0 my ($fname, $href, $commented_href) = @_ ;
921            
922 0   0     0 $commented_href ||= {} ;
923            
924 0 0       0 open my $fh, ">$fname" or die "Error: Unable to write $fname : $!" ;
925            
926             # Write config information
927             #
928            
929             ## Global first then channel specific (sort automatically picks global key first)
930 0         0 my %seen ;
931 0         0 foreach my $section (sort keys %$href)
932             {
933 0 0       0 if ($section ne $ADVERT_GLOBAL_SECTION)
934             {
935 0         0 print $fh "\n\n" ;
936 0         0 print $fh "# ====================================================================\n" ;
937 0         0 print $fh "# $section channel-specific settings\n" ;
938 0         0 print $fh "# ====================================================================\n" ;
939 0         0 print $fh "[$section]\n" ;
940             }
941             else
942             {
943 0         0 print $fh "\n\n" ;
944 0         0 print $fh "# ====================================================================\n" ;
945 0         0 print $fh "# Global settings\n" ;
946 0         0 print $fh "# \n" ;
947 0         0 print $fh "# (These settings propagate to any unset channel-specific settings)\n" ;
948 0         0 print $fh "# ====================================================================\n" ;
949             }
950 0         0 print $fh "\n\n" ;
951            
952 0   0     0 $seen{$section} ||= {} ;
953            
954             ## Drive output from template
955 0         0 for my $region (@SETTINGS_REGIONS)
956             {
957 0         0 my $buffer = "" ;
958 0         0 my $clear_buffer = 0 ;
959            
960 0         0 foreach my $line (@{$SETTINGS_TEMPLATE{$region}})
  0         0  
961             {
962 0 0 0     0 if (!$line || ($line =~ /#/))
963             {
964 0 0       0 $buffer = "" if $clear_buffer ;
965 0         0 $buffer .= "$line\n" ;
966             }
967             else
968             {
969             # this is a variable
970 0         0 ++$clear_buffer ;
971 0         0 my $printed = 0 ;
972 0         0 my ($field, $subvar) = _field_parse($line) ;
973 0         0 my $comment = _lookup_comment($field, $subvar) ;
974            
975 0         0 my $val = $href->{$section}{$field} ;
976 0 0       0 if ($subvar)
977             {
978 0 0 0     0 if (exists($val->{$subvar}) && defined($val->{$subvar}))
979             {
980 0         0 print $fh $buffer ;
981 0         0 ++$printed ;
982            
983 0 0       0 print $fh "# $comment\n" if $comment ;
984 0 0       0 print $fh "#" if ($commented_href->{$section}{$field}{$subvar}) ;
985 0         0 print $fh "$field.$subvar = $val->{$subvar}\n" ;
986             }
987            
988 0   0     0 $seen{$section}{$field} ||= {} ;
989 0         0 $seen{$field}{$subvar} = 1 ;
990             }
991             else
992             {
993 0 0       0 if (defined($val))
994             {
995 0         0 print $fh $buffer ;
996 0         0 ++$printed ;
997            
998 0 0       0 print $fh "# $comment\n" if $comment ;
999 0 0       0 print $fh "#" if ($commented_href->{$section}{$field}) ;
1000            
1001 0 0       0 if ($field eq $METHOD_VAR)
1002             {
1003 0         0 my $str = method_string($val) ;
1004 0         0 print $fh "$field = $str\n" ;
1005             }
1006             else
1007             {
1008 0         0 print $fh "$field = $val\n" ;
1009             }
1010             }
1011 0         0 $seen{$section}{$field} = 1 ;
1012             }
1013            
1014 0 0       0 if ($printed)
1015             {
1016 0         0 print $fh "\n" ;
1017 0         0 $buffer = "" ;
1018             }
1019             }
1020             }
1021            
1022             # ## Catch any "unseen" (i.e. I've forgotten to update the templates!)
1023             # foreach my $field (sort keys %{$href->{$section}})
1024             # {
1025             # my $val = $href->{$section}{$field} ;
1026             # if (ref($val) eq 'HASH')
1027             # {
1028             # foreach my $subvar (sort keys %{$val})
1029             # {
1030             # next if ($seen{$section}{$field}{$subvar}) ;
1031             # print $fh "$field.$subvar = $val->{$subvar}\n" ;
1032             # }
1033             # }
1034             # else
1035             # {
1036             # next if ($seen{$section}{$field}) ;
1037             #
1038             # if ($field eq $METHOD_VAR)
1039             # {
1040             # my $str = method_string($val) ;
1041             # print $fh "$field = $str\n" ;
1042             # }
1043             # else
1044             # {
1045             # if ($val =~ /\S+/)
1046             # {
1047             # print $fh "$field = $val\n" ;
1048             # }
1049             # }
1050             # }
1051             # }
1052             }
1053            
1054 0         0 print $fh "\n" ;
1055             }
1056            
1057 0         0 close $fh ;
1058             }
1059            
1060            
1061             #----------------------------------------------------------------------
1062             sub method_string
1063             {
1064 0     0 0 0 my ($val) = @_ ;
1065 0         0 my $method_str = "" ;
1066            
1067 0 0       0 if ($val == 0)
1068             {
1069             ## disabled
1070 0         0 $method_str = $METHOD_DISABLE ;
1071             }
1072             else
1073             {
1074             ## special
1075 0         0 foreach my $method_key (keys %{$Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method_special'}})
  0         0  
1076             {
1077 0         0 my $method_val = $Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method_special'}{$method_key} ;
1078 0 0       0 if ($val == $method_val)
1079             {
1080 0         0 $method_str = $method_key ;
1081 0         0 last ;
1082             }
1083             }
1084             }
1085            
1086             ## Not found it yet, so work out the string
1087 0 0       0 if (!$method_str)
1088             {
1089 0         0 foreach my $method_key (keys %{$Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method'}})
  0         0  
1090             {
1091 0         0 my $method_val = $Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method'}{$method_key} ;
1092 0 0       0 if ($val & $method_val)
1093             {
1094 0 0       0 $method_str .= " + " if $method_str ;
1095 0         0 $method_str .= $method_key ;
1096             }
1097             }
1098             }
1099            
1100 0 0       0 if (!$method_str)
1101             {
1102             # error catchall
1103 0         0 $method_str = 'default' ;
1104             }
1105            
1106 0         0 return $method_str ;
1107             }
1108            
1109            
1110             #----------------------------------------------------------------------
1111             sub parse_val
1112             {
1113 84     84 0 117 my ($val) = @_ ;
1114 84         80 my $ival ;
1115            
1116 84 50       334 if ($val =~ /^0x([\da-z]+)/i)
    100          
1117             {
1118 0         0 $ival = hex($1) ;
1119             }
1120             elsif ($val =~ /([\d]+)/)
1121             {
1122 71         119 $ival = int($1) ;
1123             }
1124            
1125 84         140 return $ival ;
1126             }
1127            
1128             #----------------------------------------------------------------------
1129             # Convert mode string into value (eg default - logo + audio)
1130             sub parse_method
1131             {
1132 13     13 0 22 my ($var, $val) = @_ ;
1133            
1134 13 50       234 print "parse_method($var, $val)\n" if $DEBUG >= 10 ;
1135            
1136 13         27 my $ival = parse_val($val) ;
1137 13 50       30 if (defined($ival))
1138             {
1139             ## Integer - if non-zero then ensure minimum settings are applied
1140 0         0 $val = $ival ;
1141            
1142 0 0       0 if ($val)
1143             {
1144 0         0 $val |= $Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method_special'}{'MIN'} ;
1145             }
1146             }
1147             else
1148             {
1149             # Special string 'disable' or 'none' overrides everything
1150 13 100       139 if ($val =~ /$METHOD_DISABLE_REGEXP/)
1151             {
1152             # no detection
1153 12         20 $val = 0 ;
1154             }
1155             else
1156             {
1157             # Allow definitions of the form:
1158             # default -logo +audio = (black+logo+audio) - logo +audio = black+audio
1159             # logo + audio = logo+audio -> black+logo+audio (always add MIN)
1160             # -logo +audio = (black+logo+audio) - logo +audio = black+audio (infers default)
1161             #
1162 1         9 my $got_base=0 ;
1163 1         2 my $method=0 ;
1164 1         2 my $op = '' ;
1165 1         7 while ($val =~ /([\+\-]|\S+)/g)
1166             {
1167 1         3 my $token = $1 ;
1168 1 50       3 print " + token: \"$token\" (got base=$got_base) method=$method op=$op\n" if $DEBUG >= 10 ;
1169 1 50       5 if ($token =~ /(\+|\-)/)
1170             {
1171 0 0       0 print " + + an op\n" if $DEBUG >= 10 ;
1172             ## + or - operator, see if a base value has been specified, otherwise start with default
1173 0         0 $op = $1 ;
1174 0 0       0 if (!$got_base)
1175             {
1176 0         0 $method = $Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method_special'}{'DEFAULT'} ;
1177 0         0 ++$got_base ;
1178 0 0       0 print " + + + set base method=$method\n" if $DEBUG >= 10 ;
1179             }
1180 0 0       0 print " + + op=$op\n" if $DEBUG >= 10 ;
1181             }
1182             else
1183             {
1184 1         3 my $method_key = uc $token ;
1185 1 50       3 print " + + a key $method_key\n" if $DEBUG >= 10 ;
1186 1 50       13 if (exists($Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method'}{$method_key}))
    50          
1187             {
1188 0 0       0 print " + + + exists! (op=$op)\n" if $DEBUG >= 10 ;
1189 0 0       0 if (!$op)
1190             {
1191             # Set method to this value
1192 0         0 $method = $Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method'}{$method_key} ;
1193 0         0 ++$got_base ;
1194 0 0       0 print " + + + set base method=$method\n" if $DEBUG >= 10 ;
1195             }
1196             else
1197             {
1198 0 0       0 if ($op eq '+')
1199             {
1200             ## Add
1201 0         0 $method |= $Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method'}{$method_key} ;
1202             }
1203             else
1204             {
1205             ## Subtract
1206 0         0 $method &= ~$Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method'}{$method_key} ;
1207             }
1208 0         0 $op = '' ;
1209 0 0       0 print " + + + set method=$method\n" if $DEBUG >= 10 ;
1210             }
1211             }
1212             elsif (exists($Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method_special'}{$method_key}))
1213             {
1214             ## override with special value (e.g. 'default')
1215 1         3 $method = $Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method_special'}{$method_key} ;
1216 1         2 $op = '' ;
1217 1 50       6 print " + + + set method=$method\n" if $DEBUG >= 10 ;
1218             }
1219             }
1220             }
1221            
1222 1 50       2 print " + method=$val\n" if $DEBUG >= 10 ;
1223            
1224 1 50       3 $method = 0 if $method < 0 ;
1225 1         3 $method |= $Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'Advert'}{'detection_method_special'}{'MIN'} ;
1226            
1227 1         3 $val = $method ;
1228 1 50       4 print "METHOD: method=$val\n" if $DEBUG >= 10 ;
1229             }
1230             }
1231 13 50       29 print "METHOD: val=$val\n" if $DEBUG >= 10 ;
1232            
1233 13         25 return $val ;
1234             }
1235            
1236            
1237             #----------------------------------------------------------------------
1238             sub parse_value
1239             {
1240 84     84 0 110 my ($var, $val) = @_ ;
1241            
1242 84 100       145 if ($var eq $METHOD_VAR)
1243             {
1244 13         27 $val = parse_method($var, $val) ;
1245             }
1246             else
1247             {
1248 71         122 my $ival = parse_val($val) ;
1249 71 50       437 if (defined($ival))
1250             {
1251 71         97 $val = $ival ;
1252             }
1253             }
1254 84         443 return $val ;
1255             }
1256            
1257             #----------------------------------------------------------------------
1258             sub parse_assignment
1259             {
1260 452     452 0 618 my ($line, $href) = @_ ;
1261            
1262 452 100       1536 if ($line =~ /(\S+)\s*=\s*(\S+.*)/)
1263             {
1264 84         194 my ($var, $val) = ($1, $2) ;
1265 84         252 $val =~ s/\s+$// ;
1266 84 100       294 if ($var =~ /([\w\d]+)\.([\w\d]+)/)
1267             {
1268             # of the form:
1269             # logo.logo_threshold
1270             #
1271             # so save as:
1272             # {logo}{logo_threshold}
1273             #
1274 43   100     148 $href->{$1} ||= {} ;
1275 43         70 $href->{$1}{$2} = parse_value($var, $val) ;
1276             }
1277             else
1278             {
1279 41         216 $href->{$var} = parse_value($var, $val) ;
1280             }
1281             }
1282             }
1283            
1284             #----------------------------------------------------------------------
1285             # Need to copy globals down to key (if not already set), then use defaults
1286             # if neither set
1287             #
1288             sub cascade_settings
1289             {
1290 0     0 0 0 my ($settings_href, $key, $defaults_href) = @_ ;
1291            
1292 0   0     0 $settings_href ||= {} ;
1293 0   0     0 $defaults_href ||= {} ;
1294            
1295 0         0 my $cascaded_href = {} ;
1296            
1297 0 0       0 print Data::Dumper->Dump(["cascade_settings($key) IN:", $settings_href, "DEFAULTS:", $defaults_href]) if $DEBUG >= 10 ;
1298            
1299             ## start with defaults as a baseline
1300 0 0 0     0 if ($key && exists($defaults_href->{$key}))
1301             {
1302 0         0 _hash_copy_shallow($defaults_href->{$key}, $cascaded_href) ;
1303             }
1304             else
1305             {
1306 0         0 _hash_copy_shallow($defaults_href, $cascaded_href) ;
1307             }
1308            
1309             ## copy over any settings defined in the global namespace
1310 0         0 _hash_copy_shallow($settings_href, $cascaded_href) ;
1311            
1312             ## copy over any settings defined in the key's namespace
1313 0 0 0     0 if ($key && exists($settings_href->{$key}))
1314             {
1315 0         0 _hash_copy_shallow($settings_href->{$key}, $cascaded_href) ;
1316             }
1317             else
1318             {
1319 0         0 _hash_copy_shallow($settings_href, $cascaded_href) ;
1320             }
1321 0 0       0 print Data::Dumper->Dump(["cascade_settings($key) OUT:", $cascaded_href]) if $DEBUG >= 10 ;
1322            
1323 0         0 return $cascaded_href ;
1324             }
1325            
1326             #----------------------------------------------------------------------
1327             # Do a deep copy of one HASH heirarchy of settings onto another
1328             # List of settings starting with lowest priority
1329             #
1330             sub merge_settings
1331             {
1332 6     6 0 15 my (@settings_list) = @_ ;
1333            
1334 6 50       21 print Data::Dumper->Dump(["merge_settings() IN:", \@settings_list]) if $DEBUG >= 10 ;
1335            
1336 6         49 my $merged_href = {} ;
1337 6         15 foreach my $href (@settings_list)
1338             {
1339 13         31 _hash_copy_deep($href, $merged_href);
1340             }
1341            
1342 6 50       19 print Data::Dumper->Dump(["merge_settings() OUT:", $merged_href]) if $DEBUG >= 10 ;
1343            
1344 6         48 return $merged_href ;
1345             }
1346            
1347            
1348            
1349             #----------------------------------------------------------------------
1350             # Do a deep copy of the HASH and sub-hashes, propagating global settings down onto
1351             # any unset channel settings
1352             #
1353             sub channel_settings
1354             {
1355 1     1 0 3 my ($advert_settings_href, $channel) = @_ ;
1356            
1357 1   50     3 $channel ||= "" ;
1358 1         4 $channel =~ s/^['"](.*)['"]$/$1/ ;
1359            
1360 1 50       4 print Data::Dumper->Dump(["channel_settings($channel) IN:", $advert_settings_href]) if $DEBUG >= 10 ;
1361            
1362 1         2 my $cascaded_href = {} ;
1363            
1364             ## Get copy of globals
1365 1         5 _hash_copy_deep($advert_settings_href->{$ADVERT_GLOBAL_SECTION}, $cascaded_href);
1366            
1367             ## If channel specified, overwrite globals with channel-specific
1368 1 50 33     22 if ($channel && exists($advert_settings_href->{$channel}))
1369             {
1370 0         0 _hash_copy_deep($advert_settings_href->{$channel}, $cascaded_href);
1371            
1372             ## Insert channel name into settings
1373 0         0 $cascaded_href->{'channel'} = $channel ;
1374             }
1375            
1376 1 50       4 print Data::Dumper->Dump(["channel_settings($channel) OUT:", $cascaded_href]) if $DEBUG >= 10 ;
1377 1         3 return $cascaded_href ;
1378             }
1379            
1380            
1381            
1382             # ============================================================================================
1383             # ============================================================================================
1384            
1385            
1386             #---------------------------------------------------------------------------------
1387             # Copy key values from one hash into another. Follow a single depth of hierarchy for any
1388             # HASH entries
1389             sub _hash_copy_deep
1390             {
1391 14     14   22 my ($base_href, $new_href) = @_ ;
1392            
1393 14   50     34 $base_href ||= {} ;
1394 14 50       35 croak "Error: cannot copy HASH because destination is not a HASH ref" if ref($new_href) ne 'HASH' ;
1395            
1396 14         41 foreach my $key (keys %$base_href)
1397             {
1398 79         101 my $val = $base_href->{$key} ;
1399 79 100       125 if (ref($val) eq 'HASH')
1400             {
1401             # copy HASH entries
1402 39   100     126 $new_href->{$key} ||= {} ;
1403 39         425 $new_href->{$key} = {
1404 39         40 %{$new_href->{$key}},
1405             %$val
1406             };
1407             }
1408             else
1409             {
1410             # scalar
1411 40         120 $new_href->{$key} = $val ;
1412             }
1413             }
1414             }
1415            
1416             #---------------------------------------------------------------------------------
1417             # Copy key values from one hash into another. Skips any HASH entries
1418             sub _hash_copy_shallow
1419             {
1420 0     0   0 my ($base_href, $new_href) = @_ ;
1421            
1422 0   0     0 $base_href ||= {} ;
1423            
1424 0 0       0 croak "Error: cannot copy HASH because destination is not a HASH ref" if ref($new_href) ne 'HASH' ;
1425            
1426 0         0 foreach my $key (keys %$base_href)
1427             {
1428 0         0 my $val = $base_href->{$key} ;
1429 0 0       0 if (!ref($val))
1430             {
1431             # scalar
1432 0         0 $new_href->{$key} = $val ;
1433             }
1434             }
1435             }
1436            
1437             #---------------------------------------------------------------------------------
1438             # Convert template line into field/subvar
1439             sub _field_parse
1440             {
1441 0     0   0 my ($line) = @_ ;
1442 0         0 my ($field, $subvar) = ($line, '') ;
1443            
1444 0 0       0 if ($field =~ /(\w+)\.(.*)/)
1445             {
1446 0         0 ($field, $subvar) = ($1, $2) ;
1447             }
1448 0         0 return ($field, $subvar) ;
1449             }
1450            
1451             #---------------------------------------------------------------------------------
1452             # Lookup the comment text for this field/subvar
1453             sub _lookup_comment
1454             {
1455 0     0   0 my ($field, $subvar) = @_ ;
1456            
1457 0         0 my $comment = '' ;
1458 0         0 my $name = $field ;
1459 0 0       0 $name .= ".$subvar" if $subvar ;
1460            
1461 0 0 0     0 if (exists($SETTINGS_COMMENTS{$name}))
    0          
1462             {
1463             ## use specific comment
1464 0         0 $comment = $SETTINGS_COMMENTS{$name} ;
1465             }
1466             elsif ($subvar && exists($SETTINGS_COMMENTS{$subvar}))
1467             {
1468             ## no specific comment, so use global
1469 0         0 $comment = $SETTINGS_COMMENTS{$subvar} ;
1470             }
1471 0         0 return $comment ;
1472            
1473             }
1474            
1475            
1476             # ============================================================================================
1477             # From Linux::DVB::DVBT::Config
1478             # ============================================================================================
1479            
1480             #----------------------------------------------------------------------
1481            
1482             =item B
1483            
1484             Find directories to read from - all readable directories in search path
1485            
1486             =cut
1487            
1488             sub read_dir
1489             {
1490 5     5 1 8 my ($search_path, $fname) = @_ ;
1491            
1492 5         17 my @dirs = _expand_search_path($search_path) ;
1493             # my $dir ;
1494            
1495 5         11 my @found = () ;
1496 5         11 foreach my $d (@dirs)
1497             {
1498 6         10 my $found=1 ;
1499 6 100       235 if (! -f "$d/$fname")
1500             {
1501             ## can't find file, so mark as invalid directory
1502 1         2 $found=0 ;
1503             }
1504            
1505 6 100       17 if ($found)
1506             {
1507             # $dir = $d ;
1508             # last ;
1509 5         84 push @found, $d ;
1510             }
1511             }
1512            
1513 5 100       17 @found = ('.') unless @found ;
1514 5 50       992 print "Searched [ @$search_path ] : read dir=@found\n" if $DEBUG ;
1515            
1516             # return $dir ;
1517 5         24 return @found ;
1518             }
1519            
1520             #----------------------------------------------------------------------
1521            
1522             =item B
1523            
1524             Find directory to write to - first writeable directory in search path
1525            
1526             =cut
1527            
1528             sub write_dir
1529             {
1530 0     0 1 0 my ($search_path, $fname) = @_ ;
1531            
1532 0         0 my @dirs = _expand_search_path($search_path) ;
1533 0         0 my $dir ;
1534            
1535 0 0       0 print STDERR "Find dir to write to from $search_path ...\n" if $DEBUG ;
1536            
1537 0         0 foreach my $d (@dirs)
1538             {
1539 0         0 my $found=1 ;
1540            
1541 0 0       0 print STDERR " + processing $d\n" if $DEBUG ;
1542            
1543             # See if dir exists
1544 0 0       0 if (!-d $d)
1545             {
1546             # See if this user can create the dir
1547 0         0 eval {
1548 0         0 mkpath([$d], $DEBUG, 0755) ;
1549             };
1550 0 0       0 $found=0 if $@ ;
1551            
1552 0 0       0 print STDERR " + $d does not exist - attempt to mkdir=$found\n" if $DEBUG ;
1553             }
1554            
1555 0 0       0 if (-d $d)
1556             {
1557 0 0       0 print STDERR " + $d does exist ...\n" if $DEBUG ;
1558            
1559             # See if this user can write to the dir
1560 0 0       0 if (open my $fh, ">>$d/$fname")
1561             {
1562 0         0 close $fh ;
1563 0 0       0 print STDERR " + + Write to $d/$fname succeded\n" if $DEBUG ;
1564             }
1565             else
1566             {
1567 0 0       0 print STDERR " + + Unable to write to $d/$fname - aborting this dir\n" if $DEBUG ;
1568            
1569 0         0 $found = 0;
1570             }
1571             }
1572            
1573 0 0       0 if ($found)
1574             {
1575 0         0 $dir = $d ;
1576 0         0 last ;
1577             }
1578             }
1579            
1580 0 0       0 print STDERR "Searched $search_path : write dir=".($dir?$dir:"")."\n" if $DEBUG ;
    0          
1581            
1582 0         0 return $dir ;
1583             }
1584            
1585            
1586             #----------------------------------------------------------------------
1587             # Split the search path & expand all the directories to absolute paths
1588             #
1589             sub _expand_search_path
1590             {
1591 5     5   9 my ($search_path) = @_ ;
1592            
1593 5         13 my @dirs = @$search_path ;
1594 5         12 foreach my $d (@dirs)
1595             {
1596             # Replace any '~' with $HOME
1597 6         13 $d =~ s/~/\$HOME/g ;
1598            
1599             # Now replace any vars with values from the environment
1600 6         10 $d =~ s/\$(\w+)/$ENV{$1}/ge ;
  0         0  
1601            
1602             # Ensure path is clean
1603 6         220 $d = File::Spec->rel2abs($d) ;
1604             }
1605            
1606 5         16 return @dirs ;
1607             }
1608            
1609            
1610             #----------------------------------------------------------------------
1611             #
1612             sub _channel_search
1613             {
1614 0     0     my ($channel_name, $search_href) = @_ ;
1615            
1616 0           my $found_channel_name ;
1617            
1618             # start by just seeing if it's the correct name...
1619 0 0         if (exists($search_href->{$channel_name}))
1620             {
1621 0           return $channel_name ;
1622             }
1623             else
1624             {
1625             ## Otherwise, try finding variations on the channel name
1626 0           my %search ;
1627            
1628 0           $channel_name = lc $channel_name ;
1629            
1630             # lower-case, no spaces
1631 0           my $srch = $channel_name ;
1632 0           $srch =~ s/\s+//g ;
1633 0           $search{$srch}=1 ;
1634            
1635             # lower-case, replaced words with numbers, no spaces
1636 0           $srch = $channel_name ;
1637 0           foreach my $num (keys %NUMERALS)
1638             {
1639 0           $srch =~ s/\b($num)\b/$NUMERALS{$num}/ge ;
  0            
1640             }
1641 0           $srch =~ s/\s+//g ;
1642 0           $search{$srch}=1 ;
1643            
1644             # lower-case, replaced numbers with words, no spaces
1645 0           $srch = $channel_name ;
1646 0           foreach my $num (keys %NUMERALS)
1647             {
1648 0 0         print STDERR " -- $srch - replace $NUMERALS{$num} with $num..\n" if $DEBUG>3 ;
1649 0           $srch =~ s/($NUMERALS{$num})\b/$num/ge ;
  0            
1650 0 0         print STDERR " -- -- $srch\n" if $DEBUG>3 ;
1651             }
1652 0           $srch =~ s/\s+//g ;
1653 0           $search{$srch}=1 ;
1654            
1655 0 0         print STDERR " + Searching tuning info [", keys %search, "]...\n" if $DEBUG>2 ;
1656            
1657 0           foreach my $chan (keys %$search_href)
1658             {
1659 0           my $srch_chan = lc $chan ;
1660 0           $srch_chan =~ s/\s+//g ;
1661            
1662 0           foreach my $search (keys %search)
1663             {
1664 0 0         print STDERR " + + checking $search against $srch_chan \n" if $DEBUG>2 ;
1665 0 0         if ($srch_chan eq $search)
1666             {
1667 0           $found_channel_name = $chan ;
1668 0 0         print STDERR " + found $channel_name\n" if $DEBUG ;
1669 0           last ;
1670             }
1671             }
1672            
1673 0 0         last if $found_channel_name ;
1674             }
1675             }
1676            
1677 0           return $found_channel_name ;
1678             }
1679            
1680             # ============================================================================================
1681             # END OF PACKAGE
1682            
1683             =back
1684            
1685             =cut
1686            
1687             1;
1688