2.- Almost all the awards at the end of the description field (the most relevant categories)
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.
Not fully tested, but it's a start.