Querying Netflix oData Catalog via JSON and jQuery

Or, search by rating/year:
 between: and Descending Ascending

My Netflix Queue

This is the live feed of my own Netflix Queue, so you can see what movies I've got coming. Don't judge ;)

You can also load some of the other publicly available Netflix RSS feeds.


What the Hell is This?

I created this page as a means to demonstrate how to retrieve data from an oData catalog using only client-side code, in this case jQuery and JSON. Netflix recently released their oData API, and I thought it would work great for this project, as well as being a fun way to search movie and DVD titles, and easily do some minimal integration with your Netlix account and/or watch trailers. It doesn't really do anything that the regular Netlix site doesn't do, but is a good way to learn how to snatch information from remote servers.

A Note on the Trailers

The trailers integrate with the YouTube Data API. Basically, this page sends a request based on the title and the word 'trailer' to YouTube, and YouTube returns a JSON object with the first result. This will return an appropriate trailer about 90% of the time. However, the API calls for the keywords to be separated by commas, so the script breaks the titles up into keywords, but the Netflix title may not be the exact move title. For instance, if a title has extra words in it, such as "20th Anniversary Edition", then these keywords will also be passed to YouTube, and may skew the results. In addition, if there are two movies with the same name, such as 'Fight Club', then it may not return the correct result, as the more popular video would be returned at the top of the list.

between and

I threw a ton of extra crap in here to try and make the page look pretty, so if you view the source and it is a little convoluted, then here are some code snippets to get you started.

Create the initial movie query to Netflix oData

This creates the query in a string, the sends it to Netflix via JSONP. Note that this requires the use of a proxy, due to cross-domain restrictions. You can get the code for a simple PHP proxy that I built at the bottom.


   function getodata() {
        // Build OData query
        var query = "http://odata.netflix.com/Catalog" // netflix base url
            + "/Titles" // top-level resource
            + "?$filter=ReleaseYear le 2010 and ReleaseYear ge 1990 and AverageRating gt 4"  // filter by year and rating
	    + "&$orderby=ReleaseYear,ShortName desc" // the order
            + "&$format=json"; // json request

	    //URLEncode the query - this is necessary for the proxy
	    query = $.URLEncode(query);

        // Make JSONP call to Netflix
        $.ajax({
            dataType: "jsonp",
            url: 'jsonproxy.php?url=' + query,
            jsonpCallback: "callback",
            success: callback,
	    error: errorMsg
        });
  }


Processing the JSON response

This is the callback function that is called with the JSON is returned. It parses it out and outputs it to an html div.


     function callback(result) {
        // unwrap result
        var movies = result["d"]["results"];
	if(movies == null || movies == 0){
	alert('Error loading title. Try the search.');
	}else{
        // show movies in template
        var showMovie = tmpl("movieTemplate");
        var html = "";
        for (var i = 0; i < movies.length; i++) {
            // flatten movie
            movies[i].BoxArtSmallUrl = movies[i].BoxArt.LargeUrl;
	    //escape the short title for passing to functions - takes care of single quotes
	    movies[i].ShortName = escape(movies[i].ShortName);
            // render with template
            html += showMovie(movies[i]);
       		}
        $("#movieTemplateContainer").html(html);
	}
    }

Displaying the Results in HTML

This takes the responses and displays them in a template '{%= tag %}', via Microtemplates.js.


<div id="movieTemplateContainer"></div>

<script id="movieTemplate" type="text/html">
    <div class="titleholder">
	<div class="boxartholder">
        <img src="{%=BoxArtSmallUrl %}" />
	<p><a href="javascript:addQueue('{%=NetflixApiId%}','{%=Id%}');">Add to Queue</a></p>
	<p><a href="javascript:playInstant('{%=NetflixApiId%}','{%=Instant.Available%}','{%=Id%}');">Play Instantly</a></p>
	</div>
	<div class="synopsisholder">
        <strong>{%=Name%}</strong><br />
	({%=ReleaseYear %}) {%=Rating %}
        <p>{%=Synopsis %}</p>
	<p>Average Rating: {%=AverageRating %}</p> 
	<p><a onclick="viewTrailer('{%=ShortName%}');" href="javascript:void(0);">View Trailer</a></p>
	</div>
	
	<br style="clear: both;" />

	<span id="playStatus{%=Id%}"></span>
	
	<span id="trailerStatus"></span>

    </div>
	
</script>

Using the Netflix functions

You can easily create links to add a title to someone's queue or play it instantly. This requires a Netflix consumer key, which you can get for free by signing up for the NetFlix API, as well as the user to have a Netflix account.


//these are the Netflix account functions
var NFKEY = "YOURNETFLIXCONSUMERKEY";
function playInstant(movieID,movieStatus,theID){
var playThis = movieID.substring(45);
if(movieStatus == "true") {
nflx.openPlayer('http://api.netflix.com/catalog/movie/' + playThis, 0, 0, NFKEY);
	}else {
	document.getElementById("playStatus" + theID).innerHTML = "This movie is not available for instant play.";
	}
}

