[REL] IMDB

If you made a script you can offer it to the others here, or ask help to improve it. You can also report here bugs & problems with existing scripts.
antp
Site Admin
Posts: 9766
Joined: 2002-05-30 10:13:07
Location: Brussels
Contact:

Re: [REL] IMDB

Post by antp »

MrObama2022 wrote: 2025-10-07 22:16:08 IMDB 5.5 beta 5

CHANGELOG
* fix composer, actors, writers and producers

IMDB is switching to a new json format in its javascript page. This fix should work with both new and old format.

Only tested on 4 movies.
Thanks :)
If I find some time for it, I can try to also help, but I always have so many things to do in the few free time available :(
Radagast
Posts: 72
Joined: 2016-04-22 16:07:15

Re: [REL] IMDB

Post by Radagast »

MrObama2022 wrote: 2025-10-07 22:16:08 IMDB 5.5 beta 5

CHANGELOG
* fix composer, actors, writers and producers

IMDB is switching to a new json format in its javascript page. This fix should work with both new and old format.

Only tested on 4 movies.
Great to see ExternalCurlHandler master comeback :D
Mainly I use FilmAffinity to get information, but after use it I update some fields with IMDB because I like more the information get from IMDB on these fields. Producer because IMDB gives names and not company, actors because have actor name and his character name, country because filmaffinity only gets one in the case have more than one, and picture because IMDB usually gives similar quality with less filesize.

I let to show the bugs I see to help improve script. First my settings script are this.

Code: Select all

Infos.OldFormat=0
Options.ActorsImagesLayoutType=3|3|0=Original uncut image (huge!)|1=Image from movie page (fast)|2=Image from actor page (slow)|3=Do nothing
Options.ActorsImagesMode=0|0|0=No actor image needed|1=Import only actors with image
Options.ActorsImagesNumberOfActors=1|1|0=choose ONE actor|1=first FIVE actors|2=first TEN actors
Options.ActorsImagesSelectionMode=0|0|0=ADD images (Standard)|1=REPLACE selected images|2=EXCHANGE actors
Options.ActorsLayout=4|0|0=Only actor names, separated by commas|1=Only actor names, separated by linebreaks|2=Actors names with character names between parenthesis separated by commas|3=Actors names with character names between parenthesis separated by linebreaks|4=Actor names like on IMDB page, with "..." between names and roles, and separated by linebreaks
Options.AllActors=1|2|0=Import only main actors from the top of the page (forcing ActorsLayout=0)|1=Import all possible actors|2=Import 10 first actors (number defined in script parameters, below the options)
Options.AspectRatio=0|1|0=Do not import picture aspect ratio|1=Import picture aspect ratio to video format field|2=Import picture aspect ratio to resolution field
Options.Awards=0|0|0=Do not import awards|1=Import awards to Description field, after the summary|2=Import awards to Comments field, after comments
Options.BatchMode=0|0|0=Normal working mode, prompts user when needed|1=Does not display any window, takes the first movie found|2=Same as 1, but it uses the URL field if available to update movie information|3=Same as 2, but it uses the IMDB field if available to update movie information
Options.CommentType=0|0|0=Top user review|1=10 most useful user reviews|2=No user comment, clear current field contents|3=No user comment, keep current field contents (may cause problem for multiple imports on the same movie if other options append text to the comment field)
Options.ConvertToASCII=0|0|0=Do not change special characters and accents.|1=Replace special characters and accents by basic ASCII characters
Options.DescriptionSelection=0|0|0=Short summary|1=Long summary
Options.EpisodeTitleSearch=0|0|0=Use "Find more" button on results list for next result pages if available (default)|1=Use "Find more" button on results list for episode title search instead
Options.GetTagline=0|1|0=Do not get tagline|1=Put it in Description field, before the summary|2=Put it in the Comment field, before the comments
Options.ImageKind=5|1|0=No image|1=IMDB small image, no image if none available|2=IMDB small image, "No Poster Available" if none available|3=IMDB large image - size defined in parameters below the options (720p by default), no image if none available|4=IMDB original country poster|5=IMDB original country large image - size defined in parameters below the options (720p by default)
Options.MPAA=0|0|0=Do not import MPAA rating|1=Import MPAA rating to MediaType|2=Append MPAA rating and info to Comments
Options.MultipleValuesAudioFormat=3|1|0=Only take first value for Audio Format|1=Take full list, separated by commas|2=Take full list, separated by slashes|3=Do not import Audio Format / Sound Mix
Options.MultipleValuesCategory=1|1|0=Only take first value for Category|1=Take full list, separated by commas|2=Take full list, separated by slashes|3=Do not import Category
Options.MultipleValuesCountry=1|1|0=Only take first value for Country|1=Take full list, separated by commas|2=Take full list, separated by slashes|3=Do not import Country
Options.MultipleValuesLanguages=3|1|0=Only take first value for Languages|1=Take full list, separated by commas|2=Take full list, separated by slashes|3=Do not import Languages
Options.PopularSearches=1|1|0=Do not use the popular searches page directly show full search results|1=Show popular searches first, I'll click on 'Find more' if needed (much faster)
Options.RoundRating=0|0|0=Do not round Rating|1=Round Rating to whole number
Options.Trivia=0|0|0=Do not import trivia|1=Import short trivia to Description field, after the summary|2=Import short trivia to Comments field, after the comments|3=Import full trivia to Description field, after the summary (need RapidAPI key)|4=Import full trivia to Comments field, after the comments (need RapidAPI key)
Options.UserRatings=0|0|0=Import value to ratings field only (default)|1=Import value and number of votes to Media Type field|2=Import value and number of votes to field set in UserRatingsField|3=Import number of votes to field set in UserRatingsField
Options.MediaType=0|0|0=Do not import media type|1=Import media type to media label field|2=Import media type to media type field|3=Import media type to custom field (defined in "MediaTypeField" parameter)
Options.TranslatedTitle=1|2|0=No translated title|1=Default translated title from the main movie page, depending on your location/country, use RapidAPI as backup|2=Try to retrieve from AKA page, using "UserCountry" setting, use RapidAPI as backup|3=Retrieve translated title from reference page, use RapidAPI as backup|4=Retrieve all translated titles using RapidAPI
Parameters.MaxActors=10|10|Max number of actors to retrieve when AllActors is set to 2
Parameters.UserCountry=Spain|United States|Country for certification and AKA title, enter the value as it is on IMDb, e.g. one of these: United States, Canada, Mexico, Brazil, Argentina, Australia, India, Italy, Spain, Portugal, France, Germany, Netherlands, United Kingdom, Ireland, Finland, Norway, Sweden, Switzerland, ...
Parameters.RapidAPIKey=HERE HAVE MY RAPIDAPIKEY||Get a free key to fully enable title translations: https://rapidapi.com (default = Missing)
Parameters.LargePictureHeight=720|720|Height in pixels for requesting the picture when using ImageKind=3 or 5
Parameters.UserRatingsField=||Set field number for userratings, allowed values 3: media; 4: mediaType; 5: source; 6: date; 7: borrower; 10: rating; 11: originalTitle; 12: translatedTitle; 14: director; 15: producer; 16: writer; 18: actors; 19: country; 20: year; 21: length; 22: category; 24: URL; 25: description; 26: comments (default); 28: videoFormat; 29: videoBitRate; 31: audioBitRate; 32: resolution; 33: frameRate; 34: langagues; 35: subtitles; 36: size; 37: disks
1- When get information from movies and TVseries, only get actors name, not the character names

2- When get information from movies and TVseries, don't get any information on comments field

3- When get information from TVseries, don't get any information on producer, writer and composer. This bug is only in TVseries and it's not new, I had it from long time ago but didn't comment before. I try to change UserCountry but it's the same.

4- As I comment I use it to update information after FilmAffinity script and I have two kinds of error when the script is trying to get information from IMDB related to URL Fields.Excluded setting. If the URL it's not excluded there are no error and the script get the IMDB url.
When the URL it's excluded and the URLfield it's empty the script gives the error IOHandler value is not valid
When the URL it's excluded and the URLfield has a FilmAffinity URL because I'm updating fields the script gives the error HTTP/1.1 403 Forbidden
Seems like when the URL Fields.Excluded setting it's active, the script try to access to a URL using the value in URLfield. 403 it's the error that gives FilmAffinity without using ExternalCurlHandler.pas

5- When the script it's running and select a movie after the search, if finally for some reason on movie information window I click on "Skip" or "abort" button or close the window this kind of lines are add to scripts.ini file, and finally it makes so big. Maybe it's a debug information?

Code: Select all

Static.=Picture Error at movie #4883 - The Passion of the ChristPicture Error at movie #4884 - RebelPicture Error at movie #4885 - aka Charlie SheenPicture Error at movie #354 - Lin Shi RongPicture Error at movie #27 - SwiriPicture Error at movie #21 - VersaillesPicture Error at movie #29 - CollateralPicture Error at movie #29 - CollateralPicture Error at movie #29 - CollateralPicture Error at movie #17 - Finding NeverlandPicture Error at movie #17 - Finding NeverlandPicture Error at movie #4886 - CollateralPicture Error at movie #4887 - SuperstorePicture Error at movie #4887 - Superstore
Picture Error at movie #4887 - Superstore
Picture Error at movie #4887 - Superstore
Picture Error at movie #4887 - Superstore
Picture Error at movie #4887 - Superstore

Picture Error at movie #4887 - Superstore
Picture Error at movie #4887 - Superstore
Picture Error at movie #4887 - Superstore
Picture Error at movie #4887 - Superstore
Picture Error at movie #4886 - Collateral
Picture Error at movie #4886 - Collateral
Picture Error at movie #4886 - Collateral
Picture Error at movie #4886 - Collateral
Picture Error at movie #17 - Finding Neverland
Picture Error at movie #17 - Finding Neverland
Picture Error at movie #17 - Finding Neverland
Picture Error at movie #17 - Finding Neverland
Picture Error at movie #17 - Finding Neverland
Picture Error at movie #17 - Finding Neverland
Picture Error at movie #17 - Finding Neverland
Picture Error at movie #17 - Finding Neverland
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #29 - Collateral
Picture Error at movie #21 - Versailles
Picture Error at movie #21 - Versailles
Picture Error at movie #21 - Versailles
Picture Error at movie #21 - Versailles
Picture Error at movie #27 - Swiri
Picture Error at movie #27 - Swiri
Picture Error at movie #27 - Swiri
kalimagdora
Posts: 64
Joined: 2009-03-11 14:36:10
Location: Hungary

Re: [REL] IMDB

Post by kalimagdora »

Please help. IMDB problem - The actors field doesn't show the name of the movie character
only the name of the actor.
thx
Dorohedoro
Posts: 92
Joined: 2016-12-13 00:45:45

Re: [REL] IMDB

Post by Dorohedoro »

Is not extracting the Awards.

Thanks!
Dorohedoro
Posts: 92
Joined: 2016-12-13 00:45:45

Re: [REL] IMDB

Post by Dorohedoro »

Well, after so many attempts with claude and deepseek I managed to get a working code for the awards, the problem? only get the first categories shown in the award page, is unable to extract the hidden rows.

Code: Select all

procedure ImportAwards(fieldName: integer);
var
  PageText, JsonData, Value, FieldValue, ValuesDelimiter: string;
  EventName, CategoryText, RowTitle, PersonNames, CurrentEventName: string;
  TempStr, EventBlock, TempStr2: string;
  i: integer;
  TempText: string; // Temporary variable for UTF-8 fix
begin
  PageText := GetPage(MovieUrl + '/awards');
  ValuesDelimiter := #13#10;

  if (CanSetField(fieldName)) then
  begin
    FieldValue := '';
    CurrentEventName := '';

    if (PageText = '') then
      Exit;

    // Extract JSON from __NEXT_DATA__ script tag
    JsonData := TextBetween(PageText, '<script id="__NEXT_DATA__" type="application/json">', '</script>');

    if (JsonData = '') then
      Exit;

    // Check if there are any awards at all
    if (Pos('"categories"', JsonData) = 0) then
      Exit;

    // Start from categories section
    Value := TextAfter(JsonData, '"categories":[');
    i := 0;

    // Process each event
    while (Pos('"name":"', Value) > 0) and (i < 200) do
    begin
      i := i + 1;

      // Extract event name
      EventName := TextBetween(Value, '"name":"', '"');

      // Skip if empty or too long
      if (EventName = '') or (Length(EventName) < 3) or (Length(EventName) > 150) then
      begin
        Value := TextAfter(Value, '"name":"');
        Continue;
      end;

      // Extract the entire event block to process all its awards
      EventBlock := TextBetween(Value, '"name":"' + EventName + '"', '"name":"');
      if (EventBlock = '') then
        EventBlock := Copy(Value, Pos('"name":"' + EventName + '"', Value), Length(Value));

      // Check if this event has awards
      if (Pos('"items":', EventBlock) > 0) and (Pos('"awardCategoryName"', EventBlock) > 0) then
      begin
        // This is an award event!
        if (EventName <> CurrentEventName) then
        begin
          // Add event header
          if (FieldValue <> '') then
            FieldValue := FieldValue + #13#10 + #13#10;

          // Fix UTF-8 characters inline - use simple string replacement
          TempText := CleanHtml(EventName);
          TempText := StringReplace(TempText, 'é', 'é');
          TempText := StringReplace(TempText, 'è', 'è');
          TempText := StringReplace(TempText, 'ê', 'ê');
          TempText := StringReplace(TempText, 'ë', 'ë');
          TempText := StringReplace(TempText, 'á', 'á');
          TempText := StringReplace(TempText, 'Ã ', 'à');
          TempText := StringReplace(TempText, 'â', 'â');
          TempText := StringReplace(TempText, 'ã', 'ã');
          TempText := StringReplace(TempText, 'ä', 'ä');
          TempText := StringReplace(TempText, 'ñ', 'ñ');
          TempText := StringReplace(TempText, 'ó', 'ó');
          TempText := StringReplace(TempText, 'ò', 'ò');
          TempText := StringReplace(TempText, 'ö', 'ö');
          TempText := StringReplace(TempText, 'ú', 'ú');
          TempText := StringReplace(TempText, 'ü', 'ü');
          TempText := StringReplace(TempText, 'ç', 'ç');
          // Add the missing replacements for the remaining issues
          TempText := StringReplace(TempText, 'í', 'í');
          TempText := StringReplace(TempText, 'æ', 'æ');
          TempText := StringReplace(TempText, 'ø', 'ø');
          TempText := StringReplace(TempText, 'Ã…', 'Å');
          TempText := StringReplace(TempText, 'Ø', 'Ø');
          TempText := StringReplace(TempText, 'Æ', 'Æ');
          TempText := StringReplace(TempText, 'þ', 'þ');
          TempText := StringReplace(TempText, 'ÿ', 'ÿ');
          TempText := StringReplace(TempText, 'ß', 'ß');
          TempText := StringReplace(TempText, '–', '–');
          TempText := StringReplace(TempText, '—', '—');
          TempText := StringReplace(TempText, '•', '•');
          TempText := StringReplace(TempText, 'â„¢', '™');
          TempText := StringReplace(TempText, '…', '…');
          FieldValue := FieldValue + TempText;

          CurrentEventName := EventName;
        end;

        // Process all awards in this event block
        TempStr := EventBlock;
        while (Pos('"awardCategoryName"', TempStr) > 0) do
        begin
          TempStr := TextAfter(TempStr, '"awardCategoryName"');
          CategoryText := TextBetween(TempStr, '"text":"', '"');

          // Get rowTitle (contains Winner/Nominee)
          RowTitle := TextBetween(TempStr, '"rowTitle":"', '"');

          if (CategoryText <> '') and (Length(CategoryText) > 2) and (Length(CategoryText) < 200) then
          begin
            FieldValue := FieldValue + #13#10;

            // Add Winner/Nominee prefix
            if (Pos('Winner', RowTitle) > 0) then
              FieldValue := FieldValue + 'Winner: '
            else if (Pos('Nominee', RowTitle) > 0) then
              FieldValue := FieldValue + 'Nominee: ';

            // Fix UTF-8 characters for CategoryText
            TempText := CleanHtml(CategoryText);
            TempText := StringReplace(TempText, 'é', 'é');
            TempText := StringReplace(TempText, 'è', 'è');
            TempText := StringReplace(TempText, 'ê', 'ê');
            TempText := StringReplace(TempText, 'ë', 'ë');
            TempText := StringReplace(TempText, 'á', 'á');
            TempText := StringReplace(TempText, 'Ã ', 'à');
            TempText := StringReplace(TempText, 'â', 'â');
            TempText := StringReplace(TempText, 'ã', 'ã');
            TempText := StringReplace(TempText, 'ä', 'ä');
            TempText := StringReplace(TempText, 'ñ', 'ñ');
            TempText := StringReplace(TempText, 'ó', 'ó');
            TempText := StringReplace(TempText, 'ò', 'ò');
            TempText := StringReplace(TempText, 'ö', 'ö');
            TempText := StringReplace(TempText, 'ú', 'ú');
            TempText := StringReplace(TempText, 'ü', 'ü');
            TempText := StringReplace(TempText, 'ç', 'ç');
            // Add the missing replacements for the remaining issues
            TempText := StringReplace(TempText, 'í', 'í');
            TempText := StringReplace(TempText, 'æ', 'æ');
            TempText := StringReplace(TempText, 'ø', 'ø');
            TempText := StringReplace(TempText, 'Ã…', 'Å');
            TempText := StringReplace(TempText, 'Ø', 'Ø');
            TempText := StringReplace(TempText, 'Æ', 'Æ');
            TempText := StringReplace(TempText, 'þ', 'þ');
            TempText := StringReplace(TempText, 'ÿ', 'ÿ');
            TempText := StringReplace(TempText, 'ß', 'ß');
            TempText := StringReplace(TempText, '–', '–');
            TempText := StringReplace(TempText, '—', '—');
            TempText := StringReplace(TempText, '•', '•');
            TempText := StringReplace(TempText, 'â„¢', '™');
            TempText := StringReplace(TempText, '…', '…');
            FieldValue := FieldValue + TempText;

            // Extract person names
            PersonNames := '';
            if (Pos('"subListContent"', TempStr) > 0) then
            begin
              TempStr2 := TextBetween(Copy(TempStr, 1, 2000), '"subListContent":[', ']');

              if (TempStr2 <> '') then
              begin
                // Extract all names from the array
                while (Pos('"text":"', TempStr2) > 0) do
                begin
                  if (PersonNames <> '') then
                    PersonNames := PersonNames + ', ';

                  // Fix UTF-8 characters for person names
                  TempText := CleanHtml(TextBetween(TempStr2, '"text":"', '"'));
                  TempText := StringReplace(TempText, 'é', 'é');
                  TempText := StringReplace(TempText, 'è', 'è');
                  TempText := StringReplace(TempText, 'ê', 'ê');
                  TempText := StringReplace(TempText, 'ë', 'ë');
                  TempText := StringReplace(TempText, 'á', 'á');
                  TempText := StringReplace(TempText, 'Ã ', 'à');
                  TempText := StringReplace(TempText, 'â', 'â');
                  TempText := StringReplace(TempText, 'ã', 'ã');
                  TempText := StringReplace(TempText, 'ä', 'ä');
                  TempText := StringReplace(TempText, 'ñ', 'ñ');
                  TempText := StringReplace(TempText, 'ó', 'ó');
                  TempText := StringReplace(TempText, 'ò', 'ò');
                  TempText := StringReplace(TempText, 'ö', 'ö');
                  TempText := StringReplace(TempText, 'ú', 'ú');
                  TempText := StringReplace(TempText, 'ü', 'ü');
                  TempText := StringReplace(TempText, 'ç', 'ç');
                  // Add the missing replacements for the remaining issues
                  TempText := StringReplace(TempText, 'í', 'í');
                  TempText := StringReplace(TempText, 'æ', 'æ');
                  TempText := StringReplace(TempText, 'ø', 'ø');
                  TempText := StringReplace(TempText, 'Ã…', 'Å');
                  TempText := StringReplace(TempText, 'Ø', 'Ø');
                  TempText := StringReplace(TempText, 'Æ', 'Æ');
                  TempText := StringReplace(TempText, 'þ', 'þ');
                  TempText := StringReplace(TempText, 'ÿ', 'ÿ');
                  TempText := StringReplace(TempText, 'ß', 'ß');
                  TempText := StringReplace(TempText, '–', '–');
                  TempText := StringReplace(TempText, '—', '—');
                  TempText := StringReplace(TempText, '•', '•');
                  TempText := StringReplace(TempText, 'â„¢', '™');
                  TempText := StringReplace(TempText, '…', '…');
                  PersonNames := PersonNames + TempText;

                  TempStr2 := TextAfter(TempStr2, '"text":"');
                end;
              end;
            end
            else if (Pos('"caption":"', TempStr) > 0) then
            begin
              PersonNames := TextBetween(Copy(TempStr, 1, 800), '"caption":"', '"');
              if (PersonNames <> '') and (Length(PersonNames) < 100) then
              begin
                // Fix UTF-8 characters for person names
                TempText := CleanHtml(PersonNames);
                TempText := StringReplace(TempText, 'é', 'é');
                TempText := StringReplace(TempText, 'è', 'è');
                TempText := StringReplace(TempText, 'ê', 'ê');
                TempText := StringReplace(TempText, 'ë', 'ë');
                TempText := StringReplace(TempText, 'á', 'á');
                TempText := StringReplace(TempText, 'Ã ', 'à');
                TempText := StringReplace(TempText, 'â', 'â');
                TempText := StringReplace(TempText, 'ã', 'ã');
                TempText := StringReplace(TempText, 'ä', 'ä');
                TempText := StringReplace(TempText, 'ñ', 'ñ');
                TempText := StringReplace(TempText, 'ó', 'ó');
                TempText := StringReplace(TempText, 'ò', 'ò');
                TempText := StringReplace(TempText, 'ö', 'ö');
                TempText := StringReplace(TempText, 'ú', 'ú');
                TempText := StringReplace(TempText, 'ü', 'ü');
                TempText := StringReplace(TempText, 'ç', 'ç');
                // Add the missing replacements for the remaining issues
                TempText := StringReplace(TempText, 'í', 'í');
                TempText := StringReplace(TempText, 'æ', 'æ');
                TempText := StringReplace(TempText, 'ø', 'ø');
                TempText := StringReplace(TempText, 'Ã…', 'Å');
                TempText := StringReplace(TempText, 'Ø', 'Ø');
                TempText := StringReplace(TempText, 'Æ', 'Æ');
                TempText := StringReplace(TempText, 'þ', 'þ');
                TempText := StringReplace(TempText, 'ÿ', 'ÿ');
                TempText := StringReplace(TempText, 'ß', 'ß');
                TempText := StringReplace(TempText, '–', '–');
                TempText := StringReplace(TempText, '—', '—');
                TempText := StringReplace(TempText, '•', '•');
                TempText := StringReplace(TempText, 'â„¢', '™');
                TempText := StringReplace(TempText, '…', '…');
                PersonNames := TempText;
              end
              else
                PersonNames := '';
            end;

            if (PersonNames <> '') then
              FieldValue := FieldValue + ' - ' + PersonNames;
          end;
        end;
      end;

      // Move to next event
      if (EventBlock <> '') and (Length(EventBlock) < Length(Value)) then
        Value := TextAfter(Value, EventBlock)
      else
        Value := TextAfter(Value, '"name":"' + EventName + '"');
    end;

    // Save the field if we found awards
    if (FieldValue <> '') then
    begin
      if (GetField(fieldName) <> '') then
        SetField(fieldName, GetField(fieldName) + #13#10 + #13#10 + 'AWARDS:' + #13#10 + FieldValue)
      else
        SetField(fieldName, 'AWARDS:' + #13#10 + FieldValue);
    end;
  end;
end;
antp
Site Admin
Posts: 9766
Joined: 2002-05-30 10:13:07
Location: Brussels
Contact:

Re: [REL] IMDB

Post by antp »

Hidden rows can't be extracted indeed, we had the same issue with the translated titles.
These rows are not really hidden, they are not in the page, and are fetch via javascript from the server when you click the link to show more.
It is difficult (impossible? if they protected well their service) to retrieve these from AMC then.

Thanks for the fix, I'll try to take a look to integrate that, as well as the characters names issue, if MrObama2022 does not have time for it (I don't have much either).
For all the StringReplace to translate UTF8 entities, there is a "UTF8Decode" function in AMC for that, but I suppose that the AI didn't know it :D
You can also use ConvertToASCII for example that is already used in other places of the IMDb script.
Dorohedoro
Posts: 92
Joined: 2016-12-13 00:45:45

Re: [REL] IMDB

Post by Dorohedoro »

antp wrote: 2025-10-21 07:07:22 Hidden rows can't be extracted indeed, we had the same issue with the translated titles.
These rows are not really hidden, they are not in the page, and are fetch via javascript from the server when you click the link to show more.
It is difficult (impossible? if they protected well their service) to retrieve these from AMC then.

Thanks for the fix, I'll try to take a look to integrate that, as well as the characters names issue, if MrObama2022 does not have time for it (I don't have much either).
For all the StringReplace to translate UTF8 entities, there is a "UTF8Decode" function in AMC for that, but I suppose that the AI didn't know it :D
You can also use ConvertToASCII for example that is already used in other places of the IMDb script.

Thank you so much!

I better hurry with my project, those guys at imdb are making the extracting process much more difficult lately. Maybe the best approach is to use the API? (but I don't know how to integrate the vote count and the awards).
antp
Site Admin
Posts: 9766
Joined: 2002-05-30 10:13:07
Location: Brussels
Contact:

Re: [REL] IMDB

Post by antp »

If IMDb is doing many changes it may be better to use the API version until they finish their change, to do all at once when they are done.
But if some info can't be retrieved at all via the site, but others are not available via the API, maybe for the future we should combine both in one script then.
Dorohedoro
Posts: 92
Joined: 2016-12-13 00:45:45

Re: [REL] IMDB

Post by Dorohedoro »

I think for now that should be the best approach, Munkey API is working flawlessly, but is missing a lot of features. A mix of the two methods should be ideal.
LeMoi
Posts: 180
Joined: 2006-04-09 11:26:43

Re: [REL] IMDB

Post by LeMoi »

Is it me or is the script no longer working? Lots of 404 errors and no field retrieved
Dorohedoro
Posts: 92
Joined: 2016-12-13 00:45:45

Re: [REL] IMDB

Post by Dorohedoro »

Yeah, the script is dead right now, along with the actor images, that one is also dead, is extracting only one actor.
antp
Site Admin
Posts: 9766
Joined: 2002-05-30 10:13:07
Location: Brussels
Contact:

Re: [REL] IMDB

Post by antp »

I suppose they continued to make changes to their pages.
I'll try to take a look, in the meantime the IMDb API script can help to get data, even if it does not give as much info.
humungus
Posts: 38
Joined: 2010-03-23 00:29:01

Re: [REL] IMDB

Post by humungus »

The script worked for me just now when I used the movie URL instead of its title for search. That suggests it's the transition between search results and the movie reference page that has changed on IMDb.

I have switched to API script for the most part, but I do miss the following in it:
- the option to separate country/category values with / rather than a comma;
- Trivia
- actor tags ('uncredited' etc.);
- AKA titles (I forget details, but as I remember I could never get it to return the same/as many AKAs as RapidAPI part of this script).
Dorohedoro
Posts: 92
Joined: 2016-12-13 00:45:45

Re: [REL] IMDB

Post by Dorohedoro »

Is not working for me, 404, 401, 400 errors and a warning about the api address.

I think the best is just migrate to the api method because imdb is changing things every week to make us crazy (I think they are doing this to avoid data scrapping), but as humungus said, the Munkey API is missing for me:

1.- The amount of votes a rating has.

2.- The Video format (aspect ratio)

3.- The awards


I'm triyng to make a working version with Claude/Deepseek but it's hard.
antp
Site Admin
Posts: 9766
Joined: 2002-05-30 10:13:07
Location: Brussels
Contact:

Re: [REL] IMDB

Post by antp »

Dorohedoro wrote: 2025-10-25 08:29:22 I think the best is just migrate to the api method because imdb is changing things every week to make us crazy (I think they are doing this to avoid data scrapping)
I don't think they change on purpose for that. It seems they are modernizing their system (hence using more json than just generate plain HTML), and probably do by wave instead of one big change.
Dorohedoro wrote: 2025-10-25 08:29:22 I'm triyng to make a working version with Claude/Deepseek but it's hard.
It is indeed not easy to use AI when it is difficult to describe exactly the expected result (I often use Claude for specific .NET/C# functions, for that it is quite helpful).
Also the fact that AMC is using a not very common script engine (Innerfuse Pascal Script) with some custom functions does not help I suppose.
Though that I could already use it for one specific function for one of the other scripts, by describing specifically the limitations of the script engine.
Dorohedoro
Posts: 92
Joined: 2016-12-13 00:45:45

Re: [REL] IMDB

Post by Dorohedoro »

antp wrote: 2025-10-25 09:28:24
It is indeed not easy to use AI when it is difficult to describe exactly the expected result (I often use Claude for specific .NET/C# functions, for that it is quite helpful).
Also the fact that AMC is using a not very common script engine (Innerfuse Pascal Script) with some custom functions does not help I suppose.
Though that I could already use it for one specific function for one of the other scripts, by describing specifically the limitations of the script engine.
I'm working with the Munkey API and Deepseek and already got the vote count and aspect ratio. Now I'm working to get the awards, also the country because for some reason is not working for me.

It seems that the AI is working better with the API version.


When I finish this, should I share the results in here or in the Munkey thread?


EDIT: after some deepseek work awards are working in the API version (taken form the code the AI made last week), now I'm working with the country field.

EDIT 2: country extraction working
Dorohedoro
Posts: 92
Joined: 2016-12-13 00:45:45

Re: [REL] IMDB

Post by Dorohedoro »

Ok, this is the Munkeys API script with these additional features:

1.- country (all of them if a movie has multiple)

2.- Almost all the awards at the end of the description field (the most relevant categories)

3.- The amount of user votes of the rating in the subtitle field

4.- All the aspect ratio formats for all the movies

I'm sharing the code for you experts to be able to have at least a foundation to work on, this additional features were made 100% with deepseek:

Code: Select all

program IMDB_ALT;

uses
  StringUtils1;

const
  Query_Search_by_Title          = '{ "query": "query { advancedTitleSearch( first: replace_max_titles constraints: { titleTextConstraint:{searchTerm:\"replace_search_string\"} explicitContentConstraint: { explicitContentFilter: INCLUDE_ADULT } } ) { edges { node { title { titleText { text } releaseYear { year } titleType { text } id principalCredits { category { id } credits { name { nameText { text } } } } } } } } }" }';
  Query_Search_by_Title_and_Year = '{ "query": "query { advancedTitleSearch( first: replace_max_titles constraints: { titleTextConstraint:{searchTerm:\"replace_search_string\"} explicitContentConstraint: { explicitContentFilter: INCLUDE_ADULT } releaseDateConstraint: {releaseDateRange: {start:\"replace_start_year-01-01\",end:\"replace_end_year-12-31\"}} } ) { edges { node { title { titleText { text } releaseYear { year } titleType { text } id principalCredits { category { id } credits { name { nameText { text } } } } } } } } }" }';
  Query_ID = '{ "query": "query { title(id: \"replace_imdbid\") { id titleText { text isOriginalTitle } originalTitleText { text } releaseYear { year } titleGenres { genres { genre { text } } } runtime { seconds } countriesOfOrigin { countries { id } } certificate { rating } titleType { id } taglines(first: 1) { edges { node { text } } } plot { plotText { plainText } } ratingsSummary { aggregateRating voteCount } primaryImage { url } runtimes(first: 25) { edges { node { displayableProperty { value { plainText } } seconds attributes { text } country { text } } } } credits(first: 250, filter: { categories: [\"director\", \"producer\", \"writer\", \"composer\"] }) { edges { node { category { categoryId } name { nameText { text } } } } } } }" }';
  Query_userRating = '{ "query": "query { title(id: \"replace_imdbid\") { userRating(userId: \"replace_userid\") { value } } }" }';
  Query_actors = '{ "query": "query { title(id: \"replace_imdbid\") { credits(first: replace_max_actors, filter: { categories: [\"cast\"] }) { edges { node { name { nameText { text } } } } } } }" }';
  Query_actors_paired = '{ "query": "query { title(id: \"replace_imdbid\") { credits(first: replace_max_actors, filter: { categories: [\"cast\"] }) { edges { node { name { nameText { text } } ... on Cast { characters { name } } } } } } }" }';

var
  MovieName: string;


//==============================================================================
//    Helping functions
//==============================================================================

function ExpandCountryCode(Code: string): string;
begin
  // Convert ISO country codes to full country names
  if Code = 'US' then Result := 'United States'
  else if Code = 'USA' then Result := 'United States'
  else if Code = 'UK' then Result := 'United Kingdom'
  else if Code = 'GB' then Result := 'United Kingdom'
  else if Code = 'FR' then Result := 'France'
  else if Code = 'DE' then Result := 'Germany'
  else if Code = 'IT' then Result := 'Italy'
  else if Code = 'ES' then Result := 'Spain'
  else if Code = 'CA' then Result := 'Canada'
  else if Code = 'AU' then Result := 'Australia'
  else if Code = 'JP' then Result := 'Japan'
  else if Code = 'KR' then Result := 'South Korea'
  else if Code = 'CN' then Result := 'China'
  else if Code = 'IN' then Result := 'India'
  else if Code = 'RU' then Result := 'Russia'
  else if Code = 'BR' then Result := 'Brazil'
  else if Code = 'MX' then Result := 'Mexico'
  else if Code = 'SE' then Result := 'Sweden'
  else if Code = 'NO' then Result := 'Norway'
  else if Code = 'DK' then Result := 'Denmark'
  else if Code = 'FI' then Result := 'Finland'
  else if Code = 'NL' then Result := 'Netherlands'
  else if Code = 'BE' then Result := 'Belgium'
  else if Code = 'CH' then Result := 'Switzerland'
  else if Code = 'AT' then Result := 'Austria'
  else if Code = 'PL' then Result := 'Poland'
  else if Code = 'CZ' then Result := 'Czech Republic'
  else if Code = 'HU' then Result := 'Hungary'
  else if Code = 'PT' then Result := 'Portugal'
  else if Code = 'GR' then Result := 'Greece'
  else if Code = 'TR' then Result := 'Turkey'
  else if Code = 'AR' then Result := 'Argentina'
  else if Code = 'CL' then Result := 'Chile'
  else if Code = 'CO' then Result := 'Colombia'
  else if Code = 'PE' then Result := 'Peru'
  else if Code = 'ZA' then Result := 'South Africa'
  else if Code = 'EG' then Result := 'Egypt'
  else if Code = 'NG' then Result := 'Nigeria'
  else if Code = 'KE' then Result := 'Kenya'
  else if Code = 'IL' then Result := 'Israel'
  else if Code = 'AE' then Result := 'United Arab Emirates'
  else if Code = 'SA' then Result := 'Saudi Arabia'
  else if Code = 'SG' then Result := 'Singapore'
  else if Code = 'MY' then Result := 'Malaysia'
  else if Code = 'TH' then Result := 'Thailand'
  else if Code = 'VN' then Result := 'Vietnam'
  else if Code = 'PH' then Result := 'Philippines'
  else if Code = 'ID' then Result := 'Indonesia'
  else
    Result := Code; // Return original code if unknown
end;


function GetJsonBlock(Json, SearchStr: string): string;
var
  SearchPos, BlockStart, NextOpen, NextClose, CurPos, Level: Integer;
begin
  Result := '';

  SearchPos := Pos(SearchStr, Json);
  if SearchPos = 0 then
    Exit;

  BlockStart := Pos('{', Copy(Json, SearchPos, Length(Json) - SearchPos + 1));
  if BlockStart = 0 then
    Exit;

  BlockStart := BlockStart + SearchPos - 1;

  CurPos := BlockStart + 1;
  Level := 1;

  while Level > 0 do
  begin
    NextOpen := Pos('{', Copy(Json, CurPos, Length(Json) - CurPos + 1));
    if NextOpen > 0 then
      NextOpen := NextOpen + CurPos - 1;

    NextClose := Pos('}', Copy(Json, CurPos, Length(Json) - CurPos + 1));
    if NextClose > 0 then
      NextClose := NextClose + CurPos - 1;

    if (NextOpen > 0) and ((NextOpen < NextClose) or (NextClose = 0)) then
    begin
      Level := Level + 1;
      CurPos := NextOpen + 1;
    end
    else if NextClose > 0 then
    begin
      Level := Level - 1;
      CurPos := NextClose + 1;
    end
    else
      Exit;
  end;
  Result := Copy(Json, BlockStart, CurPos - BlockStart);
end;


function ConvertToASCII(AText: string): string;
begin
  Result := UTF8Decode(AText);
  if Result = '' then
    Result := AText; // in case of a UTF8 decoding error
  if GetOption('ConvertToASCII') = 1 then
    Result := Cp1252ToASCII(Result);
end;


//==============================================================================
//    Extract country information (reliable approach)
//==============================================================================

function ExtractCountries(IMDbID: string): string;
var
  PageText, CountriesBlock, Country, TempStr: string;
  i, j, k: Integer;
  CountriesList: TStringList;
begin
  Result := '';

  // Get the main page
  PageText := GetPage('https://www.imdb.com/title/' + IMDbID + '/');

  if (PageText = '') then
    Exit;

  PageText := ConvertToASCII(PageText);

  CountriesList := TStringList.Create;

  // Method 1: Look for country in the details section
  i := Pos('data-testid="title-details-country"', PageText);
  if i > 0 then
  begin
    // Extract the country section
    TempStr := Copy(PageText, i, 2000);
    j := Pos('</ul>', TempStr);
    if j > 0 then
      TempStr := Copy(TempStr, 1, j + 4);

    // Extract all country links
    while Pos('href="/search/title?country_of_origin=', TempStr) > 0 do
    begin
      TempStr := TextAfter(TempStr, 'href="/search/title?country_of_origin=');
      Country := TextBetween(TempStr, '>', '<');
      if Country <> '' then
      begin
        Country := Trim(Country);
        if CountriesList.IndexOf(Country) = -1 then
          CountriesList.Add(Country);
      end;
    end;
  end;

  // Method 2: Look for country in JSON-LD data
  if CountriesList.Count = 0 then
  begin
    i := Pos('"countryOfOrigin"', PageText);
    if i > 0 then
    begin
      TempStr := Copy(PageText, i, 500);
      // Extract country name from JSON-LD
      j := Pos('"name":"', TempStr);
      if j > 0 then
      begin
        Country := TextBetween(Copy(TempStr, j + 8, Length(TempStr) - j - 7), '', '"');
        if Country <> '' then
          CountriesList.Add(Country);
      end;
    end;
  end;

  // Method 3: Look for countries in the API response pattern
  if CountriesList.Count = 0 then
  begin
    i := Pos('"countriesOfOrigin"', PageText);
    if i > 0 then
    begin
      TempStr := TextBetween(PageText, '"countriesOfOrigin":[', ']');
      if TempStr <> '' then
      begin
        // Extract country IDs and convert to names
        while Pos('"id":"', TempStr) > 0 do
        begin
          Country := TextBetween(TempStr, '"id":"', '"');
          if Country <> '' then
          begin
            Country := ExpandCountryCode(Country);
            if (Country <> '') and (CountriesList.IndexOf(Country) = -1 then
              CountriesList.Add(Country);
          end;
          TempStr := TextAfter(TempStr, '"id":"');
        end;
      end;
    end;
  end;

  // Format the result
  if CountriesList.Count > 0 then
  begin
    if GetOption('MultipleValuesCountry') = 0 then
      Result := CountriesList[0]
    else
    begin
      for i := 0 to CountriesList.Count - 1 do
      begin
        if Result <> '' then
          Result := Result + ', ';
        Result := Result + CountriesList[i];
      end;
    end;
  end;

  CountriesList.Free;
end;


//==============================================================================
//    Decode Unicode escape sequences
//==============================================================================

function DecodeUnicodeEscapes(Text: string): string;
begin
  Result := Text;
  // Replace common Unicode escape sequences
  Result := StringReplace(Result, '\u0026', '&');
  Result := StringReplace(Result, '\u0027', '''');
  Result := StringReplace(Result, '\u0022', '"');
  Result := StringReplace(Result, '\u003C', '<');
  Result := StringReplace(Result, '\u003E', '>');
  // Add more if needed...
end;

//==============================================================================
//    Extract awards information (adapted from working script)
//==============================================================================

function ExtractAwards(IMDbID: string): string;
var
  PageText, JsonData, Value, FieldValue, EventName, CategoryText, RowTitle, PersonNames, CurrentEventName: string;
  TempStr, EventBlock, TempStr2, TempText: string;
  i: integer;
begin
  Result := '';
  PageText := GetPage('https://www.imdb.com/title/' + IMDbID + '/awards');

  if (PageText = '') then
    Exit;

  // Extract JSON from __NEXT_DATA__ script tag
  JsonData := TextBetween(PageText, '<script id="__NEXT_DATA__" type="application/json">', '</script>');

  if (JsonData = '') then
    Exit;

  // Check if there are any awards at all
  if (Pos('"categories"', JsonData) = 0) then
    Exit;

  // Start from categories section
  Value := TextAfter(JsonData, '"categories":[');
  i := 0;
  CurrentEventName := '';

  // Process each event
  while (Pos('"name":"', Value) > 0) and (i < 200) do
  begin
    i := i + 1;

    // Extract event name
    EventName := TextBetween(Value, '"name":"', '"');

    // Skip if empty or too long
    if (EventName = '') or (Length(EventName) < 3) or (Length(EventName) > 150) then
    begin
      Value := TextAfter(Value, '"name":"');
      Continue;
    end;

    // Extract the entire event block to process all its awards
    EventBlock := TextBetween(Value, '"name":"' + EventName + '"', '"name":"');
    if (EventBlock = '') then
      EventBlock := Copy(Value, Pos('"name":"' + EventName + '"', Value), Length(Value));

    // Check if this event has awards
    if (Pos('"items":', EventBlock) > 0) and (Pos('"awardCategoryName"', EventBlock) > 0) then
    begin
      // This is an award event!
      if (EventName <> CurrentEventName) then
      begin
        // Add event header
        if (Result <> '') then
          Result := Result + #13#10 + #13#10;

        // Fix UTF-8 characters inline and decode Unicode escapes
        TempText := EventName;
        TempText := StringReplace(TempText, '\u0026', '&');
        TempText := StringReplace(TempText, 'é', 'é');
        TempText := StringReplace(TempText, 'è', 'è');
        TempText := StringReplace(TempText, 'ê', 'ê');
        TempText := StringReplace(TempText, 'ë', 'ë');
        TempText := StringReplace(TempText, 'á', 'á');
        TempText := StringReplace(TempText, 'Ã ', 'à');
        TempText := StringReplace(TempText, 'â', 'â');
        TempText := StringReplace(TempText, 'ã', 'ã');
        TempText := StringReplace(TempText, 'ä', 'ä');
        TempText := StringReplace(TempText, 'ñ', 'ñ');
        TempText := StringReplace(TempText, 'ó', 'ó');
        TempText := StringReplace(TempText, 'ò', 'ò');
        TempText := StringReplace(TempText, 'ö', 'ö');
        TempText := StringReplace(TempText, 'ú', 'ú');
        TempText := StringReplace(TempText, 'ü', 'ü');
        TempText := StringReplace(TempText, 'ç', 'ç');
        TempText := StringReplace(TempText, 'í', 'í');
        TempText := StringReplace(TempText, 'æ', 'æ');
        TempText := StringReplace(TempText, 'ø', 'ø');
        TempText := StringReplace(TempText, 'Ã…', 'Å');
        TempText := StringReplace(TempText, 'Ø', 'Ø');
        TempText := StringReplace(TempText, 'Æ', 'Æ');
        TempText := StringReplace(TempText, 'þ', 'þ');
        TempText := StringReplace(TempText, 'ÿ', 'ÿ');
        TempText := StringReplace(TempText, 'ß', 'ß');
        TempText := StringReplace(TempText, 'â€"', '–');
        TempText := StringReplace(TempText, 'â€"', '—');
        TempText := StringReplace(TempText, '•', '•');
        TempText := StringReplace(TempText, 'â„¢', '™');
        TempText := StringReplace(TempText, '…', '…');
        Result := Result + TempText;

        CurrentEventName := EventName;
      end;

      // Process all awards in this event block
      TempStr := EventBlock;
      while (Pos('"awardCategoryName"', TempStr) > 0) do
      begin
        TempStr := TextAfter(TempStr, '"awardCategoryName"');
        CategoryText := TextBetween(TempStr, '"text":"', '"');

        // Get rowTitle (contains Winner/Nominee)
        RowTitle := TextBetween(TempStr, '"rowTitle":"', '"');

        if (CategoryText <> '') and (Length(CategoryText) > 2) and (Length(CategoryText) < 200) then
        begin
          Result := Result + #13#10;

          // Add Winner/Nominee prefix
          if (Pos('Winner', RowTitle) > 0) then
            Result := Result + 'Winner: '
          else if (Pos('Nominee', RowTitle) > 0) then
            Result := Result + 'Nominee: ';

          // Fix UTF-8 characters for CategoryText and decode Unicode escapes
          TempText := CategoryText;
          TempText := StringReplace(TempText, '\u0026', '&');
          TempText := StringReplace(TempText, 'é', 'é');
          TempText := StringReplace(TempText, 'è', 'è');
          TempText := StringReplace(TempText, 'ê', 'ê');
          TempText := StringReplace(TempText, 'ë', 'ë');
          TempText := StringReplace(TempText, 'á', 'á');
          TempText := StringReplace(TempText, 'Ã ', 'à');
          TempText := StringReplace(TempText, 'â', 'â');
          TempText := StringReplace(TempText, 'ã', 'ã');
          TempText := StringReplace(TempText, 'ä', 'ä');
          TempText := StringReplace(TempText, 'ñ', 'ñ');
          TempText := StringReplace(TempText, 'ó', 'ó');
          TempText := StringReplace(TempText, 'ò', 'ò');
          TempText := StringReplace(TempText, 'ö', 'ö');
          TempText := StringReplace(TempText, 'ú', 'ú');
          TempText := StringReplace(TempText, 'ü', 'ü');
          TempText := StringReplace(TempText, 'ç', 'ç');
          TempText := StringReplace(TempText, 'í', 'í');
          TempText := StringReplace(TempText, 'æ', 'æ');
          TempText := StringReplace(TempText, 'ø', 'ø');
          TempText := StringReplace(TempText, 'Ã…', 'Å');
          TempText := StringReplace(TempText, 'Ø', 'Ø');
          TempText := StringReplace(TempText, 'Æ', 'Æ');
          TempText := StringReplace(TempText, 'þ', 'þ');
          TempText := StringReplace(TempText, 'ÿ', 'ÿ');
          TempText := StringReplace(TempText, 'ß', 'ß');
          TempText := StringReplace(TempText, 'â€"', '–');
          TempText := StringReplace(TempText, 'â€"', '—');
          TempText := StringReplace(TempText, '•', '•');
          TempText := StringReplace(TempText, 'â„¢', '™');
          TempText := StringReplace(TempText, '…', '…');
          Result := Result + TempText;

          // Extract person names
          PersonNames := '';
          if (Pos('"subListContent"', TempStr) > 0) then
          begin
            TempStr2 := TextBetween(Copy(TempStr, 1, 2000), '"subListContent":[', ']');

            if (TempStr2 <> '') then
            begin
              // Extract all names from the array
              while (Pos('"text":"', TempStr2) > 0) do
              begin
                if (PersonNames <> '') then
                  PersonNames := PersonNames + ', ';

                // Fix UTF-8 characters for person names and decode Unicode escapes
                TempText := TextBetween(TempStr2, '"text":"', '"');
                TempText := StringReplace(TempText, '\u0026', '&');
                TempText := StringReplace(TempText, 'é', 'é');
                TempText := StringReplace(TempText, 'è', 'è');
                TempText := StringReplace(TempText, 'ê', 'ê');
                TempText := StringReplace(TempText, 'ë', 'ë');
                TempText := StringReplace(TempText, 'á', 'á');
                TempText := StringReplace(TempText, 'Ã ', 'à');
                TempText := StringReplace(TempText, 'â', 'â');
                TempText := StringReplace(TempText, 'ã', 'ã');
                TempText := StringReplace(TempText, 'ä', 'ä');
                TempText := StringReplace(TempText, 'ñ', 'ñ');
                TempText := StringReplace(TempText, 'ó', 'ó');
                TempText := StringReplace(TempText, 'ò', 'ò');
                TempText := StringReplace(TempText, 'ö', 'ö');
                TempText := StringReplace(TempText, 'ú', 'ú');
                TempText := StringReplace(TempText, 'ü', 'ü');
                TempText := StringReplace(TempText, 'ç', 'ç');
                TempText := StringReplace(TempText, 'í', 'í');
                TempText := StringReplace(TempText, 'æ', 'æ');
                TempText := StringReplace(TempText, 'ø', 'ø');
                TempText := StringReplace(TempText, 'Ã…', 'Å');
                TempText := StringReplace(TempText, 'Ø', 'Ø');
                TempText := StringReplace(TempText, 'Æ', 'Æ');
                TempText := StringReplace(TempText, 'þ', 'þ');
                TempText := StringReplace(TempText, 'ÿ', 'ÿ');
                TempText := StringReplace(TempText, 'ß', 'ß');
                TempText := StringReplace(TempText, 'â€"', '–');
                TempText := StringReplace(TempText, 'â€"', '—');
                TempText := StringReplace(TempText, '•', '•');
                TempText := StringReplace(TempText, 'â„¢', '™');
                TempText := StringReplace(TempText, '…', '…');
                PersonNames := PersonNames + TempText;

                TempStr2 := TextAfter(TempStr2, '"text":"');
              end;
            end;
          end
          else if (Pos('"caption":"', TempStr) > 0) then
          begin
            PersonNames := TextBetween(Copy(TempStr, 1, 800), '"caption":"', '"');
            if (PersonNames <> '') and (Length(PersonNames) < 100) then
            begin
              // Fix UTF-8 characters for person names and decode Unicode escapes
              TempText := PersonNames;
              TempText := StringReplace(TempText, '\u0026', '&');
              TempText := StringReplace(TempText, 'é', 'é');
              TempText := StringReplace(TempText, 'è', 'è');
              TempText := StringReplace(TempText, 'ê', 'ê');
              TempText := StringReplace(TempText, 'ë', 'ë');
              TempText := StringReplace(TempText, 'á', 'á');
              TempText := StringReplace(TempText, 'Ã ', 'à');
              TempText := StringReplace(TempText, 'â', 'â');
              TempText := StringReplace(TempText, 'ã', 'ã');
              TempText := StringReplace(TempText, 'ä', 'ä');
              TempText := StringReplace(TempText, 'ñ', 'ñ');
              TempText := StringReplace(TempText, 'ó', 'ó');
              TempText := StringReplace(TempText, 'ò', 'ò');
              TempText := StringReplace(TempText, 'ö', 'ö');
              TempText := StringReplace(TempText, 'ú', 'ú');
              TempText := StringReplace(TempText, 'ü', 'ü');
              TempText := StringReplace(TempText, 'ç', 'ç');
              TempText := StringReplace(TempText, 'í', 'í');
              TempText := StringReplace(TempText, 'æ', 'æ');
              TempText := StringReplace(TempText, 'ø', 'ø');
              TempText := StringReplace(TempText, 'Ã…', 'Å');
              TempText := StringReplace(TempText, 'Ø', 'Ø');
              TempText := StringReplace(TempText, 'Æ', 'Æ');
              TempText := StringReplace(TempText, 'þ', 'þ');
              TempText := StringReplace(TempText, 'ÿ', 'ÿ');
              TempText := StringReplace(TempText, 'ß', 'ß');
              TempText := StringReplace(TempText, 'â€"', '–');
              TempText := StringReplace(TempText, 'â€"', '—');
              TempText := StringReplace(TempText, '•', '•');
              TempText := StringReplace(TempText, 'â„¢', '™');
              TempText := StringReplace(TempText, '…', '…');
              PersonNames := TempText;
            end
            else
              PersonNames := '';
          end;

          if (PersonNames <> '') then
            Result := Result + ' - ' + PersonNames;
        end;
      end;
    end;

    // Move to next event
    if (EventBlock <> '') and (Length(EventBlock) < Length(Value)) then
      Value := TextAfter(Value, EventBlock)
    else
      Value := TextAfter(Value, '"name":"' + EventName + '"');
  end;

  // Add header if we found awards
  if (Result <> '') then
    Result := 'AWARDS:' + #13#10 + Result;
end;


//==============================================================================
//    Select most helpful review
//==============================================================================

function GetMostHelpfulReview(Json: string): string;
var
  Block, ScoreStr: string;
  Score, MaxScore: double;
begin
  Result := '';
  MaxScore := -1;

  while true do
  begin
    Block :=  GetJsonBlock(Json, '"node"');
    if Block = '' then
      break;

    ScoreStr := TextBetween(Block, '"score":', '}');
    if (ScoreStr <> '') and (AnsiCompareStr(ScoreStr, 'null') <> 0) then
    begin
      Score := StrToFloat(Trim(ScoreStr));
      if Score > MaxScore then
      begin
        MaxScore := Score;
        Result := Block;
      end;
    end;
    Json := TextAfter(Json, Block);
  end;
end;


//==============================================================================
//    Extract the title runtimes of the alt versions
//==============================================================================

function ExtractAllRuntimes(Json: string): string;
var
  RuntimesBlock, NodeBlock, PlainText, Seconds, AttributeText, CountryText, ResultText: string;
  Count: Integer;
begin
  ResultText := '';
  RuntimesBlock := GetJsonBlock(Json, '"runtimes":{"edges"');
  /////////////////// Exit if "node" is one or zero
  Count := 0;
  RegExprSet('"node"');
  if RegExprExec(RuntimesBlock) then
  begin
    Count := Count + 1;
    while RegExprExecNext do
      Count := Count + 1;
  end;
  if Count < 2 then
  begin
    Result := '';
    Exit;
  end;
  ///////////////////
  while Pos('"node":{', RuntimesBlock) > 0 do
  begin
    NodeBlock := GetJsonBlock(RuntimesBlock, '"node"');

    PlainText := TextBetween(NodeBlock, '"plainText":"', '"');
    Seconds := TextBetween(NodeBlock, '"seconds":', ',');
    if (AnsiCompareStr(Seconds, 'null') <> 0) and (Seconds <> '') then
      Seconds := IntToStr(StrToInt(Seconds, 10) div 60)
    else
      Seconds := '0';

    AttributeText := TextBetween(NodeBlock, '"attributes":[{"text":"', '"');
    if AttributeText <> '' then
      AttributeText := ' (' + AttributeText + ')'
    else
      AttributeText := '';

    CountryText := TextBetween(NodeBlock, '"country":{"text":"', '"');
    if CountryText <> '' then
      CountryText := ' (' + CountryText + ')'
    else
      CountryText := '';

    if ResultText <> '' then
      ResultText := ResultText + #13#10;
    ResultText := ResultText + PlainText + ' (' + Seconds + ' min)' + AttributeText + CountryText;

    RuntimesBlock := TextAfter(RuntimesBlock, NodeBlock);
  end;
  Result := ResultText;
end;


//==============================================================================
//    Pair Actors with Characters
//==============================================================================

function PairActorsWithCharacters(Json: string): string;
var
  OneCredit, Actor, CharacterBlock, Character, CharacterList: string;
begin
  Result := '';
  while Pos('{', Json) > 0 do
  begin
    OneCredit := GetJsonBlock(Json, '"node"');
    Actor := TextBetween(OneCredit, '"nameText":{"text":"', '"');

    if Pos('"characters":null', OneCredit) > 0 then
    begin
      CharacterList := '';
    end else
    begin
      CharacterBlock := TextBetween(OneCredit, '"characters":[', ']');
      CharacterList := '';

      while Pos('"name":"', CharacterBlock) > 0 do
      begin
        Character := TextBetween(CharacterBlock, '"name":"', '"');
        if CharacterList = '' then
          CharacterList := Character
        else
          CharacterList := CharacterList + ' / ' + Character;
        CharacterBlock := TextAfter(CharacterBlock, Character);
      end;
    end;

    if Actor <> '' then
    begin
      if Result <> '' then
        Result := Result + ', ';

      if CharacterList <> '' then
        Result := Result + Actor + ' ... ' + CharacterList
      else
        Result := Result + Actor;
    end;
    Json := TextAfter(Json, OneCredit);
  end;
end;


//==============================================================================
//    Query IMDb's API
//==============================================================================

function QueryIMDb_API(MovieTitleOrID: string): string;
var
  query, url, contentType, referer, headers, response, replace_max_titles, Year, replace_start_year, replace_end_year: string;
  Deviation: Integer;
  forceHTTP11, forceEncodeParams: Boolean;
begin
  url := 'https://api.graphql.imdb.com';
  contentType := 'application/json';
  headers := 'Content-Type=application/json';
  referer := '';
  forceHTTP11 := True;
  forceEncodeParams := False;

  if RegExprSetExec('tt[0-9]+', MovieTitleOrID) then
  begin
    query := Query_ID;
    query := StringReplace(query, 'replace_imdbid', MovieTitleOrID);
  end else
  begin
    query := Query_Search_by_Title;
    /////////////////////////////// SearchWithYear block
    MovieTitleOrID := Trim(MovieTitleOrID);
    if (GetOption('SearchWithYear') = 1) and (Length(MovieTitleOrID) > 6) and (RegExprSetExec('\d{4}$', MovieTitleOrID)) then
    begin
      Year := RegExprMatch(0);
      if (Copy(Year, 1, 2) = '19') or (Copy(Year, 1, 2) = '20') then
      begin
        MovieTitleOrID := Copy(MovieTitleOrID, 1, Length(MovieTitleOrID) - 4);
        case GetOption('SearchYearDeviation') of
          0:  Deviation := 0;
          1:  Deviation := 1;
          2:  Deviation := 3;
        end;
        replace_start_year := IntToStr(StrToInt(Year, 10) - Deviation);
        replace_end_year := IntToStr(StrToInt(Year, 10) + Deviation);
        query := Query_Search_by_Title_and_Year;
        query := StringReplace(query, 'replace_start_year', replace_start_year);
        query := StringReplace(query, 'replace_end_year', replace_end_year);
      end;
    end;
    ////////////////////////////////////////////////////
    if GetOption('BatchMode') > 0 then
      replace_max_titles := '1'
    else
      case GetOption('TitleSelection') of
        1:  replace_max_titles := '5';
        2:  replace_max_titles := '10';
        3:  replace_max_titles := '25';
    end;
    query := StringReplace(query, 'replace_search_string', MovieTitleOrID);
    query := StringReplace(query, 'replace_max_titles', replace_max_titles);
  end;
  response := PostPage3(url, query, contentType, referer, forceHTTP11, forceEncodeParams, headers);
  response := ConvertToASCII(response);
  Result := response;
end;


//==============================================================================
//    Query IMDb's API2
//==============================================================================

function QueryIMDb_API2(MovieID: string; UserID: string; job: string): string;
var
  query, url, contentType, referer, headers, response, max_actors: string;
  forceHTTP11, forceEncodeParams: Boolean;
begin
  url := 'https://api.graphql.imdb.com';
  contentType := 'application/json';
  headers := 'Content-Type=application/json';
  referer := '';
  forceHTTP11 := True;
  forceEncodeParams := False;

  if job = 'get_myrating' then
  begin
    query := Query_userRating;
    query := StringReplace(query, 'replace_imdbid', MovieID);
    query := StringReplace(query, 'replace_userid', UserID);
  end

  else if (job = 'get_actors') or (job = 'get_actors_paired') then
  begin
    case GetOption('ActorsLimit') of
      0:  max_actors := '10';
      1:  max_actors := '25';
      2:  max_actors := '50';
      3:  max_actors := '250';
    end;
    query := Query_actors;
    if GetOption('GetCharacters') = 1 then
      query := Query_actors_paired;
    query := StringReplace(query, 'replace_imdbid', MovieID);
    query := StringReplace(query, 'replace_max_actors', max_actors);
  end
  else
    ShowMessage('ERROR: Incorrect job for QueryIMDb_API2!');

  response := PostPage3(url, query, contentType, referer, forceHTTP11, forceEncodeParams, headers);
  response := ConvertToASCII(response);
  Result := response;
end;


//==============================================================================
//    Perform search and ask to select a movie from a list
//==============================================================================

procedure SearchForMovie(MovieTitleOrID: string);
var
  Json, imdbID: string;
begin
  Json := QueryIMDb_API(MovieTitleOrID);
  if GetOption('BatchMode') = 0 then
  begin
    PickTreeClear;
    PickTreeAdd('Titles search results', '');
    AddMovieTitles(Json);
    if PickTreeExec(MovieTitleOrID) then
    begin
      if RegExprSetExec('tt[0-9]+', MovieTitleOrID) then
        MovieTitleOrID := RegExprMatch(0);
      GetMovieInfo(MovieTitleOrID);
    end;
  end else
  begin
    imdbID := TextBetween(Json, ',"id":"', '"');
    if (imdbID <> '') then
    begin
      GetMovieInfo(imdbID);
    end;
  end;
end;


//==============================================================================
//    Adds the movie titles found on IMDB to a list
//==============================================================================

function AddMovieTitles(Json: string): Boolean;
var
  Item, Title, Year, TypeText, Id, TitleLine, url, Director: string;
begin
  Result := False;

  while Pos('"node":', Json) > 0 do
  begin
    Item     := GetJsonBlock(Json, '"node"');
    Title    := TextBetween(Item, '"titleText":{"text":"', '"}');
    Year     := TextBetween(Item, '"releaseYear":{"year":', '}');
    TypeText := TextBetween(Item, '"titleType":{"text":"', '"');
    Id       := TextBetween(Item, ',"id":"', '"');
    Director := TextBetween(Item, '"id":"director"},"credits":[{"name":{"nameText":{"text":"', '"');

    if (Title <> '') and (Id <> '') then
    begin
      TitleLine := Title + ' (' + Year + ') - ' + TypeText + ' - ' + Director;
      url := 'https://www.imdb.com/title/' + Id;
      PickTreeAdd(TitleLine, url);
      Result := True;
    end;
    Json := TextAfter(Json, '"node":');
  end;
end;


//==============================================================================
//    Fills the fields with a movie info
//==============================================================================

procedure GetMovieInfo(IMDbID: string);
var
  StrVal1, StrVal2, StrVal3, Json, Json2, originalTitle, TranslatedTitle, PictureHeight, Block, Directors, Writers, Actors, Genres, Countries, Producers, Composers, TempStr, AwardsInfo, MainPage, TempStr2, AdditionalInfo, AspectRatioStr, Country: string;
  runTime, i, j, k, l, p, q: Integer;
begin
  Json := QueryIMDb_API(IMDbID);
  MainPage := ''; // Initialize main page variable

  // URL
  if CanSetField(fieldURL) then
  begin
    StrVal1 := 'https://www.imdb.com/title/' + IMDbID;
    SetField(fieldURL, StrVal1);
  end;

  // Original title
  originalTitle := TextBetween(Json, '"originalTitleText":{"text":"', '"}');
  if CanSetField(fieldOriginalTitle) then
  begin
    SetField(fieldOriginalTitle, originalTitle);
  end;

  // Translated title
  if CanSetField(fieldTranslatedTitle) then
  begin
    TranslatedTitle := TextBetween(Json, '"titleText":{"text":"', '",');
    if AnsiCompareStr(TranslatedTitle, originalTitle) <> 0 then
    begin
      SetField(fieldTranslatedTitle, TranslatedTitle);
    end;
  end;

  // Year
  if CanSetField(fieldYear) then
  begin
    StrVal1 := TextBetween(Json, '"releaseYear":{"year":', '}');
    SetField(fieldYear, StrVal1);
  end;

  // Picture
  if CanSetPicture and (GetOption('ImageSize') <> 0) then
  begin
    case GetOption('ImageSize') of
      1:  PictureHeight := '200';
      2:  PictureHeight := '500';
      3:  PictureHeight := '700';
      4:  PictureHeight := '1100';
      5:  PictureHeight := '1500';
      6:  PictureHeight := '2000';
      7:  PictureHeight := '9999';
    end;
    StrVal1 := TextBetween(Json, '"primaryImage":{"url":"', '"}');
    if StrVal1 <> '' then
    begin
      StrVal1 := TextBefore(StrVal1, '._', '') + '._V1_SY' + PictureHeight + '_AL_.jpg';
      GetPicture(StrVal1);
    end;
  end;

  // Length
  if CanSetField(fieldLength) then
  begin
    StrVal1 := TextBetween(Json, '"runtime":{"seconds":', '}');
    if StrVal1 <> '' then
    begin
      StrVal1 := IntToStr(StrToInt(StrVal1, 10) div 60);
      SetField(fieldLength, StrVal1);
    end;
  end;

  // Category (Genres)
  if CanSetField(fieldCategory) then
  begin
    Json2 := GetJsonBlock(Json, '"titleGenres"');
    while Pos('{', Json2) > 0 do
    begin
      Block := GetJsonBlock(Json2, '"genre"');
      Genres := Genres + ', ' + TextBetween(Block, '"text":"', '"');
      Json2 := TextAfter(Json2, Block);
    end;
    if Pos(', ', Genres) = 1 then
      Genres := Copy(Genres, 3, Length(Genres));
    if GetOption('MultipleValuesCategory') = 0 then
      if RegExprSetExec('^[^,]+', Genres) then
        Genres := RegExprMatch(0);
    SetField(fieldCategory, Genres);
  end;

  // Rating
  if CanSetField(fieldRating) then
  begin
    StrVal1 := TextBetween(Json, '"aggregateRating":', '}');
    if (AnsiCompareStr(StrVal1, 'null') <> 0) and (StrVal1 <> '') then
    begin
      // Extract just the rating number (before any comma or closing brace)
      i := Pos(',', StrVal1);
      if i > 0 then
        StrVal1 := Copy(StrVal1, 1, i - 1);
      // Remove any quotes and trim
      StrVal1 := StringReplace(StrVal1, '"', '');
      StrVal1 := Trim(StrVal1);
      // Take only first 3 characters for rating (e.g., "7.8")
      if Length(StrVal1) > 3 then
        StrVal1 := Copy(StrVal1, 1, 3);
      // Only set if it's a valid number (not 0.0)
      if (StrVal1 <> '0') and (StrVal1 <> '0.0') and (StrVal1 <> '') then
        SetField(fieldRating, StrVal1);
    end;
  end;

  // Votes (Rating Count) - stored in Subtitles field
  if CanSetField(fieldSubtitles) then
  begin
    StrVal1 := TextBetween(Json, '"voteCount":', ',');
    if (AnsiCompareStr(StrVal1, 'null') <> 0) and (StrVal1 <> '') then
    begin
      // Remove any trailing "}" character
      if Copy(StrVal1, Length(StrVal1), 1) = '}' then
        StrVal1 := Copy(StrVal1, 1, Length(StrVal1) - 1);

      SetField(fieldSubtitles, StrVal1);
    end;
  end;

// Aspect Ratio - stored in VideoFormat field
if CanSetField(fieldVideoFormat) then
begin
  StrVal1 := ''; // Reset for aspect ratio

  // Use the main page we already fetched, or fetch it if not available
  if MainPage = '' then
  begin
    MainPage := GetPage('https://www.imdb.com/title/' + IMDbID + '/');
    MainPage := ConvertToASCII(MainPage);
  end;

  if (MainPage <> '') and (Length(MainPage) > 1000) then
  begin
    // Method 1: Try the technical specifications page
    TempStr := GetPage('https://www.imdb.com/title/' + IMDbID + '/technical/');
    if TempStr <> '' then
    begin
      TempStr := ConvertToASCII(TempStr);

      // Look for aspect ratio in the technical page
      i := Pos('data-testid="title-techspec_aspectratio"', TempStr);
      if i > 0 then
      begin
        // Extract a larger section after the testid to capture ALL aspect ratios
        TempStr := Copy(TempStr, i, 10000);

        // Find the end of the entire aspect ratio section (look for next section or closing tag)
        j := Pos('</ul>', TempStr);
        if j > 0 then
          TempStr := Copy(TempStr, 1, j + 5)
        else
        begin
          // If no </ul> found, look for the next technical spec section
          j := Pos('data-testid="title-techspec_', Copy(TempStr, 500, Length(TempStr)));
          if j > 0 then
            TempStr := Copy(TempStr, 1, j + 499);
        end;

        // Now use your original parsing logic but on the larger section
        StrVal1 := '';
        StrVal2 := TempStr;

        while Pos(':', StrVal2) > 0 do
        begin
          // Find position of colon
          k := Pos(':', StrVal2);

          // Look backwards for the number before the colon (up to 10 chars)
          l := k - 1;
          j := k - 1;
          while (j > 0) and (j > k - 10) do
          begin
            if (Pos(Copy(StrVal2, j, 1), '0123456789. ') > 0) then
              l := j
            else if (Pos(Copy(StrVal2, j, 1), '0123456789. ') = 0) then
              break;
            j := j - 1;
          end;

          // Extract the part before colon
          StrVal3 := Copy(StrVal2, l, k - l);
          StrVal3 := Trim(StrVal3);

          // Look forward after the colon for the number (up to 10 chars)
          j := k + 1;
          while (j <= Length(StrVal2)) and (j <= k + 10) and (Pos(Copy(StrVal2, j, 1), '0123456789. ') > 0) do
            j := j + 1;

          // Extract the part after colon
          TempStr2 := Copy(StrVal2, k + 1, j - k - 1);
          TempStr2 := Trim(TempStr2);

          // Check if this looks like an aspect ratio (both parts are numbers)
          if (StrVal3 <> '') and (TempStr2 <> '') and (Length(StrVal3) < 6) and (Length(TempStr2) < 6) then
          begin
            // Check if StrVal3 starts with a digit
            if (Length(StrVal3) > 0) and (Pos(Copy(StrVal3, 1, 1), '0123456789') > 0) then
            begin
              // Check if TempStr2 starts with a digit
              if (Length(TempStr2) > 0) and (Pos(Copy(TempStr2, 1, 1), '0123456789') > 0) then
              begin
                // This is likely an aspect ratio - now look for parentheses nearby
                AdditionalInfo := '';

                // Look for opening parenthesis within the next 100 characters
                p := Pos('(', Copy(StrVal2, j, 100));
                if p > 0 then
                begin
                  // Found opening parenthesis - look for closing parenthesis
                  q := Pos(')', Copy(StrVal2, j + p, 100));
                  if q > 0 then
                  begin
                    // Extract the text between parentheses
                    AdditionalInfo := Copy(StrVal2, j + p - 1, q + 1);
                  end;
                end;

                // Build the final aspect ratio string
                AspectRatioStr := StrVal3 + ' : ' + TempStr2;
                if AdditionalInfo <> '' then
                  AspectRatioStr := AspectRatioStr + ' ' + AdditionalInfo;

                // Add to the result
                if StrVal1 <> '' then
                  StrVal1 := StrVal1 + ' / ';
                StrVal1 := StrVal1 + AspectRatioStr;
              end;
            end;
          end;

          // Move past this colon to find the next one
          StrVal2 := Copy(StrVal2, k + 1, Length(StrVal2) - k);
        end;
      end;
    end;

    // Method 2: Check JSON-LD data for aspectRatio (fallback)
    if StrVal1 = '' then
    begin
      i := Pos('"aspectRatio":"', MainPage);
      if i > 0 then
      begin
        // Extract the value after the colon and quote
        TempStr := Copy(MainPage, i + 15, 50);
        j := Pos('"', TempStr);
        if j > 0 then
        begin
          StrVal1 := Copy(TempStr, 1, j - 1);
          StrVal1 := Trim(StrVal1);
        end;
      end;
    end;

    // Method 3: Another JSON pattern without quotes
    if StrVal1 = '' then
    begin
      i := Pos('"aspectRatio":', MainPage);
      if i > 0 then
      begin
        TempStr := Copy(MainPage, i + 14, 50);
        // Skip any whitespace and quotes
        j := 1;
        while (j <= Length(TempStr)) and (TempStr[j] in [' ', '"', ':']) do
          Inc(j);
        // Find the end (quote, comma, or brace)
        k := j;
        while (k <= Length(TempStr)) and not (TempStr[k] in ['"', ',', '}']) do
          Inc(k);
        if k > j then
        begin
          StrVal2 := Copy(TempStr, j, k - j);
          StrVal2 := Trim(StrVal2);
          // Only use if it contains : or .
          if (Pos(':', StrVal2) > 0) or (Pos('.', StrVal2) > 0) then
            if (StrVal2 <> 'null') and (StrVal2 <> '') then
              StrVal1 := StrVal2;
        end;
      end;
    end;

    // Clean up the aspect ratio string
    if StrVal1 <> '' then
    begin
      // Remove any HTML tags that might be left
      while Pos('<', StrVal1) > 0 do
      begin
        i := Pos('<', StrVal1);
        j := Pos('>', StrVal1);
        if j > i then
          StrVal1 := Copy(StrVal1, 1, i - 1) + Copy(StrVal1, j + 1, Length(StrVal1) - j)
        else
          break;
      end;
      StrVal1 := Trim(StrVal1);

      // Final validation
      if (Pos(':', StrVal1) = 0) and (Pos('.', StrVal1) = 0) then
        StrVal1 := '';
      if (StrVal1 = 'null') or (StrVal1 = 'None') then
        StrVal1 := '';
    end;
  end;

  if StrVal1 <> '' then
  begin
    SetField(fieldVideoFormat, StrVal1);
  end;
end;
  
  // Description (+Tagline) (+all runtimes) (+awards) - WITHOUT vote count
  if CanSetField(fieldDescription) then
  begin
    StrVal1 := TextBetween(Json, '"taglines":{"edges":[{"node":{"text":"', '"}');
    // Fix escaped quotes in tagline
    StrVal1 := StringReplace(StrVal1, '\"', '"');

    StrVal2 := TextBetween(Json, '"plotText":{"plainText":"', '"}');
    // Fix escaped quotes in plot
    StrVal2 := StringReplace(StrVal2, '\"', '"');
    StrVal2 := StringReplace(StrVal2, '\', '');

    StrVal3 := ExtractAllRuntimes(Json);

    // Extract awards information using the robust method
    AwardsInfo := ExtractAwards(IMDbID);

    if StrVal1 <> '' then
    begin
      StrVal1 := ' ' + StrVal1;
      StrVal1 := StrVal1 + #13#10 + #13#10 + StrVal2;
      if StrVal3 <> '' then
        StrVal1 := StrVal1 + #13#10 + #13#10 + 'All runtimes:' + #13#10 + StrVal3;
    end else
    begin
      StrVal1 := StrVal2;
      if StrVal3 <> '' then
        StrVal1 := StrVal1 + #13#10 + #13#10 + 'All runtimes:' + #13#10 + StrVal3;
    end;

    // Add awards information at the end of description
    if AwardsInfo <> '' then
      StrVal1 := StrVal1 + #13#10 + #13#10 + AwardsInfo;

    SetField(fieldDescription, StrVal1);
  end;

  // Country
  if CanSetField(fieldCountry) then
  begin
    Json2 := GetJsonBlock(Json, '"countriesOfOrigin"');
    Countries := '';
    while Pos('"id":"', Json2) > 0 do
    begin
      Block := TextBetween(Json2, '"id":"', '"');
      Country := ExpandCountryCode(Block);
      if Country <> '' then
      begin
        if Countries <> '' then
          Countries := Countries + ', ';
        Countries := Countries + Country;
      end;
      Json2 := TextAfter(Json2, '"id":"');
    end;

    // Handle MultipleValuesCountry option
    if (Countries <> '') and (GetOption('MultipleValuesCountry') = 0) then
    begin
      // Take only the first country
      i := Pos(',', Countries);
      if i > 0 then
        Countries := Copy(Countries, 1, i - 1);
    end;

    SetField(fieldCountry, Countries);
  end;

  // Certification
  if CanSetField(fieldCertification) then
  begin
    StrVal1 := TextBetween(Json, '"certificate":{"rating":"', '"}');
    SetField(fieldCertification, StrVal1);
  end;

  // Comment
  if CanSetField(fieldComments) then
  begin
    Json2 := GetJsonBlock(Json, '"reviews"');
    if Json2 <> '' then
    begin
      Json2 := GetMostHelpfulReview(Json2);
      StrVal1 := TextBetween(Json2, '"summary":{"originalText":"', '"}') + ' ( ' + TextBetween(Json2, '"authorRating":', ',') + '/10 by ' + TextBetween(Json2, '"author":{"nickName":"', '"}') + ' on ' + TextBetween(Json2, '"submissionDate":"', '"') + ' )';
      StrVal2 := TextBetween(Json2, '"text":{"originalText":{"plainText":"', '"}');
      if StrVal2 <> '' then
      begin
        StrVal2 := StringReplace(StrVal2, '\n', #13#10);
        StrVal3 := StringReplace(StrVal1 + #13#10 + #13#10 + StrVal2, '\', '');
        SetField(fieldComments, StrVal3);
      end;
    end;
  end;

  // Director / Writer / Producer / Composer
  if CanSetField(fieldDirector) or CanSetField(fieldWriter) or CanSetField(fieldProducer) or CanSetField(fieldComposer) then
  begin
    Json2 := GetJsonBlock(Json, '"credits":{"edges"');
    while Pos('{', Json2) > 0 do
    begin
      Block := GetJsonBlock(Json2, '"node"');
      if TextBetween(Block, '"categoryId":"', '"') = 'director' then
      begin
        StrVal1 := TextBetween(Block, '"nameText":{"text":"', '"');
        if Directors <> '' then
          Directors := Directors + ', ';
        Directors := Directors + StrVal1;
      end

      else if TextBetween(Block, '"categoryId":"', '"') = 'writer' then
      begin
        StrVal1 := TextBetween(Block, '"nameText":{"text":"', '"');
        if Writers <> '' then
          Writers := Writers + ', ';
        Writers := Writers + StrVal1;
      end

      else if TextBetween(Block, '"categoryId":"', '"') = 'producer' then
      begin
        StrVal1 := TextBetween(Block, '"nameText":{"text":"', '"');
        if Producers <> '' then
          Producers := Producers + ', ';
        Producers := Producers + StrVal1;
      end

      else if TextBetween(Block, '"categoryId":"', '"') = 'composer' then
      begin
        StrVal1 := TextBetween(Block, '"nameText":{"text":"', '"');
        if Composers <> '' then
          Composers := Composers + ', ';
        Composers := Composers + StrVal1;
      end;

      Json2 := TextAfter(Json2, Block);
    end;
    if CanSetField(fieldDirector) then
      SetField(fieldDirector, Directors);
    if CanSetField(fieldWriter) then
      SetField(fieldWriter, Writers);
    if CanSetField(fieldProducer) then
      SetField(fieldProducer, Producers);
    if CanSetField(fieldComposer) then
      SetField(fieldComposer, Composers);
  end;

  // Actors / Actors + Characters
  if CanSetField(fieldActors) then
  begin
    //Sleep(100);
    Json2 := QueryIMDb_API2(IMDbID, 'none', 'get_actors');
    if GetOption('GetCharacters') = 0 then
    begin
      while Pos('{', Json2) > 0 do
      begin
        Block := GetJsonBlock(Json2, '"node"');
        StrVal1 := TextBetween(Block, '"nameText":{"text":"', '"');
        if Actors <> '' then
          Actors := Actors + ', ';
        Actors := Actors + StrVal1;
        Json2 := TextAfter(Json2, Block);
      end;
      SetField(fieldActors, Actors);
    end

    else if GetOption('GetCharacters') = 1 then
    begin
      Actors := PairActorsWithCharacters(Json2);
      if Actors <> '' then
      begin
        Actors := StringReplace(Actors,  ', ', #13#10);
        SetField(fieldActors, Actors);
      end;
    end;
  end;

  // My rating
  if (GetParam('IMDB_userID') <> 'ur00000000') and (GetParam('IMDB_userID') <> '') then
  begin
    if CanSetField(fieldUserRating) then
    begin
      //Sleep(100);
      Json2 := QueryIMDb_API2(IMDbID, GetParam('IMDB_userID'), 'get_myrating');
      StrVal1 := TextBetween(Json2, '"userRating":{"value":', '}');
      if (AnsiCompareStr(StrVal1, 'null') <> 0) and (StrVal1 <> '') then
        SetField(fieldUserRating, StrVal1)
      else if RegExprSetExec('FORBIDDEN', Json2) then
        ShowMessage('ERROR: Your ratings are not public.')
      else if RegExprSetExec('BAD_USER_INPUT', Json2) then
        ShowMessage('ERROR: Wrong IMDB_userID.')
    end;
  end;
end;


//============================================================================//
//================================  MAIN  ====================================//
//============================================================================//


begin
  // ShowMessage(MovieName); // Debug
  // Check for current AMC version
  if CheckVersion(4,2,3) then
  begin
    MovieName := '';
    if GetOption('BatchMode') = 2 then
    begin
      MovieName := GetField(fieldURL);
      if Pos('imdb.com', MovieName) = 0 then
        MovieName := '';
    end;
    if MovieName = '' then
      MovieName := GetField(fieldOriginalTitle);
    if MovieName = '' then
      MovieName := GetField(fieldTranslatedTitle);

    if GetOption('BatchMode') = 0 then
    begin
      if not Input('IMDB Import', 'Enter the title or the IMDB ID/URL:', MovieName) then
        Exit;
    end else
      Sleep(100);

    if MovieName <> '' then
    begin
      if RegExprSetExec('tt[0-9]+', MovieName) then
      begin
        MovieName := RegExprMatch(0);
        GetMovieInfo(MovieName);
      end else
      begin
        MovieName := StringReplace(MovieName, '.', ' ');
        SearchForMovie(MovieName);
      end;
    end;
  end else
    ShowInformation('This script requires a newer version of Ant Movie Catalog, at least the version 4.2.3');
end.

With a proper template you could get this:

Image

Not fully tested, but it's a start.
Post Reply