Get Movie Details from TheMovieDB API (Help Needed)

Tap Forms – Organizer Database App for Mac, iPhone, and iPad Forums Script Talk Get Movie Details from TheMovieDB API (Help Needed)

Viewing 7 reply threads
  • Author
    Posts
  • June 6, 2021 at 6:03 PM #44538

    JCK
    Participant

    Inspired 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 Moffatt
    Participant

    Let’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 a credits object, so let’s do a little rename of our methods and return the internal cast 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 the credits response object. This means if it is set it should be credits.cast, you can see why we renamed the variables now. The ? syntax is a ternary operator and what we’re doing is checking if credits.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 using addNewRecordToField to get a new record (similar to form.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 use setFieldValues 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 precautionary document.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. The document.saveAllChanges() call is an alias for form.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

    JCK
    Participant

    Sam, 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 Moffatt
    Participant

    You’re using a bunch of global variables, each time you do var thing then thing 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 do var inside the function, it’s scoped to the function and not accessible outside it. Let’s rewrite this slightly to use an input variable for inputID 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 the tmdbAPI 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 literally inputID 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

    JCK
    Participant

    Ohhhh 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

    JCK
    Participant

    Disregard! 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 Moffatt
    Participant

    Good to hear you got it sorted :)

    July 3, 2021 at 12:01 AM #44709

    JCK
    Participant

    Anoooooother 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 Moffatt
    Participant

    Something 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.

Viewing 7 reply threads

You must be logged in to reply to this topic.