File Coverage

blib/lib/Linux/DVB/DVBT/Advert.pm
Criterion Covered Total %
statement 78 1000 7.8
branch 9 508 1.7
condition 4 140 2.8
subroutine 17 59 28.8
pod 12 46 26.0
total 120 1753 6.8


line stmt bran cond sub pod time code
1             package Linux::DVB::DVBT::Advert ;
2            
3             =head1 NAME
4            
5             Linux::DVB::DVBT::Advert - Advert (commercials) detection and removal
6            
7             =head1 SYNOPSIS
8            
9             use Linux::DVB::DVBT::Advert ;
10            
11             # Read advert config info
12             my $ad_config_href = ad_config() ;
13            
14             # skip advert detection
15             if (!ok_to_detect($results_settings_href))
16             {
17             print "Skipping advert detection...\n" ;
18             exit 0 ;
19             }
20            
21             # detect
22             my $settings_href = {
23             'debug' => $DEBUG,
24             'progress_callback' => \&progress,
25             } ;
26             $results_href = detect($file, $settings_href, $channel, $ad_config_href, $det) ;
27            
28             # .. or re-use saved deetction
29             $results_href = detect_from_file($detect_file) ;
30            
31             # analyse
32             my @cut_list = analyse($file, $results_href, $ad_config_href, $channel, $csv, $expected_aref, $settings_href) ;
33            
34             # remove adverts
35             ad_cut($file, $cutfile, \@cut_list) ;
36            
37             # ..or split file at advert boundaries
38             ad_split($file, $cutfile, \@cut_list) ;
39            
40            
41            
42             =head1 DESCRIPTION
43            
44             Module provides the interface into the advert (commercials) detection and removal utilities.
45             As well as an underlying transport stream parsing framework, this module also incorporates
46             MPEG2 video decoding and AAC audio decoding (see L module for full details).
47            
48             =head2 Basic Operation
49            
50             Advert removal is split into 2 phases: detection and analysis. The detection phase processes the
51             video and audio data, producing raw statistics for each video frame (I effectively sunchronise the
52             audio frames and group their results into video frames). These raw statistics are then post-processed
53             in the analysis phase to determine the (hopefully!) actual location of the commercial breaks.
54            
55             The detection phase is completely run in C code under XS; the analysis phase is completely run in Perl.
56            
57            
58             =head2 Settings
59            
60             Settings are passed into the routines via a HASH ref. Settings also come from the default set, and
61             from any config file parameters. Please see L for full details.
62            
63             In general, you will probably only be interested in changing the analysis settings to tweak the results for
64             a particular channel (or to completely disable advert detection for a channel). The detection parameters
65             seem to be pretty good for all channels.
66            
67             =head2 Results Files
68            
69             The output from each phase can be stored into files for later re-use or analysis. The detection phase
70             output file can be reloaded and passed to the analyse phase multiple times to try out different analysis
71             settings. The analyse phase output can be plotted to show the effectiveness of the algorithms used.
72            
73            
74             =cut
75            
76             #============================================================================================
77             # USES
78             #============================================================================================
79 12     12   491630 use strict ;
  12         27  
  12         491  
80 12     12   23194 use Env ;
  12         39901  
  12         68  
81 12     12   7322 use Carp ;
  12         26  
  12         680  
82 12     12   68 use File::Basename ;
  12         23  
  12         1334  
83 12     12   75 use File::Path ;
  12         31  
  12         817  
84            
85 12     12   13361 use Linux::DVB::DVBT::Advert::Config ;
  12         41  
  12         587  
86 12     12   131 use Linux::DVB::DVBT::Advert::Constants ;
  12         21  
  12         273  
87            
88 12     12   9183 use Linux::DVB::DVBT::Advert::Mem ;
  12         35  
  12         405  
89            
90 12     12   87 use Data::Dumper ;
  12         21  
  12         4135  
