Tap Forms – Organizer Database App for Mac, iPhone, and iPad › Forums › Script Talk › Get Movie Details from TheMovieDB API (Help Needed)
- This topic has 8 replies, 2 voices, and was last updated 3 years, 4 months ago by Sam Moffatt.
-
AuthorPosts
-
June 6, 2021 at 6:03 PM #44538
JCKParticipantInspired by this post detailing how to get watched TV shows, I decided to attempt a Movie import script via TheMovieDB’s API. I’m very new to scripting (really only used iOS Shortcuts) and have gotten stuck and could use some direction or assistance.
The big issue I can’t seem to crack is iterating through the JSON for to pull out the cast and add them to the cast table on the form. I’ve looked through the example of the above of pulling the singular episode details, but the JSON for TheMovieDB is more robust than OMDB and I’ve confused myself.
Any Ideas?
var tmdbAPI = 'xxx'; var title_id = 'fld-7f17a3883cf742ca90a732565f687953'; var released_id = 'fld-4f1d3a5878914910954b65c2f782abfd'; var imdbid_id = 'fld-0b8bd8338d8f494aa5b7099c42230e70'; var poster_id = 'fld-bace3b81b9ab4cc9951a9445d12a63b3'; var summary_id = 'fld-d16b4361266b48ee9c3b88afd29fd5ac'; var runtime_id = 'fld-f096b51db4c447e18bf10298135dfaa8'; var tagline_id = 'fld-ac1ad056b5004ed8a19f8d272ae01e2b'; var cast_id = 'fld-0b85d9aef49f4fd58726f6830a03ba11'; var actor_id = 'fld-07249465a7ea45e8830da27e62b3121d'; var role_id = 'fld-bf225b3c443248fd97c5737312acd28b'; var itemID; function fetchDetailsFromURL() { fetchURL = <code>https://api.themoviedb.org/3/movie/${itemID}?api_key=${tmdbAPI}&language=en-US</code>; return Utils.getJsonFromUrl(fetchURL); } function fetchCastFromURL() { fetchURL = <code>https://api.themoviedb.org/3/movie/${itemID}/credits?api_key=${tmdbAPI}&language=en-US</code>; return Utils.getJsonFromUrl(fetchURL); } function getCast() { var cast = fetchCastFromURL() return cast } function getData() { var film = fetchDetailsFromURL(); var imdbID = film.imdb_id; console.log(imdbID) var itemIds = new Set(); var currentItemsByImdbID = {}; var allCurrentItems = form.getRecords(); for (let currentItems of allCurrentItems) { currentItemsByImdbID[currentItems.getFieldValue(imdbid_id)] = currentItems; itemIds.add(currentItems.getFieldValue(imdbid_id)); } let newRecord; if (itemIds.has("http://imdb.com/title/" + imdbID)) { Utils.alertWithMessage(film.title + ' already exists.', 'Sorry.'); } else { newRecord = form.addNewRecord(); newRecord.setFieldValues({ [title_id]: film.title, [released_id]: film.release_date, [imdbid_id]: "http://imdb.com/title/" + film.imdb_id, [summary_id]: film.overview, [runtime_id]: film.runtime, [tagline_id]: film.tagline, }) } var Poster = "https://www.themoviedb.org/t/p/w1280/" + film.poster_path if (Poster != null) { newRecord.addPhotoFromUrlToField(Poster, poster_id); } form.saveAllChanges(); } var prompter = Prompter.new(); prompter.cancelButtonTitle = 'Cancel'; prompter.continueButtonTitle = 'Go'; prompter.addParameter('TMDB Number', 'itemID'); prompter.show('Enter an TMDB code', getData)
Here are the TMdb API details: https://developers.themoviedb.org/3/movies/get-movie-credits
June 7, 2021 at 12:22 PM #44563
Sam MoffattParticipantLet’s take a step back and look at focusing on just handling the object we’re getting back. Looking at the API, it’s returning an object and then an array for cast. Let’s look at what printing that out could look like as a script. I’m going to remove the variables in the header and prompter at the bottom just to cut down a little and let us focus.
// variables header above here function fetchCastFromURL() { fetchURL = `https://api.themoviedb.org/3/movie/${itemID}/credits?api_key=${tmdbAPI}&language=en-US`; return Utils.getJsonFromUrl(fetchURL); } function getCast() { var cast = fetchCastFromURL(); return cast; } function getData() { getCast(); } // prompter code below here
That’s not going to do a lot beyond (hopefully) make the web request. Let’s expand
getCast
a little:function getCast() { var cast = fetchCastFromURL(); console.log(JSON.stringify(cast)); return cast; }
All going well you should see the same JSON representation in the console as we would see from the API. Sometimes
cast
can be falsy if the request fails, so let’s handle that:function getCast() { var cast = fetchCastFromURL(); if (!cast) { console.log("Request to get cast failed"); return []; } console.log(JSON.stringify(cast)); return cast; }
We’re just going to return an empty array here and log a message when we fail the request. The
cast
result is actually acredits
object, so let’s do a little rename of our methods and return the internalcast
response:function fetchCreditsFromURL() { fetchURL = `https://api.themoviedb.org/3/movie/${itemID}/credits?api_key=${tmdbAPI}&language=en-US`; return Utils.getJsonFromUrl(fetchURL); } function getCast() { var credits = fetchCreditsFromURL(); if (!credits) { console.log("Request to get credits failed"); return []; } console.log(JSON.stringify(credits)); return credits.cast ? credits.cast : []; }
When we look at the documentation,
cast
is listed as an optional array element inside thecredits
response object. This means if it is set it should becredits.cast
, you can see why we renamed the variables now. The?
syntax is a ternary operator and what we’re doing is checking ifcredits.cast
is truthy and if it is we return it otherwise we hand back an empty array.Now it’s time to expand out
getData()
and we’re going to put a loop in just to see what we get back:function getData() { let cast = getCast(); for (let castMember of cast) { console.log(castMember.name); } }
All going well when this is tested we should see an array of cast members’ names printed out into our console. We might have an empty array returned and if that was an error hopefully we also got a log message saying as much (e.g. the “Request to get credits failed”) but otherwise we’ve got our listing of cast members that we can iterate over now. Let’s put the bulk of
getData()
back together and we’ll add some code to create a new record via a link to form field or table field:function getData() { var film = fetchDetailsFromURL(); var imdbID = film.imdb_id; console.log(imdbID) var itemIds = new Set(); var currentItemsByImdbID = {}; var allCurrentItems = form.getRecords(); for (let currentItems of allCurrentItems) { currentItemsByImdbID[currentItems.getFieldValue(imdbid_id)] = currentItems; itemIds.add(currentItems.getFieldValue(imdbid_id)); } let newRecord; if (itemIds.has("http://imdb.com/title/" + imdbID)) { Utils.alertWithMessage(film.title + ' already exists.', 'Sorry.'); } else { newRecord = form.addNewRecord(); newRecord.setFieldValues({ [title_id]: film.title, [released_id]: film.release_date, [imdbid_id]: "http://imdb.com/title/" + film.imdb_id, [summary_id]: film.overview, [runtime_id]: film.runtime, [tagline_id]: film.tagline, }); document.saveAllChanges(); let cast = getCast(); for (let castMember of cast) { let actorRecord = newRecord.addNewRecordToField(cast_table_field_id); actorRecord.setFieldValues({ [actor_id]: castMember.name, [role_id]: castMember.character, [tmdb_actor_id]: castMember.id }); document.saveAllChanges(); } } var Poster = "https://www.themoviedb.org/t/p/w1280/" + film.poster_path if (Poster != null) { newRecord.addPhotoFromUrlToField(Poster, poster_id); } form.saveAllChanges(); }
We’ve pulled back in the original
getData()
and I’ve added a block for getting the cast and inside it we’re usingaddNewRecordToField
to get a new record (similar toform.addNewRecord
) added to the table field of the record we just created. Behind the scenes this ensures that Tap Forms links the data for us properly. We usesetFieldValues
as was done earlier because we’re just working with a record object (albeit one that’s a record in a table). I also add in two precautionarydocument.saveAllChanges()
because when dealing with record creation and record linking via the scripting interface there are some quirks that can appear and explicitly flushing the state down generally makes that more consistent. Thedocument.saveAllChanges()
call is an alias forform.saveAllChanges()
and does the same thing.I think this should work or at the very least get you a little closer to your journey.
June 10, 2021 at 12:10 AM #44590
JCKParticipantSam, thank you SO MUCH. That was incredibly helpful and well laid out that it finally make sense for me. I’ve got everything inputting exactly as I want now.
One last question if I may – my last hurdle is shifting it to search by IMDB ID and return the TMDB results. I’ve gotten as far as this:
function convertIMDBtoTMDB() { fetchURL = <code>https://api.themoviedb.org/3/find/${inputID}?api_key=${tmdbAPI}&language=en-US&external_source=imdb_id</code>; var tmdbResp = Utils.getJsonFromUrl(fetchURL); var tmdbID = tmdbResp.movie_results[0].id; console.log(JSON.stringify(tmdbID)) if (!tmdbID) { console.log("Request to get code failed"); return []; } else { return []; } }
My hiccup here (and its probably an easy fix) is I can’t get the result of this to set the tmdbID to pass into the other functions. I know its just an order of operations thing, but can’t crack it. No matter where I try and set the variable by function, it doesn’t pass through to the functions needing that variable in the getData function. What am I doing wrong?
June 10, 2021 at 11:38 PM #44595
Sam MoffattParticipantYou’re using a bunch of global variables, each time you do
var thing
thenthing
becomes a global variable when you’re doing it outside of a function. That’s why you can use the variables almost anywhere though in general that’s not best practice. The prompter uses this technique in part because it’s an async call but once we’ve stepped out of that, it’s best to use actual arguments and return values. When you dovar
inside the function, it’s scoped to the function and not accessible outside it. Let’s rewrite this slightly to use an input variable forinputID
and return a value:function convertIMDBtoTMDB(inputID) { fetchURL = ` https : // api.themoviedb.org/3/find/${inputID}?api_key=${tmdbAPI}&language=en-US&external_source=imdb_id`; var tmdbResp = Utils.getJsonFromUrl(fetchURL); var tmdbID = tmdbResp.movie_results[0].id; console.log(JSON.stringify(tmdbID)) if (! tmdbID) { console.log("Request to get code failed"); return ""; } else { return tmdbID; } }
Same function except we’re accepting an inputID as an argument and then returning the
tmdbID
. I left thetmdbAPI
alone because I feel it’s a constant more than a variable and something you’d want to leave in global scope. Where possible you want to explicitly tell a function what it’s inputs are, in this case literallyinputID
but even in the other places it’s better to be explicit:function fetchCreditsFromURL(itemID) { fetchURL = `https://api.themoviedb.org/3/movie/${itemID}/credits?api_key=${tmdbAPI}&language=en-US`; return Utils.getJsonFromUrl(fetchURL); }
That way you know where a variable is coming from and you’re less likely to have a variable in global scope accidentally trashed. Nothing in Javascript is straight forwards so I was looking for a good resource and this page on demystifying Javascript variable scope has some great details on how it works and where some of the pitfalls are.
Instead of relying on the global changing, assign it to a variable in the right scope and then pass it along to anything else that needs it. Hopefully that helps get you a little further :)
June 12, 2021 at 11:33 PM #44611
JCKParticipantOhhhh kay. That makes sense. I realize now that I was calling the convertIMDBtoTMDB function at the wrong time.
How do I add to a variable? Or is this something I’m confusing from coming from Shortcuts on iOS.
The use case I’m running in to is for films with multiple directors. I’ve figured out how to loop through the results to get the names of both directors listed in the console, but I’ve run into a issue trying to add the positive results together.
function getCrew() { var fullcredits = fetchCreditsFromURL(); if (! fullcredits) { console.log("Request to get credits failed"); return []; } return fullcredits.crew ? fullcredits.crew : []; } function getDirectors() { let crew = getCrew(); for (let crewMember of crew) { if (crewMember.job === 'Director') { console.log(crewMember.name); } continue; } }
In Shortcuts, I would add an “Add to Variable” option, but there doesn’t seem to be anything like that in javascript. Adding a return function at any point seems to stop the loop, or only give the last result.
Ideally I would like all positive results to be returned in a single string that I could add to a field. What am I doing wrong?
June 13, 2021 at 12:08 AM #44612
JCKParticipantDisregard! I sorted it with this code. :)
function getDirectors() { let crew = getCrew(); var directorList = Array(); for (let crewMember of crew) { if (crewMember.job === 'Director') { directorList.push(crewMember.name); } continue; } return directorList.join(', ') } function process() { let directors = getDirectors(); console.log(directors) return directors; }
June 13, 2021 at 10:47 AM #44614
Sam MoffattParticipantGood to hear you got it sorted :)
July 3, 2021 at 12:01 AM #44709
JCKParticipantAnoooooother question.
How would I simplify the following code to only make ‘convertIMDBtoTMDB()’ run once inside the addFilm() function? Each way I’ve tried fails.
function convertIMDBtoTMDB() { fetchURL = <code>http://api.themoviedb.org/3/find/${inputID}?api_key=${tmdbAPI}&language=en-US&external_source=imdb_id</code>; var tmdbResp = Utils.getJsonFromUrl(fetchURL); if (!tmdbResp) { Utils.alertWithMessage('No TMDB record.'); return ""; } else { var tmdbNum = tmdbResp.movie_results[0].id; return tmdbNum; } } function fetchDetailsFromURL() { var tmdbID = convertIMDBtoTMDB(); fetchURL = <code>https://api.themoviedb.org/3/movie/${tmdbID}?api_key=${tmdbAPI}&language=en-US</code>; return Utils.getJsonFromUrl(fetchURL); } function fetchCreditsFromURL() { var tmdbID = convertIMDBtoTMDB(); fetchURL = <code>https://api.themoviedb.org/3/movie/${tmdbID}/credits?api_key=${tmdbAPI}&language=en-US</code>; return Utils.getJsonFromUrl(fetchURL); } function fetchTagsFromURL() { var tmdbID = convertIMDBtoTMDB(); fetchURL = <code>https://api.themoviedb.org/3/movie/${tmdbID}/keywords?api_key=${tmdbAPI}</code>; return Utils.getJsonFromUrl(fetchURL); } function addFilm() { let filmDeets = fetchDetailsFromURL(); let filmCred = fetchCreditsFromURL(); let filmTags = fetchTagsFromURL(); }
July 3, 2021 at 9:57 AM #44714
Sam MoffattParticipantSomething like this to introduce a cache dictionary should do the trick:
var tmdbCache = {}; function convertIMDBtoTMDB() { if (inputID in tmdbCache) { return tmdbCache[inputID]; } fetchURL = `http://api.themoviedb.org/3/find/${inputID}?api_key=${tmdbAPI}&language=en-US&external_source=imdb_id`; var tmdbResp = Utils.getJsonFromUrl(fetchURL); if (!tmdbResp) { Utils.alertWithMessage('No TMDB record.'); tmdbCache[inputID] = ""; return ""; } else { var tmdbNum = tmdbResp.movie_results[0].id; tmdbCache[inputID] = tmdbNum; return tmdbNum; } }
Basically if the entry exists, return it; if it doesn’t exist do the main logic. Didn’t test it but something close to it should work.
-
AuthorPosts
You must be logged in to reply to this topic.