File Coverage

blib/lib/IMDB/Local.pm
Criterion Covered Total %
statement 21 896 2.3
branch 0 484 0.0
condition 0 104 0.0
subroutine 6 38 15.7
pod 6 25 24.0
total 33 1547 2.1


line stmt bran cond sub pod time code
1             package IMDB::Local;
2              
3             #
4             # Suggestions for improvements
5             # -
6             #
7             #
8              
9 1     1   12449 use 5.006;
  1         2  
  1         29  
10 1     1   4 use strict;
  1         1  
  1         26  
11 1     1   2 use warnings;
  1         4  
  1         53  
12              
13             =head1 NAME
14              
15             IMDB::Local - Tools to dowload and manage a local copy of the IMDB list files in a database.
16              
17             =cut
18              
19             our $VERSION = '1.4';
20              
21             =head1 SYNOPSIS
22              
23             Quick summary of what the module does.
24              
25             Perhaps a little code snippet.
26              
27             my $foo = new IMDB::Local('imdbDir' => "./imdb-data",
28             'listsDir' => "./imdb-data/lists",
29             'showProgressBar' => 1);
30              
31             for my $type ( $foo->listTypes() ) {
32             if ( $foo->importList($type) != 0 ) {
33             warn("$type import failed, check $foo->{imdbDir}/stage-$type.log");
34             }
35             }
36             ...
37              
38             =head1 EXPORT
39              
40             A list of functions that can be exported. You can delete this section
41             if you don't export anything, such as for a purely object-oriented module.
42              
43             =head1 SUBROUTINES/METHODS
44              
45             =cut
46              
47             # Use Term::ProgressBar if installed.
48 1         0 use constant Have_bar => eval {
49 1         490 require Term::ProgressBar;
50 1         46432 $Term::ProgressBar::VERSION >= 2;
51 1     1   5 };
  1         1  
52              
53 1     1   337 use IMDB::Local::DB;
  1         2  
  1         280  
54              
55             =head2 new
56              
57             Create new IMDB::Local object.
58              
59             Arguments:
60              
61             imdbDir - required or die
62              
63             verbose - optional, default is 0.
64              
65             listsDir - folder where list files exist (see IMDB::Local::Download).
66              
67             showProgressBar - if non-zero and Term::ProgressBar is available progress bars in import methods will be displayed. Ignored if Term::ProgressBar is not available.
68              
69             =cut
70              
71             sub new
72             {
73 0     0 1   my ($type) = shift;
74 0           my $self={ @_ }; # remaining args become attributes
75              
76 0           for ('imdbDir', 'verbose') {
77 0 0         die "invalid usage - no $_" if ( !defined($self->{$_}));
78             }
79            
80             #$self->{stages} = { 1=>'movies', 2=>'directors', 3=>'actors', 4=>'actresses', 5=>'genres', 6=>'ratings', 7=>'keywords', 8=>'plot' };
81             #$self->{optionalStages} = { 'keywords' => 7, 'plot' => 8 }; # list of optional stages - no need to download files for these
82              
83 0           $self->{moviedbInfo}="$self->{imdbDir}/moviedb.info";
84 0           $self->{moviedbOffline}="$self->{imdbDir}/moviedb.offline";
85            
86 0 0         if ( defined($self->{listsDir}) ) {
87 0           $self->{listFiles}=new IMDB::Local::ListFiles(listsDir=>$self->{listsDir});
88             }
89            
90             # only leave progress bar on if its available
91 0           if ( !Have_bar ) {
92             $self->{showProgressBar}=0;
93             }
94              
95 0           bless($self, $type);
96 0           return($self);
97             }
98              
99             #sub openDB
100             #{
101             # my ($self)=@_;
102             #
103             # my $DB=new IMDB::Local::DB(database=>"$self->{imdbDir}/imdb.db");
104             #
105             # if ( !$DB->connect() ) {
106             # carp "imdbdb connect failed:$DBI::errstr";
107             # }
108             # $self->{DB}=$DB;
109             #
110             # return($DB);
111             #}
112             #
113             #sub closeDB
114             #{
115             # my ($self)=@_;
116             #
117             # $self->{DB}->disconnect();
118             # undef $self->{DB};
119             #}
120              
121             =head2 listTypes
122              
123             Returns an array of list files supported (currently 'movies', 'directors', 'actors', 'actresses', 'genres', 'ratings', 'keywords', 'plot')
124              
125             =cut
126              
127             sub listTypes($)
128             {
129 0     0 1   my $self=shift;
130              
131 0           return( $self->{listFiles}->types() );
132             }
133              
134              
135             sub error($$)
136             {
137 0     0 0   my $self=shift;
138 0 0         if ( defined($self->{logfd}) ) {
139 0           print {$self->{logfd}} $_[0]."\n";
  0            
140 0           $self->{errorCountInLog}++;
141             }
142             else {
143 0           print STDERR $_[0]."\n";
144             }
145             }
146              
147             sub status($$)
148             {
149 0     0 0   my $self=shift;
150              
151 0 0         if ( $self->{verbose} ) {
152 0           print STDERR $_[0]."\n";
153             }
154             }
155              
156             sub withThousands ($)
157             {
158 0     0 0   my ($val) = @_;
159 0           $val =~ s/(\d{1,3}?)(?=(\d{3})+$)/$1,/g;
160 0           return $val;
161             }
162              
163 1         1 use constant Have_gunzip => eval {
164 1         451 require IO::Uncompress::Gunzip;
165 1     1   6 };
  1         1  