91            
92             #============================================================================================
93             # EXPORTER
94             #============================================================================================
95             require Exporter;
96             our @ISA = qw(Exporter);
97            
98             our @EXPORT = qw/
99             ad_config
100             ad_debug
101             detect
102             detect_from_file
103             analyse
104             ad_cut
105             ad_split
106             ok_to_detect
107             / ;
108            
109             our @CHECK_OK = qw/
110             read_adv
111             adv_to_cutlist
112             / ;
113            
114             our @OK = qw/
115             ad_config_search
116             channel_settings
117             read_expected
118             write_default_config
119             / ;
120            
121             our @EXPORT_OK = (@OK, @CHECK_OK) ;
122            
123             our %EXPORT_TAGS = (
124             'all' => [ @EXPORT, @EXPORT_OK ],
125             'check' => [ @EXPORT, @CHECK_OK ],
126             ) ;
127            
128             #============================================================================================
129             # GLOBALS
130             #============================================================================================
131             our $VERSION = '0.04' ;
132             our $DEBUG = 0 ;
133            
134             our $USE_XS_MEM = 3 ;
135            
136            
137             #our $CONFIG_DIR = $Linux::DVB::DVBT::Advert::Config::DEFAULT_CONFIG_PATH ;
138             our $CONFIG_DIR ;
139            
140             #============================================================================================
141             # XS
142             #============================================================================================
143             require XSLoader;
144            
145             if (!$ENV{'ADVERT_NO_XS'})
146             {
147             XSLoader::load('Linux::DVB::DVBT::Advert', $VERSION);
148             }
149             else
150             {
151             print STDERR "WARNING: Running Linux::DVB::DVBT::Advert without XS\n" ;
152             }
153            
154             #============================================================================================
155             BEGIN {
156            
157 12     12   206768 $CONFIG_DIR = $Linux::DVB::DVBT::Advert::Config::DEFAULT_CONFIG_PATH ;
158            
159             }
160            
161             #============================================================================================
162             my $FPS = $Linux::DVB::DVBT::Advert::Constants::CONSTANTS{'FRAMES_PER_SEC'} ;
163             my $FRAME_FIELD = 'frame' ;
164             my $FRAME_END_FIELD = 'frame_end' ;
165             my $PROG_FIELD = 'program' ;
166             my $PACKET_FIELD = 'start_pkt' ;
167             my $PACKET_END_FIELD = 'end_pkt' ;
168             my $PACKET_GOP_FIELD = 'gop_pkt' ;
169             my $EXPECTED_FIELD = 'expected' ;
170             my $LOGO_PROCESSED_FIELD = 'logo_proc' ;
171             my $LOGO_COALESCED_FIELD = 'logo_coal' ;
172             my $REDUCED_LOGO_COALESCED_FIELD = 'reduced_logo_coal' ;
173             my $BLACK_COALESCED_FIELD = 'black_coal' ;
174             my $SILENT_COALESCED_FIELD = 'silent_coal' ;
175             my $SILENT_BLACK_FIELD = 'silent_black' ;
176             my $REDUCED_SILENT_BLACK_FIELD = 'reduced_silent_black' ;
177            
178             my $_FRAMENUMS_KEY = '_framenums' ;
179            
180             #============================================================================================
181            
182             =head2 Functions
183            
184             =over 4
185            
186             =cut
187            
188            
189             #----------------------------------------------------------------------
190            
191             =item B
192            
193             Get advert configuration information from a config file. Optionally sets the
194             search path - which is an ARRAY ref containing the list of directories to search.
195            
196             Returns the HASH ref of advert settings.
197            
198             =cut
199            
200             sub ad_config
201             {
202 5     5 1 9708 my ($search) = @_ ;
203            
204 5   66     29 $search ||= $CONFIG_DIR ;
205 5         19 $CONFIG_DIR = $search ;
206            
207 5         25 my $ad_config_href = Linux::DVB::DVBT::Advert::Config::read_dvb_adv($CONFIG_DIR) ;
208 5         15 return $ad_config_href ;
209             }
210            
211            
212             #----------------------------------------------------------------------
213            
214             =item B
215            
216             Set debug level.
217            
218             =cut
219            
220             sub ad_debug
221             {
222 0     0 1 0 my ($level) = @_ ;
223 0 0       0 $DEBUG = $level if defined($level) ;
224             }
225            
226            
227             #----------------------------------------------------------------------
228            
229             =item B
230            
231             Get/set search path for advert config file.
232            
233             Returns the current setting.
234            
235             =cut
236            
237             sub ad_config_search
238             {
239 2     2 1 4181 my ($new_path) = @_ ;
240            
241 2 50       11 if ($new_path)
242             {
243 2         5 $CONFIG_DIR = $new_path ;
244             }
245 2         7 return $CONFIG_DIR ;
246             }
247            
248            
249             #----------------------------------------------------------------------
250             #
251             #=item B<_advert_settings($ad_config_href [, $channel])>
252             #
253             #Using the reference to the tuning info HASH (normally read in by B),
254             #returns a HASH containing just the advert settings information.
255             #
256             #If no channel name is specified then this just returns the global settings.
257             #If a channel name is specified (and that channel can be found in the settings HASH),
258             #then merges any global settings with the channel-sepcific settings.
259             #
260             #If I<$ad_config_href> is undef, then this function first reads the config files (NOTE: this will
261             #only use the default search path).
262             #
263             #Returns the HASH ref of settings.
264             #
265             #=cut
266            
267             sub _advert_settings
268             {
269 1     1   2 my ($ad_config_href, $channel) = @_ ;
270            
271 1 50       4 print "_advert_settings($ad_config_href, $channel)\n" if $DEBUG>=10 ;
272            
273 1 50       5 if (!$ad_config_href)
274             {
275 0         0 $ad_config_href = ad_config() ;
276             }
277            
278 1 50       4 print Data::Dumper->Dump(["_advert_settings($channel) advert settings:", $ad_config_href]) if $DEBUG>=10 ;
279            
280 1         6 my $settings_href = Linux::DVB::DVBT::Advert::Config::channel_settings($ad_config_href, $channel) ;
281            
282 1 50       4 print Data::Dumper->Dump(["_advert_settings($channel) OUT:", $settings_href]) if $DEBUG>=10 ;
283            
284 1         3 return $settings_href ;
285             }
286            
287            
288             #-----------------------------------------------------------------------------
289            
290             =item B
291            
292             Returns a HASH ref containing advert settings from the config file
293             (if available).
294            
295             The B<$settings_href> settings HASH ref contains any new settings that the user wishes to
296             use, overriding global values or config file values.
297            
298             The B<$ad_config_href> parameter is expected to be the tuning info HASH ref read in using
299             L. It is used to set any settings read in
300             from the default config file.
301            
302             =cut
303            
304             sub channel_settings
305             {
306 1     1 1 3136 my ($settings_href, $channel, $ad_config_href) = @_ ;
307            
308 1   50     6 $channel ||= "" ;
309            
310             ## if channel specified, get channel-specific config
311 1         6 my $config_settings_href = _advert_settings($ad_config_href, $channel) ;
312            
313             ## Get defaults
314 1         112 my $default_settings_href = Linux::DVB::DVBT::Advert::dvb_advert_def_settings() ;
315            
316 1 50       5 if ($DEBUG)
317             {
318 0         0 print Data::Dumper->Dump(["config settings:", $config_settings_href]) ;
319 0         0 print Data::Dumper->Dump(["default settings:", $default_settings_href]) ;
320             }
321            
322             ## Merge them all together
323 1         6 my $chan_settings_href = Linux::DVB::DVBT::Advert::Config::merge_settings(
324             $default_settings_href,
325             $config_settings_href,
326             $settings_href,
327             ) ;
328            
329 1         15 return $chan_settings_href ;
330             }
331            
332            
333             #-----------------------------------------------------------------------------
334            
335             =item B
336            
337             Read the source TS file I<$src> and return a HASH containing the detection statistics
338             for each frame.
339            
340             The B<$ad_config_href> parameter is expected to be the tuning info HASH ref read in using
341             L. It is used to set any settings read in
342             from the default config file. If it is undef then a default search path is used.
343            
344             The B<$settings_href> settings HASH ref contains any new settings that the user wishes to
345             use, overriding global values or config file values.
346            
347             The optional I<$channel> parameter is used to specify the TV channel that the video was recorded
348             from. This then allows the config fiel to contain channel-specific settings which are used in the
349             detection. If no channel is specified (or the channel name is not found in the config file) then just
350             default settings are used.
351            
352             If the optional I<$detect> parameter is specified then the results are saved into the text file
353             named by the parameter
354            
355             =cut
356            
357             sub detect
358             {
359 0     0 1 0 my ($src, $settings_href, $channel, $ad_config_href, $detect) = @_ ;
360            
361 0 0       0 if ($DEBUG)
362             {
363 0         0 print Data::Dumper->Dump(["===detect====", "settings:", $settings_href, "AD ($channel) config", $ad_config_href]) ;
364             }
365            
366 0   0     0 $channel ||= "" ;
367 0         0 my $results_href = {} ;
368            
369             ## Get combined settings for this channel
370 0         0 $settings_href = channel_settings($settings_href, $channel, $ad_config_href) ;
371            
372 0 0       0 if ($DEBUG)
373             {
374 0         0 print Data::Dumper->Dump(["channel settings:", $settings_href]) ;
375             }
376            
377             ## Skip if disabled
378 0 0       0 if (ok_to_detect($settings_href))
379             {
380 0         0 my $adata_ref ;
381            
382 0         0 $settings_href->{'debug'} =0;
383            
384             ## Do detection
385 0         0 my $det_aref = Linux::DVB::DVBT::Advert::dvb_advert_detect($src, $settings_href) ;
386            
387 0         0 ($results_href, $adata_ref) = @$det_aref ;
388            
389 0 0       0 if ($DEBUG)
390             {
391 0         0 print Data::Dumper->Dump(["after detect - results settings:", $results_href->{'settings'}]) ;
392             }
393            
394             # tie an array to the internal data - this is *much* more effecient than letting Perl gobble up
395             # 10x the memory
396 0         0 my @frames ;
397 0         0 tie @frames, 'Linux::DVB::DVBT::Advert', 'ADATA', [$$adata_ref] ;
398 0         0 $results_href->{'frames'} = \@frames ;
399            
400 0 0       0 if ($DEBUG)
401             {
402 0         0 print "Read $results_href->{settings}{num_frames} frames\n" ;
403 0         0 print Data::Dumper->Dump(["Results", $results_href]) ;
404             }
405             }
406            
407             ## Optionally save results
408 0 0       0 if ($detect)
409             {
410 0 0       0 open my $fh, ">$detect" or die "Error: unable to write to detect file $detect : $!" ;
411            
412             ## Save settings
413 0         0 my $save_settings_href = $results_href->{'settings'} ;
414 0         0 foreach my $var (sort keys %$save_settings_href)
415             {
416 0 0       0 if (ref($save_settings_href->{$var}) eq 'HASH')
417             {
418 0         0 foreach my $subvar (sort keys %{$save_settings_href->{$var}})
  0         0  
419             {
420 0         0 print $fh "# $var.$subvar = $save_settings_href->{$var}{$subvar}\n" ;
421             }
422             }
423             else
424             {
425 0         0 print $fh "# $var = $save_settings_href->{$var}\n" ;
426             }
427             }
428            
429             ## Save frames
430 0         0 my $frame_href = $results_href->{'frames'}[0] ;
431 0         0 my $line = $FRAME_FIELD ;
432 0         0 foreach my $field (sort keys %$frame_href)
433             {
434 0 0       0 next unless !ref($frame_href->{$field}) ;
435 0 0       0 next if $field eq $FRAME_FIELD ;
436 0         0 $line .= ",$field" ;
437             }
438 0         0 print $fh "$line\n" ;
439 0         0 for (my $idx=0; $idx < $results_href->{'settings'}{'num_frames'}; ++$idx)
440             {
441 0         0 $frame_href = $results_href->{'frames'}[$idx] ;
442 0 0       0 next unless scalar(keys %$frame_href) ;
443 0         0 my $frame = $frame_href->{$FRAME_FIELD} ;
444 0         0 $line = "$frame" ;
445 0         0 foreach my $field (sort keys %$frame_href)
446             {
447 0 0       0 next unless !ref($frame_href->{$field}) ;
448 0 0       0 next if $field eq $FRAME_FIELD ;
449 0         0 $line .= ",$frame_href->{$field}" ;
450             }
451 0         0 print $fh "$line\n" ;
452             }
453            
454 0         0 close $fh ;
455             }
456            
457 0         0 return $results_href ;
458             }
459            
460            
461             #-----------------------------------------------------------------------------
462            
463             =item B
464            
465             Read the text file named by the I<$detect> parameter and return a HASH containing the detection statistics
466             for each frame. All settings are read in from the detection file (but any settings may be overridden in the
467             L function).
468            
469             =cut
470            
471             sub detect_from_file
472             {
473 5     5 1 204 my ($detect, $settings_href) = @_ ;
474            
475 5   50     20 $settings_href ||= {} ;
476            
477             # check file
478 5 50       276 open my $fh, "<$detect" or die "Error: unable to read to detect file $detect : $!" ;
479 5         50 close $fh ;
480            
481             ## Do detection
482 5         64542 my $det_aref = Linux::DVB::DVBT::Advert::dvb_advert_detect_from_file($detect, $settings_href) ;
483            
484 5         35 my ($results_href, $adata_ref) = @$det_aref ;
485            
486             # tie an array to the internal data - this is *much* more effecient than letting Perl gobble up
487             # 10x the memory
488 5         16 my @frames ;
489 5         157 tie @frames, 'Linux::DVB::DVBT::Advert', 'ADATA', [$$adata_ref] ;
490 5         28 $results_href->{'frames'} = \@frames ;
491             # $results_href->{'__adata'} = $adata_ref ;
492            
493 5 100       38 if ($DEBUG >= 10)
494             {
495 1         56 print "Read $results_href->{settings}{num_frames} frames\n" ;
496 1         27 print Data::Dumper->Dump(["Results", $results_href]) ;
497             }
498            
499 5         101635 return $results_href ;
500             }
501            
502            
503            
504             #-----------------------------------------------------------------------------
505            
506             =item B
507            
508             Read in expected results file. Used more for debug / display purposes.
509            
510             =cut
511            
512             sub read_expected
513             {
514 0     0 1 0 my ($expected_file) = @_ ;
515            
516             ## check for expected results
517 0         0 my @expected ;
518             #print "expected results: $expected_file\n" ;
519 0 0       0 if (open my $fh, "<$expected_file")
520             {
521 0         0 my $line ;
522 0         0 while (defined($line=<$fh>))
523             {
524 0         0 chomp $line ;
525            
526             # expected:
527             # 0) 1 1387 0:00:55.43
528             # 1) 22208 25186 0:01:59.12
529             # 2) 40451 42151 0:01:08.00
530             # 3) 46763 48741 0:01:19.12
531             #
532 0 0       0 if ($line =~ /^\s*(\d+)\)\s+(\d+)\s+(\d+)\s+(\d+):(\d+):(\d+)\.(\d+)/)
533             {
534 0         0 my ($idx, $start, $end, $hh, $mm, $ss, $ms) = ($1, $2, $3, $4, $5, $6, $7) ;
535 0         0 push @expected, {
536             'start' => $start,
537             'end' => $end,
538             } ;
539             #print "$idx) $start .. $end\n" ;
540             }
541             }
542 0         0 close $fh ;
543             }
544            
545 0         0 return @expected ;
546             }
547            
548            
549             #-----------------------------------------------------------------------------
550            
551             =item B
552            
553             Process the results to create a cut list for the video using the results gathered by
554             L or L.
555             Results from detection are stored in the B<$results_href> HASH ref.
556            
557             The B<$ad_config_href> parameter is expected to be the tuning info HASH ref read in using
558             L. It is used to set any settings read in
559             from the default config file. If it is undef then a default search path is used.
560            
561             The optional I<$channel> parameter is used to specify the TV channel that the video was recorded
562             from. This then allows the config fiel to contain channel-specific settings which are used in the
563             detection. If no channel is specified (or the channel name is not found in the config file) then just
564             default settings are used.
565            
566             Optionally specify a filename using B<$csv> to save the analysis results in a comma-separated
567             output file (from use in GUI viewing tool).
568            
569             Optionally specify an ARRAY ref of expected results (read in using L) to
570             allow the GUI viewing tool to mark the positions of the expected program breaks.
571            
572             Optionally specify extra settings in order to override the defaults and those used during detection.
573            
574            
575             =cut
576            
577            
578             sub analyse
579             {
580 0     0 1 0 my ($src, $results_href, $ad_config_href, $channel, $csv, $expected_aref, $extra_settings_href) = @_ ;
581            
582 0         0 my @cut_list = () ;
583            
584 0         0 Linux::DVB::DVBT::Advert::Mem::print_used("Start of analyse") ;
585            
586             ## Frame results
587 0         0 my $frames_adata_aref = $results_href->{'frames'} ;
588            
589             ## Should contain all the settings used during detection
590 0   0     0 my $results_settings_href = $results_href->{'settings'} || {} ;
591            
592             # if no channel specified try using value stored in results
593 0   0     0 $channel ||= $results_settings_href->{'channel'} ;
594            
595 0 0       0 if ($DEBUG)
596             {
597 0         0 print Data::Dumper->Dump(["===analyse====", "det file settings:", $results_settings_href]) ;
598             }
599            
600             # get defaults used by C routines
601 0         0 my $default_settings_href = Linux::DVB::DVBT::Advert::dvb_advert_def_settings() ;
602 0 0       0 if ($DEBUG)
603             {
604 0         0 print Data::Dumper->Dump(["defaults:", $default_settings_href]) ;
605             }
606            
607             # merge together all defaults with the settings used during detection to create a complete set of settings
608 0         0 $results_settings_href = Linux::DVB::DVBT::Advert::Config::merge_settings(
609             $default_settings_href,
610             $results_settings_href,
611             ) ;
612            
613             ## if channel specified, get channel-specific config
614 0         0 my $config_settings_href = _advert_settings($ad_config_href, $channel) ;
615            
616 0 0       0 if ($DEBUG)
617             {
618 0         0 print Data::Dumper->Dump(["config file settings [chan=$channel]:", $config_settings_href]) ;
619             }
620            
621             ## Merge together the default
622 0         0 $extra_settings_href = Linux::DVB::DVBT::Advert::Config::merge_settings(
623             $config_settings_href,
624             $extra_settings_href,
625             ) ;
626            
627             ## Add expected results
628             # actually a list of adverts (i.e. and advert is between start & end)
629 0 0 0     0 if ($expected_aref && (ref($expected_aref) eq 'ARRAY') )
630             {
631 0         0 my $expect_href = shift @$expected_aref ;
632 0         0 foreach my $frame_href (@$frames_adata_aref)
633             {
634 0         0 my $framenum = $frame_href->{'frame'} ;
635 0         0 $frame_href->{$EXPECTED_FIELD} = 1 ;
636 0 0       0 if ($expect_href)
637             {
638 0 0 0     0 if ( ($framenum >= $expect_href->{'start'}) && ($framenum <= $expect_href->{'end'}) )
    0          
639             {
640 0         0 $frame_href->{$EXPECTED_FIELD} = 0 ;
641             }
642             elsif ($framenum > $expect_href->{'end'})
643             {
644 0         0 $expect_href = shift @$expected_aref ;
645             }
646             }
647             }
648             }
649            
650 0 0       0 prt_frames($frames_adata_aref) if $DEBUG >= 3 ;
651            
652             # total number of frames
653 0         0 my $last_frame = -1 ;
654 0 0       0 if (scalar(@$frames_adata_aref))
655             {
656 0         0 $last_frame = $frames_adata_aref->[-1]{'frame'} ;
657             }
658 0         0 my $total_frames = $last_frame + 1 ;
659 0         0 $results_settings_href->{'num_frames'} = $total_frames ;
660            
661 0 0       0 return @cut_list unless $total_frames ;
662            
663            
664             # total packets
665 0         0 my $total_pkts = $frames_adata_aref->[$last_frame]{'end_pkt'} ;
666            
667 0 0       0 print "== analyse() == : total frames:$total_frames, pkts:$total_pkts\n" if $DEBUG ;
668            
669             ## Split frame results out into arrays (containing the HASH refs stored in results) where the specified
670             ## field flag is true
671 0         0 my $black_frames_ada_ref = frames_list($results_href, 'black_frame') ;
672             # my $scene_frames_ada_aref = frames_list($results_href, 'scene_frame') ;
673             # my $size_frames_ada_aref = frames_list($results_href, 'size_change') ;
674 0         0 my $logo_frames_ada_aref = frames_list($results_href, 'logo_frame') ;
675 0         0 my $silent_frames_ada_aref = frames_list($results_href, 'silent_frame') ;
676             # my $all_frames_ada_aref = frames_list($results_href, '') ;
677            
678 0         0 my $csv_frames_aref = new_csv_frames($results_href) ;
679            
680 0         0 Linux::DVB::DVBT::Advert::Mem::print_used(" + created ADA arrays") ;
681            
682            
683             # if ($DEBUG)
684             # {
685             ## dump_frames(\@size_frames, "All SIZE frames") if (@size_frames) ;
686             # dump_frames(\@scene_frames, "All SCENE frames") if (@scene_frames) ;
687             # dump_frames(\@black_frames, "All BLACK frames") if (@black_frames) ;
688             # dump_frames(\@logo_frames, "All LOGO frames") if (@logo_frames) ;
689             # dump_frames(\@silent_frames, "All SILENT frames") if (@silent_frames) ;
690             # #dump_frames(\@banner_frames, "All BANNER frames") if (@banner_frames) ;
691             # #dump_frames(\@audio_frames, "All AUDIO frames") if (@audio_frames) ;
692             # }
693            
694             ## Analysis results
695 0         0 my @black_cut_list ;
696             my @silent_cut_list ;
697 0         0 my @logo_cut_list ;
698            
699             ## Settings
700 0         0 my $global_settings_href = Linux::DVB::DVBT::Advert::Config::cascade_settings($extra_settings_href, '', $results_settings_href) ;
701 0         0 my $black_settings_href = Linux::DVB::DVBT::Advert::Config::cascade_settings($extra_settings_href, 'frame', $results_settings_href) ;
702 0         0 my $logo_settings_href = Linux::DVB::DVBT::Advert::Config::cascade_settings($extra_settings_href, 'logo', $results_settings_href) ;
703 0         0 my $silent_settings_href = Linux::DVB::DVBT::Advert::Config::cascade_settings($extra_settings_href, 'audio', $results_settings_href) ;
704            
705             # return cascaded settings
706 0         0 $results_href->{'settings'} = {
707             $global_settings_href,
708             'frame' => $black_settings_href,
709             'logo' => $logo_settings_href,
710             'audio' => $silent_settings_href,
711             } ;
712            
713 0   0     0 my $rise_thresh = $logo_settings_href->{'logo_rise_threshold'} || 1 ;
714 0   0     0 my $fall_thresh = $logo_settings_href->{'logo_fall_threshold'} || 1 ;
715            
716 0         0 Linux::DVB::DVBT::Advert::Mem::print_used(" + got settings") ;
717            
718            
719             ## Skip if disabled
720 0 0       0 if (!ok_to_detect($global_settings_href))
721             {
722 0         0 return @cut_list ;
723             }
724            
725            
726             ## Saved CSV file for post-detection analysis
727 0         0 my @csv_settings ;
728 0         0 csv_add_setting(\@csv_settings, "frame", "0::") ;
729 0         0 csv_add_setting(\@csv_settings, $PACKET_FIELD, "0::") ;
730 0         0 csv_add_setting(\@csv_settings, $PACKET_END_FIELD, "0::") ;
731 0         0 csv_add_setting(\@csv_settings, $PACKET_GOP_FIELD, "0::") ;
732 0         0 csv_add_setting(\@csv_settings, "black_frame", "0:1:1") ;
733 0         0 csv_add_setting(\@csv_settings, "scene_frame", "0:1:1") ;
734 0         0 csv_add_setting(\@csv_settings, "size_change", "0:1:1") ;
735 0         0 csv_add_setting(\@csv_settings, "match_percent", "0:$rise_thresh:100") ;
736 0         0 csv_add_setting(\@csv_settings, "ave_percent", "0:$rise_thresh/$fall_thresh:100") ;
737 0         0 csv_add_setting(\@csv_settings, "volume_dB", "-96:-60:0") ;
738 0         0 csv_add_setting(\@csv_settings, "silent_frame", "0:1:1") ;
739 0         0 csv_add_setting(\@csv_settings, $PROG_FIELD, "0:1:100") ;
740            
741 0 0       0 if ($expected_aref)
742             {
743 0         0 csv_add_setting(\@csv_settings, $EXPECTED_FIELD, "0:1:1") ;
744             }
745            
746             ## Check that this channel doesn't splat logos across the adverts too!
747 0         0 my $logo_frames_percent = (100.0 * $results_settings_href->{'total_logo_frames'}) / (1.0 * $results_settings_href->{'num_frames'}) ;
748 0 0       0 print "CUTS: Logo % = $logo_frames_percent\n" if $DEBUG ;
749            
750 0 0       0 if ($logo_frames_percent > 90.0)
751             {
752 0 0       0 print "CUTS: Skipping ALL-LOGOS frames...\n" if $DEBUG ;
753             ##TODO: fix....
754 0         0 @$logo_frames_ada_aref = () ;
755             }
756            
757             ##--[ Black detect ]----------------------------------------------------
758 0         0 Linux::DVB::DVBT::Advert::Mem::print_used("Black detect") ;
759 0         0 my $new_black_frames_aref = [] ;
760 0 0       0 if (@$black_frames_ada_ref)
761             {
762             #print STDERR "black detect\n" ;
763             ## process
764 0         0 @black_cut_list = process_black_frames($black_frames_ada_ref, $new_black_frames_aref,
765             $total_pkts, $total_frames, $black_settings_href,
766             $frames_adata_aref, $csv_frames_aref, \@csv_settings) ;
767            
768 0         0 $black_frames_ada_ref = undef ;
769            
770             ## validate cuts
771 0         0 validate_cutlist(\@black_cut_list, $black_settings_href) ;
772            
773             # default to using the black cut list
774 0         0 @cut_list = @black_cut_list ;
775            
776 0 0       0 print "BLACK CUTS: " . scalar(@black_cut_list) . "\n" if $DEBUG ;
777            
778             #print STDERR "black detect - done\n" ;
779             }
780            
781             ##--[ Logo detect ]----------------------------------------------------
782 0 0       0 if (@$logo_frames_ada_aref)
783             {
784             #print STDERR "logo detect\n" ;
785 0         0 Linux::DVB::DVBT::Advert::Mem::print_used("Logo detect") ;
786            
787 0         0 my $scene_frames_ada_aref = frames_list($results_href, 'scene_frame') ;
788 0         0 my $all_frames_ada_aref = frames_list($results_href, '') ;
789            
790             ## process
791 0         0 @logo_cut_list = process_logo_frames($all_frames_ada_aref, $new_black_frames_aref, $scene_frames_ada_aref,
792             $total_pkts, $total_frames, $logo_settings_href,
793             $frames_adata_aref, $csv_frames_aref, \@csv_settings) ;
794            
795 0         0 $scene_frames_ada_aref = undef ;
796 0         0 $all_frames_ada_aref = undef ;
797 0         0 $logo_frames_ada_aref = undef ;
798            
799             ## validate cuts
800 0         0 validate_cutlist(\@logo_cut_list, $logo_settings_href) ;
801            
802 0 0       0 print "LOGO CUTS: " . scalar(@logo_cut_list) . "\n" if $DEBUG ;
803            
804             # use this logo list
805 0 0       0 if (@logo_cut_list >= @cut_list)
806             {
807 0         0 @cut_list = @logo_cut_list ;
808             }
809             else
810             {
811 0         0 @logo_cut_list = () ;
812 0 0       0 print " + Cleared LOGO CUTS\n" if $DEBUG ;
813             }
814             #print STDERR "logo detect - done\n" ;
815             }
816            
817            
818             ##--[ Silence detect ]----------------------------------------------------
819 0 0 0     0 if (!@logo_cut_list && @$new_black_frames_aref && $silent_frames_ada_aref)
      0        
820             {
821             #print STDERR "silence detect\n" ;
822 0         0 Linux::DVB::DVBT::Advert::Mem::print_used("Silence detect") ;
823            
824             ## process
825 0         0 @silent_cut_list = process_silent_frames($new_black_frames_aref, $silent_frames_ada_aref,
826             $total_pkts, $total_frames, $silent_settings_href,
827             $frames_adata_aref, $csv_frames_aref, \@csv_settings) ;
828            
829 0         0 $silent_frames_ada_aref = undef ;
830            
831             ## validate cuts
832 0         0 validate_cutlist(\@silent_cut_list, $silent_settings_href) ;
833            
834 0 0       0 print "SILENT CUTS: " . scalar(@silent_cut_list) . "\n" if $DEBUG ;
835            
836             # default to using the black cut list
837 0 0       0 if (@silent_cut_list)
838             {
839 0         0 @cut_list = @silent_cut_list ;
840             }
841             #print STDERR "silence detect - done\n" ;
842             }
843            
844             #print STDERR "Detect - end\n" ;
845            
846             ##--[ Final ]----------------------------------------------------
847 0         0 Linux::DVB::DVBT::Advert::Mem::print_used("Detect end") ;
848            
849 0 0       0 if ($DEBUG)
850             {
851             #print STDERR "printing cut lists...\n" ;
852 0 0       0 if (@black_cut_list)
853             {
854 0         0 dump_cutlist("BLACK CUT LIST", \@black_cut_list, "#") ;
855             }
856 0 0       0 if (@logo_cut_list)
857             {
858 0         0 dump_cutlist("LOGO CUT LIST", \@logo_cut_list, "#") ;
859             }
860 0 0       0 if (@silent_cut_list)
861             {
862 0         0 dump_cutlist("SILENT CUT LIST", \@silent_cut_list, "#") ;
863             }
864            
865 0         0 dump_cutlist("FINAL CUT LIST", \@cut_list, "") ;
866             #print STDERR "done printing cut lists...\n" ;
867             }
868            
869             ## Save CSV info
870 0 0       0 if ($csv)
871             {
872             #print STDERR "write csv\n" ;
873 0         0 Linux::DVB::DVBT::Advert::Mem::print_used("Writing CSV") ;
874            
875             ## Add cut list as program boundaries
876 0         0 csv_add_prog($results_href, $csv_frames_aref, $PROG_FIELD, \@cut_list) ;
877            
878             ## write out csv
879 0         0 write_csv($csv, $results_href, $csv_frames_aref, @csv_settings) ;
880            
881             #print STDERR "write csv - done\n" ;
882             }
883            
884             ## Tidy up
885 0         0 $results_href = undef ;
886            
887 0         0 Linux::DVB::DVBT::Advert::Mem::print_used("End of analyse") ;
888            
889             #print STDERR "Analyse - done\n" ;
890            
891             ## return results
892 0         0 return @cut_list ;
893             }
894            
895            
896             #-----------------------------------------------------------------------------
897            
898             =item B
899            
900             Cut the $src_file at the points specified in the ARRAY ref $cut_list_aref, writing the output
901             to $cut_file
902            
903             =cut
904            
905             sub ad_cut
906             {
907 0     0 1 0 my ($src_file, $cut_file, $cut_list_aref) = @_ ;
908            
909 0 0       0 croak "Unable to read \"$src_file\"" unless -f $src_file ;
910 0 0       0 croak "Zero-length file \"$src_file\"" unless -s $src_file ;
911 0 0       0 croak "Must specify a destination filename" unless $cut_file ;
912            
913             # ensure dest directory exists
914 0         0 my $dir = dirname($cut_file) ;
915 0 0       0 if (! -d $dir)
916             {
917             # create dir
918 0 0       0 mkpath([$dir], $DEBUG, 0755) or return "Unable to create directory $dir : $!" ;
919             }
920            
921             # run cut
922 0         0 my $rc = dvb_ad_cut($src_file, $cut_file, $cut_list_aref) ;
923            
924 0         0 return $rc ;
925             }
926            
927             #-----------------------------------------------------------------------------
928            
929             =item B
930            
931             Split the $src_file at the points specified in the ARRAY ref $cut_list_aref, writing the output files
932             to $cut_file with suffix XXXX where XXXX is in incrementing count starting at 0001
933            
934             =cut
935            
936             sub ad_split
937             {
938 0     0 1 0 my ($src_file, $cut_file, $cut_list_aref) = @_ ;
939            
940 0 0       0 croak "Unable to read \"$src_file\"" unless -f $src_file ;
941 0 0       0 croak "Zero-length file \"$src_file\"" unless -s $src_file ;
942 0 0       0 croak "Must specify a destination filename" unless $cut_file ;
943            
944             # ensure dest directory exists
945 0         0 my $dir = dirname($cut_file) ;
946 0 0       0 if (! -d $dir)
947             {
948             # create dir
949 0 0       0 mkpath([$dir], $DEBUG, 0755) or return "Unable to create directory $dir : $!" ;
950             }
951            
952             # run cut
953 0         0 my $rc = dvb_ad_split($src_file, $cut_file, $cut_list_aref) ;
954            
955 0         0 return $rc ;
956             }
957            
958            
959             #-----------------------------------------------------------------------------
960            
961             =item B
962            
963             Looks at the settings and returns TRUE if the settings are such that advert detection
964             will be preformed (i.e. detection_method is not 'disabled' or 0)
965            
966             =cut
967            
968             sub ok_to_detect
969             {
970 0     0 1 0 my ($settings_href) = @_ ;
971            
972 0         0 my $ok = 0 ;
973 0 0 0     0 if (exists($settings_href->{'detection_method'}) && $settings_href->{'detection_method'})
974             {
975 0         0 $ok = 1 ;
976             }
977            
978 0         0 return $ok ;
979             }
980            
981            
982             #-----------------------------------------------------------------------------
983            
984             =item B
985            
986             Writes a default Advert config file. If the optional B<$force> parameter is set, then
987             writes a new file even if one already exists. Uses the optional search path to find
988             a writeable directory (other than the default search path).
989            
990             =cut
991            
992             sub write_default_config
993             {
994 0     0 1 0 my ($force, $search_path) = @_ ;
995            
996 0   0     0 $search_path ||= $CONFIG_DIR ;
997 0         0 $CONFIG_DIR = $search_path ;
998            
999 0         0 my $fname = Linux::DVB::DVBT::Advert::Config::write_filename($search_path) ;
1000 0 0       0 if ($fname)
1001             {
1002             ## only write if it doesn't exist OR we're forced to overwrite
1003 0 0 0     0 if ($force || (!$force && ! -f $fname))
      0        
1004             {
1005             # get defaults used by C routines
1006 0         0 my $default_settings_href = Linux::DVB::DVBT::Advert::dvb_advert_def_settings() ;
1007 0         0 my %settings = (
1008             $Linux::DVB::DVBT::Advert::Config::ADVERT_GLOBAL_SECTION => $default_settings_href,
1009             ) ;
1010            
1011             # write config
1012 0         0 Linux::DVB::DVBT::Advert::Config::write_default_dvb_adv(\%settings, $search_path) ;
1013             }
1014             }
1015             }
1016            
1017            
1018             #============================================================================================
1019             # PRIVATE
1020             #============================================================================================
1021            
1022             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1023             # FRAMES LISTS
1024             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1025            
1026             #-----------------------------------------------------------------------------
1027             # Split frame results out into arrays (containing the HASH refs stored in results) where the specified
1028             # field flag is true. If flag_field is empty then return the list of all frames
1029             #
1030             # XS
1031             #
1032             sub frames_list
1033             {
1034 2     2 0 23 my ($results_href, $flag_field) = @_ ;
1035            
1036 2         5 my @list_ada ;
1037            
1038 2         4 my $thing = tied @{$results_href->{'frames'}} ;
  2         7  
1039            
1040 2         37 tie @list_ada, 'Linux::DVB::DVBT::Advert', 'FILTER',
1041             [$thing, $flag_field, 1] ;
1042            
1043 2         6 my $ada = tied @list_ada ;
1044 2         32 $ada->update_gaps() ;
1045            
1046 2         24 return \@list_ada ;
1047             }
1048            
1049             #-----------------------------------------------------------------------------
1050             # Pull out any entries where the specified field >= threshold
1051             #
1052             sub frames_matching
1053             {
1054 0     0 0 0 my ($frames_adata_aref, $flag_field, $threshold) = @_ ;
1055            
1056 0         0 my @list ;
1057            
1058 0         0 my $thing = tied @$frames_adata_aref ;
1059            
1060 0         0 my @list_ada ;
1061 0         0 tie @list_ada, 'Linux::DVB::DVBT::Advert', 'FILTER',
1062             [$thing, $flag_field, $threshold] ;
1063            
1064             #dump_frames(\@list_ada, "frames_matching() - raw") ;
1065            
1066 0         0 my $ada = tied @list_ada ;
1067 0         0 $ada->update_gaps() ;
1068            
1069             #dump_frames(\@list_ada, "frames_matching() - updated gap") ;
1070            
1071             # turn into a list of frame HASH entries
1072 0         0 @list = frames_array_to_hashlist(\@list_ada) ;
1073            
1074             #dump_frames(\@list, "frames_matching() - frames_array_to_hashlist") ;
1075            
1076 0         0 return \@list ;
1077             }
1078            
1079            
1080            
1081            
1082             #---------------------------------------------------------------------------------
1083             # Convert a list of all frames into a list of frame HASH entries
1084             sub frames_array_to_hashlist
1085             {
1086 0     0 0 0 my ($frames_aref) = @_ ;
1087            
1088             ## coalesce (also updates the gap settings)
1089 0         0 my @frames = coalesce_frames($frames_aref,
1090             {
1091             'frame_window' => 1,
1092             'min_frames' => 1,
1093             }
1094             ) ;
1095            
1096 0         0 return @frames ;
1097             }
1098            
1099            
1100            
1101             #---------------------------------------------------------------------------------
1102             sub frames_subtract
1103             {
1104 0     0 0 0 my ($src_frames_aref, $sub_frames_aref, $fuzziness) = @_ ;
1105            
1106             ## add
1107 0         0 my @frames = frames_subtract_array($src_frames_aref, $sub_frames_aref, $fuzziness) ;
1108            
1109             ## convert to list of frame hashs
1110 0         0 @frames = frames_array_to_hashlist(\@frames) ;
1111            
1112             return @frames
1113 0         0 }
1114            
1115             #---------------------------------------------------------------------------------
1116             # Add frames list - return the array of all frames
1117             sub frames_add_array
1118             {
1119 0     0 0 0 my ($src_frames_aref, $add_frames_aref, $fuzziness) = @_ ;
1120            
1121             ## get first entry from source to use to replicate into newly added entries
1122 0         0 my $new_href = $add_frames_aref->[0] ;
1123            
1124             ## pre-process subtracting array
1125 0         0 my $first_frame = $add_frames_aref->[0]{'frame'} - $fuzziness ;
1126 0         0 my $last_frame = $add_frames_aref->[-1]{'frame_end'} + $fuzziness ;
1127 0         0 foreach my $href (@$add_frames_aref)
1128             {
1129 0         0 my $frame_start = $href->{'frame'} - $fuzziness ;
1130 0         0 my $frame_end = $href->{'frame_end'} + $fuzziness ;
1131             }
1132            
1133             ## pre-process source array
1134 0         0 my @frames ;
1135 0 0       0 $first_frame = $add_frames_aref->[0]{'frame'} if $first_frame > $add_frames_aref->[0]{'frame'} ;
1136 0 0       0 $last_frame = $add_frames_aref->[-1]{'frame_end'} if $last_frame < $add_frames_aref->[-1]{'frame_end'} ;
1137 0         0 my %add_frames ;
1138 0         0 foreach my $href (@$add_frames_aref)
1139             {
1140 0         0 my $frame_start = $href->{'frame'} ;
1141 0         0 my $frame_end = $href->{'frame_end'} ;
1142 0         0 foreach my $fnum ($frame_start..$frame_end)
1143             {
1144 0         0 $add_frames{$fnum} = $href ;
1145             }
1146             }
1147            
1148             ## Merge the two arrays
1149 0         0 foreach my $fnum ($first_frame..$last_frame)
1150             {
1151 0 0       0 if (exists($add_frames{$fnum}))
    0          
1152             {
1153 0         0 push @frames, $add_frames{$fnum} ;
1154             }
1155             elsif (exists($add_frames{$fnum}))
1156             {
1157 0         0 push @frames, {
1158             %$new_href,
1159 0         0 %{$add_frames{$fnum}},
1160             } ;
1161             }
1162             }
1163 0         0 update_gap(\@frames) ;
1164            
1165             return @frames
1166 0         0 }
1167            
1168            
1169            
1170             #---------------------------------------------------------------------------------
1171             # Subtract frames list - return the array of all frames
1172             sub frames_subtract_array
1173             {
1174 0     0 0 0 my ($src_frames_aref, $sub_frames_aref, $fuzziness) = @_ ;
1175            
1176             ## Make subtracting frames "fuzzy"
1177 0         0 my %fuzzy_frames ;
1178 0         0 foreach my $href (@$sub_frames_aref)
1179             {
1180 0         0 my $frame_start = $href->{'frame'} - $fuzziness ;
1181 0         0 my $frame_end = $href->{'frame_end'} + $fuzziness ;
1182 0 0       0 $frame_start=0 if ($frame_start<0) ;
1183 0         0 foreach my $fnum ($frame_start..$frame_end)
1184             {
1185 0         0 $fuzzy_frames{$fnum} = $href ;
1186             }
1187             }
1188            
1189             ## Remove source frames that do not coincide with subtracted
1190 0         0 my @frames ;
1191 0         0 foreach my $href (@$src_frames_aref)
1192             {
1193 0         0 my $framenum = $href->{'frame'} ;
1194 0         0 my $framenum_end = $href->{'frame_end'} ;
1195            
1196 0         0 my $ok = 0 ;
1197 0         0 foreach my $fnum ($href->{'frame'}..$href->{'frame_end'})
1198             {
1199 0 0       0 if (exists($fuzzy_frames{$fnum}))
1200             {
1201 0         0 $ok=1 ;
1202             }
1203             }
1204 0 0       0 if ($ok)
1205             {
1206 0         0 push @frames, $href ;
1207             }
1208             }
1209            
1210 0         0 update_gap(\@frames) ;
1211            
1212             return @frames
1213 0         0 }
1214            
1215             #---------------------------------------------------------------------------------
1216             # Add frames list - return a list of frame HASH refs
1217             sub frames_add
1218             {
1219 0     0 0 0 my ($src_frames_aref, $add_frames_aref, $fuzziness) = @_ ;
1220            
1221             ## add
1222 0         0 my @frames = frames_add_array($src_frames_aref, $add_frames_aref, $fuzziness) ;
1223            
1224             ## convert to list of frame hashs
1225 0         0 @frames = frames_array_to_hashlist(\@frames) ;
1226            
1227             return @frames
1228 0         0 }
1229            
1230             #---------------------------------------------------------------------------------
1231             # Reduce the program length of the specified frame HASH entry to the nearest gap start
1232             # in the given list
1233             #
1234             # HASH entry:
1235             #
1236             # numframes=n
1237             # |----------------------------------------------->|
1238             # |
1239             # _...............................................
1240             # | | :
1241             # ___________| |______________________________________________:____
1242             # ^ ^
1243             # frame=f frame_end
1244             # |<----------window--------------------:
1245             #
1246             # Closest entry in list:
1247             #
1248             # |<---min_gap--------->|
1249             #
1250             # | |
1251             # |<--------------------|
1252             # gap _...........
1253             # : | | :
1254             # _________________________:_____________________| |__________:____
1255             # ^ ^
1256             # frame=f frame_end
1257             #
1258             #
1259             # HASH entry after reduction:
1260             #
1261             # numframes
1262             # |------------>|
1263             # |
1264             # _............
1265             # | | :
1266             # ___________| |___________:_______________________________________
1267             # ^ ^
1268             # frame=f frame_end
1269             #
1270             #
1271             sub frames_reduce_end
1272             {
1273 0     0 0 0 my ($frame_href, $frames_aref, $window, $min_gap) = @_ ;
1274            
1275 0         0 my $gap_href ;
1276            
1277 0 0       0 if ($DEBUG) {print "frames_reduce_end(win=$window, gap=$min_gap) -START : "; dump_frame($frame_href) ;}
  0         0  
  0         0  
1278            
1279             ## Find any gaps that are within the specified window AND gap >= min_gap
1280             ## (If window=0, allow any gaps)
1281             ## $gap_href will be set to the PREVIOUS entry so that the 'frame_end' and 'end_pkt'
1282             ## values can be used
1283             ##
1284 0         0 my $min_framenum = $frame_href->{'frame_end'} - $window ;
1285 0         0 my $max_framenum = $frame_href->{'frame_end'} ;
1286 0 0       0 $min_framenum = 0 if !$window ;
1287 0         0 my $prev_href = {'frame_end'=>0, 'end_pkt'=>0} ;
1288 0         0 foreach my $this_href (@$frames_aref)
1289             {
1290 0 0       0 if ($DEBUG) {print " + evaluating gap : "; dump_frame($this_href) ;}
  0         0  
  0         0  
1291            
1292             ## Stop at first valid match
1293 0 0 0     0 if (($this_href->{'frame'} >= $min_framenum) && ($this_href->{'frame'} >= $min_framenum) && ($this_href->{'gap'} >= $min_gap))
      0        
1294             {
1295 0         0 $gap_href = $prev_href ;
1296 0 0       0 if ($DEBUG) {print " + + found gap : using "; dump_frame($gap_href) ;}
  0         0  
  0         0  
1297 0         0 last ;
1298             }
1299 0         0 $prev_href = $this_href ;
1300             }
1301            
1302            
1303             ## Reduce end point to beginning of gap
1304 0 0       0 if ($gap_href)
1305             {
1306 0         0 $frame_href->{'frame_end'} = $gap_href->{'frame_end'} ;
1307 0         0 $frame_href->{'end_pkt'} = $gap_href->{'end_pkt'} ;
1308            
1309 0 0       0 if ($DEBUG) {print " ++ Reduced "; dump_frame($frame_href) ;}
  0         0  
  0         0  
1310             }
1311            
1312 0 0       0 if ($DEBUG) {print "frames_reduce_end(win=$window, gap=$min_gap) -END : "; dump_frame($frame_href) ;}
  0         0  
  0         0  
1313            
1314 0         0 return $frame_href
1315             }
1316            
1317             #---------------------------------------------------------------------------------
1318             # !!!TBD!!!
1319             #
1320             # TODO: Work out what to do here!
1321             #
1322             # Reduce the program length of the specified frame HASH entry to the nearest gap start
1323             # in the given list
1324             #
1325             # HASH entry:
1326             #
1327             # numframes=n
1328             # |----------------------------------------------->|
1329             # |
1330             # _...............................................
1331             # | | :
1332             # ___________| |______________________________________________:____
1333             # ^ ^
1334             # frame=f frame_end
1335             # |<----------window--------------------:
1336             #
1337             # Closest entry in list:
1338             #
1339             # |<---min_gap--------->|
1340             #
1341             # | |
1342             # |<--------------------|
1343             # gap _...........
1344             # : | | :
1345             # _________________________:_____________________| |__________:____
1346             # ^ ^
1347             # frame=f frame_end
1348             #
1349             #
1350             # HASH entry after reduction:
1351             #
1352             # numframes
1353             # |------------>|
1354             # |
1355             # _............
1356             # | | :
1357             # ___________| |___________:_______________________________________
1358             # ^ ^
1359             # frame=f frame_end
1360             #
1361             #
1362             sub frames_increase_start
1363             {
1364 0     0 0 0 my ($frame_href, $frames_aref, $window, $min_gap) = @_ ;
1365            
1366             # my $gap_href ;
1367             #
1368             #if ($DEBUG) {print "frames_reduce_end(win=$window, gap=$min_gap) -START : "; dump_frame($frame_href) ;}
1369             #
1370             # ## Find any gaps that are within the specified window AND gap >= min_gap
1371             # ## (If window=0, allow any gaps)
1372             # ## $gap_href will be set to the PREVIOUS entry so that the 'frame_end' and 'end_pkt'
1373             # ## values can be used
1374             # ##
1375             # my $min_framenum = $frame_href->{'frame_end'} - $window ;
1376             # my $max_framenum = $frame_href->{'frame_end'} ;
1377             # $min_framenum = 0 if !$window ;
1378             # my $prev_href = {'frame_end'=>0, 'end_pkt'=>0} ;
1379             # foreach my $this_href (@$frames_aref)
1380             # {
1381             #if ($DEBUG) {print " + evaluating gap : "; dump_frame($this_href) ;}
1382             #
1383             # ## Stop at first valid match
1384             # if (($this_href->{'frame'} >= $min_framenum) && ($this_href->{'frame'} >= $min_framenum) && ($this_href->{'gap'} >= $min_gap))
1385             # {
1386             # $gap_href = $prev_href ;
1387             #if ($DEBUG) {print " + + found gap : using "; dump_frame($gap_href) ;}
1388             # last ;
1389             # }
1390             # $prev_href = $this_href ;
1391             # }
1392             #
1393             #
1394             # ## Reduce end point to beginning of gap
1395             # if ($gap_href)
1396             # {
1397             # $frame_href->{'frame_end'} = $gap_href->{'frame_end'} ;
1398             # $frame_href->{'end_pkt'} = $gap_href->{'end_pkt'} ;
1399             #
1400             #if ($DEBUG) {print " ++ Reduced "; dump_frame($frame_href) ;}
1401             # }
1402             #
1403             #if ($DEBUG) {print "frames_reduce_end(win=$window, gap=$min_gap) -END : "; dump_frame($frame_href) ;}
1404            
1405 0         0 return $frame_href
1406             }
1407            
1408            
1409            
1410             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1411             # CSV
1412             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1413            
1414             #-----------------------------------------------------------------------------
1415             #
1416             sub new_csv_frames
1417             {
1418 3     3 0 24 my ($results_href) = @_ ;
1419            
1420             #print STDERR "new_csv_frames()\n";
1421            
1422             # Create
1423 3         6 my @list ;
1424            
1425 3         7 my $thing = tied @{$results_href->{'frames'}} ;
  3         9  
1426            
1427 3         95 tie @list, 'Linux::DVB::DVBT::Advert', 'ADV', [$thing] ;
1428            
1429 3         12 return \@list ;
1430             }
1431            
1432            
1433             #---------------------------------------------------------------------------------
1434             sub csv_add_setting
1435             {
1436 0     0 0   my ($settings_aref, $key, $threshold) = @_ ;
1437            
1438 0           push @{$settings_aref->[0]}, $key ;
  0            
1439 0           push @{$settings_aref->[1]}, $threshold ;
  0            
1440             }
1441            
1442             #---------------------------------------------------------------------------------
1443             sub csv_add_prog
1444             {
1445 0     0 0   my ($results_href, $csv_frames_aref, $prog_field, $cutlist_aref) = @_ ;
1446            
1447 0 0         print "csv_add_prog()\n" if $DEBUG;
1448            
1449 0           my @cuts = @$cutlist_aref ;
1450 0           my $cut_href = shift @cuts ;
1451            
1452 0           my $adv = tied @$csv_frames_aref ;
1453 0           $adv->add_key($prog_field) ;
1454            
1455 0           for(my $i=0; $i < scalar(@$csv_frames_aref); ++$i)
1456             {
1457             #my $href = $csv_frames_aref->[$i] ;
1458 0           my $href = {} ;
1459 0           my $framenum = $csv_frames_aref->[$i]->{'frame'} ;
1460            
1461 0 0         print " + frame $framenum : cut_href s=$cut_href->{'frame'} .. e=$cut_href->{'frame_end'}\n" if $DEBUG;
1462            
1463 0           $href->{$prog_field} = 100 ;
1464 0           my $done = 0 ;
1465 0   0       while ($cut_href && !$done)
1466             {
1467 0 0 0       if ($framenum < $cut_href->{'frame'})
    0          
    0          
1468             {
1469 0           $href->{$prog_field} = 100 ;
1470 0           ++$done ;
1471             }
1472             elsif ( ($framenum >= $cut_href->{'frame'}) && ($framenum <= $cut_href->{'frame_end'}))
1473             {
1474 0           $href->{$prog_field} = 0 ;
1475 0           ++$done ;
1476             }
1477             elsif ( ($framenum > $cut_href->{'frame_end'}))
1478             {
1479             # get next in the list
1480 0 0         if (@cuts)
1481             {
1482 0           $cut_href = shift @cuts ;
1483             }
1484             else
1485             {
1486 0           $href->{$prog_field} = 100 ;
1487 0           ++$done ;
1488             }
1489             }
1490             }
1491            
1492 0           $csv_frames_aref->[$i] = $href ;
1493             }
1494            
1495 0 0         print "csv_add_prog() - END\n" if $DEBUG;
1496            
1497             }
1498            
1499             #-----------------------------------------------------------------------------
1500             sub csv_add_frames
1501             {
1502 0     0 0   my ($csv_settings_aref, $frames_adata_aref, $csv_frames_aref, $new_frames_aref, $field, $threshold, $new_field) = @_ ;
1503            
1504             #print STDERR "csv_add_frames()\n";
1505            
1506 0           push @{$csv_settings_aref->[0]}, $field ;
  0            
1507 0           push @{$csv_settings_aref->[1]}, $threshold ;
  0            
1508            
1509             # start by clearing
1510 0           my $adv = tied @$csv_frames_aref ;
1511 0           $adv->add_key($field) ;
1512            
1513             # next add frames
1514 0           foreach my $buff_href (@$new_frames_aref)
1515             {
1516 0           my $fnum_start = $buff_href->{'frame'} ;
1517 0   0       my $fnum_end = $buff_href->{'frame_end'} || $fnum_start ;
1518            
1519 0 0 0       if ( defined($fnum_end) && ($fnum_end > $fnum_start))
1520             {
1521 0           foreach my $fnum ($fnum_start..$fnum_end)
1522             {
1523 0           $csv_frames_aref->[$fnum] = { $field => $buff_href->{$new_field} };
1524             }
1525             }
1526             else
1527             {
1528 0           $csv_frames_aref->[$fnum_start] = { $field => $buff_href->{$new_field} };
1529             }
1530             }
1531            
1532             #print STDERR "csv_add_frames() - END\n";
1533            
1534             }
1535            
1536             #-----------------------------------------------------------------------------
1537             # Write CSV
1538             sub write_csv
1539             {
1540 0     0 0   my ($fname, $results_href, $csv_frames_aref, $headings_aref, $levels_aref) = @_;
1541            
1542 0 0         print "Writing CSV $fname ... \n" if $DEBUG ;
1543            
1544 0 0         open my $fh, ">$fname" or die "Unable to write CSV $fname : $!" ;
1545 0           print $fh "$headings_aref->[0]" ;
1546 0           for (my $i=1; $i < scalar(@$headings_aref); ++$i)
1547             {
1548 0           print $fh ",$headings_aref->[$i] [$levels_aref->[$i]]" ;
1549             }
1550 0           print $fh "\n" ;
1551            
1552 0           my $frames_adata_aref = $results_href->{'frames'} ;
1553 0           foreach my $frame_href (@$frames_adata_aref)
1554             {
1555 0           my $frame = $frame_href->{'frame'} ;
1556 0           my $href = $csv_frames_aref->[$frame] ;
1557            
1558 0           my $head = $headings_aref->[0] ;
1559 0           print $fh "$href->{$head}" ;
1560 0           for (my $i=1; $i < scalar(@$headings_aref); ++$i)
1561             {
1562 0           $head = $headings_aref->[$i] ;
1563 0 0         my $val = exists($href->{$head}) ? $href->{$head} : $frame_href->{$head} ;
1564 0           print $fh ",$val" ;
1565             }
1566 0           print $fh "\n" ;
1567             }
1568            
1569 0           close $fh ;
1570             }
1571            
1572            
1573             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1574             # FRAME HASH
1575             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1576            
1577             # Each frame HASH entry, along with specific information, stores the relationship with it's
1578             # previous entry
1579             #
1580             # numframes=n
1581             # | |----------->|
1582             # |<--------------------|
1583             # _............ gap _...........
1584             # | | : | | :
1585             # ___________| |___________:_____________________| |__________:____
1586             # ^ ^
1587             # frame=f frame_end
1588             #
1589             #
1590            
1591            
1592            
1593            
1594             #-----------------------------------------------------------------------------
1595             # Set the gap counts - the distance each frame is from it's previous frame
1596             #
1597             # numframes=n' numframes=n
1598             # |------------>| |----------->|
1599             # |<--------------------|
1600             # _............ gap _...........
1601             # | | : | | :
1602             # ___________| |___________:_____________________| |__________:____
1603             # ^ ^ ^
1604             # frame=f' frame_end=e' frame=f
1605             #
1606             #
1607             # | f' ..... e' | e'+1 ......... f-1 |
1608             # |------------>|
1609             # n'=e'-f'+1 |
1610             # |<--------------------|
1611             # gap = (f-1) - (e'+1) + 1
1612             #
1613             #
1614             #
1615             # For frame f:
1616             #
1617             # gap = f - e' - 1
1618             #
1619             sub calc_gap
1620             {
1621 0     0 0   my ($frame, $prev_frame_end) = @_ ;
1622            
1623 0           return $frame - $prev_frame_end - 1 ;
1624             }
1625            
1626             #-----------------------------------------------------------------------------
1627             #
1628             sub update_gap
1629             {
1630 0     0 0   my ($frames_aref) = @_ ;
1631            
1632 0           my $prev_frame_end = -1 ;
1633 0           foreach my $href (@$frames_aref)
1634             {
1635 0           my $frame = $href->{'frame'} ;
1636 0           $href->{'gap'} = calc_gap($frame, $prev_frame_end) ;
1637            
1638 0           $prev_frame_end = $href->{'frame_end'} ;
1639             }
1640             }
1641            
1642            
1643             #-----------------------------------------------------------------------------
1644             # Return the number of frames for this frame entry
1645             #
1646             # numframes=n' numframes=n
1647             # |------------>| |----------->|
1648             # _............ _...........
1649             # | | : | | :
1650             # ___________| |___________:_____________________| |__________:____
1651             # frame=f' frame_end=e' frame=f frame_end=e
1652             #
1653             # For frame f:
1654             #
1655             # numframes: n = e - f + 1
1656             #
1657            
1658             sub numframes
1659             {
1660 0     0 0   my ($frame_href) = @_ ;
1661            
1662 0           return $frame_href->{frame_end} - $frame_href->{frame} + 1 ;
1663             }
1664            
1665             #-----------------------------------------------------------------------------
1666             # Set the type based on section length
1667             sub _prog_type
1668             {
1669 0     0     my ($duration, $settings_href) = @_;
1670            
1671             # could be either
1672 0           my $type = "advert/prog" ;
1673 0 0         if ($duration <= $settings_href->{'max_advert'})
    0          
1674             {
1675 0           $type = "advert" ;
1676 0 0         print "_prog_type=$type : $duration <= $settings_href->{'max_advert'}\n" if $DEBUG >= 2 ;
1677             }
1678             elsif ($duration >= $settings_href->{'min_program'})
1679             {
1680 0           $type = "program" ;
1681 0 0         print "_prog_type=$type : $duration >= $settings_href->{'min_program'}\n" if $DEBUG >= 2 ;
1682             }
1683 0           return $type ;
1684             }
1685            
1686             #---------------------------------------------------------------------------------
1687             # Ensure each cut is of a valid length
1688             sub validate_cutlist
1689             {
1690 0     0 0   my ($cutlist_aref, $settings_href) = @_ ;
1691            
1692 0 0         print "validate_cutlist:\n" if $DEBUG ;
1693            
1694             ## Throw away rubbish (e.g. at start of video when there is actually nothing to cut)
1695 0           my $prev_end = 0 ;
1696 0           my @list ;
1697 0           my $num_entries = scalar(@$cutlist_aref) ;
1698 0           for (my $i=0; $i < $num_entries; ++$i)
1699             {
1700 0           my $cut_href = shift @$cutlist_aref ;
1701 0           my $period = ($cut_href->{'frame_end'}-$cut_href->{'frame'}+1) ;
1702 0 0         if ($period > 0)
1703             {
1704            
1705             # see if gap (i.e. program) long enough
1706 0           my $ok=1 ;
1707 0           my $prog_period =($cut_href->{'frame'}-$prev_end+1) ;
1708 0 0         if ($DEBUG) { print " + checking (prog=$prog_period min=$settings_href->{'min_program'}) : "; dump_frame($cut_href) ; }
  0            
  0            
1709 0 0         if ($prog_period < $settings_href->{'min_program'})
1710             {
1711 0 0         print " !! Program period too small (prog=$prog_period min=$settings_href->{'min_program'})" if $DEBUG ;
1712 0 0         if (scalar(@list))
1713             {
1714 0 0         if ($DEBUG) { print " , appending new to end of previous" ; dump_frame($list[-1]) ; }
  0            
  0            
1715 0           $ok=0 ;
1716 0           $list[-1]{'frame_end'} = $cut_href->{'frame_end'} ;
1717 0           $list[-1]{'end_pkt'} = $cut_href->{'end_pkt'} ;
1718             }
1719             else
1720             {
1721 0 0         print ", setting start to 0\n" if $DEBUG ;
1722             # start of list, amend first frame
1723 0           $cut_href = { %$cut_href } ;
1724 0           $cut_href->{'frame'} = 0 ;
1725 0           $cut_href->{'start_pkt'} = 0 ;
1726 0           $cut_href->{'gap'} = 0 ;
1727             }
1728             }
1729            
1730 0 0         if ($ok)
1731             {
1732 0 0         if ($DEBUG) { print " + + saved : " ; dump_frame($cut_href) ; }
  0            
  0            
1733 0           push @list, $cut_href ;
1734 0           $prev_end = $cut_href->{'frame_end'} ;
1735             }
1736             }
1737             }
1738            
1739             ## Build new list
1740 0           $prev_end = 0 ;
1741 0           $num_entries = scalar(@list) ;
1742 0           for (my $i=0; $i < $num_entries; ++$i)
1743             {
1744 0           my $cut_href = $list[$i] ;
1745            
1746 0 0         if (defined($prev_end))
1747             {
1748 0           my $prog_period = ($cut_href->{'frame'}-$prev_end+1) ;
1749 0 0         printf("(PROG $prev_end .. $cut_href->{'frame'} period=$prog_period (min=$settings_href->{'min_program'})") if $DEBUG ;
1750 0 0         if ($prog_period >= $settings_href->{'min_program'})
1751             {
1752 0 0         print " - OK" if $DEBUG ;
1753             }
1754 0 0         print " )\n" if $DEBUG ;
1755            
1756             }
1757            
1758             # don't check start/end
1759 0           my $period = ($cut_href->{'frame_end'}-$cut_href->{'frame'}+1) ;
1760            
1761 0 0         printf("%2d: $cut_href->{'frame'}..$cut_href->{'frame_end'} period=$period (min=$settings_href->{'min_advert'})", $i) if $DEBUG ;
1762 0 0 0       if ( ($i==0) || ($period >= $settings_href->{'min_advert'}) || ($i==$num_entries-1) )
      0        
1763             {
1764 0           push @$cutlist_aref, $cut_href ;
1765 0 0         print " - OK" if $DEBUG ;
1766             }
1767 0 0         print "\n" if $DEBUG ;
1768            
1769 0           $prev_end = $cut_href->{'frame_end'} ;
1770             }
1771             }
1772            
1773            
1774             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1775             # ANALYSIS UTILS
1776             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1777            
1778             #---------------------------------------------------------------------------------
1779             #
1780             sub coalesce_frames
1781             {
1782 0     0 0   my ($frames_aref, $settings_href, $start_framenum, $title) = @_ ;
1783            
1784 0   0       $title ||= "" ;
1785 0 0         print "coalesce_frames($title)\n" if $DEBUG ;
1786            
1787 0   0       $start_framenum ||= 0 ;
1788            
1789 0           my @frames ;
1790             my $curr_href ;
1791 0           for (my $idx=0; $idx < scalar(@$frames_aref); $idx++)
1792             {
1793 0 0         if ($DEBUG >= 2) { print " -> frame "; dump_frame($frames_aref->[$idx]) ; }
  0            
  0            
1794            
1795             # start of new "block"
1796 0 0         if ($frames_aref->[$idx]{'gap'} > $settings_href->{'frame_window'})
1797             {
1798 0 0         if ($DEBUG) { print "new block : "; dump_frame($curr_href) ; }
  0            
  0            
1799            
1800             ## check existing
1801            
1802             # curr idx
1803             # |||| ||| |
1804             # curr
1805             # <----gap idx
1806             # <---gap
1807             #
1808             # Can now check to see if previous block (the "current" HASH) is a spurious block
1809             #
1810 0 0 0       if ($curr_href &&
1811             (numframes($curr_href) < $settings_href->{'min_frames'})
1812             )
1813             {
1814 0 0         if ($DEBUG) { print " - (curr gap = $frames_aref->[$idx]{'gap'}, curr numframes = $curr_href->{'numframes'}) removed spurious : "; dump_frame($curr_href) ; }
  0            
  0            
1815            
1816             # remove spurious
1817 0           pop @frames ;
1818             }
1819            
1820             # start new
1821             $curr_href = {
1822 0           'frame_start' => $frames_aref->[$idx]{'frame'},
1823             'frame_end' => $frames_aref->[$idx]{'frame'},
1824 0           %{$frames_aref->[$idx]},
1825             } ;
1826 0           push @frames, $curr_href ;
1827            
1828 0           my $prev_frame_end = $start_framenum ;
1829 0 0         if (scalar(@frames) >= 2)
1830             {
1831 0           $prev_frame_end = $frames[-2]{'frame_end'} ;
1832 0 0         if ($DEBUG) { print " - calc prev : "; dump_frame($frames[-2]) ; }
  0            
  0            
1833             }
1834 0           $curr_href->{'gap'} = calc_gap($curr_href->{'frame_start'}, $prev_frame_end ) ;
1835             }
1836             else
1837             {
1838 0 0         if (!$curr_href)
1839             {
1840             # start new
1841 0           $curr_href = {
1842             'frame_start' => $frames_aref->[$idx]{'frame'},
1843             'frame_end' => $frames_aref->[$idx]{'frame_end'},
1844 0           %{$frames_aref->[$idx]},
1845             } ;
1846 0           push @frames, $curr_href ;
1847             }
1848             else
1849             {
1850             # expand end time
1851 0           $curr_href->{'end_pkt'} = $frames_aref->[$idx]{'end_pkt'} ;
1852 0           $curr_href->{'frame_end'} = $frames_aref->[$idx]{'frame_end'} ;
1853             }
1854             }
1855             }
1856            
1857 0 0 0       if ($curr_href && (numframes($curr_href) < $settings_href->{'min_frames'}))
1858             {
1859 0 0         if ($DEBUG) { print " - removed spurious : "; dump_frame($curr_href) ; }
  0            
  0            
1860             # remove spurious
1861 0           pop @frames ;
1862             }
1863            
1864 0 0         print "coalesce_frames($title) - DONE\n" if $DEBUG ;
1865            
1866 0           update_gap(\@frames) ;
1867            
1868 0           return @frames ;
1869             }
1870            
1871             #============================================================================================
1872             # DEBUG
1873             #============================================================================================
1874            
1875             #-----------------------------------------------------------------------------
1876             # format fps into time
1877             sub fps_time
1878             {
1879 0     0 0   my ($fps_duration) = @_;
1880 0           my $str ;
1881            
1882 0           my $fsecs = $fps_duration * 1.0 / $FPS ;
1883 0           my $secs = int($fps_duration / $FPS) ;
1884 0           my ($mins, $hours) ;
1885            
1886 0 0         if ($secs > 60)
1887             {
1888 0 0         if ($secs > 60*60)
1889             {
1890 0           $hours = int($secs / (60*60)) ;
1891 0           $secs -= $hours * 60*60 ;
1892             }
1893            
1894 0           $mins = int($secs / (60)) ;
1895 0           $secs -= $mins * 60 ;
1896             }
1897            
1898 0 0         if ($hours)
1899             {
1900 0           $str .= sprintf "%d hours ", $hours ;
1901             }
1902 0 0         if ($mins)
1903             {
1904 0           $str .= sprintf "%d mins ", $mins ;
1905             }
1906 0           $str .= sprintf "%d secs", $secs ;
1907            
1908 0           return $str ;
1909             }
1910            
1911            
1912             #-----------------------------------------------------------------------------
1913             # format fps into time
1914             sub fps_timestamp
1915             {
1916 0     0 0   my ($fps_duration) = @_;
1917 0           my $str ;
1918            
1919 0           my $fsecs = $fps_duration * 1.0 / $FPS ;
1920 0           my $secs = int($fps_duration / $FPS) ;
1921 0           my ($mins, $hours, $msec) = (0, 0, 0);
1922            
1923 0           $msec = int($fsecs*1000 - $secs*1000) ;
1924            
1925 0 0         if ($secs > 60)
1926             {
1927 0 0         if ($secs > 60*60)
1928             {
1929 0           $hours = int($secs / (60*60)) ;
1930 0           $secs -= $hours * 60*60 ;
1931             }
1932            
1933 0           $mins = int($secs / (60)) ;
1934 0           $secs -= $mins * 60 ;
1935             }
1936            
1937 0           $str = sprintf "%0d:%02d:%02d.%03d", $hours, $mins, $secs, $msec ;
1938            
1939 0           return $str ;
1940             }
1941            
1942             #---------------------------------------------------------------------------------
1943             #
1944             sub dump_cutlist
1945             {
1946 0     0 0   my ($title, $cutlist_aref, $prefix) = @_ ;
1947            
1948 0           print "\n\n# $title\n" ;
1949 0           foreach my $cut_href (@$cutlist_aref)
1950             {
1951 0           printf "${prefix}# frame=%d:%d %s\n", $cut_href->{'frame'}, $cut_href->{'frame_end'}, fps_time($cut_href->{'frame_end'}-$cut_href->{'frame'}+1) ;
1952             }
1953 0           foreach my $cut_href (@$cutlist_aref)
1954             {
1955 0           printf "${prefix}p=%d:%d\n", $cut_href->{'start_pkt'}, $cut_href->{'end_pkt'} ;
1956             }
1957             }
1958            
1959            
1960            
1961            
1962             #-----------------------------------------------------------------------------
1963             # Display this black frame entry
1964             sub dump_frame
1965             {
1966 0     0 0   my ($frame_href) = @_;
1967            
1968 0           printf("frame=%d [%s] gap=%d (%s) numframes=%d : ",
1969             $frame_href->{'frame'},
1970             fps_timestamp($frame_href->{'frame'}),
1971             $frame_href->{'gap'},
1972             fps_time($frame_href->{'gap'}),
1973             numframes($frame_href),
1974             ) ;
1975 0 0         if (exists($frame_href->{'match_percent'}))
1976             {
1977 0           printf "Qual=%d%% : ", $frame_href->{'match_percent'} ;
1978             }
1979 0 0         if (exists($frame_href->{'weight'}))
1980             {
1981 0           printf "Weight=%d%% : ", $frame_href->{'weight'} ;
1982             }
1983 0 0         if (exists($frame_href->{'ave_percent'}))
1984             {
1985 0           printf "Ave. Qual=%d%% : ", $frame_href->{'ave_percent'} ;
1986             }
1987 0           printf("%d .. %d",
1988             $frame_href->{'start_pkt'}, $frame_href->{'end_pkt'},
1989             ) ;
1990            
1991 0 0         if (exists($frame_href->{'type'}))
1992             {
1993 0           print " : Type=$frame_href->{'type'}" ;
1994             }
1995            
1996 0 0         if (exists($frame_href->{'adverts'}))
1997             {
1998 0           print " : Ads=$frame_href->{'adverts'}" ;
1999             }
2000 0 0         if (exists($frame_href->{'frame_start'}))
2001             {
2002 0           print " : Frames $frame_href->{'frame_start'} .. $frame_href->{'frame_end'} duration (" .
2003             fps_time(numframes($frame_href))
2004             . ")" ;
2005             }
2006 0           print "\n" ;
2007             }
2008            
2009             #-----------------------------------------------------------------------------
2010             # Show the current black frames list
2011             sub dump_frames
2012             {
2013 0     0 0   my ($frames_aref, $msg) = @_;
2014            
2015 0           my @edges ;
2016             my $edge_href ;
2017            
2018 0           print "\n----[ $msg (", scalar(@$frames_aref)," frames) ]------------------------------\n" ;
2019 0           foreach my $href (@$frames_aref)
2020             {
2021 0   0       while ( $edge_href && ($href->{'frame'} > $edge_href->{'frame'}) )
2022             {
2023 0 0         print "*** $edge_href->{'frame'} ** " . ($edge_href->{'type'} eq 'start_pkt' ? "vvvvvvvvvv" : "^^^^^^^^^^") . "******\n" ;
2024 0           $edge_href = shift @edges ;
2025             }
2026            
2027 0 0         print "---------\n" if ($href->{'gap'}>1);
2028            
2029 0 0 0       if ( $edge_href && ($href->{'frame'} == $edge_href->{'frame'}) && ($edge_href->{'type'} eq 'start_pkt'))
      0        
2030             {
2031 0 0         print "*** $edge_href->{'frame'} ** " . ($edge_href->{'type'} eq 'start_pkt' ? "vvvvvvvvvv" : "^^^^^^^^^^") . "******\n" ;
2032 0           $edge_href = shift @edges ;
2033             }
2034 0 0         print "???BAD??? " if ($href->{'gap'}<0);
2035            
2036 0           dump_frame($href) ;
2037            
2038 0 0 0       if ( $edge_href && ($href->{'frame'} == $edge_href->{'frame'}) && ($edge_href->{'type'} eq 'end_pkt'))
      0        
2039             {
2040 0 0         print "*** $edge_href->{'frame'} ** " . ($edge_href->{'type'} eq 'start_pkt' ? "vvvvvvvvvv" : "^^^^^^^^^^") . "******\n" ;
2041 0           $edge_href = shift @edges ;
2042             }
2043             }
2044 0           print "\n----------------------------------\n" ;
2045             }
2046            
2047             sub prt_frame
2048             {
2049 0     0 0   my ($frames_aref, $framenum) = @_;
2050            
2051 0           print "$framenum : " ;
2052 0           foreach my $key (sort keys %{$frames_aref->[$framenum]})
  0            
2053             {
2054 0           print " $key=$frames_aref->[$framenum]{$key}" ;
2055             }
2056 0           print "\n" ;
2057             }
2058             sub prt_frames
2059             {
2060 0     0 0   my ($frames_aref) = @_;
2061            
2062 0           foreach my $frame_href (@$frames_aref)
2063             {
2064 0           prt_frame($frames_aref, $frame_href->{'frame'}) ;
2065             }
2066             }
2067            
2068             #=================================================================================
2069             # BLACK FRAMES
2070             #=================================================================================
2071            
2072             #---------------------------------------------------------------------------------
2073             #
2074             sub black_frame_cutlist
2075             {
2076 0     0 0   my ($frames_aref, $total_pkts, $total_frames, $settings_href) = @_ ;
2077 0           my @cut_list ;
2078            
2079 0 0         print "--- black_frame_cutlist() ---\n" if $DEBUG ;
2080            
2081             # : start : : end :
2082             # : pad : : pad :
2083             # _________|||____________|||___________|||______
2084             # : :
2085             #
2086            
2087             #
2088             # _____|||____________|||___________|||__________
2089             # : :
2090             #
2091            
2092             #
2093             # __|||____________|||___________|||_____________
2094             # : :
2095             #
2096            
2097 0           my $curr_href=undef ;
2098 0           foreach my $href (@$frames_aref)
2099             {
2100 0           my $type = _prog_type($href->{'gap'}, $settings_href) ;
2101            
2102 0 0         if ($DEBUG)
2103             {
2104 0           print "Cutlist len = " . scalar(@cut_list)."\n" ;
2105 0           print "[$type] " ; dump_frame($href) ;
  0            
2106             }
2107            
2108             # start of new "block"
2109 0 0         if ($type eq 'program')
2110             {
2111 0 0         print " + New prog\n" if $DEBUG ;
2112            
2113             # start new
2114 0           $curr_href = {
2115             'adverts' => 0,
2116             'type' => $type,
2117             %$href,
2118             } ;
2119 0           push @cut_list, $curr_href ;
2120            
2121 0 0         print " + new prog added\n" if $DEBUG ;
2122             }
2123             else
2124             {
2125 0 0         if (!$curr_href)
2126             {
2127             # start new
2128 0           $curr_href = {
2129             'adverts' => 0,
2130             'type' => $type,
2131             %$href,
2132             } ;
2133 0           push @cut_list, $curr_href ;
2134 0 0         print " + new advert\n" if $DEBUG ;
2135             }
2136             else
2137             {
2138             # inc advert count
2139 0           $curr_href->{'adverts'}++ ;
2140            
2141             # expand end time
2142 0           $curr_href->{'end_pkt'} = $href->{'end_pkt'} ;
2143 0           $curr_href->{'frame_end'} = $href->{'frame_end'} ;
2144 0 0         print " + extend\n" if $DEBUG ;
2145             }
2146             }
2147             }
2148            
2149             ## process start and end
2150 0 0         if (@cut_list)
2151             {
2152             ## start
2153 0           my $start_href = $cut_list[0] ;
2154 0 0         if ($start_href->{'type'} ne 'program')
2155             {
2156 0           $start_href->{'start_pkt'} = 0 ;
2157 0           $start_href->{'frame_start'} = 0 ; # for debug
2158             }
2159            
2160             ## end
2161 0           my $end_href = $cut_list[-1] ;
2162 0           my $end_gap = $total_frames - $end_href->{'frame_end'} - 1 ;
2163 0           my $end_type = _prog_type($end_gap, $settings_href) ;
2164 0 0         if ($end_type ne 'program')
2165             {
2166 0           $end_href->{'end_pkt'} = $total_pkts-1 ;
2167 0           $end_href->{'frame_end'} = $total_frames-1 ; # for debug
2168             }
2169             }
2170            
2171            
2172 0           return @cut_list ;
2173             }
2174            
2175             #---------------------------------------------------------------------------------
2176             #
2177             sub process_black_frames
2178             {
2179 0     0 0   my ($black_frames_ada_ref, $new_black_frames_aref, $total_pkts, $total_frames, $settings_href, $frames_adata_aref, $csv_frames_aref, $csv_settings_aref) = @_ ;
2180            
2181 0 0         if ($DEBUG)
2182             {
2183 0           print "\n=================================================\n" ;
2184 0           print "process_black_frames()\n" ;
2185 0           print Data::Dumper->Dump(["Settings:", $settings_href]) ;
2186             }
2187            
2188             ## strip out any spurious frames
2189            
2190             # start by coalescing the contiguous black frames
2191 0           my @frames = coalesce_frames($black_frames_ada_ref, $settings_href, 0) ;
2192            
2193 0 0         dump_frames(\@frames, "BLACK coalesced") if $DEBUG >= 2 ;
2194            
2195 0           csv_add_frames($csv_settings_aref, $frames_adata_aref, $csv_frames_aref, \@frames,
2196             $BLACK_COALESCED_FIELD, "0:1:1", 'black_frame') ;
2197            
2198             ## update input array with coalesced version
2199 0           my $num_black = scalar(@$black_frames_ada_ref) ;
2200 0           foreach my $href (@frames)
2201             {
2202 0           push @$new_black_frames_aref, $href ;
2203             }
2204            
2205             ## Create black frame cutlist
2206 0           my @cut_list = black_frame_cutlist(\@frames, $total_pkts, $total_frames, $settings_href) ;
2207            
2208 0 0         dump_frames(\@cut_list, "Final BLACK Cut List") if $DEBUG >= 2 ;
2209            
2210 0           return @cut_list ;
2211             }
2212            
2213             #---------------------------------------------------------------------------------
2214             #
2215             sub process_silent_frames
2216             {
2217 0     0 0   my ($black_frames_aref, $silent_frames_ada_aref, $total_pkts, $total_frames, $settings_href, $frames_adata_aref, $csv_frames_aref, $csv_settings_aref) = @_ ;
2218            
2219 0 0         if ($DEBUG)
2220             {
2221 0           print "\n=================================================\n" ;
2222 0           print "process_silent_frames()\n" ;
2223 0           print Data::Dumper->Dump(["Settings:", $settings_href]) ;
2224             }
2225            
2226             ## strip out any spurious frames
2227            
2228             # start by coalescing the contiguous black frames
2229 0           my @silent_frames = coalesce_frames($silent_frames_ada_aref, $settings_href, 0) ;
2230            
2231 0           csv_add_frames($csv_settings_aref, $frames_adata_aref, $csv_frames_aref, \@silent_frames,
2232             $SILENT_COALESCED_FIELD, "0:1:1", 'silent_frame') ;
2233            
2234             #my $SILENCE_WINDOW = 100 ;
2235            
2236             ## Remove black frames that do not coincide with silence (with silence "fuzzy")
2237 0           my @frames = frames_subtract($black_frames_aref, \@silent_frames, $settings_href->{'silence_window'}) ;
2238            
2239 0 0         if ($DEBUG >= 2)
2240             {
2241 0           dump_frames(\@silent_frames, "SILENT") ;
2242 0           dump_frames(\@frames, "SILENT BLACK") ;
2243             }
2244            
2245             # Now have blocks of silence (in @silence_frames) along with spikes of black frames that are "silent".
2246             # Overlay the silent blocks with the black blocks, coalesce (again!) and we should have the answer
2247 0           my @combined_frames ;
2248 0           my %silent_frames = map { $_->{'frame'} => $_ } @silent_frames ;
  0            
2249 0           my %silent_black_frames = map { $_->{'frame'} => $_ } @frames ;
  0            
2250 0           my $last_framenum = $silent_frames[-1]{'frame'} ;
2251 0 0         $last_framenum = $frames[-1]{'frame'} if $last_framenum < $frames[-1]{'frame'} ;
2252 0 0         print "Process frames 0..$last_framenum\n" if $DEBUG ;
2253 0           for (my $framenum=0; $framenum <= $last_framenum; ++$framenum)
2254             {
2255 0           my $href ;
2256 0 0         if (exists($silent_frames{$framenum}))
    0          
2257             {
2258 0           $href = $silent_frames{$framenum} ;
2259 0 0         if ($DEBUG) {print " + silent @ $framenum : " ; dump_frame($href) ;}
  0            
  0            
2260             }
2261             elsif (exists($silent_black_frames{$framenum}))
2262             {
2263 0           $href = $silent_black_frames{$framenum} ;
2264 0 0         if ($DEBUG) {print " + silent_black @ $framenum : " ; dump_frame($href) ;}
  0            
  0            
2265             }
2266            
2267 0 0         if ($href)
2268             {
2269 0           push @combined_frames, { %$href, 'black_frame'=>1 } ;
2270 0           $framenum = $href->{'frame_end'} ;
2271             }
2272             }
2273 0           update_gap(\@combined_frames) ;
2274            
2275 0           @combined_frames = coalesce_frames(\@combined_frames, $settings_href, 0) ;
2276 0 0         dump_frames(\@combined_frames, "COMBINED COAL") if $DEBUG >= 2 ;
2277            
2278 0           csv_add_frames($csv_settings_aref, $frames_adata_aref, $csv_frames_aref, \@combined_frames,
2279             $SILENT_BLACK_FIELD, "0:1:1", 'black_frame') ;
2280            
2281             ##NEW###################################################
2282             #my $reduce_end = 15 * $FPS ; # 15 sec window
2283             #my $reduce_min_gap = 2 * $FPS ; # need at least 2 sec gap
2284            
2285 0 0         if ($settings_href->{'reduce_end'})
2286             {
2287             ## reduce the program end to the nearest silent region within
2288             ## the window of the end
2289 0           foreach my $frame_href (@combined_frames)
2290             {
2291 0           frames_reduce_end($frame_href, \@silent_frames, $settings_href->{'reduce_end'}, $settings_href->{'reduce_min_gap'}) ;
2292             }
2293            
2294 0           csv_add_frames($csv_settings_aref, $frames_adata_aref, $csv_frames_aref, \@combined_frames,
2295             $REDUCED_SILENT_BLACK_FIELD, "0:1:1", 'black_frame') ;
2296            
2297             }
2298            
2299            
2300             ##NEW###################################################
2301            
2302            
2303            
2304            
2305            
2306             ## Create black frame cutlist
2307 0           my @cut_list = black_frame_cutlist(\@combined_frames, $total_pkts, $total_frames, $settings_href) ;
2308            
2309 0 0         dump_frames(\@cut_list, "Final SILENT Cut List") if $DEBUG >= 2 ;
2310            
2311 0           return @cut_list ;
2312             }
2313            
2314            
2315             #=================================================================================
2316             # LOGO FRAMES
2317             #=================================================================================
2318            
2319             # TODO: Handle all start cases - record after start of prog (i.e. logo = 100%), record during adverts, record during end of previous
2320             # TODO: Handle all end cases - record end before end of prog, record end during adverts, record end at start of next prog
2321            
2322             #-----------------------------------------------------------------------------
2323             # Given a frame number and a list of frames, find the frames from the list that
2324             # are immediately adjacent to this one.
2325             #
2326             sub bounding_frames
2327             {
2328 0     0 0   my ($framenum, $frames_aref) = @_ ;
2329            
2330 0           my ($before, $after) ;
2331 0           foreach my $href (@$frames_aref)
2332             {
2333 0           my $frame_end = $href->{'frame_end'} ;
2334 0 0         if ($frame_end <= $framenum)
    0          
2335             {
2336 0           $before = $href->{'frame_end'} ;
2337             }
2338             elsif ($href->{'frame'} > $framenum)
2339             {
2340 0           $after = $href->{'frame'} ;
2341 0           last ;
2342             }
2343             }
2344 0           return ($before, $after) ;
2345             }
2346            
2347            
2348             #---------------------------------------------------------------------------------
2349             sub logo_add_frames
2350             {
2351 0     0 0   my ($msg_str, $frames_adata_aref, $logo_frames_aref, $start_frame, $end_frame, $settings_href, $edge_ref) = @_ ;
2352            
2353             # my @add_frames ;
2354 0           foreach my $fnum ($start_frame..$end_frame)
2355             {
2356             # save first edge
2357 0 0         if ($edge_ref)
2358             {
2359 0 0         $$edge_ref = $fnum unless defined($$edge_ref) ;
2360             }
2361            
2362             # spoof an entry that looks like a valid logo detection
2363 0           my $buff_href = { %{$frames_adata_aref->[$fnum]} } ;
  0            
2364 0           $buff_href->{'match_percent'} = $settings_href->{'logo_rise_threshold'} ;
2365 0           $buff_href->{'ave_percent'} = $settings_href->{'logo_rise_threshold'} ;
2366            
2367 0 0         if ($DEBUG) {print " + + $msg_str extended by : " ; dump_frame($buff_href) ;}
  0            
  0            
2368            
2369             # push @add_frames, $buff_href ;
2370 0           push @$logo_frames_aref, $buff_href ;
2371             }
2372             # push @$logo_frames_aref, @add_frames ;
2373            
2374             }
2375            
2376             #---------------------------------------------------------------------------------
2377             #
2378             sub process_logo_frames
2379             {
2380 0     0 0   my ($logo_all_frames_ada_aref, $black_frames_aref, $scene_frames_ada_aref, $total_pkts, $total_frames, $settings_href,
2381             $frames_adata_aref, $csv_frames_aref, $csv_settings_aref) = @_ ;
2382            
2383 0           my @cut_list ;
2384            
2385 0 0         if ($DEBUG)
2386             {
2387 0           print "\n=================================================\n" ;
2388 0           print "process_logo_frames()\n" ;
2389 0           print Data::Dumper->Dump(["Settings:", $settings_href]) ;
2390             }
2391            
2392 0           my $logo_frames_adl_aref ;
2393 0           $logo_frames_adl_aref = [] ;
2394            
2395 0           my @lf ;
2396 0           my $thing = tied @$frames_adata_aref ;
2397            
2398 0           tie @lf, 'Linux::DVB::DVBT::Advert', 'LOGO',
2399             [$thing] ;
2400            
2401 0           $logo_frames_adl_aref = \@lf ;
2402            
2403 0           my $adl = tied @$logo_frames_adl_aref ;
2404            
2405            
2406             ## Threshold the frames based on average quality
2407 0           my $prev = 0 ;
2408 0           my $detect_mode = 'rise' ;
2409 0           foreach my $href (@$logo_all_frames_ada_aref)
2410             {
2411 0           my $framenum = $href->{'frame'} ;
2412            
2413 0 0         if ($DEBUG)
2414             {
2415 0           $adl->logo_frames_sanity($framenum) ;
2416             }
2417            
2418             ## threshold detection with hysteresis
2419 0           my $above = 0 ;
2420 0 0         if ($detect_mode eq 'rise')
2421             {
2422             # rising detect
2423 0 0         if ($href->{'ave_percent'} >= $settings_href->{'logo_rise_threshold'})
2424             {
2425 0           $above = 1 ;
2426 0           $detect_mode = 'fall' ;
2427             }
2428             }
2429             else
2430             {
2431             # falling detect
2432 0           $above = 1 ;
2433 0 0         if ($href->{'ave_percent'} < $settings_href->{'logo_fall_threshold'})
2434             {
2435 0           $above = 0 ;
2436 0           $detect_mode = 'rise' ;
2437             }
2438             }
2439            
2440             ## use detected threshold
2441 0 0         if ($above)
2442             {
2443 0 0         if (!$prev)
2444             {
2445 0 0         if ($DEBUG) {print " + rising edge : " ; dump_frame($href) ;}
  0            
  0            
2446            
2447             ## rising edge - prefix by previous points to previous scene change
2448            
2449            
2450             # See if any scene changes are within (yet another) window of the new start edge
2451             #
2452             # Scene Change: | | | |
2453             # Logo ave quality: ||||||||||||||||||||||||||||||
2454             # Extended (scene): ::::::::::::||||||||||||||||||||||||||||||...
2455             #
2456 0           my $start_framenum = $framenum ;
2457            
2458             ## extend back while "raw" quality > threshold
2459 0           my $extend_start = $start_framenum - $settings_href->{'logo_ave_points'} ;
2460 0 0         $extend_start = 0 if ($extend_start < 0) ;
2461 0           for (my $fnum = $start_framenum-1; $fnum >= $extend_start; --$fnum)
2462             {
2463 0 0         if (($frames_adata_aref->[$fnum]{'match_percent'} >= $settings_href->{'logo_rise_threshold'}))
2464             {
2465 0           $start_framenum = $fnum ;
2466 0 0         if ($DEBUG) {print " + + match extended by : " ; dump_frame($frames_adata_aref->[$fnum]) ;}
  0            
  0            
2467             }
2468             else
2469             {
2470             # stop
2471 0           last ;
2472             }
2473             }
2474            
2475 0           my $found_edge = 0 ;
2476 0           my $edge = undef ;
2477            
2478             # find any black frames around new start frame
2479 0 0         print "rising black bounding..\n" if $DEBUG ;
2480 0           my ($black_before, $black_after) = bounding_frames($start_framenum, $black_frames_aref) ;
2481            
2482 0 0         print " - black : rising frame $start_framenum, black before $black_before, black after $black_after\n" if $DEBUG ;
2483            
2484             # find any scene changes around new start frame
2485 0 0         print "rising scene bounding..\n" if $DEBUG ;
2486 0           my ($scene_before, $scene_after) = bounding_frames($start_framenum, $scene_frames_ada_aref) ;
2487            
2488 0 0         print " - scene : rising frame $start_framenum, scene before $scene_before, scene after $scene_after\n" if $DEBUG ;
2489            
2490             # if change occurs before the start frame AND it's not too far away, then extend to this point
2491 0 0 0       if (($black_before < $start_framenum) && ( ($start_framenum-$black_before) < $settings_href->{'logo_ave_points'}))
2492             {
2493 0           ++$found_edge ;
2494 0           logo_add_frames("black", $frames_adata_aref, $logo_frames_adl_aref, $black_before, $framenum-1, $settings_href, \$edge) ;
2495             }
2496            
2497            
2498             # if scene change occurs before the start frame AND it's not too far away, then extend to this point
2499 0 0 0       if (!$found_edge && ($scene_before < $start_framenum) && ( ($start_framenum-$scene_before) < $settings_href->{'logo_ave_points'}))
      0        
2500             {
2501 0           ++$found_edge ;
2502 0           logo_add_frames("scene", $frames_adata_aref, $logo_frames_adl_aref, $scene_before, $framenum-1, $settings_href, \$edge) ;
2503             }
2504            
2505 0 0         print " - found? $found_edge : edge=$edge\n" if $DEBUG ;
2506            
2507             ## if this is the start of the video, see if we can extend to the start (use the lower threshold)
2508 0 0 0       if ($found_edge && ($edge) && ($edge <= $settings_href->{'logo_ave_points'}) )
      0        
2509             {
2510 0 0         print " + + start extending...\n" if $DEBUG ;
2511 0           my $fnum = $edge-1 ;
2512 0           my $window_count = 0 ;
2513 0   0       while ( ($fnum >= 0) && ($window_count < $settings_href->{'frame_window'}) )
2514             {
2515 0 0         if ($frames_adata_aref->[$fnum]{'match_percent'} >= $settings_href->{'logo_fall_threshold'})
2516             {
2517 0           $window_count = 0 ;
2518             }
2519             else
2520             {
2521 0           ++$window_count ;
2522             }
2523 0           --$fnum ;
2524             }
2525            
2526             # if we're nearly at the start, then just start at 0
2527 0           ++$fnum ;
2528 0 0         $fnum = 0 if ($fnum <= $settings_href->{frame_window}) ;
2529            
2530             # add frames (skip any < threshold)
2531 0           my @start_frames ;
2532 0           while ($fnum < $edge)
2533             {
2534 0 0         if ($frames_adata_aref->[$fnum]{'match_percent'} >= $settings_href->{'logo_rise_threshold'})
2535             {
2536 0 0         if ($DEBUG) {print " + + start-extended by : " ; dump_frame($frames_adata_aref->[$fnum]) ;}
  0            
  0            
2537             # push @start_frames, $frames_adata_aref->[$fnum] ;
2538 0           unshift @$logo_frames_adl_aref, $frames_adata_aref->[$fnum] ;
2539             }
2540 0           ++$fnum ;
2541             }
2542            
2543             # insert these at the start
2544             # unshift @$logo_frames_adl_aref, @start_frames ;
2545             }
2546            
2547             ## fall back on extending as much as possible
2548 0 0         if (!$found_edge)
2549             {
2550             # failed to use scene change - fall back on using raw quality
2551            
2552             ## rising edge - prefix by previous points > threshold
2553            
2554             # calc where to start from (allow a window where quality can be < threshold)
2555             # (need to use frame buffer)
2556 0           my $end_index = $framenum-1 ;
2557 0           my $start_index = $end_index ;
2558 0           my $window_count = 0 ;
2559 0   0       while ( ($start_index > 0) && ($end_index-$start_index < $settings_href->{'logo_ave_points'}) && ($window_count < $settings_href->{'frame_window'}) )
      0        
2560             {
2561 0 0         if ($frames_adata_aref->[$start_index]{'match_percent'} >= $settings_href->{'logo_rise_threshold'})
2562             {
2563 0           $window_count = 0 ;
2564             }
2565             else
2566             {
2567 0           ++$window_count ;
2568             }
2569 0           --$start_index ;
2570             }
2571            
2572 0 0         if ($DEBUG) {print " + start..end : $start_index .. $end_index\n" ; }
  0            
2573            
2574             # add frames (skip any < threshold)
2575 0           ++$start_index ;
2576 0           foreach my $buff_href (@$frames_adata_aref[$start_index..$end_index])
2577             {
2578 0 0         if ($buff_href->{'match_percent'} > $settings_href->{'logo_rise_threshold'})
2579             {
2580 0 0         if ($DEBUG) {print " + + extended by : " ; dump_frame($buff_href) ;}
  0            
  0            
2581 0           push @$logo_frames_adl_aref, $buff_href ;
2582             }
2583             }
2584             }
2585            
2586 0           $adl->update_gaps() ;
2587            
2588 0 0         dump_frames($logo_frames_adl_aref, "LOGO after extending due to rising edge") if $DEBUG >= 2;
2589            
2590             }
2591            
2592             ## add this frame
2593 0           push @$logo_frames_adl_aref, $href ;
2594            
2595 0           $prev = 1 ;
2596             }
2597             else
2598             {
2599 0 0         if ($prev)
2600             {
2601 0 0         if ($DEBUG) {print " + falling edge : " ; dump_frame($href) ;}
  0            
  0            
2602            
2603 0           $adl->update_gaps() ;
2604            
2605 0 0         dump_frames($logo_frames_adl_aref, "LOGO before reducing due to falling edge") if $DEBUG >= 2;
2606            
2607             ## trailing edge - remove any raw points < threshold
2608             # use logo array we're building
2609            
2610             # remove ALL frames for the length of the buffer, then start adding them back iff > threshold AND not too far away
2611 0           my $end_index = scalar(@$logo_frames_adl_aref)-1 ;
2612 0           my $start_index = $end_index-$settings_href->{'logo_ave_points'} ;
2613 0 0         $start_index = 0 if $start_index < 0 ;
2614 0           my $num_end_frames = $end_index - $start_index + 1 ;
2615 0 0         if ($DEBUG) {print " + + reduced by $num_end_frames frames (start idx=$start_index, end idx=$end_index) to : " ; dump_frame($logo_frames_adl_aref->[$start_index]) ;}
  0            
  0            
2616            
2617 0           splice @$logo_frames_adl_aref, $start_index ;
2618            
2619             ## check we have some points left?
2620 0 0         if (scalar(@$logo_frames_adl_aref))
2621             {
2622            
2623             # create a list of these removed frames that are > threshold
2624 0           my @end_frames = () ;
2625 0 0         print STDERR "logo_frames_adl_aref size = ",scalar(@$logo_frames_adl_aref),"\n" if $DEBUG >= 2;
2626 0 0         print STDERR "About to read from logo_frames_adl_aref[-1] ...\n" if $DEBUG >= 2;
2627 0           my $new_framenum = $logo_frames_adl_aref->[-1]{'frame'}+1 ;
2628 0           foreach (1..$num_end_frames)
2629             {
2630 0 0         if ($frames_adata_aref->[$new_framenum]{'match_percent'} >= $settings_href->{'logo_rise_threshold'})
2631             {
2632 0 0         if ($DEBUG) {print " >> end_frames + $new_framenum " ; dump_frame($frames_adata_aref->[$new_framenum]) ;}
  0            
  0            
2633 0           push @end_frames, $frames_adata_aref->[$new_framenum] ;
2634             }
2635 0           ++$new_framenum ;
2636             }
2637            
2638             # coalesce valid frames together
2639 0           update_gap(\@end_frames) ;
2640 0           @end_frames = coalesce_frames(\@end_frames, $settings_href, $logo_frames_adl_aref->[-1]{'frame'}, "logo end frames") ;
2641            
2642 0 0         dump_frames(\@end_frames, "coalesced end logo frames") if $DEBUG >= 2;
2643            
2644             # Just use the first block - the end *should* be the real end of the program
2645 0 0         if (@end_frames)
2646             {
2647 0           my $f_href = $end_frames[0] ;
2648 0           foreach my $new_framenum ($f_href->{'frame'}..$f_href->{'frame_end'})
2649             {
2650 0           push @$logo_frames_adl_aref, $frames_adata_aref->[$new_framenum] ;
2651 0 0         if ($DEBUG) {print " + + re-extend by : " ; dump_frame($frames_adata_aref->[$new_framenum]) ;}
  0            
  0            
2652             }
2653             }
2654            
2655 0           @end_frames = () ;
2656            
2657 0           $adl->update_gaps() ;
2658            
2659 0 0         dump_frames($logo_frames_adl_aref, "LOGO after reducing") if $DEBUG >= 2 ;
2660            
2661            
2662             ## see if we can expand out to a scene change
2663 0           my $end_framenum = $logo_frames_adl_aref->[-1]{'frame'} ;
2664 0 0         print " end frame=$end_framenum\n" if $DEBUG ;
2665            
2666             # find any black frames around new end frame
2667 0 0         print "falling black bounding..\n" if $DEBUG ;
2668 0           my ($black_before, $black_after) = bounding_frames($end_framenum, $black_frames_aref) ;
2669            
2670 0 0         print " - black : falling frame $end_framenum, black before $black_before, black after $black_after\n" if $DEBUG ;
2671            
2672             # find any scene changes around new end frame
2673 0 0         print "falling scene bounding..\n" if $DEBUG ;
2674 0           my ($scene_before, $scene_after) = bounding_frames($end_framenum, $scene_frames_ada_aref) ;
2675            
2676 0 0         print " - scene : falling frame $end_framenum, scene before $scene_before, scene after $scene_after\n" if $DEBUG ;
2677            
2678 0           my $found_edge = 0 ;
2679            
2680             # if black frame occurs after the end frame AND it's not too far away, then extend to this point
2681 0 0 0       if (($black_after > $end_framenum) && ( ($black_after-$end_framenum) < $settings_href->{'logo_ave_points'}))
2682             {
2683 0           ++$found_edge ;
2684 0           logo_add_frames("black", $frames_adata_aref, $logo_frames_adl_aref, $end_framenum+1, $black_after, $settings_href) ;
2685             }
2686            
2687             # if scene change occurs after the end frame AND it's not too far away, then extend to this point
2688 0 0 0       if (!$found_edge && ($scene_after > $end_framenum) && ( ($scene_after-$end_framenum) < $settings_href->{'logo_ave_points'}))
      0        
2689             {
2690 0           ++$found_edge ;
2691 0           logo_add_frames("scene", $frames_adata_aref, $logo_frames_adl_aref, $end_framenum+1, $scene_after, $settings_href) ;
2692             }
2693            
2694 0 0 0       if (!$found_edge && $DEBUG)
2695             {
2696 0           print "Bugger - failed to find edge!\n" ;
2697             }
2698            
2699 0           $adl->update_gaps() ;
2700            
2701 0 0         dump_frames($logo_frames_adl_aref, "LOGO after re-extending") if $DEBUG >= 2 ;
2702            
2703             } # if got some logo frames left after splice?
2704             }
2705            
2706 0           $prev = 0 ;
2707             }
2708             }
2709            
2710            
2711             ## update gap's
2712 0           $adl->update_gaps() ;
2713            
2714            
2715             ## Add processed information
2716 0   0       my $rise_thresh = $settings_href->{'logo_rise_threshold'} || 1 ;
2717 0   0       my $fall_thresh = $settings_href->{'logo_fall_threshold'} || 1 ;
2718            
2719 0 0         dump_frames($logo_frames_adl_aref, "LOGO processed") if $DEBUG >= 2 ;
2720            
2721 0           csv_add_frames($csv_settings_aref, $frames_adata_aref, $csv_frames_aref, $logo_frames_adl_aref,
2722             $LOGO_PROCESSED_FIELD, "0:$rise_thresh/$fall_thresh:100", 'match_percent') ;
2723            
2724             ## start by coalescing the contiguous frames
2725 0           my @frames = coalesce_frames($logo_frames_adl_aref, $settings_href, 0, "logo frames") ;
2726            
2727 0 0         dump_frames(\@frames, "LOGO coalesced") if $DEBUG >= 2 ;
2728            
2729 0           csv_add_frames($csv_settings_aref, $frames_adata_aref, $csv_frames_aref, \@frames,
2730             $LOGO_COALESCED_FIELD, "0:$rise_thresh/$fall_thresh:100", 'match_percent') ;
2731            
2732            
2733             ##NEW###################################################
2734             #my $reduce_end = 15 * $FPS ; # 15 sec window
2735             #my $reduce_min_gap = 2 * $FPS ; # need at least 2 sec gap
2736            
2737             ## calc logo match frames - used for frame end reduction
2738 0           my $logo_match_frames_aref = frames_matching($logo_all_frames_ada_aref, 'match_percent', $settings_href->{'logo_rise_threshold'});
2739 0           csv_add_frames($csv_settings_aref, $frames_adata_aref, $csv_frames_aref, $logo_match_frames_aref,
2740             'logo_match', "0:$rise_thresh/$rise_thresh:100", 'match_percent') ;
2741            
2742            
2743             #dump_frames($logo_match_frames_aref, "LOGO match frames") ;
2744            
2745             ## process end reduction
2746 0 0         if ($settings_href->{'reduce_end'})
2747             {
2748             ## reduce the program end to the nearest silent region within
2749             ## the window of the end
2750 0           foreach my $frame_href (@frames)
2751             {
2752 0           frames_reduce_end($frame_href, $logo_match_frames_aref, $settings_href->{'reduce_end'}, $settings_href->{'reduce_min_gap'}) ;
2753             }
2754            
2755 0           csv_add_frames($csv_settings_aref, $frames_adata_aref, $csv_frames_aref, \@frames,
2756             $REDUCED_LOGO_COALESCED_FIELD, "0:$rise_thresh/$fall_thresh:100", 'match_percent') ;
2757            
2758             }
2759            
2760            
2761             ##NEW###################################################
2762            
2763            
2764            
2765             ## Now glue together blocks
2766 0           my @blocks ;
2767 0           my $curr_href=undef ;
2768 0           foreach my $href (@frames)
2769             {
2770 0 0         printf("frame=%d gap=%d (%8.3f) numframes=%d : %d .. %d\n",
2771             $href->{'frame'},
2772             $href->{'gap'},
2773             $href->{'gap'}*1.0 / $FPS,
2774             numframes($href),
2775             $href->{'start_pkt'}, $href->{'end_pkt'},
2776             ) if $DEBUG ;
2777            
2778             # start of new "block"
2779 0 0         if ($href->{'gap'} >= $settings_href->{'max_gap'})
2780             {
2781 0 0         print " + New\n" if $DEBUG ;
2782             # start new
2783 0           $curr_href = {
2784             %$href,
2785             } ;
2786 0           push @blocks, $curr_href ;
2787             }
2788             else
2789             {
2790 0 0         print " - extend : new numframes=",numframes($href),", new gap=$href->{'gap'}\n" if $DEBUG ;
2791 0 0         if (!$curr_href)
2792             {
2793 0 0         print " - + extend NEW\n" if $DEBUG ;
2794             # start new
2795 0           $curr_href = {
2796             %$href,
2797             } ;
2798 0           push @blocks, $curr_href ;
2799             }
2800             else
2801             {
2802 0 0         print " - + extend curr numframes=",numframes($href),"\n" if $DEBUG ;
2803             # expand end time
2804             ###$curr_href->{'numframes'} += $href->{'numframes'} ;
2805 0           $curr_href->{'end_pkt'} = $href->{'end_pkt'} ;
2806 0           $curr_href->{'frame_end'} = $href->{'frame_end'} ;
2807             }
2808             }
2809             }
2810 0 0         dump_frames(\@blocks, "Logo Blocks") if $DEBUG ;
2811            
2812            
2813             ## Create cut list
2814 0 0         if (@blocks)
2815             {
2816 0           my $cut_href = {'start_pkt'=>0, 'frame'=>0} ;
2817 0           push @cut_list, $cut_href ;
2818 0           foreach my $href (@blocks)
2819             {
2820 0           $cut_href->{'end_pkt'} = $href->{'start_pkt'}-1 ;
2821 0           $cut_href->{'frame_end'} = $href->{'frame'}-1 ;
2822            
2823 0           $cut_href = {
2824             'start_pkt' => $href->{'end_pkt'}+1,
2825             'frame' => $href->{'frame_end'}+1,
2826             } ;
2827 0           push @cut_list, $cut_href ;
2828             }
2829 0           $cut_href->{'end_pkt'} = $total_pkts-1 ;
2830 0           $cut_href->{'frame_end'} = $total_frames-1 ;
2831            
2832             # check last (first?) entry has a valid length
2833 0 0         if ($cut_href->{'frame'} >= $cut_href->{'frame_end'})
2834             {
2835 0           pop @cut_list ;
2836             }
2837            
2838             }
2839            
2840 0           return @cut_list ;
2841             }
2842            
2843            
2844             #-----------------------------------------------------------------------------
2845             sub _no_once_warning
2846             {
2847 0     0     return \%Linux::DVB::DVBT::Advert::Constants::CONSTANTS ;
2848             }
2849            
2850            
2851             #-----------------------------------------------------------------------------
2852             sub read_adv
2853             {
2854 0     0 0   my ($advfile) = @_ ;
2855            
2856 0           my %adv ;
2857 0 0         open my $fh, "<$advfile" or die "Error: unable to read to adv file $advfile : $!" ;
2858 0           my $line = "" ;
2859 0           my @head ;
2860 0           my $file_settings_href = {} ;
2861            
2862 0           while (defined($line=<$fh>))
2863             {
2864 0           chomp $line ;
2865 0           $line =~ s/#.*$// ;
2866 0           $line =~ s/\s+$// ;
2867 0           $line =~ s/^\s+// ;
2868 0 0         next unless $line ;
2869            
2870 0           my @fields = split(/,/, $line) ;
2871            
2872             ## Save frames
2873             # first line is fields definition
2874 0 0         if (@head)
2875             {
2876             # got head, so save data
2877 0           my $href = {} ;
2878 0           my $framenum ;
2879 0           for(my $i=0; $i < scalar(@head); ++$i)
2880             {
2881 0           $href->{$head[$i]} = $fields[$i] ;
2882            
2883 0 0         $framenum = $fields[$i] if $head[$i] eq $FRAME_FIELD ;
2884            
2885             }
2886 0 0         $adv{$framenum} = $href if defined($framenum) ;
2887             }
2888             else
2889             {
2890             # get head
2891 0           @head = @fields ;
2892            
2893 0           foreach my $head (@head)
2894             {
2895 0           $head =~ s/\s*\[.*$// ;
2896             }
2897             }
2898             }
2899 0           close $fh ;
2900            
2901 0           return \%adv ;
2902             }
2903            
2904            
2905             #-----------------------------------------------------------------------------
2906             sub adv_to_cutlist
2907             {
2908 0     0 0   my ($adv_href) = @_ ;
2909            
2910 0           my @cutlist ;
2911             my $prog ;
2912 0           my $cut_href ;
2913 0           foreach my $framenum (sort {$a <=> $b} keys %$adv_href)
  0            
2914             {
2915             # ____________
2916             # prog _________| |_______________
2917             #
2918             # cut s--------e s---------------e
2919             #
2920             # ____________
2921             # prog |_______________
2922             #
2923             # cut s---------------e
2924             #
2925 0   0       my $prog_change = !defined($prog) || ($prog != $adv_href->{$framenum}{$PROG_FIELD}) ;
2926 0           $prog = $adv_href->{$framenum}{$PROG_FIELD} ;
2927            
2928             # look for start of advert
2929 0 0 0       if (!$prog && $prog_change )
2930             {
2931 0           $cut_href = {
2932             $FRAME_FIELD => $adv_href->{$framenum}{$FRAME_FIELD},
2933             $FRAME_END_FIELD => $adv_href->{$framenum}{$FRAME_FIELD},
2934             $PACKET_FIELD => $adv_href->{$framenum}{$PACKET_FIELD},
2935             $PACKET_END_FIELD => $adv_href->{$framenum}{$PACKET_END_FIELD},
2936             } ;
2937             }
2938            
2939             # keep track of end of advert
2940 0 0         if (!$prog)
2941             {
2942 0           $cut_href->{$FRAME_END_FIELD} = $adv_href->{$framenum}{$FRAME_FIELD} ;
2943 0           $cut_href->{$PACKET_END_FIELD} = $adv_href->{$framenum}{$PACKET_END_FIELD} ;
2944             }
2945            
2946             # look for end
2947 0 0 0       if ($prog && $prog_change )
2948             {
2949 0 0         if ($cut_href)
2950             {
2951 0           push @cutlist, $cut_href ;
2952 0           $cut_href = undef ;
2953             }
2954             }
2955             }
2956            
2957             # catch end of video
2958 0 0         if ($cut_href)
2959             {
2960 0           push @cutlist, $cut_href ;
2961             }
2962            
2963 0           return @cutlist ;
2964             }
2965            
2966             # ============================================================================================
2967             # END OF PACKAGE
2968            
2969             1;
2970            
2971             #Start of analyse: Memory used 50.6484375 MB (since last call 50.6484375 MB)
2972             # + created ADA arrays: Memory used 179.1953125 MB (since last call 128.546875 MB)
2973             # + got settings: Memory used 179.19921875 MB (since last call 0.00390625 MB)
2974             #Black detect: Memory used 179.19921875 MB (since last call 0 MB)
2975             #Logo detect: Memory used 189.3984375 MB (since last call 10.19921875 MB)
2976             #Detect end: Memory used 940.23046875 MB (since last call 750.83203125 MB)
2977             #End of analyse: Memory used 1313.71484375 MB (since last call 373.484375 MB)
2978            
2979             __END__