{"id":129,"date":"2010-08-24T04:41:26","date_gmt":"2010-08-24T08:41:26","guid":{"rendered":"http:\/\/halnesbitt.com\/blog\/?p=129"},"modified":"2010-08-24T04:49:49","modified_gmt":"2010-08-24T08:49:49","slug":"querying-netflix-odata-catalog-via-json-and-jquery","status":"publish","type":"post","link":"https:\/\/halnesbitt.com\/blog\/2010\/08\/24\/querying-netflix-odata-catalog-via-json-and-jquery\/","title":{"rendered":"Querying Netflix oData Catalog via JSON and jQuery"},"content":{"rendered":"<p><!--more--><\/p>\n<h4>What the Hell is This?<\/h4>\n<p>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&#8217;t really do anything that the regular Netlix site doesn&#8217;t do, but is a good way to learn how to snatch information from remote servers.<\/p>\n<h4>A Note on the Trailers<\/h4>\n<p>The trailers integrate with the YouTube Data API. Basically, this page sends a request based on the title and the word &#8216;trailer&#8217; 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 &#8220;20th Anniversary Edition&#8221;, 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 &#8216;Fight Club&#8217;, then it may not return the correct result, as the more popular video would be returned at the top of the list.<\/p>\n<p>You can get the code snippets below, or <a target=\"_blank\" href=\"http:\/\/www.halnesbitt.com\/pages\/netflix.php\">view a live demo<\/a>.<\/p>\n<h4>Create the initial movie query to Netflix oData<\/h4>\n<p>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.<\/p>\n<pre>\r\n<code>\r\n   function getodata() {\r\n        \/\/ Build OData query\r\n        var query = &quot;http:\/\/odata.netflix.com\/Catalog&quot; \/\/ netflix base url\r\n            + &quot;\/Titles&quot; \/\/ top-level resource\r\n            + &quot;?$filter=ReleaseYear le 2010 and ReleaseYear ge 1990 and AverageRating gt 4&quot;  \/\/ filter by year and rating\r\n\t    + &quot;&amp;$orderby=ReleaseYear,ShortName desc&quot; \/\/ the order\r\n            + &quot;&amp;$format=json&quot;; \/\/ json request\r\n \r\n\t    \/\/URLEncode the query - this is necessary for the proxy\r\n\t    query = $.URLEncode(query);\r\n \r\n        \/\/ Make JSONP call to Netflix\r\n        $.ajax({\r\n            dataType: &quot;jsonp&quot;,\r\n            url: 'jsonproxy.php?url=' + query,\r\n            jsonpCallback: &quot;callback&quot;,\r\n            success: callback,\r\n\t    error: errorMsg\r\n        });\r\n  }\r\n \r\n<\/code>\r\n<\/pre>\n<h4>Processing the JSON response<\/h4>\n<p>This is the callback function that is called with the JSON is returned. It parses it out and outputs it to an html div.<\/p>\n<pre>\r\n<code>\r\n     function callback(result) {\r\n        \/\/ unwrap result\r\n        var movies = result[&quot;d&quot;][&quot;results&quot;];\r\n\tif(movies == null || movies == 0){\r\n\talert('Error loading title. Try the search.');\r\n\t}else{\r\n        \/\/ show movies in template\r\n        var showMovie = tmpl(&quot;movieTemplate&quot;);\r\n        var html = &quot;&quot;;\r\n        for (var i = 0; i &lt; movies.length; i++) {\r\n            \/\/ flatten movie\r\n            movies[i].BoxArtSmallUrl = movies[i].BoxArt.LargeUrl;\r\n\t    \/\/escape the short title for passing to functions - takes care of single quotes\r\n\t    movies[i].ShortName = escape(movies[i].ShortName);\r\n            \/\/ render with template\r\n            html += showMovie(movies[i]);\r\n       \t\t}\r\n        $(&quot;#movieTemplateContainer&quot;).html(html);\r\n\t}\r\n    }\r\n<\/code>\r\n<\/pre>\n<h4>Displaying the Results in HTML<\/h4>\n<p>This takes the responses and displays them in a template &#8216;{%= tag %}&#8217;, via Microtemplates.js.<\/p>\n<pre>\r\n<code>\r\n&lt;div id=&quot;movieTemplateContainer&quot;&gt;&lt;\/div&gt;\r\n \r\n&lt;script id=&quot;movieTemplate&quot; type=&quot;text\/html&quot;&gt;\r\n    &lt;div class=&quot;titleholder&quot;&gt;\r\n\t&lt;div class=&quot;boxartholder&quot;&gt;\r\n        &lt;img src=&quot;{%=BoxArtSmallUrl %}&quot; \/&gt;\r\n\t&lt;p&gt;&lt;a href=&quot;javascript:addQueue('{%=NetflixApiId%}','{%=Id%}');&quot;&gt;Add to Queue&lt;\/a&gt;&lt;\/p&gt;\r\n\t&lt;p&gt;&lt;a href=&quot;javascript:playInstant('{%=NetflixApiId%}','{%=Instant.Available%}','{%=Id%}');&quot;&gt;Play Instantly&lt;\/a&gt;&lt;\/p&gt;\r\n\t&lt;\/div&gt;\r\n\t&lt;div class=&quot;synopsisholder&quot;&gt;\r\n        &lt;strong&gt;{%=Name%}&lt;\/strong&gt;&lt;br \/&gt;\r\n\t({%=ReleaseYear %}) {%=Rating %}\r\n        &lt;p&gt;{%=Synopsis %}&lt;\/p&gt;\r\n\t&lt;p&gt;Average Rating: {%=AverageRating %}&lt;\/p&gt; \r\n\t&lt;p&gt;&lt;a onclick=&quot;viewTrailer('{%=ShortName%}');&quot; href=&quot;javascript:void(0);&quot;&gt;View Trailer&lt;\/a&gt;&lt;\/p&gt;\r\n\t&lt;\/div&gt;\r\n\t\r\n\t&lt;br style=&quot;clear: both;&quot; \/&gt;\r\n \r\n\t&lt;span id=&quot;playStatus{%=Id%}&quot;&gt;&lt;\/span&gt;\r\n\t\r\n\t&lt;span id=&quot;trailerStatus&quot;&gt;&lt;\/span&gt;\r\n \r\n    &lt;\/div&gt;\r\n\t\r\n&lt;\/script&gt;\r\n<\/code>\r\n<\/pre>\n<h4>Using the Netflix functions<\/h4>\n<p>You can easily create links to add a title to someone&#8217;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.<\/p>\n<pre>\r\n<code>\r\n\/\/these are the Netflix account functions\r\nvar NFKEY = &quot;YOURNETFLIXCONSUMERKEY&quot;;\r\nfunction playInstant(movieID,movieStatus,theID){\r\nvar playThis = movieID.substring(45);\r\nif(movieStatus == &quot;true&quot;) {\r\nnflx.openPlayer('http:\/\/api.netflix.com\/catalog\/movie\/' + playThis, 0, 0, NFKEY);\r\n\t}else {\r\n\tdocument.getElementById(&quot;playStatus&quot; + theID).innerHTML = &quot;This movie is not available for instant play.&quot;;\r\n\t}\r\n}\r\n \r\n\/\/Add this title to your NetFlix queue\r\nfunction addQueue(movieID,theID){\r\nvar addThis = movieID.substring(45);\r\nvar getX = $(window).width()\/2;\r\nvar getY = document.getElementById(&quot;playStatus&quot; + theID).offsetTop;\r\ngetX = getX - 200;\r\ngetY = getY - 400;\r\nnflx.addToQueue('http:\/\/api.netflix.com\/catalog\/movie\/' + addThis, getX, getY, NFKEY, 'disc', 'addMe');\r\n}\r\n<\/code>\r\n<\/pre>\n<h4>Opening the trailer<\/h4>\n<p>Since Netflix doesn&#8217;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, &#8216;trailer&#8217;. Sometimes it works, sometimes it doesn&#8217;t. YouTube returns a JSON file, which I then parse to get the ID, then display it via a Highslide overlay.<\/p>\n<pre>\r\n<code>\r\n\/\/View the Trailer from YouTube\r\nfunction viewTrailer(movieName){\r\nmovieName = unescape(movieName);\r\n \r\n\/\/breaking the name up and getting rid of some known bullshit\r\n\/\/first, get rid of symbols\/spaces\r\nvar ytURL = movieName.replace(\/: \/g, &quot;\/&quot;);\r\n \r\n\/\/now lets replace certain words\r\n  var replace = new Array(&quot;Special &quot;, &quot;Edition&quot;, &quot;Bonus &quot;, &quot;Material&quot;, &quot;&amp;&quot;, &quot;,&quot;); \r\n  var by = new Array(&quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&amp;&quot;, &quot;&quot;); \r\n  for (var i=0; i&lt;replace.length; i++) { \r\n     ytURL = ytURL.replace(replace[i], by[i]); \r\n  } \r\n \r\n\/\/finally, lets replace the the spaces with encoded commas - %2C\r\nytURL = ytURL.replace(\/\\s\/g, &quot;%2C&quot;);\r\n \r\n\/\/append the url info to it\r\nytURL = &quot;http:\/\/gdata.youtube.com\/feeds\/api\/videos?category=trailer%2C&quot; + ytURL + &quot;&amp;max-results=1&amp;alt=jsonc&amp;v=2&quot;;\r\n \r\n \r\n\/\/Now we get the XML from YouTube and Parse It\r\n        \/\/ Make JSONP call to Netflix\r\n        $.ajax({\r\n            dataType: &quot;jsonp&quot;,\r\n            url: ytURL,\r\n            jsonpCallback: &quot;callbackyt&quot;,\r\n            success: callbackyt,\r\n\t    error: errorMsg\r\n        });\r\n \r\n \r\n} \/\/ end viewTrailer\r\n \r\n \r\n\/\/this is the youtube view trailer callback\r\n    function callbackyt(result) {\r\n        \/\/ unwrap result\r\n        var movies = result[&quot;data&quot;][&quot;items&quot;];\r\n\tif(movies == null || movies == 0){\r\n\talert('Sorry, no trailer is available for this title.');\r\n\t}else{\r\n\t\/\/load the trailer\r\n        for (var i = 0; i &lt; movies.length; i++) {\r\n        \/\/call the youtube function\r\n\topenYouTube('http:\/\/www.youtube.com\/v\/' + movies[i].id + '&amp;amp;hl=en_US&amp;amp;fs=1');\r\n\t\t}\r\n\t}\r\n    }\r\n \r\n \r\n \r\n\/\/this opens the youtube link in a highslide overlay\r\nfunction openYouTube(opener) {\r\nhs.preserveContent = false;\r\nhs.htmlExpand(null, { \r\n\t\tsrc: opener,\r\n\t\tobjectType: 'swf', \r\n\t\tobjectWidth: 640, \r\n\t\tobjectHeight: 385, \r\n\t\twidth: 640, \r\n\t\tswfOptions: { \r\n\t\t\tparams: { \r\n\t\t\t\tallowfullscreen: 'true' \r\n\t\t\t}\r\n\t\t}, \r\n\t\tmaincontentText: 'You need to upgrade your Flash player' \r\n\t});\r\n}\r\n<\/code>\r\n<\/pre>\n<h4>PHP Proxy for importing JSON data via cURL<\/h4>\n<p>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.<\/p>\n<pre>\r\n<code>\r\n&lt;?php\r\n\/\/getting the variables\r\n$callback = $_GET['callback'];\r\n$url=urldecode(stripslashes($_GET['url']));\r\n \r\n\/\/replacing the spaces with + signs - otherwise cURL throws a bad request\r\n$url=str_replace(&quot; &quot;, &quot;+&quot;, $url);\r\n \r\n\/\/replacing all # symbols\r\n$url=str_replace(&quot;#&quot;, &quot;%23&quot;, $url);\r\n \r\n \r\n\/\/single quotes in movie titles fuck everything up\r\n\/\/replacing all single quotes with two single quotes\r\n$url=str_replace(&quot;'&quot;, &quot;''&quot;, $url);\r\n \r\n\/\/replacing the first two single quotes back to one\r\n$url=preg_replace(&quot;\/''\/&quot;, &quot;'&quot;, $url, 1); \r\n \r\n\/\/replacing the last two single quotes back to one\r\n$lastq = strrpos($url, &quot;''&quot;);\r\nif($lastq != NULL || $lastq != &quot;&quot; || $lastq != FALSE || $lastq != &quot;0&quot;){\r\n$lastq = $lastq-1;\r\n$url=substr_replace($url, &quot;'&quot;, $lastq,2);\r\n}\r\n \r\n\/\/now we must replace all amersands in the titles\r\n$start_limiter = '(';\r\n$end_limiter = ')';\r\n$haystack = $url;\r\n$start_pos = strpos($haystack,$start_limiter);\r\n$end_pos = strpos($haystack,$end_limiter,$start_pos);\r\n \r\nif ($start_pos != FALSE &amp;&amp; $end_pos != FALSE)\r\n{\r\n$needle = substr($haystack, $start_pos+1, ($end_pos-1)-$start_pos);\r\n$newneedle = str_replace(&quot;&amp;&quot;, &quot;%26&quot;, $needle);\r\n \r\n\/\/spitting new title, sans ampersand back into the url\r\n$url = str_replace($needle, $newneedle, $url);\r\n}\r\n \r\n \r\n\/\/outputting the json file\r\nprint_r($callback . '(' . get_data($url) . ')'); \r\n \r\n\/* gets the data from a URL *\/\r\n \r\nfunction get_data($url)\r\n{\r\n$ch = curl_init();\r\n$timeout = 15;\r\ncurl_setopt($ch,CURLOPT_URL,$url);\r\ncurl_setopt($ch,CURLOPT_RETURNTRANSFER,1);\r\ncurl_setopt($ch,CURLOPT_CONNECTTIMEOUT,$timeout);\r\n$data = curl_exec($ch);\r\ncurl_close($ch);\r\nreturn $data;\r\n}\r\n?&gt;\r\n \r\n<\/code>\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,1],"tags":[],"class_list":["post-129","post","type-post","status-publish","format-standard","hentry","category-computer-stuff","category-show-all"],"_links":{"self":[{"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/posts\/129","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/comments?post=129"}],"version-history":[{"count":6,"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/posts\/129\/revisions"}],"predecessor-version":[{"id":135,"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/posts\/129\/revisions\/135"}],"wp:attachment":[{"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/media?parent=129"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/categories?post=129"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/halnesbitt.com\/blog\/wp-json\/wp\/v2\/tags?post=129"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}