//Add this title to your NetFlix queue
function addQueue(movieID,theID){
var addThis = movieID.substring(45);
var getX = $(window).width()/2;
var getY = document.getElementById("playStatus" + theID).offsetTop;
getX = getX - 200;
getY = getY - 400;
nflx.addToQueue('http://api.netflix.com/catalog/movie/' + addThis, getX, getY, NFKEY, 'disc', 'addMe');
}

Opening the trailer

Since Netflix doesn't offer trailers in the oData API, what I did is take the title, then run a query to the YouTube API, and return the first result that has the title keywords, as well as the word, 'trailer'. Sometimes it works, sometimes it doesn't. YouTube returns a JSON file, which I then parse to get the ID, then display it via a Highslide overlay.


//View the Trailer from YouTube
function viewTrailer(movieName){
movieName = unescape(movieName);

//breaking the name up and getting rid of some known bullshit
//first, get rid of symbols/spaces
var ytURL = movieName.replace(/: /g, "/");

//now lets replace certain words
  var replace = new Array("Special ", "Edition", "Bonus ", "Material", "&", ","); 
  var by = new Array("", "", "", "", "&", ""); 
  for (var i=0; i<replace.length; i++) { 
     ytURL = ytURL.replace(replace[i], by[i]); 
  } 

//finally, lets replace the the spaces with encoded commas - %2C
ytURL = ytURL.replace(/\s/g, "%2C");

//append the url info to it
ytURL = "http://gdata.youtube.com/feeds/api/videos?category=trailer%2C" + ytURL + "&max-results=1&alt=jsonc&v=2";


//Now we get the XML from YouTube and Parse It
        // Make JSONP call to Netflix
        $.ajax({
            dataType: "jsonp",
            url: ytURL,
            jsonpCallback: "callbackyt",
            success: callbackyt,
	    error: errorMsg
        });


} // end viewTrailer


//this is the youtube view trailer callback
    function callbackyt(result) {
        // unwrap result
        var movies = result["data"]["items"];
	if(movies == null || movies == 0){
	alert('Sorry, no trailer is available for this title.');
	}else{
	//load the trailer
        for (var i = 0; i < movies.length; i++) {
        //call the youtube function
	openYouTube('http://www.youtube.com/v/' + movies[i].id + '&amp;hl=en_US&amp;fs=1');
		}
	}
    }



//this opens the youtube link in a highslide overlay
function openYouTube(opener) {
hs.preserveContent = false;
hs.htmlExpand(null, { 
		src: opener,
		objectType: 'swf', 
		objectWidth: 640, 
		objectHeight: 385, 
		width: 640, 
		swfOptions: { 
			params: { 
				allowfullscreen: 'true' 
			}
		}, 
		maincontentText: 'You need to upgrade your Flash player' 
	});
}

PHP Proxy for importing JSON data via cURL

You may find it necessary to use a proxy to import the data due to cross-domain restrictions. I found this out the hard way when Netflix suddenly dropped JSONP support and instead output the data as plain JSON, so I made this script to pull the data in and wrap my callback function name around it. There is a bunch of crap code in there to deal with certain characters in the querystring, but it works.


<?php
//getting the variables
$callback = $_GET['callback'];
$url=urldecode(stripslashes($_GET['url']));

//replacing the spaces with + signs - otherwise cURL throws a bad request
$url=str_replace(" ", "+", $url);

//replacing all # symbols
$url=str_replace("#", "%23", $url);


//single quotes in movie titles fuck everything up
//replacing all single quotes with two single quotes
$url=str_replace("'", "''", $url);

//replacing the first two single quotes back to one
$url=preg_replace("/''/", "'", $url, 1); 

//replacing the last two single quotes back to one
$lastq = strrpos($url, "''");
if($lastq != NULL || $lastq != "" || $lastq != FALSE || $lastq != "0"){
$url=substr_replace($url, "'", $lastq,2);
}

//now we must replace all amersands in the titles
$start_limiter = '(';
$end_limiter = ')';
$haystack = $url;
$start_pos = strpos($haystack,$start_limiter);
$end_pos = strpos($haystack,$end_limiter,$start_pos);

if ($start_pos != FALSE && $end_pos != FALSE)
{
$needle = substr($haystack, $start_pos+1, ($end_pos-1)-$start_pos);
$newneedle = str_replace("&", "%26", $needle);

//spitting new title, sans ampersand back into the url
$url = str_replace($needle, $newneedle, $url);
}


//outputting the json file
print_r($callback . '(' . get_data($url) . ')'); 
 
/* gets the data from a URL */
 
function get_data($url)
{
$ch = curl_init();
$timeout = 15;
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,$timeout);
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
?>


Have Fun!

The Netflix oData API is still in 'preview' mode, so it can be a little buggy, but is very cool to play around with. If you have any questions, comments, or random insults, feel free to email me at hal@halnesbitt.com.

Developer Resources