166              
167              
168             sub openMaybeGunzip($)
169             {
170 0     0 0   my ($file)=@_;
171            
172 0 0         if ($file=~m/\.gz$/ ) {
173 0           if ( Have_gunzip ) {
174 0           return new IO::Uncompress::Gunzip($file);
175             }
176             else {
177             my $fd;
178              
179             if ( open($fd, "gzip -d < $file |") ) {
180             return($fd);
181             }
182             carp("no suitable gzip decompression found");
183             }
184             }
185             else {
186 0           require IO::File;
187 0           return new IO::File("< $file");
188             }
189             }
190              
191             sub closeMaybeGunzip($$)
192             {
193 0     0 0   my ($file, $fd)=@_;
194              
195 0 0         if ($file=~m/\.gz$/ ) {
196 0           if ( Have_gunzip ) {
197 0           $fd->close();
198             }
199             else {
200             close($fd);
201             }
202             }
203             else {
204 0           $fd->close();
205             }
206             }
207              
208             sub decodeImdbKey($$$)
209             {
210 0     0 0   my ($self, $DB, $dbkey, $year, $titleID)=@_;
211            
212 0           my %hash;
213            
214 0           $hash{parentId}=0;
215 0           $hash{series}=0;
216 0           $hash{episode}=0;
217 0           $hash{airdate}=0;
218            
219              
220             # drop episode information - ex: "Studio One" (1948) {Twelve Angry Men (#7.1)}
221 0 0         if ( $dbkey=~s/\s*\{([^\}]+)\}//o ) {
222 0           my $s=$1;
223 0 0         if ( $s=~s/\s*\(\#(\d+)\.(\d+)\)$// ) {
    0          
224 0           $hash{series}=$1;
225 0           $hash{episode}=$2;
226 0           $hash{title}=$s;
227             #print "title: $s\n";
228              
229             # attempt to locate parentId matching series title
230             #my $parentKey=$dbkey;
231             #$parentKey=~s/^\"//o;
232             #$parentKey=~s/\" \(/ \(/o;
233              
234             #warn("checkskey $dbkey");
235             #if ( defined($self->{seriesKeys}->{$parentKey}) ) {
236             #$hash{parentId}=$self->{seriesKeys}->{$parentKey};
237             #}
238             }
239             # "EastEnders" (1985) {(1991-10-15)} 1991
240             elsif ( $s=~s/^\((\d\d\d\d)\-(\d\d)\-(\d\d)\)$// ) {
241 0           $hash{airdate}=int("$1$2$3");
242              
243             }
244             else {
245 0           $hash{title}=$s;
246             }
247              
248             # attempt to locate parentId matching series title
249 0           my $parentKey=$dbkey;
250 0           $parentKey=~s/^\"//o;
251 0           $parentKey=~s/\" \(/ \(/o;
252            
253             #warn("checkskey $dbkey");
254 0 0         if ( defined($self->{seriesKeys}->{$parentKey}) ) {
255 0           $hash{parentId}=$self->{seriesKeys}->{$parentKey};
256             }
257             }
258            
259            
260             # change double-quotes around title to be (made-for-tv) suffix instead
261 0 0 0       if ( $dbkey=~s/^\"//o && $dbkey=~s/\" \(/ \(/o) {
    0          
    0          
    0          
262 0 0         if ( $dbkey=~s/\s+\(mini\)$//o ) {
263 0 0         if ( $hash{parentId} == 0 ) {
264 0           $hash{qualifier}=$self->{QualifierTypeIDs}->{"tv_mini_series"};
265 0           $self->{seriesKeys}->{$dbkey}=$titleID;
266             }
267             else {
268 0           $hash{qualifier}=$self->{QualifierTypeIDs}->{"episode_of_tv_mini_series"};
269             }
270             }
271             else {
272 0 0         if ( $hash{parentId} == 0 ) {
273 0           $hash{qualifier}=$self->{QualifierTypeIDs}->{"tv_series"};
274 0           $self->{seriesKeys}->{$dbkey}=$titleID;
275             }
276             else {
277 0           $hash{qualifier}=$self->{QualifierTypeIDs}->{"episode_of_tv_series"};
278             }
279             }
280             }
281             elsif ( $dbkey=~s/\s+\(TV\)$//o ) {
282             # how rude, some entries have (TV) appearing more than once.
283             #$dbkey=~s/\s*\(TV\)$//o;
284 0           $hash{qualifier}=$self->{QualifierTypeIDs}->{"tv_movie"};
285             }
286             elsif ( $dbkey=~s/\s+\(V\)$//o ) {
287 0           $hash{qualifier}=$self->{QualifierTypeIDs}->{"video_movie"};
288             }
289             elsif ( $dbkey=~s/\s+\(VG\)$//o ) {
290 0           $hash{qualifier}=$self->{QualifierTypeIDs}->{"video_game"};
291             }
292             else {
293 0           $hash{qualifier}=$self->{QualifierTypeIDs}->{"movie"};
294             }
295            
296             #if ( $dbkey=~s/\s+\((tv_series|tv_mini_series|tv_movie|video_movie|video_game)\)$//o ) {
297             # $qualifier=$1;
298             #}
299 0           $hash{dbkey}=$dbkey;
300              
301 0           my $title=$dbkey;
302            
303             # todo - this is the wrong year for episode titles
304 0 0 0       if ( $title=~m/^\"/o && $title=~m/\"\s*\(/o ) { #"
305 0           $title=~s/^\"//o; #"
306 0           $title=~s/\"(\s*\()/$1/o; #"
307             }
308            
309 0 0 0       if ( $title=~s/\s+\((\d\d\d\d)\)$//o ||
    0 0        
310             $title=~s/\s+\((\d\d\d\d)\/[IVXL]+\)$//o ) {
311             # over-ride with what is given
312 0 0         if ( !defined($year) ) {
313 0           $hash{year}=$1;
314             }
315             else {
316 0           $hash{year}=$year;
317             }
318             }
319             elsif ( $title=~s/\s+\((\?\?\?\?)\)$//o ||
320             $title=~s/\s+\((\?\?\?\?)\/[IVXL]+\)$//o ) {
321             # over-ride with what is given
322 0 0         if ( !defined($year) ) {
323 0           $hash{year}=0;
324             }
325             else {
326 0           $hash{year}=$year;
327             }
328             }
329             else {
330 0           $self->error("movie list format failed to decode year from title '$title'");
331            
332             # over-ride with what is given
333 0 0         if ( ! defined($year) ) {
334 0           $hash{year}=0;
335             }
336             else {
337 0           $hash{year}=$year;
338             }
339             }
340 0           $title=~s/(.*),\s*(The|A|Une|Las|Les|Los|L\'|Le|La|El|Das|De|Het|Een)$/$2 $1/og;
341              
342             # leave searchtitle empty for tv series'
343 0 0 0       if ( $hash{series} != 0 || $hash{airdate} != 0 ) {
344 0           $hash{searchTitle}='';
345             }
346             else {
347 0 0         if ( !defined($hash{title}) ) {
348 0           $hash{title}=$title;
349 0           $hash{searchTitle}=$DB->makeSearchableTitle($title, 0);
350              
351             # todo - is this more useful ?
352             #$hash{searchTitleWithYear}=MakeSearchtitle($DB, $title."(".$hash{year}.")", 0);
353             }
354             else {
355 0           $hash{searchTitle}=$DB->makeSearchableTitle($title, 0);
356             #$hash{searchTitle}=$DB->makeSearchableTitle($title."(".$hash{year}.")", 0);
357            
358             # todo - is this more useful ?
359             #$hash{searchTitleWithYear}=$DB->makeSearchableTitle($title."(".$hash{year}.")", 0);
360             }
361             }
362              
363 0           return(\%hash);
364             }
365              
366             sub importMovies($$$$)
367             {
368 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
369 0           my $startTime=time();
370 0           my $lineCount=0;
371              
372 0   0       my $fh = openMaybeGunzip($file) || return(-2);
373 0           while(<$fh>) {
374 0           $lineCount++;
375 0 0         if ( m/^MOVIES LIST/o ) {
    0          
376 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
377 0           $self->error("missing ======= after 'MOVIES LIST' at line $lineCount");
378 0           closeMaybeGunzip($file, $fh);
379 0           return(-1);
380             }
381 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
382 0           $self->error("missing empty line after ======= at line $lineCount");
383 0           closeMaybeGunzip($file, $fh);
384 0           return(-1);
385             }
386 0           last;
387             }
388             elsif ( $lineCount > 1000 ) {
389 0           $self->error("$file: stopping at line $lineCount, didn't see \"MOVIES LIST\" line");
390 0           closeMaybeGunzip($file, $fh);
391 0           return(-1);
392             }
393             }
394              
395 0 0         my $progress=Term::ProgressBar->new({name => "importing Movies",
396             count => $countEstimate,
397             ETA => 'linear'})
398             if ( $self->{showProgressBar} );
399              
400 0 0         $progress->minor(0) if ($self->{showProgressBar});
401 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
402 0           my $next_update=0;
403              
404             # preload qualifier types
405 0           $self->{QualifierTypeIDs}=$DB->select2Hash("select Name, QualifierTypeID from QualifierTypes");
406              
407             #$DB->runSQL("BEGIN TRANSACTION");
408              
409 0           my $count=0;
410 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Titles (TitleID, SearchTitle, Title, QualifierTypeID, Year, ParentID, Series, Episode, AirDate) VALUES (?,?,?,?,?,?,?,?,?)');
411              
412 0           my $potentialEntries=0;
413            
414 0           while(<$fh>) {
415 0           $lineCount++;
416 0           my $line=$_;
417              
418             # end is line consisting of only '-'
419 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
420              
421 0 0         next if ( $line=~m/\{\{SUSPENDED\}\}/o );
422 0           $line=~s/\n$//o;
423            
424             #next if ( !($line=~m/biography/io) );
425              
426             #print "read line $lineCount:$line\n";
427 0           $potentialEntries++;
428              
429 0           my $tab=index($line, "\t");
430 0 0         if ( $tab != -1 ) {
431 0           my $ykey=substr($line, $tab+1);
432 0 0         if ( $ykey=m/\s+(\d\d\d\d)$/ ) {
    0          
    0          
    0          
433 0           $ykey=$1;
434             }
435             elsif ( $ykey=m/\s+(\?\?\?\?)$/ ) {
436 0           $ykey=undef;
437             }
438             elsif ( $ykey=m/\s+(\d\d\d\d)\-(\?\?\?\?)$/ ) {
439 0           $ykey=$1;
440             }
441             elsif ( $ykey=m/\s+(\d\d\d\d)\-(\d\d\d\d)$/ ) {
442 0           $ykey=$1;
443             }
444             else {
445 0           warn("invalid year ($ykey) - $line");
446             #$ykey=undef;
447             }
448            
449 0           my $mkey=substr($line, 0, $tab);
450              
451             # lets not import video games
452             #if ( $decoded->{qualifier} != $self->{QualifierTypeIDs}->{'video_game'} ) {
453             # returned count is number of titles found
454 0           $count++;
455              
456 0           my $decoded=$self->decodeImdbKey($DB, $mkey, $ykey, $count);
457              
458 0           $tableInsert_sth->execute($count,
459             $decoded->{searchTitle},
460             $decoded->{title},
461             $decoded->{qualifier},
462             $decoded->{year},
463             $decoded->{parentId},
464             $decoded->{series},
465             $decoded->{episode},
466             $decoded->{airdate});
467            
468 0           $self->{imdbMovie2DBKey}->{$mkey}=$count;
469              
470             #if ( ($count % 50000) == 0 ) {
471             #$DB->commit();
472             #}
473             #}
474            
475 0 0         if ( $self->{showProgressBar} ) {
476             # re-adjust target so progress bar doesn't seem too wonky
477 0 0         if ( $count > $countEstimate ) {
    0          
478 0           $countEstimate = $progress->target($count+1000);
479 0           $next_update=$progress->update($count);
480             }
481             elsif ( $count > $next_update ) {
482 0           $next_update=$progress->update($count);
483             }
484             }
485             }
486             else {
487 0           $self->error("$file:$lineCount: unrecognized format (missing tab)");
488             }
489             }
490             #$DB->runSQL("END TRANSACTION");
491              
492 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
493              
494 0           $self->status(sprintf("importing Movies found ".withThousands($count)." in ".
495             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
496              
497 0           closeMaybeGunzip($file, $fh);
498 0           $DB->commit();
499 0           return($count);
500             }
501              
502             sub importGenres($$$$)
503             {
504 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
505 0           my $startTime=time();
506 0           my $lineCount=0;
507              
508 0   0       my $fh = openMaybeGunzip($file) || return(-2);
509 0           while(<$fh>) {
510 0           $lineCount++;
511 0 0         if ( m/^8: THE GENRES LIST/o ) {
    0          
512 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
513 0           $self->error("missing ======= after 'THE GENRES LIST' at line $lineCount");
514 0           closeMaybeGunzip($file, $fh);
515 0           return(-1);
516             }
517 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
518 0           $self->error("missing empty line after ======= at line $lineCount");
519 0           closeMaybeGunzip($file, $fh);
520 0           return(-1);
521             }
522 0           last;
523             }
524             elsif ( $lineCount > 1000 ) {
525 0           $self->error("$file: stopping at line $lineCount, didn't see \"THE GENRES LIST\" line");
526 0           closeMaybeGunzip($file, $fh);
527 0           return(-1);
528             }
529             }
530              
531 0 0         my $progress=Term::ProgressBar->new({name => "importing Genres",
532             count => $countEstimate,
533             ETA => 'linear'})
534             if ( $self->{showProgressBar} );
535              
536 0 0         $progress->minor(0) if ($self->{showProgressBar});
537 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
538 0           my $next_update=0;
539              
540             # preload qualifier types
541 0           $self->{QualifierTypeIDs}=$DB->select2Hash("select Name, QualifierTypeID from QualifierTypes");
542              
543             #$DB->runSQL("BEGIN TRANSACTION");
544              
545 0           my $count=0;
546 0           my $potentialEntries=0;
547 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Titles2Genres (TitleID, GenreID) VALUES (?,?)');
548              
549 0           while(<$fh>) {
550 0           $lineCount++;
551 0           my $line=$_;
552             #print "read line $lineCount:$line\n";
553              
554             # end is line consisting of only '-'
555 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
556 0 0         next if ( $line=~m/\s*\{\{SUSPENDED\}\}/o);
557              
558 0           $potentialEntries++;
559              
560 0           $line=~s/\n$//o;
561              
562 0           my $tab=index($line, "\t");
563 0 0         if ( $tab != -1 ) {
564 0           my $mkey=substr($line, 0, $tab);
565              
566             # ignore {Twelve Angry Men (1954)}
567             # TODO - do we want this ?
568             #$mkey=~s/\s*\{[^\}]+\}//go;
569            
570             # skip enties that have {} in them since they're tv episodes
571             #next if ( $mkey=~s/\s*\{[^\}]+\}$//o );
572            
573 0           my $genre=substr($line, $tab);
574            
575             # genres sometimes has more than one tab
576 0           $genre=~s/^\t+//og;
577            
578 0 0         if ( $self->{imdbMovie2DBKey}->{$mkey} ) {
579             # insert into db as discovered
580 0 0         if ( ! defined($self->{GenreID}->{$genre}) ) {
581 0           $self->{GenreID}->{$genre}=$DB->insert_row('Genres', 'GenreID', Name=>$genre);
582             }
583 0           $tableInsert_sth->execute($self->{imdbMovie2DBKey}->{$mkey},
584             $self->{GenreID}->{$genre});
585            
586             # returned count is number of titles found
587 0           $count++;
588            
589 0 0         if ( ($count % 50000) ==0 ) {
590 0           $DB->commit();
591             }
592             }
593            
594 0 0         if ( $self->{showProgressBar} ) {
595             # re-adjust target so progress bar doesn't seem too wonky
596 0 0         if ( $count > $countEstimate ) {
    0          
597 0           $countEstimate = $progress->target($count+1000);
598 0           $next_update=$progress->update($count);
599             }
600             elsif ( $count > $next_update ) {
601 0           $next_update=$progress->update($count);
602             }
603             }
604             }
605             else {
606 0           $self->error("$file:$lineCount: unrecognized format (missing tab)");
607             }
608             }
609             #$DB->runSQL("END TRANSACTION");
610              
611 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
612              
613 0           $self->status(sprintf("importing Genres found ".withThousands($count)." in ".
614             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
615              
616 0           closeMaybeGunzip($file, $fh);
617 0           $DB->commit();
618 0           return($count);
619             }
620              
621             sub importActors($$$$)
622             {
623 0     0 0   my ($self, $whichCastType, $castCountEstimate, $file, $DB)=@_;
624 0           my $startTime=time();
625              
626 0 0         if ( $whichCastType eq "Actors" ) {
627 0 0 0       if ( $DB->table_row_count('Actors') > 0 ||
      0        
      0        
628             $DB->table_row_count('Titles2Actors') > 0 ||
629             $DB->table_row_count('Titles2Hosts') > 0 ||
630             $DB->table_row_count('Titles2Narrators') > 0 ) {
631 0           $self->status("clearing previously loaded data..");
632 0           $DB->table_clear('Actors');
633 0           $DB->table_clear('Titles2Actors');
634 0           $DB->table_clear('Titles2Hosts');
635 0           $DB->table_clear('Titles2Narrators');
636             }
637             }
638              
639 0           my $header;
640             my $whatAreWeParsing;
641 0           my $lineCount=0;
642              
643 0 0         if ( $whichCastType eq "Actors" ) {
    0          
644 0           $header="THE ACTORS LIST";
645 0           $whatAreWeParsing=1;
646             }
647             elsif ( $whichCastType eq "Actresses" ) {
648 0           $header="THE ACTRESSES LIST";
649 0           $whatAreWeParsing=2;
650             }
651             else {
652 0           die "why are we here ?";
653             }
654              
655 0   0       my $fh = openMaybeGunzip($file) || return(-2);
656 0 0         my $progress=Term::ProgressBar->new({name => "importing $whichCastType",
657             count => $castCountEstimate,
658             ETA => 'linear'})
659             if ($self->{showProgressBar});
660 0 0         $progress->minor(0) if ($self->{showProgressBar});
661 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
662 0           my $next_update=0;
663            
664 0           while(<$fh>) {
665 0           $lineCount++;
666 0 0         if ( m/^$header/ ) {
    0          
667 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
668 0           $self->error("missing ======= after $header at line $lineCount");
669 0           closeMaybeGunzip($file, $fh);
670 0           return(-1);
671             }
672 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
673 0           $self->error("missing empty line after ======= at line $lineCount");
674 0           closeMaybeGunzip($file, $fh);
675 0           return(-1);
676             }
677 0 0 0       if ( !($_=<$fh>) || !m/^Name\s+Titles\s*$/o ) {
678 0           $self->error("missing name/titles line after ======= at line $lineCount");
679 0           closeMaybeGunzip($file, $fh);
680 0           return(-1);
681             }
682 0 0 0       if ( !($_=<$fh>) || !m/^[\s\-]+$/o ) {
683 0           $self->error("missing name/titles suffix line after ======= at line $lineCount");
684 0           closeMaybeGunzip($file, $fh);
685 0           return(-1);
686             }
687 0           last;
688             }
689             elsif ( $lineCount > 1000 ) {
690 0           $self->error("$file: stopping at line $lineCount, didn't see \"$header\" line");
691 0           closeMaybeGunzip($file, $fh);
692 0           return(-1);
693             }
694             }
695              
696 0           my $cur_name;
697 0           my $count=0;
698 0           my $castNames=0;
699 0           my $tableInsert_sth1=$DB->prepare('INSERT INTO Actors (ActorID, SearchName, Name) VALUES (?,?,?)');
700 0           my $tableInsert_sth2=$DB->prepare('INSERT INTO Titles2Hosts (TitleID, ActorID) VALUES (?,?)');
701 0           my $tableInsert_sth3=$DB->prepare('INSERT INTO Titles2Narrators (TitleID, ActorID) VALUES (?,?)');
702 0           my $tableInsert_sth4=$DB->prepare('INSERT INTO Titles2Actors (TitleID, ActorID, Billing) VALUES (?,?,?)');
703            
704 0           my $cur_actorId=$DB->select2Scalar('Select MAX(ActorID) from Actors');
705 0 0         if ( !defined($cur_actorId) ) {
706 0           $cur_actorId=0;
707             }
708              
709 0           my $potentialEntries=0;
710 0           while(<$fh>) {
711 0           $lineCount++;
712 0           my $line=$_;
713 0           $line=~s/\n$//o;
714             #$self->status("read line $lineCount:$line");
715              
716             # end is line consisting of only '-'
717 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
718            
719 0 0         next if ( length($line) == 0 );
720              
721             # try ignoring these
722 0 0         next if ($line=~m/\s*\{\{SUSPENDED\}\}/o);
723              
724 0           $potentialEntries++;
725 0           my $billing=9999;
726            
727             # actors or actresses
728 0 0         if ( $line=~s/\s*<(\d+)>//o ) {
729 0           $billing=int($1);
730 0 0         next if ( $billing >3 );
731             }
732            
733 0 0         if ( $line=~s/^([^\t]+)\t+//o ) {
734 0           $cur_name=$1;
735 0           $castNames++;
736              
737 0           $cur_actorId++;
738              
739 0           my $c=$cur_name;
740 0           $c=~s/\s*\([IVXL]+\)//o;
741 0           $tableInsert_sth1->execute($cur_actorId, $DB->makeSearchableTitle($c, 0), $cur_name);
742              
743 0 0         if ( $self->{showProgressBar} ) {
744             # re-adjust target so progress bar doesn't seem too wonky
745 0 0         if ( $castNames > $castCountEstimate ) {
    0          
746 0           $castCountEstimate = $progress->target($castNames+100);
747 0           $next_update=$progress->update($castNames);
748             }
749             elsif ( $castNames > $next_update ) {
750 0           $next_update=$progress->update($castNames);
751             }
752             }
753             }
754            
755 0           my $isHost=0;
756 0           my $isNarrator=0;
757 0 0         if ( (my $start=index($line, " [")) != -1 ) {
758             #my $end=rindex($line, "]");
759 0           my $ex=substr($line, $start+1);
760            
761 0 0         if ( $ex=~s/Host//o ) {
762 0           $isHost=1;
763             }
764 0 0         if ( $ex=~s/Narrator//o ) {
765 0           $isNarrator=1;
766             }
767 0           $line=substr($line, 0, $start);
768             # ignore character name
769             }
770            
771             # TODO - do we want to just ignore these ?
772 0 0         if ( $line=~s/\s*\(aka ([^\)]+)\).*$//o ) {
773             #$attrs=$1;
774             }
775            
776             # TODO - what are we ignoring here ?
777 0 0         if ( $line=~s/ (\(.*)$//o ) {
778             #$attrs=$1;
779             }
780 0           $line=~s/^\s+//og;
781 0           $line=~s/\s+$//og;
782              
783             # TODO - does this exist ?
784 0 0         if ( $line=~s/\s+Narrator$//o ) {
785 0           $self->error("extra narrator on line: $lineCount");
786             # TODO - do we want to store this ? Does it actually occur ?
787             # ignore
788             }
789              
790             #if ( $line=~s/\s*\([A-Z]+\)$//o ) {
791             #}
792              
793 0           my $titleID=$self->{imdbMovie2DBKey}->{$line};
794 0 0         if ( $titleID ) {
795 0 0         if ( $isHost ) {
796 0           $tableInsert_sth2->execute($titleID, $cur_actorId);
797             }
798 0 0         if ( $isNarrator ) {
799 0           $tableInsert_sth3->execute($titleID, $cur_actorId);
800             }
801 0 0 0       if ( !$isHost && !$isNarrator ) {
802 0           $tableInsert_sth4->execute($titleID, $cur_actorId, $billing);
803             }
804            
805 0           $count++;
806 0 0         if ( ($count % 50000) == 0 ) {
807 0           $DB->commit();
808             }
809             }
810             else {
811             #warn($line);
812             }
813             }
814 0 0         $progress->update($castCountEstimate) if ($self->{showProgressBar});
815              
816 0           $self->status(sprintf("importing $whichCastType found ".withThousands($castNames)." names, ".
817             withThousands($count)." titles in ".withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
818            
819 0           closeMaybeGunzip($file, $fh);
820              
821 0           $DB->commit();
822 0           return($castNames);
823             }
824              
825             sub importDirectors($$$)
826             {
827 0     0 0   my ($self, $castCountEstimate, $file, $DB)=@_;
828 0           my $startTime=time();
829              
830 0           my $lineCount=0;
831              
832 0 0 0       if ( $DB->table_row_count('Directors') > 0 ||
833             $DB->table_row_count('Titles2Directors') > 0 ) {
834 0           $self->status("clearing previously loaded data..");
835 0           $DB->table_clear('Directors');
836 0           $DB->table_clear('Titles2Directors');
837             }
838              
839 0   0       my $fh = openMaybeGunzip($file) || return(-2);
840 0 0         my $progress=Term::ProgressBar->new({name => "importing Directors",
841             count => $castCountEstimate,
842             ETA => 'linear'})
843             if ($self->{showProgressBar});
844 0 0         $progress->minor(0) if ($self->{showProgressBar});
845 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
846 0           my $next_update=0;
847 0           while(<$fh>) {
848 0           $lineCount++;
849 0 0         if ( m/^THE DIRECTORS LIST/ ) {
    0          
850 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
851 0           $self->error("missing ======= after THE DIRECTORS LIST at line $lineCount");
852 0           closeMaybeGunzip($file, $fh);
853 0           return(-1);
854             }
855 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
856 0           $self->error("missing empty line after ======= at line $lineCount");
857 0           closeMaybeGunzip($file, $fh);
858 0           return(-1);
859             }
860 0 0 0       if ( !($_=<$fh>) || !m/^Name\s+Titles\s*$/o ) {
861 0           $self->error("missing name/titles line after ======= at line $lineCount");
862 0           closeMaybeGunzip($file, $fh);
863 0           return(-1);
864             }
865 0 0 0       if ( !($_=<$fh>) || !m/^[\s\-]+$/o ) {
866 0           $self->error("missing name/titles suffix line after ======= at line $lineCount");
867 0           closeMaybeGunzip($file, $fh);
868 0           return(-1);
869             }
870 0           last;
871             }
872             elsif ( $lineCount > 1000 ) {
873 0           $self->error("$file: stopping at line $lineCount, didn't see \"THE DIRECTORS LIST\" line");
874 0           closeMaybeGunzip($file, $fh);
875 0           return(-1);
876             }
877             }
878              
879 0           my $cur_name;
880 0           my $count=0;
881 0           my $castNames=0;
882 0           my %found;
883 0           my $directorCount=0;
884 0           my $potentialEntries=0;
885              
886 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Directors (DirectorID, SearchName, Name) VALUES (?,?,?)');
887 0           my $tableInsert_sth2=$DB->prepare('INSERT INTO Titles2Directors (TitleID, DirectorID) VALUES (?,?)');
888 0           while(<$fh>) {
889 0           $lineCount++;
890            
891             #last if ( $lineCount > 10000);
892 0           my $line=$_;
893 0           $line=~s/\n$//o;
894             #$self->status("read line $lineCount:$line");
895              
896             # end is line consisting of only '-'
897 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
898 0 0         next if ( length($line) == 0 );
899            
900             # try ignoring these
901 0 0         next if ($line=~m/\s*\{\{SUSPENDED\}\}/o);
902            
903 0           $potentialEntries++;
904            
905 0 0         if ( $line=~s/^([^\t]+)\t+//o ) {
906 0           $cur_name=$1;
907 0           $castNames++;
908              
909 0 0         if ( $self->{showProgressBar} ) {
910             # re-adjust target so progress bar doesn't seem too wonky
911 0 0         if ( $castNames > $castCountEstimate ) {
    0          
912 0           $castCountEstimate = $progress->target($castNames+100);
913 0           $next_update=$progress->update($castNames);
914             }
915             elsif ( $castNames > $next_update ) {
916 0           $next_update=$progress->update($castNames);
917             }
918             }
919             }
920            
921             # BUG
922             # ##ignore {Twelve Angry Men (1954)}
923             #$line=~s/\s*\{[^\}]+\}//o;
924              
925             # sometimes there are extra bits of info attached at the end of lines, we'll ignore these
926             #
927             # examples:
928             # Deszcz (1997) (as Tomasz Baginski)
929             # Adventures of Modest Mouse (2008) (co-director)
930             # Vida (2010) (collaborating director)
931             # Rex Harrison Presents Stories of Love (1974) (TV) (segment "Epicac")
932 0 0         if ( $line=~s/ (\(.*)$//o ) {
933             # $attrs=$1;
934             }
935            
936 0           $line=~s/^\s+//og;
937 0           $line=~s/\s+$//og;
938              
939 0 0         if ( $self->{imdbMovie2DBKey}->{$line} ) {
940              
941 0 0         if ( !defined($found{$cur_name}) ) {
942 0           $directorCount++;
943 0           $found{$cur_name}=$directorCount;
944              
945 0           my $c=$cur_name;
946 0           $c=~s/\s*\([IVXL]+\)//o;
947 0           $tableInsert_sth->execute($directorCount, $DB->makeSearchableTitle($c, 0), $cur_name);
948             }
949              
950 0           $tableInsert_sth2->execute($self->{imdbMovie2DBKey}->{$line}, $found{$cur_name});
951 0           $count++;
952 0 0         if ( ($count % 50000) == 0 ) {
953 0           $DB->commit();
954             }
955             }
956             else {
957 0           $self->error("$lineCount: unable to match title key '$line'");
958             }
959             }
960 0 0         $progress->update($castCountEstimate) if ($self->{showProgressBar});
961              
962 0           $self->status(sprintf("importing Directors found ".withThousands($castNames)." names, ".
963             withThousands($count)." titles in ".withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
964            
965 0           closeMaybeGunzip($file, $fh);
966              
967 0           $DB->commit();
968 0           return($castNames);
969             }
970              
971             sub importRatings($$)
972             {
973 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
974 0           my $startTime=time();
975 0           my $lineCount=0;
976              
977 0 0         if ( $DB->table_row_count('Ratings') > 0 ) {
978 0           $self->status("clearing previously loaded data..");
979 0           $DB->table_clear('Ratings');
980             }
981              
982 0   0       my $fh = openMaybeGunzip($file) || return(-2);
983 0           while(<$fh>) {
984 0           $lineCount++;
985 0 0         if ( m/^MOVIE RATINGS REPORT/o ) {
    0          
986 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o) {
987 0           $self->error("missing empty line after \"MOVIE RATINGS REPORT\" at line $lineCount");
988 0           closeMaybeGunzip($file, $fh);
989 0           return(-1);
990             }
991 0 0 0       if ( !($_=<$fh>) || !m/^New Distribution Votes Rank Title/o ) {
992 0           $self->error("missing \"New Distribution Votes Rank Title\" at line $lineCount");
993 0           closeMaybeGunzip($file, $fh);
994 0           return(-1);
995             }
996 0           last;
997             }
998             elsif ( $lineCount > 1000 ) {
999 0           $self->error("$file: stopping at line $lineCount, didn't see \"MOVIE RATINGS REPORT\" line");
1000 0           closeMaybeGunzip($file, $fh);
1001 0           return(-1);
1002             }
1003             }
1004              
1005 0 0         my $progress=Term::ProgressBar->new({name => "importing Ratings",
1006             count => $countEstimate,
1007             ETA => 'linear'})
1008             if ($self->{showProgressBar});
1009              
1010 0 0         $progress->minor(0) if ($self->{showProgressBar});
1011 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
1012 0           my $next_update=0;
1013              
1014 0           my $countImported=0;
1015 0           my $count=0;
1016 0           my $potentialEntries=0;
1017 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Ratings (TitleID, Distribution, Votes, Rank) VALUES (?,?,?,?)');
1018 0           while(<$fh>) {
1019 0           $lineCount++;
1020 0           my $line=$_;
1021             #print "read line $lineCount:$line";
1022              
1023 0           $line=~s/\n$//o;
1024            
1025             # skip empty lines (only really appear right before last line ending with ----
1026 0 0         next if ( $line=~m/^\s*$/o );
1027             # end is line consisting of only '-'
1028 0 0         last if ( $line=~m/^\-\-\-\-\-\-\-+/o );
1029              
1030 0           $potentialEntries++;
1031            
1032             # e.g. New Distribution Votes Rank Title
1033             # 0000000133 225568 8.9 12 Angry Men (1957)
1034 0 0         if ( $line=~m/^\s+([\.|\*|\d]+)\s+(\d+)\s+(\d+\.\d+)\s+(.+)$/o ) {
1035              
1036 0 0         if ( $self->{imdbMovie2DBKey}->{$4} ) {
1037 0           $tableInsert_sth->execute($self->{imdbMovie2DBKey}->{$4}, $1, $2, $3);
1038 0           $countImported++;
1039 0 0         if ( ($countImported % 50000) == 0 ) {
1040 0           $DB->commit();
1041             }
1042             }
1043            
1044 0           $count++;
1045              
1046             #$self->{movies}{$line}=[$1,$2,"$3.$4"];
1047 0 0         if ( $self->{showProgressBar} ) {
1048             # re-adjust target so progress bar doesn't seem too wonky
1049 0 0         if ( $count > $countEstimate ) {
    0          
1050 0           $countEstimate = $progress->target($count+1000);
1051 0           $next_update=$progress->update($count);
1052             }
1053             elsif ( $count > $next_update ) {
1054 0           $next_update=$progress->update($count);
1055             }
1056             }
1057             }
1058             else {
1059 0           $self->error("$file:$lineCount: unrecognized format");
1060             }
1061             }
1062 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
1063              
1064 0           $self->status(sprintf("importing Ratings found ".withThousands($count)." in ".
1065             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
1066            
1067 0           closeMaybeGunzip($file, $fh);
1068            
1069 0           $DB->commit();
1070 0           return($count);
1071             }
1072              
1073             sub importKeywords($$$$)
1074             {
1075 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
1076 0           my $startTime=time();
1077 0           my $lineCount=0;
1078              
1079 0 0         if ( $DB->table_row_count('Keywords') > 0 ) {
1080 0           $self->status("clearing previously loaded data..");
1081 0           $DB->table_clear('Keywords');
1082             }
1083              
1084 0   0       my $fh = openMaybeGunzip($file) || return(-2);
1085 0           while(<$fh>) {
1086 0           $lineCount++;
1087              
1088 0 0         if ( m/THE KEYWORDS LIST/ ) {
    0          
1089 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
1090 0           $self->error("missing ======= after \"THE KEYWORDS LIST\" at line $lineCount");
1091 0           closeMaybeGunzip($file, $fh);
1092 0           return(-1);
1093             }
1094 0 0 0       if ( !($_=<$fh>) || !m/^\s*$/o ) {
1095 0           $self->error("missing empty line after ======= at line $lineCount");
1096 0           closeMaybeGunzip($file, $fh);
1097 0           return(-1);
1098             }
1099 0           last;
1100             }
1101             elsif ( $lineCount > 200000 ) {
1102 0           $self->error("$file: stopping at line $lineCount, didn't see \"THE KEYWORDS LIST\" line");
1103 0           closeMaybeGunzip($file, $fh);
1104 0           return(-1);
1105             }
1106             }
1107              
1108 0 0         my $progress=Term::ProgressBar->new({name => "importing Keywords",
1109             count => $countEstimate,
1110             ETA => 'linear'})
1111             if ($self->{showProgressBar});
1112              
1113 0 0         $progress->minor(0) if ($self->{showProgressBar});
1114 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
1115 0           my $next_update=0;
1116              
1117 0           my $count=0;
1118 0           my $countImported=0;
1119 0           my %found;
1120 0           my $tableInsert_sth1=$DB->prepare('INSERT INTO Keywords (KeywordID, Name) VALUES (?,?)');
1121 0           my $tableInsert_sth2=$DB->prepare('INSERT INTO Titles2Keywords (TitleID, KeywordID) VALUES (?,?)');
1122 0           my $keywordCount=0;
1123 0           my $potentialEntries=0;
1124            
1125 0           while(<$fh>) {
1126 0           $lineCount++;
1127 0           my $line=$_;
1128 0           chomp($line);
1129 0 0         next if ($line =~ m/^\s*$/);
1130            
1131 0           $potentialEntries++;
1132            
1133 0           my ($title, $keyword) = ($line =~ m/^(.*)\s+(\S+)\s*$/);
1134 0 0 0       if ( defined($title) and defined($keyword) ) {
1135              
1136 0           my ($episode) = $title =~ m/\s+(\{.*\})$/o;
1137            
1138 0 0         if ( $self->{imdbMovie2DBKey}->{$title} ) {
1139 0 0         if ( !defined($found{$keyword}) ) {
1140 0           $keywordCount++;
1141            
1142 0           $found{$keyword}=$keywordCount;
1143 0           $tableInsert_sth1->execute($keywordCount, $keyword);
1144             #=$DB->insert_row('Keywords', 'KeywordID', Name=>$keyword);
1145             }
1146 0           $tableInsert_sth2->execute($self->{imdbMovie2DBKey}->{$title}, $found{$keyword});
1147            
1148             #$DB->insert_row('Titles2Keywords', undef, TitleID=>$self->{imdbMovie2DBKey}->{$title}, KeywordID=>$found{$keyword});
1149 0           $countImported++;
1150 0 0         if ( ($countImported % 50000) == 0 ) {
1151 0           $DB->commit();
1152             }
1153             }
1154 0           $count++;
1155 0 0         if ( $self->{showProgressBar} ) {
1156             # re-adjust target so progress bar doesn't seem too wonky
1157 0 0         if ( $count > $countEstimate ) {
    0          
1158 0           $countEstimate = $progress->target($count+1000);
1159 0           $next_update=$progress->update($count);
1160             }
1161             elsif ( $count > $next_update ) {
1162 0           $next_update=$progress->update($count);
1163             }
1164             }
1165             } else {
1166 0           $self->error("$file:$lineCount: unrecognized format \"$line\"");
1167             }
1168            
1169             }
1170 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
1171              
1172 0           $self->status(sprintf("importing Keywords found ".withThousands($count)." in ".
1173             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
1174              
1175 0           closeMaybeGunzip($file, $fh);
1176 0           $DB->commit();
1177 0           return($count);
1178             }
1179              
1180             sub importPlots($$$$)
1181             {
1182 0     0 0   my ($self, $countEstimate, $file, $DB)=@_;
1183 0           my $startTime=time();
1184 0           my $lineCount=0;
1185              
1186 0 0         if ( $DB->table_row_count('Plots') > 0 ) {
1187 0           $self->status("clearing previously loaded data..");
1188 0           $DB->table_clear('Plots');
1189             }
1190              
1191 0   0       my $fh = openMaybeGunzip($file) || return(-2);
1192 0           while(<$fh>) {
1193 0           $lineCount++;
1194              
1195 0 0         if ( m/PLOT SUMMARIES LIST/ ) {
    0          
1196 0 0 0       if ( !($_=<$fh>) || !m/^===========/o ) {
1197 0           $self->error("missing ======= after \"PLOT SUMMARIES LIST\" at line $lineCount");
1198 0           closeMaybeGunzip($file, $fh);
1199 0           return(-1);
1200             }
1201 0 0 0       if ( !($_=<$fh>) || !m/^-----------/o ) {
1202 0           $self->error("missing ------- line after ======= at line $lineCount");
1203 0           closeMaybeGunzip($file, $fh);
1204 0           return(-1);
1205             }
1206 0           last;
1207             }
1208             elsif ( $lineCount > 500 ) {
1209 0           $self->error("$file: stopping at line $lineCount, didn't see \"PLOT SUMMARIES LIST\" line");
1210 0           closeMaybeGunzip($file, $fh);
1211 0           return(-1);
1212             }
1213             }
1214              
1215 0 0         my $progress=Term::ProgressBar->new({name => "importing Plots",
1216             count => $countEstimate,
1217             ETA => 'linear'})
1218             if ($self->{showProgressBar});
1219              
1220 0 0         $progress->minor(0) if ($self->{showProgressBar});
1221 0 0         $progress->max_update_rate(1) if ($self->{showProgressBar});
1222 0           my $next_update=0;
1223              
1224 0           my $count=0;
1225 0           my $potentialEntries=0;
1226 0           my $tableInsert_sth=$DB->prepare('INSERT INTO Plots (TitleID, Sequence, Description, Author) VALUES (?,?,?,?)');
1227 0           while(<$fh>) {
1228 0           $lineCount++;
1229 0           my $line=$_;
1230 0           chomp($line);
1231 0 0         next if ($line =~ m/^\s*$/);
1232 0 0         next if ($line=~m/\s*\{\{SUSPENDED\}\}/o);
1233            
1234 0           $potentialEntries++;
1235              
1236 0           my ($title, $episode) = ($line =~ m/^MV:\s(.*?)\s?(\{.*\})?$/);
1237 0 0         if ( defined($title) ) {
1238            
1239 0           $line =~s/^MV:\s*//;
1240              
1241 0           my $sequence=1;
1242 0           my $plot = '';
1243              
1244 0           while ( my $l = <$fh> ) {
1245 0           $lineCount++;
1246 0           chomp($l);
1247            
1248 0 0         next if ($l =~ m/^\s*$/);
1249            
1250 0 0         if ( $l =~ m/PL:\s(.*)$/ ) { # plot summary is a number of lines starting "PL:"
1251 0 0         $plot .= ($plot ne '' ?' ':'') . $1;
1252             }
1253              
1254 0 0 0       if ( $l =~ m/BY:\s(.*)$/ || $l =~ m/^(\-\-\-\-\-\-\-\-)/o ) {
1255 0           my $token=$1;
1256 0           my $author=$1;
1257            
1258 0 0         if ( $token eq "\-\-\-\-\-\-\-\-" ) {
1259 0 0         if ( $plot eq '' ) {
1260 0           last;
1261             }
1262 0           $author='';
1263             }
1264            
1265 0 0         if ( $self->{imdbMovie2DBKey}->{$line} ) {
1266 0           $tableInsert_sth->execute($self->{imdbMovie2DBKey}->{$line}, $sequence, $plot, $author);
1267            
1268 0           $count++;
1269 0 0         if ( ($count % 50000) == 0 ) {
1270 0           $DB->commit();
1271             }
1272             }
1273             else {
1274 0           $self->error("$lineCount: unable to match title key '$line'");
1275             }
1276            
1277 0           $plot='';
1278 0           $sequence++;
1279              
1280 0 0         if ( $token eq "\-\-\-\-\-\-\-\-" ) {
1281 0           last;
1282             }
1283             }
1284             }
1285              
1286 0 0         if ( length($plot) ) {
1287 0           $self->error("$lineCount: truncated plot with title key '$line'");
1288             }
1289            
1290 0 0         if ( $self->{showProgressBar} ) {
1291             # re-adjust target so progress bar doesn't seem too wonky
1292 0 0         if ( $count > $countEstimate ) {
    0          
1293 0           $countEstimate = $progress->target($count+1000);
1294 0           $next_update=$progress->update($count);
1295             }
1296             elsif ( $count > $next_update ) {
1297 0           $next_update=$progress->update($count);
1298             }
1299             }
1300             } else {
1301             # skip lines up to the next "MV:"
1302 0 0         if ($line !~ m/^(---|PL:|BY:)/ ) {
1303 0           $self->error("$file:$lineCount: unrecognized format \"$line\"");
1304             }
1305             #$next_update=$progress->update($count) if ($self->{showProgressBar});
1306 0 0         if ( $count > $next_update ) {
1307 0 0         if ($self->{showProgressBar}) {
1308 0           $next_update=$progress->update($count) ;
1309 0           warn "next $count -> $next_update";
1310             }
1311             }
1312             }
1313             }
1314 0 0         $progress->update($countEstimate) if ($self->{showProgressBar});
1315              
1316 0           $self->status(sprintf("importing Plots found ".withThousands($count)." in ".
1317             withThousands($potentialEntries)." entries in %d seconds",time()-$startTime));
1318              
1319 0           closeMaybeGunzip($file, $fh);
1320 0           $DB->commit();
1321 0           return($count);
1322             }
1323              
1324             sub loadDBInfo($)
1325             {
1326 0     0 0   my $file=shift;
1327 0           my $info;
1328              
1329 0 0         open(INFO, "< $file") || return("imdbDir index file \"$file\":$!");
1330 0           while() {
1331 0           chop();
1332 0 0         if ( s/^([^:]+)://o ) {
1333 0           $info->{$1}=$_;
1334             }
1335             }
1336 0           close(INFO);
1337 0           return($info);
1338             }
1339              
1340             sub dbinfoLoad($)
1341             {
1342 0     0 0   my $self=shift;
1343              
1344 0           my $info=loadDBInfo($self->{moviedbInfo});
1345 0 0         if ( ref $info ne 'HASH' ) {
1346 0           return(1);
1347             }
1348 0           $self->{dbinfo}=$info;
1349 0           return(undef);
1350             }
1351              
1352             sub dbinfoAdd($$$)
1353             {
1354 0     0 0   my ($self, $key, $value)=@_;
1355 0           $self->{dbinfo}->{$key}=$value;
1356             }
1357              
1358             sub dbinfoGet($$$)
1359             {
1360 0     0 0   my ($self, $key, $defaultValue)=@_;
1361 0 0         if ( defined($self->{dbinfo}->{$key}) ) {
1362 0           return($self->{dbinfo}->{$key});
1363             }
1364 0           return($defaultValue);
1365             }
1366              
1367             sub dbinfoSave($)
1368             {
1369 0     0 0   my $self=shift;
1370 0 0         open(INFO, "> $self->{moviedbInfo}") || return(1);
1371 0           for (sort keys %{$self->{dbinfo}}) {
  0            
1372 0           print INFO "".$_.":".$self->{dbinfo}->{$_}."\n";
1373             }
1374 0           close(INFO);
1375 0           return(0);
1376             }
1377              
1378             sub dbinfoGetFileSize($$)
1379             {
1380 0     0 0   my ($self, $key)=@_;
1381            
1382              
1383 0 0         if ( !defined($self->{listFiles}->paths_isset($key) ) ) {
1384 0           die ("invalid call for $key");
1385             }
1386 0           my $filePath=$self->{listFiles}->paths_index($key);
1387 0 0         if ( ! -f $filePath ) {
1388 0           return(0);
1389             }
1390              
1391 0           my $fileSize=int(-s $filePath);
1392              
1393             # if compressed, then attempt to run gzip -l
1394 0 0         if ( $filePath=~m/.gz$/) {
1395 0 0         if ( open(my $fd, "gzip -l $filePath |") ) {
1396             # if parse fails, then defalt to wild ass guess of compression of 65%
1397 0           $fileSize=int(($fileSize*100)/(100-65));
1398              
1399 0           while(<$fd>) {
1400 0 0         if ( m/^\s*\d+\s+(\d+)/ ) {
1401 0           $fileSize=$1;
1402             }
1403             }
1404 0           close($fd);
1405             }
1406             else {
1407             # wild ass guess of compression of 65%
1408 0           $fileSize=int(($fileSize*100)/(100-65));
1409             }
1410             }
1411 0           return($fileSize);
1412             }
1413              
1414             sub _redirect($$)
1415             {
1416 0     0     my ($self, $file)=@_;
1417            
1418 0 0         if ( defined($file) ) {
1419 0 0         if ( !open($self->{logfd}, "> $file") ) {
1420 0           print STDERR "$file:$!\n";
1421 0           return(0);
1422             }
1423 0           $self->{errorCountInLog}=0;
1424             }
1425             else {
1426 0           close($self->{logfd});
1427 0           $self->{logfd}=undef;
1428             }
1429 0           return(1);
1430             }
1431              
1432             =head2 importListComplete
1433              
1434             Check to see if spcified list file has been successfully imported
1435              
1436             =cut
1437              
1438             sub importListComplete($)
1439             {
1440 0     0 1   my ($self, $type)=@_;
1441              
1442 0 0         if ( -f "$self->{imdbDir}/stage-$type.log" ) {
1443 0           return(1);
1444             }
1445 0           return(0);
1446             }
1447              
1448             sub _prepStage
1449             {
1450 0     0     my ($self, $type)=@_;
1451            
1452 0           my $DB=new IMDB::Local::DB(database=>"$self->{imdbDir}/imdb.db");
1453              
1454             # if we're restarting, lets start fresh
1455 0 0         if ( $type eq 'movies' ) {
1456             #warn("recreating db ".$DB->database());
1457 0           $DB->delete();
1458              
1459 0           for my $type ( $self->listTypes() ) {
1460 0           unlink("$self->{imdbDir}/stage-$type.log");
1461             }
1462              
1463             }
1464            
1465 0 0         if ( !$self->_redirect(sprintf("%s/stage-$type.log", $self->{imdbDir})) ) {
1466 0           return(1);
1467             }
1468            
1469 0 0         if ( !$DB->connect() ) {
1470 0           die "imdbdb connect failed:$DBI::errstr";
1471             }
1472              
1473 0           $DB->runSQL("PRAGMA synchronous = OFF");
1474 0           return($DB);
1475              
1476             }
1477              
1478             sub _unprepStage
1479             {
1480 0     0     my ($self, $db)=@_;
1481            
1482 0           $db->commit();
1483 0           $db->disconnect();
1484              
1485 0           $self->_redirect(undef);
1486             }
1487              
1488             sub _importListFile($$$)
1489             {
1490 0     0     my ($self, $DB, $type)=@_;
1491              
1492              
1493 0 0         if ( !grep(/^$type$/, $self->listTypes()) ) {
1494 0           die "invalid type $type";
1495             }
1496            
1497             my $dbinfoCalcEstimate=sub {
1498 0     0     my ($self, $key)=@_;
1499            
1500 0           my %estimateSizePerEntry=(movies=>47,
1501             directors=>258,
1502             actors=>695,
1503             actresses=>779,
1504             genres=>38,
1505             ratings=>68,
1506             keywords=>47,
1507             plot=>731);
1508 0           my $fileSize=$self->dbinfoGetFileSize($key);
1509            
1510 0           my $countEstimate=int($fileSize/$estimateSizePerEntry{$key});
1511            
1512 0           my $filePath=$self->{listFiles}->paths_index($key);
1513            
1514 0           $self->dbinfoAdd($key."_list_file", $filePath);
1515 0           $self->dbinfoAdd($key."_list_file_size", "".int(-s $filePath));
1516 0           $self->dbinfoAdd($key."_list_file_size_uncompressed", $fileSize);
1517 0           $self->dbinfoAdd($key."_list_count_estimate", $countEstimate);
1518 0           return($countEstimate);
1519 0           };
1520              
1521             my $dbinfoCalcBytesPerEntry = sub {
1522 0     0     my ($self, $key, $calcActualForThisNumber)=@_;
1523 0           my $fileSize=$self->dbinfoGetFileSize($key);
1524 0           return(int($fileSize/$calcActualForThisNumber));
1525 0           };
1526              
1527              
1528 0 0         if ( ! -f $self->{listFiles}->paths_index($type) ) {
1529 0           $self->status("no $type file available");
1530 0           return(1);
1531             }
1532              
1533 0 0         if ( $type eq 'movies') {
1534              
1535 0           $DB->drop_table_indexes('Titles');
1536            
1537 0           my $countEstimate=&$dbinfoCalcEstimate($self, "movies");
1538              
1539 0           my $num=$self->importMovies($countEstimate, $self->{listFiles}->paths_index('movies'), $DB);
1540 0 0         if ( $num < 0 ) {
    0          
1541 0 0         if ( $num == -2 ) {
1542 0           $self->error("you need to download ".$self->{listFiles}->paths_index('movies')." from ftp.imdb.com");
1543             }
1544 0           return(1);
1545             }
1546             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1547 0           my $better=&$dbinfoCalcBytesPerEntry($self, "movies", $num);
1548 0           $self->status("ARG estimate of $countEstimate for movies needs updating, found $num ($better bytes/entry)");
1549             }
1550              
1551 0 0         open(OUT, "> $self->{imdbDir}/titles.tsv") || die "$self->{imdbDir}/titles.tsv:$!";
1552 0           for my $mkey (sort keys %{$self->{imdbMovie2DBKey}}) {
  0            
1553 0           print OUT "".$self->{imdbMovie2DBKey}->{$mkey}."\t".$mkey."\n";
1554             }
1555 0           close(OUT);
1556              
1557 0           $self->dbinfoAdd("db_stat_movie_count", "$num");
1558              
1559 0           $self->status("Creating Table indexes..");
1560 0           $DB->create_table_indexes('Titles');
1561              
1562 0           return(0);
1563             }
1564              
1565             # read in keys so we have them for follow-up stages
1566 0 0         if ( !defined($self->{imdbMovie2DBKey}) ) {
1567             #$self->{imdbMovie2DBKey}=$DB->select2Hash("select IMDBKey, TitleID from Titles");
1568            
1569 0           if ( 1 ) {
1570 0 0         open(IN, "< $self->{imdbDir}/titles.tsv") || die "$self->{imdbDir}/titles.tsv:$!";
1571 0           while () {
1572 0           chomp();
1573 0 0         if ( m/^(\d+)\t(.+)/o ) {
1574 0           $self->{imdbMovie2DBKey}->{$2}=$1;
1575             }
1576             }
1577 0           close(IN);
1578             }
1579             }
1580              
1581             # need to read-movie kesy
1582 0 0         if ( $type eq 'directors') {
1583              
1584 0           $DB->drop_table_indexes('Directors');
1585            
1586 0           my $countEstimate=&$dbinfoCalcEstimate($self, "directors");
1587              
1588 0           my $num=$self->importDirectors($countEstimate, $self->{listFiles}->paths_index('directors'), $DB);
1589 0 0         if ( $num < 0 ) {
    0          
1590 0 0         if ( $num == -2 ) {
1591 0           $self->error("you need to download ".$self->{listFiles}->paths_index('directors')." from ftp.imdb.com (see http://www.imdb.com/interfaces)");
1592             }
1593 0           return(1);
1594             }
1595             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1596 0           my $better=&$dbinfoCalcBytesPerEntry($self, "directors", $num);
1597 0           $self->status("ARG estimate of $countEstimate for directors needs updating, found $num ($better bytes/entry)");
1598             }
1599 0           $self->dbinfoAdd("db_stat_director_count", "$num");
1600            
1601 0           $self->status("Creating Table indexes..");
1602 0           $DB->create_table_indexes('Directors');
1603              
1604 0           return(0);
1605             }
1606              
1607 0 0         if ( $type eq 'actors') {
1608 0           $DB->drop_table_indexes('Actors');
1609              
1610             #print "re-reading movies into memory for reverse lookup..\n";
1611 0           my $countEstimate=&$dbinfoCalcEstimate($self, "actors");
1612              
1613             #my $num=$self->readCast("Actors", $countEstimate, "$self->{imdbListFiles}->{actors}");
1614 0           my $num=$self->importActors("Actors", $countEstimate, $self->{listFiles}->paths_index('actors'), $DB);
1615 0 0         if ( $num < 0 ) {
    0          
1616 0 0         if ( $num == -2 ) {
1617 0           $self->error("you need to download ".$self->{listFiles}->paths_index('actors')." from ftp.imdb.com (see http://www.imdb.com/interfaces)");
1618             }
1619 0           return(1);
1620             }
1621             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1622 0           my $better=&$dbinfoCalcBytesPerEntry($self, "actors", $num);
1623 0           $self->status("ARG estimate of $countEstimate for actors needs updating, found $num ($better bytes/entry)");
1624             }
1625 0           $self->dbinfoAdd("db_stat_actor_count", "$num");
1626 0           return(0);
1627             }
1628            
1629 0 0         if ( $type eq 'actresses') {
1630              
1631 0           my $countEstimate=&$dbinfoCalcEstimate($self, "actresses");
1632 0           my $num=$self->importActors("Actresses", $countEstimate, $self->{listFiles}->paths_index('actresses'), $DB);
1633 0 0         if ( $num < 0 ) {
    0          
1634 0 0         if ( $num == -2 ) {
1635 0           $self->error("you need to download ".$self->{listFiles}->paths_index('actresses')." from ftp.imdb.com (see http://www.imdb.com/interfaces)");
1636             }
1637 0           return(1);
1638             }
1639             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1640 0           my $better=&$dbinfoCalcBytesPerEntry($self, "actresses", $num);
1641 0           $self->status("ARG estimate of $countEstimate for actresses needs updating, found $num ($better bytes/entry)");
1642             }
1643 0           $self->dbinfoAdd("db_stat_actress_count", "$num");
1644            
1645 0           $self->status("Creating Table indexes..");
1646 0           $DB->create_table_indexes('Actors');
1647            
1648 0           return(0);
1649             }
1650            
1651 0 0         if ( $type eq 'genres') {
1652 0           $DB->drop_table_indexes('Genres');
1653            
1654 0           my $countEstimate=&$dbinfoCalcEstimate($self, "genres");
1655              
1656 0           my $num=$self->importGenres($countEstimate, $self->{listFiles}->paths_index('genres'), $DB);
1657 0 0         if ( $num < 0 ) {
    0          
1658 0 0         if ( $num == -2 ) {
1659 0           $self->error("you need to download ".$self->{listFiles}->paths_index('genres')." from ftp.imdb.com");
1660             }
1661 0           return(1);
1662             }
1663             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1664 0           my $better=&$dbinfoCalcBytesPerEntry($self, "genres", $num);
1665 0           $self->status("ARG estimate of $countEstimate for genres needs updating, found $num ($better bytes/entry)");
1666             }
1667 0           $self->dbinfoAdd("db_stat_genres_count", "$num");
1668            
1669 0           $self->status("Creating Table indexes..");
1670 0           $DB->create_table_indexes('Genres');
1671              
1672 0           return(0);
1673             }
1674            
1675 0 0         if ( $type eq 'ratings') {
1676 0           $DB->drop_table_indexes('Ratings');
1677            
1678 0           my $countEstimate=&$dbinfoCalcEstimate($self, "ratings");
1679              
1680 0           my $num=$self->importRatings($countEstimate, $self->{listFiles}->paths_index('ratings'), $DB);
1681 0 0         if ( $num < 0 ) {
    0          
1682 0 0         if ( $num == -2 ) {
1683 0           $self->error("you need to download ".$self->{listFiles}->paths_index('ratings')." from ftp.imdb.com");
1684             }
1685 0           return(1);
1686             }
1687             elsif ( abs($num - $countEstimate) > $countEstimate*.10 ) {
1688 0           my $better=&$dbinfoCalcBytesPerEntry($self, "ratings", $num);
1689 0           $self->status("ARG estimate of $countEstimate for ratings needs updating, found $num ($better bytes/entry)");
1690             }
1691 0           $self->dbinfoAdd("db_stat_ratings_count", "$num");
1692              
1693 0           $self->status("Creating Table indexes..");
1694 0           $DB->create_table_indexes('Ratings');
1695              
1696 0           return(0);
1697             }
1698            
1699 0 0         if ( $type eq 'keywords') {
1700 0           $DB->drop_table_indexes('Keywords');
1701            
1702 0           my $countEstimate=&$dbinfoCalcEstimate($self, "keywords");
1703             #my $countEstimate=5554178;
1704              
1705 0           my $num=$self->importKeywords($countEstimate, $self->{listFiles}->paths_index('keywords'), $DB);
1706 0 0         if ( $num < 0 ) {
    0          
1707 0 0         if ( $num == -2 ) {
1708 0           $self->error("you need to download ".$self->{listFiles}->paths_index('keywords')." from ftp.imdb.com");
1709             }
1710 0           return(1);
1711             }
1712             elsif ( abs($num - $countEstimate) > $countEstimate*.05 ) {
1713 0           $self->status("ARG estimate of $countEstimate for keywords needs updating, found $num");
1714             }
1715 0           $self->dbinfoAdd("keywords_list_file", $self->{listFiles}->paths_index('keywords'));
1716 0           $self->dbinfoAdd("keywords_list_file_size", -s $self->{listFiles}->paths_index('keywords'));
1717 0           $self->dbinfoAdd("db_stat_keywords_count", "$num");
1718              
1719 0           $self->status("Creating Table indexes..");
1720 0           $DB->create_table_indexes('Keywords');
1721              
1722 0           return(0);
1723             }
1724              
1725 0 0         if ( $type eq 'plot') {
1726 0           $DB->drop_table_indexes('Plots');
1727            
1728 0           my $countEstimate=&$dbinfoCalcEstimate($self, "plot");
1729 0           my $num=$self->importPlots($countEstimate, $self->{listFiles}->paths_index('plot'), $DB);
1730 0 0         if ( $num < 0 ) {
    0          
1731 0 0         if ( $num == -2 ) {
1732 0           $self->error("you need to download ".$self->{listFiles}->paths_index('plot')." from ftp.imdb.com");
1733             }
1734 0           return(1);
1735             }
1736             elsif ( abs($num - $countEstimate) > $countEstimate*.05 ) {
1737 0           $self->status("ARG estimate of $countEstimate for plots needs updating, found $num");
1738             }
1739 0           $self->dbinfoAdd("plots_list_file", $self->{listFiles}->paths_index('plot'));
1740 0           $self->dbinfoAdd("plots_list_file_size", -s $self->{listFiles}->paths_index('plot'));
1741 0           $self->dbinfoAdd("db_stat_plots_count", "$num");
1742            
1743 0           $self->status("Creating Table indexes..");
1744 0           $DB->create_table_indexes('Plots');
1745            
1746 0           return(0);
1747             }
1748              
1749 0           $self->error("invalid type $type");
1750 0           return(1);
1751             }
1752              
1753             =head2 importList
1754              
1755             Import a list file from 'listsDir' into the IMDB::Local Database.
1756             Note: when 'movies' type is specified the database is reset from scratch
1757              
1758             =cut
1759              
1760             sub importList($$)
1761             {
1762 0     0 1   my ($self, $type)=@_;
1763              
1764 0           my $DB=$self->_prepStage($type);
1765              
1766             # lets load our stats
1767 0           $self->dbinfoLoad();
1768              
1769 0           my $startTime=time();
1770 0 0         if ( $self->_importListFile($DB, $type) != 0 ) {
1771 0           $DB->disconnect();
1772 0           return(1);
1773             }
1774              
1775 0           $self->dbinfoAdd("seconds_to_complete_prep_stage_$type", (time()-$startTime));
1776 0           $self->dbinfoSave();
1777              
1778 0           $self->_unprepStage($DB);
1779 0           return(0);
1780             }
1781              
1782             =head2 importAll
1783              
1784             Import all available list files from 'listsDir' into the IMDB::Local Database.
1785             Returns # of list files that produced errors.
1786              
1787             =cut
1788              
1789             sub importAll($$)
1790             {
1791 0     0 1   my ($self, $type)=@_;
1792              
1793 0           my $err=0;
1794 0           for my $type ( $self->listTypes() ) {
1795 0 0         if ( $self->importList($type) != 0 ) {
1796 0           warn("list import $type failed to load, $self->{errorCountInLog} errors in $self->{imdbDir}/stage-$type.log");
1797 0           $err++;
1798             }
1799             }
1800 0           return($err);
1801             }
1802              
1803             =head2 optimize
1804              
1805             Optimize the database for better performance.
1806              
1807             =cut
1808             sub optimize($)
1809             {
1810 0     0 1   my ($self)=@_;
1811              
1812 0           my $DB=new IMDB::Local::DB(database=>"$self->{imdbDir}/imdb.db", db_AutoCommit=>1);
1813            
1814 0 0         if ( !$DB->connect() ) {
1815 0           die "imdbdb connect failed:$DBI::errstr";
1816             }
1817              
1818 0           $DB->runSQL("VACUUM");
1819 0           $DB->disconnect();
1820 0           return(1);
1821             }
1822              
1823             sub _NOT_USED_checkSantity($)
1824             {
1825 0     0     my ($self)=@_;
1826              
1827 0           $self->dbinfoAdd("db_version", $IMDB::Local::VERSION);
1828              
1829 0 0         if ( $self->dbinfoSave() ) {
1830 0           $self->error("$self->{moviedbInfo}:$!");
1831 0           return(1);
1832             }
1833            
1834 0           $self->status("running quick sanity check on database indexes...");
1835 0           my $imdb=new IMDB::Local('imdbDir' => $self->{imdbDir},
1836             'verbose' => $self->{verbose});
1837            
1838 0 0         if ( -e "$self->{moviedbOffline}" ) {
1839 0           unlink("$self->{moviedbOffline}");
1840             }
1841            
1842 0 0         if ( my $errline=$imdb->sanityCheckDatabase() ) {
1843 0 0         open(OFF, "> $self->{moviedbOffline}") || die "$self->{moviedbOffline}:$!";
1844 0           print OFF $errline."\n";
1845 0           print OFF "one of the prep stages' must have produced corrupt data\n";
1846 0           print OFF "report the following details to xmltv-devel\@lists.sf.net\n";
1847            
1848 0           my $info=loadDBInfo($self->{moviedbInfo});
1849 0 0         if ( ref $info eq 'HASH' ) {
1850 0           for my $key (sort keys %{$info}) {
  0            
1851 0           print OFF "\t$key:$info->{$key}\n";
1852             }
1853             }
1854             else {
1855 0           print OFF "\tdbinfo file corrupt\n";
1856 0           print OFF "\t$info";
1857             }
1858 0           print OFF "database taken offline\n";
1859 0           close(OFF);
1860 0 0         open(OFF, "< $self->{moviedbOffline}") || die "$self->{moviedbOffline}:$!";
1861 0           while() {
1862 0           chop();
1863 0           $self->error($_);
1864             }
1865 0           close(OFF);
1866 0           return(1);
1867             }
1868 0           $self->status("sanity intact :)");
1869 0           return(0);
1870             }
1871              
1872             =head1 AUTHOR
1873              
1874             jerryv, C<< >>
1875              
1876             =head1 BUGS
1877              
1878             Please report any bugs or feature requests to C, or through
1879             the web interface at L. I will be notified, and then you'll
1880             automatically be notified of progress on your bug as I make changes.
1881              
1882              
1883              
1884              
1885             =head1 SUPPORT
1886              
1887             You can find documentation for this module with the perldoc command.
1888              
1889             perldoc IMDB::Local
1890              
1891              
1892             You can also look for information at:
1893              
1894             =over 4
1895              
1896             =item * RT: CPAN's request tracker (report bugs here)
1897              
1898             L
1899              
1900             =item * AnnoCPAN: Annotated CPAN documentation
1901              
1902             L
1903              
1904             =item * CPAN Ratings
1905              
1906             L
1907              
1908             =item * Search CPAN
1909              
1910             L
1911              
1912             =back
1913              
1914              
1915             =head1 ACKNOWLEDGEMENTS
1916              
1917              
1918             =head1 LICENSE AND COPYRIGHT
1919              
1920             Copyright 2015 jerryv.
1921              
1922             This program is free software; you can redistribute it and/or modify it
1923             under the terms of the the Artistic License (2.0). You may obtain a
1924             copy of the full license at:
1925              
1926             L
1927              
1928             Any use, modification, and distribution of the Standard or Modified
1929             Versions is governed by this Artistic License. By using, modifying or
1930             distributing the Package, you accept this license. Do not use, modify,
1931             or distribute the Package, if you do not accept this license.
1932              
1933             If your Modified Version has been derived from a Modified Version made
1934             by someone other than you, you are nevertheless required to ensure that
1935             your Modified Version complies with the requirements of this license.
1936              
1937             This license does not grant you the right to use any trademark, service
1938             mark, tradename, or logo of the Copyright Holder.
1939              
1940             This license includes the non-exclusive, worldwide, free-of-charge
1941             patent license to make, have made, use, offer to sell, sell, import and
1942             otherwise transfer the Package with respect to any patent claims
1943             licensable by the Copyright Holder that are necessarily infringed by the
1944             Package. If you institute patent litigation (including a cross-claim or
1945             counterclaim) against any party alleging that the Package constitutes
1946             direct or contributory patent infringement, then this Artistic License
1947             to you shall terminate on the date that such litigation is filed.
1948              
1949             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
1950             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
1951             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
1952             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
1953             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
1954             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
1955             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
1956             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1957              
1958              
1959             =cut
1960              
1961             1; # End of IMDB::Local