Augmenter: move processing from test program into module
[nonametv.git] / lib / NonameTV / Augmenter.pm
blobc52e89c2bcb02b50b05aacfca6666eeec2e89fc6
1 package NonameTV::Augmenter;
3 use strict;
4 use warnings;
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
46 # run Augmenters
47 # run Importers to copy/mix/transform (combine, downconvert, timeshift)
48 # run Exporters
51 # Logic
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
55 # 4) for each batch
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
67 # initialize
68 # create backend API instances etc.
70 # augment (Programme)
71 # input: programme + rule
72 # output: programme + error
75 sub new( @@ ){
76 my $class = ref( $_[0] ) || $_[0];
78 my $self = { };
79 bless $self, $class;
81 $self->{datastore} = $_[1];
83 return $self;
86 sub ReadLastUpdate( @ ){
87 my $self = shift;
89 my $ds = $self->{datastore};
91 my $last_update = $ds->sa->Lookup( 'state', { name => "augmenter_last_update" },
92 'value' );
94 if( not defined( $last_update ) )
96 $ds->sa->Add( 'state', { name => "augmenter_last_update", value => 0 } );
97 $last_update = 0;
100 return $last_update;
103 sub WriteLastUpdate( @@ ){
104 my $self = shift;
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} )){
115 return 0;
116 } elsif(!defined( $a->{score} ) ){
117 return 1;
118 } elsif(!defined( $b->{score} ) ){
119 return -1;
120 } else {
121 return -($a->{score} <=> $b->{score});
125 sub sprint_rule( @ ){
126 my ($rule_ref) = @_;
127 my $result = '';
129 if( $rule_ref ){
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} . '\', ';
139 } else {
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} . ' )';
154 return( $result );
157 sub sprint_augment( @@ ){
158 my ($programme_ref, $augment_ref) = @_;
159 my $result = '';
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
171 # } else {
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";
184 return( $result );
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( "
196 SELECT ar.*
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] );
203 my $augmenter = { };
204 my @ruleset;
206 my $iter;
207 while( defined( $iter = $sth->fetchrow_hashref() ) ){
208 # set up augmenters
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
230 my $newprogram;
231 # result code from augmenter
232 my $result;
234 ( $res, $sth ) = $self->{datastore}->sa->Sql( "
235 SELECT p.* from programs p, batches b
236 WHERE (p.batch_id = b.id)
237 AND (b.name LIKE ?)
238 ORDER BY start_time asc, end_time desc",
239 # name of batch to use for testing
240 [$batchid] );
242 my $ce;
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} );
249 } else {
250 printf( "\naugmenting program: %s\n", $ce->{title} );
253 # loop until no more rules match
254 while( 1 ){
256 # order rules by quality of match
258 foreach( @rules ){
259 my $score = 0;
261 # match by channel_id
262 if( defined( $_->{channel_id} ) ) {
263 if( $_->{channel_id} eq $ce->{channel_id} ){
264 $score += 1;
265 } else {
266 $_->{score} = undef;
267 next;
271 # match by title
272 if( defined( $_->{title} ) ) {
273 # regexp?
274 if( $_->{title} =~ m|^\^| ) {
275 if( $ce->{title} =~ m|$_->{title}| ){
276 $score += 4;
277 } else {
278 $_->{score} = undef;
279 next;
281 } else {
282 if( $_->{title} eq $ce->{title} ){
283 $score += 4;
284 } else {
285 $_->{score} = undef;
286 next;
291 # match by other field
292 if( defined( $_->{otherfield} ) && defined( $_->{othervalue} ) ) {
293 if( $_->{othervalue} eq $ce->{$_->{otherfield}} ){
294 $score += 2;
295 } else {
296 $_->{score} = undef;
297 next;
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} ) ){
312 last;
315 printf( "best matching rule: %s\n", sprint_rule( $rule ) );
317 # apply the 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 ) ) {
323 if( $value ) {
324 $ce->{$key} = $value;
325 } else {
326 delete( $ce->{$key} );
331 # go around and find the next best matching rule