1 package NonameTV
::Augmenter
;
6 use NonameTV
::Factory qw
/CreateAugmenter/;
9 # THIS IS NOT THE BASE CLASS FOR AUGMENTERS! (CONTRARY TO HOW IMPORTER.PM IS THE BASE CLASS FOR IMPORTERS)
11 # Augmenters augment programmes with additional data
13 # tools/nonametv-augment
15 # Fixups applies manually configured fixups, see tv_grab_uk_rt for use cases
16 # PreviouslyShown copies data from previous showings on the same or other channels
17 # TheTVDB replaces programme related data with community data
18 # TMDb replaces programme related data with community data
20 # The configuration is stored in one table in the database
22 # fields in the configuration
23 # channel_id - the channel id (foreign key, may be null)
24 # augmenter - name of the augmenter to run, case sensitive
25 # title - program title to match
26 # otherfield - other field to match (e.g. episodetitle, program_type, description, start_time)
27 # othervalue - value to match in otherfield
28 # remoteref - reference in foreign database, e.g. movie id in tmdb or series id in tvdb
29 # matchby - which field is matched in the remote datastore in which way
30 # e.g. match episode by episodetitle in tmdb
31 # or match episode by absolute episode number in tmdb
34 # DasErste, Fixups, Tagesschau, , , , setcategorynews
35 # DasErste, Fixups, Frauenfussballlaenderspiel, , , , settypesports
36 # Tele5 match by episodetitle for known series at TVDB
37 # Tele5 match Babylon5 by absolute number (need to add that to the backend first)
38 # Tele5 match by title for programmes with otherfield category=movie at TMDB
39 # ZDFneo, TheTVDB, Weeds, , , 74845, episodetitle
40 # ZDFneo, TheTVDB, Inspector Barnaby
41 # ZDFneo match by episodetitle for known series at TVDB
44 # Usual order of execution
45 # run Importers that really import
47 # run Importers to copy/mix/transform (combine, downconvert, timeshift)
52 # 1) get timestamp of last start of augmenter
53 # 2) find batches that have been updated (reset to station data) since then
54 # 3) order batches by batch id
56 # 5) collect all augmenters that match by channel_id
57 # 6) for each programme ordered by start time
58 # 7) select matching augmenters
59 # 7b) skip to next programme if none matches or it is the same as last time
60 # 8) order augmenters by priority
61 # 9) apply augmenter with highest priority
65 # API for each Augmenter
68 # create backend API instances etc.
71 # input: programme + rule
72 # output: programme + error
76 my $class = ref( $_[0] ) || $_[0];
81 $self->{datastore
} = $_[1];
86 sub ReadLastUpdate
( @
){
89 my $ds = $self->{datastore
};
91 my $last_update = $ds->sa->Lookup( 'state', { name
=> "augmenter_last_update" },
94 if( not defined( $last_update ) )
96 $ds->sa->Add( 'state', { name
=> "augmenter_last_update", value
=> 0 } );
103 sub WriteLastUpdate
( @@
){
105 my( $update_started ) = @_;
107 my $ds = $self->{datastore
};
109 $ds->sa->Update( 'state', { name
=> "augmenter_last_update" },
110 { value
=> $update_started } );
113 sub cmp_rules_by_score
( ){
114 if(!defined( $a->{score
} ) && !defined( $b->{score
} )){
116 } elsif(!defined( $a->{score
} ) ){
118 } elsif(!defined( $b->{score
} ) ){
121 return -($a->{score
} <=> $b->{score
});
125 sub sprint_rule
( @
){
130 if( $rule_ref->{channel_id
} ){
131 $result .= 'channel=' . $rule_ref->{channel_id
} . ', ';
133 if( $rule_ref->{title
} ){
134 $result .= 'title=\'' . $rule_ref->{title
} . '\', ';
136 if( $rule_ref->{otherfield
} ){
137 if( defined( $rule_ref->{othervalue
} ) ){
138 $result .= $rule_ref->{otherfield
} . '=\'' . $rule_ref->{othervalue
} . '\', ';
140 $result .= $rule_ref->{otherfield
} . '=NULL, ';
143 if( $rule_ref->{augmenter
} ){
144 $result .= $rule_ref->{augmenter
};
145 if( $rule_ref->{matchby
} ){
146 $result .= '::' . $rule_ref->{matchby
};
148 if( $rule_ref->{remoteref
} ){
149 $result .= '( ' . $rule_ref->{remoteref
} . ' )';
157 sub sprint_augment
( @@
){
158 my ($programme_ref, $augment_ref) = @_;
161 if( $programme_ref && $augment_ref){
162 foreach my $attribute ( 'title', 'subtitle', 'episode',
163 'program_type', 'category', 'actors' ) {
164 if( exists( $augment_ref->{$attribute} ) ){
165 if( defined( $programme_ref->{$attribute} ) && defined( $augment_ref->{$attribute} ) ) {
166 if( $programme_ref->{$attribute} ne $augment_ref->{$attribute} ){
167 $result .= ' changing ' . $attribute . " to \'" .
168 $augment_ref->{$attribute} . "\' was \'" .
169 $programme_ref->{$attribute} . "\'\n";
170 # TODO add verbose mode
172 # $result .= ' leaving ' . $attribute . " unchanged\n";
174 } elsif( defined( $programme_ref->{$attribute} ) ){
175 $result .= ' removing ' . $attribute . "\n";
176 } elsif( defined( $augment_ref->{$attribute} ) ){
177 $result .= ' adding ' . $attribute . " as \'" .
178 $augment_ref->{$attribute} . "\'\n";
187 sub AugmentBatch
( @@
) {
188 my( $self, $batchid )=@_;
191 # set up for augmenting one specific channel by batchid
193 (my $channel_xmltvid )=($batchid =~ m
|^(\S
+)_
|);
195 my( $res, $sth ) = $self->{datastore
}->sa->Sql( "
197 FROM channels c, augmenterrules ar
198 WHERE c.xmltvid LIKE ?
199 AND (ar.channel_id = c.id
200 OR ar.channel_id IS NULL)",
201 [$channel_xmltvid] );
207 while( defined( $iter = $sth->fetchrow_hashref() ) ){
209 if( !defined( $augmenter->{ $iter->{'augmenter'} } ) ){
210 printf( "creating '%s' augmenter\n", $iter->{'augmenter'} );
211 $augmenter->{ $iter->{'augmenter'} }= CreateAugmenter
( $iter->{'augmenter'}, $self->{datastore
} );
214 # append rule to array
215 push( @ruleset, $iter );
219 print( "ruleset for this batch: \n" );
220 foreach my $therule ( @ruleset ) {
221 printf( "%s\n", sprint_rule
( $therule ) );
226 # augment all programs from one batch by batchid
229 # program metadata from augmenter
231 # result code from augmenter
234 ( $res, $sth ) = $self->{datastore
}->sa->Sql( "
235 SELECT p.* from programs p, batches b
236 WHERE (p.batch_id = b.id)
238 ORDER BY start_time asc, end_time desc",
239 # name of batch to use for testing
243 while( defined( $ce = $sth->fetchrow_hashref() ) ) {
244 # copy ruleset to working set
245 my @rules = @ruleset;
247 if( defined( $ce->{subtitle
} ) ) {
248 printf( "\naugmenting program: %s - \"%s\"\n", $ce->{title
}, $ce->{subtitle
} );
250 printf( "\naugmenting program: %s\n", $ce->{title
} );
253 # loop until no more rules match
256 # order rules by quality of match
261 # match by channel_id
262 if( defined( $_->{channel_id
} ) ) {
263 if( $_->{channel_id
} eq $ce->{channel_id
} ){
272 if( defined( $_->{title
} ) ) {
274 if( $_->{title
} =~ m
|^\
^| ) {
275 if( $ce->{title
} =~ m
|$_->{title
}| ){
282 if( $_->{title
} eq $ce->{title
} ){
291 # match by other field
292 if( defined( $_->{otherfield
} ) && defined( $_->{othervalue
} ) ) {
293 if( $_->{othervalue
} eq $ce->{$_->{otherfield
}} ){
301 $_->{score
} = $score;
304 @rules = sort{ cmp_rules_by_score
}( @rules );
305 #printf( "rules after sorting: %s\n", Dumper( \@rules ) );
307 # take the best matching rule from the array (we apply it now and don't want it to match another time)
308 my $rule = shift( @rules );
310 # end loop if the best matching rule is not a mathing rule after all
311 if( !defined( $rule->{score
} ) ){
315 printf( "best matching rule: %s\n", sprint_rule
( $rule ) );
318 ( $newprogram, $result ) = $augmenter->{$rule->{augmenter
}}->AugmentProgram( $ce, $rule );
320 if( defined( $newprogram) ) {
321 printf( "augmenting as follows:\n%s", sprint_augment
( $ce, $newprogram ) );
322 while( my( $key, $value )=each( %$newprogram ) ) {
324 $ce->{$key} = $value;
326 delete( $ce->{$key} );
331 # go around and find the next best matching rule