Changeset 85

Show
Ignore:
Timestamp:
10/17/06 22:29:31 (7 years ago)
Author:
lincoln
Message:

more work on imdb postprocessor: now does online imdb lookups and caches the data; doesn't yet actually do anything with that data

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • postprocessors/imdb_augment_data

    r55 r85  
    1313# 
    1414#  based roughly on a few existing IMDB XMLTV modules and IMDB CPAN modules 
     15#  but doesn't actually use them due to the large number of interdependencies they drag in. 
     16#     most credit goes to Michael Stepanov for his excellent IMDB::Film modul 
     17#     and the regex's used to match data from IMDb pages 
    1518# 
    1619#  changelog: 
    1720#    0.01  09aug06      initial version 
     21#    0.02  17aug06      actually do imdb lookups & augment data 
    1822 
    1923use strict; 
    2024 
    2125my $progname = "imdb_augment_data"; 
    22 my $version = "0.01_09aug06"; 
     26my $version = "0.02_17aug06"; 
    2327 
    2428use LWP::UserAgent; 
     
    2832use POSIX qw(strftime mktime); 
    2933use Getopt::Long; 
    30 use HTML::TreeBuilder; 
     34use HTML::TokeParser; 
    3135use Data::Dumper; 
    3236use Compress::Zlib; 
     
    5054 
    5155my $ua; 
    52 BEGIN { 
    53         $ua = LWP::UserAgent->new( 
    54                 'timeout' => 30, 
    55                 'keep_alive' => 1, 
    56                 'agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-us)' 
    57                 ); 
    58         $ua->env_proxy; 
    59         $ua->cookie_jar({}); 
    60         $| = 1; 
    61 } 
     56$ua = LWP::UserAgent->new( 
     57        'timeout' => 30, 
     58        'keep_alive' => 1, 
     59        'agent' => "Shepherd / $progname $version" 
     60        ); 
     61$ua->env_proxy; 
     62$ua->cookie_jar({}); 
     63$| = 1; 
    6264 
    6365# 
     
    237239sub get_url 
    238240{ 
    239         my ($url,$status,$dontretry) = @_; 
     241        my ($url,$urltype,$status,$dontretry,$postvars) = @_; 
    240242        my $response; 
    241243        my $attempts = 0; 
    242244        my ($raw, $page, $base); 
    243245 
    244         my $request = HTTP::Request->new(GET => $url); 
     246        my $request; 
     247 
     248        if ($urltype eq "GET") { 
     249                $request = HTTP::Request->new(GET => $url); 
     250        } elsif ($urltype eq "POST") { 
     251                $request = HTTP::Request->new(POST => $url); # , Content_Type => 'application/x-www-form-urlencoded'); 
     252                $request->add_content($postvars); 
     253        } 
     254 
    245255        $request->header('Accept-Encoding' => 'gzip'); 
    246256 
    247         &log(sprintf "fetching %s: %s",$status,$url); 
     257        &log($status); 
    248258        for (1..3) { 
    249259                $response = $ua->request($request); 
     
    513523 
    514524###################################################################################################### 
     525# turn a string into something that can be used on a URL line 
     526 
     527sub urlify 
     528{ 
     529        my $str = shift; 
     530        $str =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; 
     531        return $str; 
     532} 
     533 
     534###################################################################################################### 
    515535 
    516536# alternativeTitles() from XMLTV::IMDB 
     
    556576 
    557577###################################################################################################### 
     578# use the online IMDb "power search" at http://www.imdb/List to try to find _1_ match 
     579 
     580sub search_imdb_online 
     581{ 
     582        my ($title, $post_fields) = @_; 
     583        $stats{imdb_lookup_added_cache_entry}++; 
     584        $data_cache->{movie_id_lookup}->{$post_fields}->{last_fetched} = time; 
     585 
     586        my $html_data = get_url("http://www.imdb.com/List","POST","  online IMDb search for '$title' with $post_fields",0,$post_fields); 
     587        if (!$html_data) { 
     588                $stats{failed_online_imdb_lookup}++; 
     589                &log("failed to search imdb movie data from http://www.imdb.com/List"); 
     590                return; 
     591        } 
     592        my $tp = HTML::TokeParser->new(\$html_data); 
     593 
     594        my $urls_found = 0; 
     595        my @urls; 
     596 
     597        # see if we can find any <a href="/title/tt[0-9]+/">{name}</a> tags 
     598        while (my $token = $tp->get_tag("a")) { 
     599                my $url = $token->[1]{href}; 
     600                if ($url =~ /\/title\/tt[0-9]+\//) { 
     601                        $urls_found++; 
     602                        push(@urls,$url); 
     603                } 
     604        } 
     605 
     606        # only insert into cache if we match exactly _1_ movie 
     607        if ($urls_found == 1) { 
     608                if ($urls[0] =~ /^http:/) { 
     609                        $data_cache->{movie_id_lookup}->{$post_fields}->{url} = $urls[0]; 
     610                } else { 
     611                        $data_cache->{movie_id_lookup}->{$post_fields}->{url} = "http://www.imdb.com".$urls[0]; 
     612                } 
     613                $stats{imdb_lookup_added_positive_cache_entry}++; 
     614        } else { 
     615                # negatively cache our failed lookup 
     616                $data_cache->{movie_id_lookup}->{$post_fields}->{url} = "-"; 
     617                $data_cache->{movie_id_lookup}->{$post_fields}->{num_choices} = $urls_found; 
     618 
     619                my $num = 0; 
     620                foreach my $url (@urls) { 
     621                        $data_cache->{movie_id_lookup}->{$post_fields}->{choices}->[$num] = $url; 
     622                        $num++; 
     623                } 
     624        } 
     625} 
     626 
     627###################################################################################################### 
     628# simple parser for imdb returned data: covers most data 
     629 
     630sub imdb_scalar_parser 
     631{ 
     632        my ($html_data, $target, $texttype, $targetb, $targetc, $targeta) = @_; 
     633 
     634        my $found = 0; 
     635        my $tp = HTML::TokeParser->new(\$html_data); 
     636 
     637        while (my $tag = $tp->get_tag('b')) { 
     638                if ($tp->get_text =~ /^$target/i) { 
     639                        $found = 1; 
     640                        last; 
     641                } 
     642        } 
     643        return undef if (!$found); 
     644 
     645        my $tag = $tp->get_tag($targeta) if (defined $targeta); 
     646 
     647        return ($tp->get_trimmed_text($targetb,$targetc)) if ($texttype eq "trimmed"); 
     648        return ($tp->get_text($targetb,$targetc)); 
     649} 
     650 
     651###################################################################################################### 
     652 
     653sub imdb_list_parser 
     654{ 
     655        my ($html_data, $target, $target2, $v) = @_; 
     656 
     657        my $tp = HTML::TokeParser->new(\$html_data); 
     658        my @list; 
     659        my $found = 0; 
     660 
     661        while (my $tag = $tp->get_tag('b')) { 
     662                if ($tp->get_text =~ /^$target/i) { 
     663                        $found = 1; 
     664                        last; 
     665                } 
     666        } 
     667        return undef if (!$found); 
     668 
     669        while (my $tag = $tp->get_tag()) { 
     670                push (@list, $tp->get_text()) if (($tag->[0] eq 'a') && ($tag->[1]{href} =~ /$target2/i)); 
     671                last if ($tag->[0] eq 'br'); 
     672        } 
     673 
     674        my $found_items = 0; 
     675        foreach my $item (@list) { 
     676                $$v->[$found_items] = $item; 
     677                $found_items++; 
     678        } 
     679} 
     680 
     681###################################################################################################### 
     682# perform a detailed movie lookup given a movie url 
     683# store what we find in our data cache 
     684 
     685sub get_imdb_movie_online 
     686{ 
     687        my ($movie_title, $movie_url) = @_; 
     688        my $html_data = get_url($movie_url,"GET","  downloading online IMDb movie data for '$movie_title'",0); 
     689 
     690        if (!$html_data) { 
     691                $stats{failed_online_imdb_lookup}++; 
     692                &log("failed to fetch imdb movie data from $movie_url"); 
     693                return; 
     694        } 
     695 
     696        $stats{imdb_movie_added_cache_entry}++; 
     697        $data_cache->{movie_lookup}->{$movie_url}->{last_fetched} = time; 
     698        my $tp; 
     699 
     700        # 
     701        # parse title and year 
     702        # 
     703        $tp = HTML::TokeParser->new(\$html_data); 
     704        my $title_token = $tp->get_tag('title'); 
     705        my $title_text = $tp->get_text(); 
     706        if ($title_text =~ /(.*?)\s+\((\d{4}).*?\)/) { 
     707                $data_cache->{movie_lookup}->{$movie_url}->{title} = $1; 
     708                $data_cache->{movie_lookup}->{$movie_url}->{year} = $2; 
     709        } 
     710 
     711        if (!defined $data_cache->{movie_lookup}->{$movie_url}->{title}) { 
     712                $stats{failed_online_imdb_title_parsing}++; 
     713                &log("failed to parse title within imdb movie data from $movie_url"); 
     714                return; 
     715        } 
     716 
     717        # 
     718        # parse cover url 
     719        # 
     720        my $title = $data_cache->{movie_lookup}->{$movie_url}->{title}; 
     721        $tp = HTML::TokeParser->new(\$html_data); 
     722        while (my $img_tag = $tp->get_tag('img')) { 
     723                next if (!$img_tag->[1]{alt}); 
     724                last if ($img_tag->[1]{alt} =~ /^poster not submitted/i); 
     725                if ($img_tag->[1]{alt} =~ /^$title$/i) { 
     726                        $data_cache->{movie_lookup}->{$movie_url}->{cover} = $img_tag->[1]{src}; 
     727                        last; 
     728                } 
     729        } 
     730 
     731        # 
     732        # parse directors 
     733        # 
     734        $tp = HTML::TokeParser->new(\$html_data); 
     735        while (my $tag = $tp->get_tag('b')) { 
     736                last if ($tp->get_text =~ /^directed/i); 
     737        } 
     738        while (my $tag = $tp->get_tag) { 
     739                my $text = $tp->get_text(); 
     740                last if (($text =~ /writing/i) || ($tag->[0] =~ /\/td/i)); 
     741                if ($tag->[0] eq 'a') { 
     742                        my $id = $tag->[1]{href}; 
     743                        next if ($id !~ /^\/name\/nm/); 
     744                        $data_cache->{movie_lookup}->{$movie_url}->{directors}->{$text} = $id; 
     745                } 
     746        } 
     747 
     748        # 
     749        # parse writers 
     750        # 
     751        $tp = HTML::TokeParser->new(\$html_data); 
     752        while (my $tag = $tp->get_tag('b')) { 
     753                last if ($tp->get_text =~ /^writing/i); 
     754        } 
     755        while (my $tag = $tp->get_tag) { 
     756                my $text = $tp->get_text(); 
     757                last if ($tag->[0] =~ /\/table/i); 
     758                if (($tag->[0] eq 'a') && ($text !~ /more/i)) { 
     759                        my $id = $tag->[1]{href}; 
     760                        next if ($id !~ /^\/name\/nm/); 
     761                        $data_cache->{movie_lookup}->{$movie_url}->{writers}->{$text} = $id; 
     762                } 
     763        } 
     764 
     765        # 
     766        # parse cast 
     767        # 
     768        $tp = HTML::TokeParser->new(\$html_data); 
     769        while (my $tag = $tp->get_tag('b')) { 
     770                next unless ((exists $tag->[1]{class}) && ($tag->[1]{class} eq 'blackcatheader')); 
     771                last if ($tp->get_text =~ /^(cast overview|credited cast|(?:series )?complete credited cast)/i); 
     772        } 
     773        while (my $tag = $tp->get_tag('a')) { 
     774                last if ($tag->[1]{href} =~ /fullcredits/i); 
     775                if (($tag->[1]{href}) && ($tag->[1]{href} =~ /(?<!tinyhead)\/name\/nm(\d+?)\//)) { 
     776                        my $person = $tp->get_text; 
     777                        # ignore id:  my $id = $1; 
     778                        my $text = $tp->get_trimmed_text('a', '/tr'); 
     779                        my $role = ""; 
     780                        $role = $1 if ($text =~ /.*?\s+(.*)$/); 
     781                        $data_cache->{movie_lookup}->{$movie_url}->{cast}->{$person} = $role; 
     782                } 
     783        } 
     784 
     785        # 
     786        # parse countries, languages, genres using generic list parser 
     787        # 
     788        &imdb_list_parser($html_data,"country","countries",\$data_cache->{movie_lookup}->{$movie_url}->{countries}); 
     789        &imdb_list_parser($html_data,"language","language",\$data_cache->{movie_lookup}->{$movie_url}->{languages}); 
     790        &imdb_list_parser($html_data,"genre","genre",\$data_cache->{movie_lookup}->{$movie_url}->{genres}); 
     791 
     792        # 
     793        # parse tagline, plot, rating, runtime, aka, trivia, goofs, awards, summary using generic scalar handler 
     794        # 
     795        $data_cache->{movie_lookup}->{$movie_url}->{tagline} = &imdb_scalar_parser($html_data,"tagline","trimmed","b","a"); 
     796        $data_cache->{movie_lookup}->{$movie_url}->{plot} = &imdb_scalar_parser($html_data,"plot","trimmed","b","a"); 
     797        $data_cache->{movie_lookup}->{$movie_url}->{rating} = &imdb_scalar_parser($html_data,"user rating","trimmed","b","a","b"); 
     798        $data_cache->{movie_lookup}->{$movie_url}->{runtime} = &imdb_scalar_parser($html_data,"runtime","trimmed","b","br"); 
     799        $data_cache->{movie_lookup}->{$movie_url}->{aka} = &imdb_scalar_parser($html_data,"(aka|also known as)","trimmed","b","b"); 
     800        $data_cache->{movie_lookup}->{$movie_url}->{trivia} = &imdb_scalar_parser($html_data,"trivia","trimmed","b","a"); 
     801        $data_cache->{movie_lookup}->{$movie_url}->{goofs} = &imdb_scalar_parser($html_data,"goofs","trimmed","b","a"); 
     802        $data_cache->{movie_lookup}->{$movie_url}->{awards} = &imdb_scalar_parser($html_data,"awards","trimmed","b","a"); 
     803        $data_cache->{movie_lookup}->{$movie_url}->{summary} = &imdb_scalar_parser($html_data,"plot summary","","b","a"); 
     804 
     805        # 
     806        # certifications 
     807        # 
     808        $tp = HTML::TokeParser->new(\$html_data); 
     809        while (my $tag = $tp->get_tag('b')) { 
     810                last if ($tp->get_text =~ /^certification/i); 
     811        } 
     812        while (my $tag = $tp->get_tag()) { 
     813                if (($tag->[0] eq "a") && ($tag->[1]{href} =~ /certificates/i)) { 
     814                        my($country, $range) = split(/:/, $tp->get_text); 
     815                        $data_cache->{movie_lookup}->{$movie_url}->{certifications}->{$country} = $range; 
     816                } 
     817                last if ($tag->[0] =~ /\/td/i); 
     818        } 
     819 
     820        # don't yet pick the following up: do we need to? 
     821        #  official_sites 
     822        #  full plot 
     823} 
     824 
     825         
     826 
     827###################################################################################################### 
    558828 
    559829sub encoding_cb( $ ) 
     
    603873        if ($interested eq "") { 
    604874                $stats{didnt_match_categories}++; 
    605                 $writer->write_programme($prog); 
    606                 return; 
     875                goto END; 
     876        } 
     877 
     878        # 
     879        # only lookup if we have a country 
     880        # 
     881        if (!defined $prog->{country}->[0]) { 
     882                $stats{didnt_have_country}++; 
     883                goto END; 
    607884        } 
    608885 
     
    614891        if ((!$t1) || (!$t2)) { 
    615892                $stats{couldnt_parse_time}++; 
    616                 $writer->write_programme($prog); 
    617                 return; 
     893                goto END; 
    618894        } 
    619895        my $prog_duration = (($t2->epoch - $t1->epoch) / 60); 
    620896        if ($prog_duration < $opt->{min_duration}) { 
    621897                $stats{prog_too_short}++; 
    622                 $writer->write_programme($prog); 
    623                 return; 
     898                goto END; 
    624899        } 
    625900        if ($prog_duration > $opt->{max_duration}) { 
    626901                $stats{prog_too_long}++; 
    627                 $writer->write_programme($prog); 
    628                 return; 
    629         } 
    630  
    631         # 
    632         # perform lookups! 
     902                goto END; 
     903        } 
     904 
     905        # 
     906        # perform local lookups! 
    633907        # 
    634908 
     
    645919                                        $found_title = $t; 
    646920                                        $stats{match_1_exact_title_and_year}++; 
    647                                         printf "MATCH: (1) exact match on title+year for \"$t\" ($y)\n" if $opt->{debug}; 
     921                                        &log("MATCH: (1) exact match on title+year for \"$t\" ($y)"); 
    648922                                        goto FOUND; 
    649923                                } 
     
    655929                        $found_title = $t; 
    656930                        $stats{match_2_exact_title}++; 
    657                         printf "MATCH: (2) exact match on title for \"$t\"\n" if $opt->{debug}; 
     931                        &log("MATCH: (2) exact match on title for \"$t\""); 
    658932                        goto FOUND; 
    659933                } 
     
    667941                                                $stats{match_3_alt_title_and_year}++; 
    668942                                                $found_title = $alt_title; 
    669                                                 printf "MATCH: (3) alt match on title+year \"$alt_title\" ($y) for \"$t\"\n" if $opt->{debug}; 
     943                                                &log("MATCH: (3) alt match on title+year \"$alt_title\" ($y) for \"$t\""); 
    670944                                                goto FOUND; 
    671945                                        } 
     
    678952                                $found_title = $alt_title; 
    679953                                $stats{match_4_alt_title}++; 
    680                                 printf "MATCH: (4) alt match on title \"$alt_title\" for \"$t\"\n" if $opt->{debug}; 
     954                                &log("MATCH: (4) alt match on title \"$alt_title\" for \"$t\""); 
    681955                                goto FOUND; 
    682956                        } 
     
    686960        # not found 
    687961        $stats{not_matched}++; 
    688         $writer->write_programme($prog); 
    689         return; 
     962        goto END; 
    690963 
    691964FOUND: 
    692965        $stats{total_matched}++; 
     966 
     967        # 
     968        # find movie url 
     969        # (either via a cached previous search or via IMDb "power search") 
     970        # 
     971 
     972        my $post_fields = "words=".urlify($found_title); 
     973        $post_fields .= "&year=".urlify($prog->{date}) if ((defined $prog->{date}) && ($prog->{date} > 0)); 
     974        $post_fields .= "&language=".urlify($prog->{language}->[0]) if ((defined $prog->{language}) && (defined $prog->{language}->[0])); 
     975        $post_fields .= "&countries=".urlify($prog->{country}->[0][0]) if ((defined $prog->{country}) && (defined $prog->{country}->[0][0])); 
     976        # $post_fields .= "&exact=y"; 
     977        # featuring=<cast/crew> 
     978 
     979        if (defined $data_cache->{movie_id_lookup}->{$post_fields}) { 
     980                $stats{imdb_lookup_used_cache_entry}++; 
     981        } else { 
     982                &search_imdb_online($found_title,$post_fields); 
     983                goto END if (!defined $data_cache->{movie_id_lookup}->{$post_fields}); 
     984        } 
     985 
     986        $data_cache->{movie_id_lookup}->{$post_fields}->{last_lookup} = time; 
     987        $data_cache->{movie_id_lookup}->{$post_fields}->{num_lookups}++; 
     988        my $movie_url = $data_cache->{movie_id_lookup}->{$post_fields}->{url}; 
     989 
     990        # no match or negative cache match - bail out 
     991        goto END if ($movie_url eq "-"); 
     992 
     993        # 
     994        # lookup movie details 
     995        # (either via previously cached entry or via an online IMDb lookup) 
     996        # 
     997 
     998        if (defined $data_cache->{movie_lookup}->{$movie_url}) { 
     999                $stats{imdb_movie_used_cache_entry}++; 
     1000        } else { 
     1001                $stats{imdb_movie_added_cache_entry}++; 
     1002                &get_imdb_movie_online($found_title,$movie_url); 
     1003                goto END if (!defined $data_cache->{movie_lookup}->{$movie_url}); 
     1004        } 
     1005 
     1006 
     1007        # 
     1008        # augment data 
     1009        # 
     1010 
     1011        $data_cache->{movie_lookup}->{$movie_url}->{last_lookup} = time; 
     1012        $data_cache->{movie_lookup}->{$movie_url}->{num_lookups}++; 
     1013 
     1014 
    6931015        # TODO 
    6941016 
     1017#          'cast' => { 
     1018 #                     'John Ales' => 'Kabul', 
     1019  #                    'Charles Durning' => 'The Director', 
     1020   #                   'Nicollette Sheridan' => 'Veronique Ukrinsky, Agent 3.14', 
     1021    #                  'Joyce Brothers' => 'Steele\'s Tag Team Member (as Dr. Joyce Brothers)', 
     1022     #               }, 
     1023      #    'goofs' => 'Continuity: When Dick is in the lab talking about new technologies, the can\'s lid pops up twice.', 
     1024       #   'writers' => { 
     1025#                         'Aaron Seltzer' => '/name/nm0783536/', 
     1026 #                        'Jason Friedberg' => '/name/nm0294997/' 
     1027  #                     }, 
     1028   #       'last_fetched' => 1161146841, 
     1029    #      'rating' => '4.5/10 (6,294 votes)', 
     1030     #     'trivia' => undef, 
     1031      #    'cover' => 'http://ia.imdb.com/media/imdb/01/I/94/64/16m.jpg', 
     1032       #   'summary' => undef, 
     1033#          'awards' => undef, 
     1034 #         'genres' => [ 
     1035  #                      'Comedy', 
     1036   #                     'Action' 
     1037    #                  ], 
     1038     #     'languages' => [ 
     1039      #                     'English' 
     1040       #                  ], 
     1041        #  'countries' => [ 
     1042        #                   'USA' 
     1043       #                  ], 
     1044      #    'directors' => { 
     1045     #                      'Rick Friedberg' => '/name/nm0295007/' 
     1046    #                     }, 
     1047   #       'certifications' => { 
     1048  #                              'UK' => 'PG', 
     1049 #                               'Spain' => 'T' 
     1050#                              }, 
     1051      #    'tagline' => 'All the action. All the women. Half the intelligence.', 
     1052     #     'runtime' => '81 min', 
     1053    #      'plot' => 'General Rancor is threatening to destroy the world with a missile he is hiding at his secret base. But to complete his goal...', 
     1054   #       'title' => 'Spy Hard', 
     1055  #        'aka' => 'Live and Let Spy (USA) (working title)', 
     1056 #         'year' => '1996' 
     1057# 
     1058 
     1059 
     1060 
     1061 
     1062END: 
    6951063        $writer->write_programme($prog); 
    6961064}