Make Your Own Instavision

Recently I built the backend for the Instavision TV and wanted to give a quick tutorial on how to build one yourself. Brian Fouhy, George Tong, Jesse Weaver and Pedro Sorrentino actually made this TV a reality.

Materials

  • Retro looking TV
  • VGA to Coaxial adapter
  • Old Laptop
  • Instagram account
  • Instagram API Client ID, Client Secret and an Access Token
  • Node.js
  • Shell access to your server

Getting Started…

The first step is to get the backend working. Register a new client on the Instagram API, just register for the url you’ll be using to setup your feed.

Next you need to store an access token to use later on in our node.js file. Once you have your Client registered with Instagram, simply input the following into your browser’s url (replacing with your newly registered client ID and your specific redirect URI):

https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token

Once you visit this page, simply login with your instagram account credentials and you should be redirected. You don’t necessarily need to have your redirect URI do anything when you land on it, what your interested in is the access token.

Copy this access token and save it for later, we’ll use it to gain access to Instagram and pull information for our TV to display.

Coding…

Lets get our code up and running. The basic idea is to have a client side page calling our server about every 5 seconds to get an update on the most recent photo. We’ll then also need something on our server to go out and get the most recent photos from Instagram, this script will also run about every 5 seconds. It would be hard to do this with PHP, sleep() and a chron job…so we’ll turn to node.js. Copy the following script and put it in the root of your public_html folder (make sure you replace your access token with the access token you got in the previous step also, you’ll need to make sure and replace all paths and urls with your own custom paths and domains).

/*
	INSTRUCTIONS

	shell: cd /home/instavis/public_html/
	shell: node instavision.js
	shell: kill ##### (process number)

	call the url: http://instavision.tv:8080/feed.html

	Needs:
			find out rate limit with instagram = 5000 requests per hour
*/
var sys = require("sys"),
    http = require("http"),
    https = require("https"),
    url = require("url"),
    path = require("path"),
    fs = require("fs"),
    util = require("util"),
    events = require("events");  

function load_static_file(uri, response) {
    var filename = path.join(process.cwd(), uri);
    path.exists(filename, function(exists) {
        if(!exists) {
            response.writeHead(404, {"Content-Type": "text/plain"});
            response.write("404 Not Foundn" + " filename: " + filename);
            response.end();
            return;
        }  

        fs.readFile(filename, "binary", function(err, file) {
            if(err) {
                response.writeHead(500, {"Content-Type": "text/plain"});
                response.write(err + "n");
                response.end();
                return;
            }  

            response.writeHead(200);
            response.write(file, "binary");
            response.end();
        });
    });
}

//var twitter_client = http.createClient(80, "api.instagram.com");  

var tweet_emitter = new events.EventEmitter();  

function get_tweets() {
    //var request = twitter_client.request("GET", "/v1/tags/bdwinstavision/media/recent?access_token=3036112.f59def8.4708bbe9fe9e43639c8d9fe93c5c41df", {"host": "api.instagram.com"}); 

    var options = {
    // public search - unauthenticated
	//host: 'api.instagram.com',
	//path: '/v1/tags/bdwinstavision/media/recent?access_token=3036112.f59def8.4708bbe9fe9e43639c8d9fe93c5c41df',

	// authenticated feed - https://api.instagram.com/v1/users/self/feed?access_token=3036112.c90c34d.411d18988857490c9032a2bac30e6943

	// instavisionTV access token
	// access_token = 3036112.c90c34d.411d18988857490c9032a2bac30e6943

	// instavision access token
	// access_token = 3060511.0b03a1e.fff65e339b694cf0844306131ab82b97
	host: 'api.instagram.com',
	path: '/v1/users/self/feed?access_token=3060511.0b03a1e.fff65e339b694cf0844306131ab82b97',
    } 

    https.get(options, function (res) {
		var raw = "";
		res.on('data', function (chunk) {
		    raw += chunk;
		});
		res.on('end', function () {
		    var response = JSON.parse(raw);

		   //sys.puts(response); 

		    if(raw.length > 0) {
	/*         	sys.puts("tweets:" + response);  */
	            tweet_emitter.emit("tweets", response);
	        }
	        //res.removeListener('connection', callback);
		});
    }).on('error', function(e) {
	  //console.error(e);
	  sys.puts(e);
	});

    /*request.addListener("response", function(response) {
        var body = "";
        response.addListener("data", function(data) {
            body += data;
            sys.puts("tweets:" + data);
        });  

        response.addListener("end", function() {
            var tweets = JSON.parse(body);
            sys.puts(body);
            if(tweets.length > 0) {
            	//sys.puts("tweets:" + tweets);
                tweet_emitter.emit("tweets", tweets);
            }
        });
    }); */ 

    //request.end();
}

setInterval(get_tweets, 5000);

http.createServer(function(request, response) {
    var uri = url.parse(request.url).pathname;
    //var uri = request.url;

    var callback = function(stream) {
	  sys.puts('listener removed!');
	};

    if(uri === "/stream") {  

        //var listener = tweet_emitter.addListener("tweets", function(tweets) {
        var listener = tweet_emitter.on("tweets", function(tweets) {
            response.writeHead(200, { "Content-Type" : "text/plain" });

            response.write(JSON.stringify(tweets));

            //sys.puts("stringify tweets:" + JSON.stringify(tweets));

            response.end();

            //var listenersForTeets = tweet_emitter.listeners(listener);

        	//sys.puts(util.inspect(listener));

        	//tweet_emitter.removeListener(tweets);
        	//tweet_emitter.removeListener(tweet_emitter);
        	//tweet_emitter.removeListener(listener);
        	//tweet_emitter.removeListener("tweets");
  			//tweet_emitter.removeListener("listener");
  			//tweet_emitter.removeListener("tweet_emitter");

  			listener.removeAllListeners("tweets");

            clearTimeout(timeout);  

        });

        //tweet_emitter.removeListener(tweets);

        //sys.puts(util.inspect(tweet_emitter.listeners(listener), true, null));

        var timeout = setTimeout(function() {
            response.writeHead(200, { "Content-Type" : "text/plain" });
            response.write(JSON.stringify([])); 

            response.end();  

            tweet_emitter.removeListener(listener);
        }, 10000); 

    }
    else {
        load_static_file(uri, response);
    }
}).listen(8080);  

sys.puts("Server running at http://instavision.com:8080/");

Note: I am pulling a user feed in this case, you can also do a search for specific photos and other users’ feeds if you call different urls. For more information read the Instagram API.

Once you have this copied and named instavision.js in your root, you need to make an html page with a special script to call instavision.js and get the most recent photos. Copy the following and save this into a file named feed.html, also in the root of your public_html folder:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link href="css/styleDark.css" rel="stylesheet" type="text/css">
<title>Instavision TV</title>
</head>

<body style="">
    <div id="wrap">

    	<div id="tweets"></div>
    </div>

	<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
	<script type="text/javascript">
	    var tweet_list = $("#tweets");
	    var tweet_caption = $("#caption"); 

	    function load_tweets() {
	        $.getJSON("/stream", function(tweets) {
	            var imageurl = tweets.data[0].images.standard_resolution.url;
	            var imageUser = tweets.data[0].user.username;
	            var imageCaption = '';
	            if (tweets.data[0].caption != null){
	            var imageCaption = tweets.data[0].caption.text;
	            }

	            console.log(imageUser);
	            console.log(imageCaption);
	            imageurl = "<img id='tweet' src='"+imageurl+"' /><br/>";
	            imageCaption = "<div id='caption'><img id='captionImage' src='/images/ColorBars.png' height='18px' width='29px'/><h1>"+imageUser+"</h1><h3>"+imageCaption+"</h3></div>";
	            console.log(imageurl);
	            $(tweet_list).html(imageurl + imageCaption);
	            load_tweets();
	        });
	    }  

	    setTimeout(load_tweets, 5000); 

	    $(document).ready(function() {
	    	load_tweets();
	    });
	</script>

</body>
</html>

Once you have feed.html saved, you can try it out! You have to run the instavision.js script as a process on your server, simply run these commands to get it running:

cd /home/instavis/public_html
node instavision.js&

Make sure you have node installed. If you find that you want this process to stop, look at its process ID and kill it by:

kill 76234

Once you have the process running, visit the feed.html page at port 8080 (place the url with your custom url):

http://your-custom-domain.com:8080/feed.html

If it worked, then you’re good to go! If it didn’t, leave a comment below and I’ll try to help out!

Once you have it up and running, its simply a matter of plugging the TV in and formatting the image, which is totally a case by case situation and thus beyond the scope of this tutorial.

Kinect Hacking with Schiffman – Part 1

In this tutorial you’ll find out how to:

  1. Install processing on your mac.
  2. Install a library to use with our X-Box Kinect.
  3. Run some example programs to start playing with the Kinect’s output.

The goal is to keep this tutorial as simple as possible.

Requirements…

You’ll need:

  • Microsoft XBox kinect with a wall unit power adapter with usb connection.
  • Unix based operating system (if your running windows, then you’ll have to run a virtual machine – try a live cd with your favorite linux distro)
  • Processing Language Installation (don’t worry if you don’t have it yet, well get to that)
  • Schiffman Kinect Library (Again, if you don’t have it, read on)

Getting Started…

First we’ll need the processing language. Go to http://processing.org/download/ and click on the install for your operating system. Install it like a regular application. Once installed processing works by running a custom editor where you can write new scripts or run example scripts. If you’re on a mac running apple’s OS then you can expect processing to automatically create a folder called “Processing” in your user’s “Documents” folder. Inside the “Processing” folder there should be two other folder labeled “sketches” and “library.” The “sketches” folder is where you’ll store all of your custom projects. The “library” folder is where you’ll store all the libraries you download and subsequently where we’ll store our schiffman library for the Kinect.

Getting Schiffman…

Now that you have processing installed it’s time to get the schiffman library. Let’s grab the latest distribution here: https://github.com/shiffman/libfreenect/tree/master/wrappers/java/processing/distribution

Once you’ve downloaded the master branch, unzip it and navigate to “wrappers” -> “java” -> “processing” -> “distribution” -> “openkinect”. Grab “openkinect” and drag it into your “Documents” -> “Processing” -> “libraries” folder. That’s it! Processing automatically knows where to look when it looks for libraries so you just need to include it with your scripts in order to make it run!

Connecting the Kinect…

Now that we have processing and our library it’s time to hook up the kinect and get started hacking! Simply plug in your kinect to any USB port and navigate to “Documents” -> “Processing” -> “libraries” -> “openkinect” -> “examples”, inside this directory are 4 (at the current schiffman library version) examples to chooose from. Simply click into one of the example folders and double click the “filename.pde” to run it in processing. You can also right click and “open with” then choose processing.

Running a script…

Processing interprets code at run time so as soon as you press the “play” button at the top left hand of the processing environment it will either compile the script or let you know you have an error. When you run the script you should see a small window appear. If you run one of the example scripts, you should see a window with live data from the Kinect. I chose to run the “Point Cloud” script.

You can see that it’s working and a point cloud of depth data is appearing on the screen. From here we can copy and paste this code into a new script. Rename and save your new script in “Documents” -> “Processing” -> “sketches” and your free to start making changes to the script itself. Don’t be afraid to mess things up, you can simply copy from the examples and start over if you do!

Up next…

In the second part of this series we’ll talk about modifying some of these scripts to do certain things. In the end we’ll end up using our XBox Kinect as a 3D scanner that can output an .stl file made for a 3D printer. This will allow us to scan things and print them in 3D! Stay tuned.

Photo Study: Light

Amazing light in the kitchen this morning so I decided to whip out the ol’ D-90 and try to get better at photography! I just wanted to study the light, specifically, what adjustments I could make to alter the way the image appears. I wanted to capture the atmosphere of the room and play with the intensity of the lighting:

Lessons learned, this photo would have been perfect but I wasn’t watching my background and managed to get a bunch of beer bottles in the frame…dangit!

This picture had good balance lightwise, but I couldn’t manage to filter out the shadows on the wall, kind of distracting I think…

After Not being able to remove the shadows from the wall I altered the f-stop and ISO settings, gave it a slower shutter speed and managed to amp up the light…but I still caught those damn shadows!

Here I boost the light too much, a really low f-stop, low light sensitivity but a really long shutter speed make this photo way too dramatic.

Finally….

This was one of the first shots I took before I started playing with lighting. At the very least, I learned to watch my backgrounds more closely.

John B. Winsor

I tweaked WordPress a bit and built a custom theme for John, a writer, who required a very intuitive backend interface. John’s goals were to crowdsource a novel and needed something more than a simple website/blog. He had a social media plan but also needed users to be able to submit their portions of a story that would be later published as a crowd sourced novel.

His target market are current/aspiring writers age 55 and up. He wanted a space where he could give advice, blog about writing/storytelling and allow other writers to submit their short stories. Among other things, he also wanted to promote his books on amazon.

Site design: Chris Znerold

Social Media: Denise Horton

Hosting: knownhost

Backend CMS: WordPress

Gluten Free Pantry

I made this site for my girlfriend to blog about gluten free living. She about everything from gluten free restaurant reviews and products to recipes and even health related products like skin care. She loves the site and the design works quite well with the overall tone.

Design: ivanov

Backend: WordPress

Hosting: Knownhost

Alice and Will get Hitched!

I was delighted when my good friend Alice asked me to do her wedding website. Of course I did it for free (what are friends for?). I chose a simple design to draw attention to these two lovebirds. The colors are more traditional Hawaiian (Since Alice is a native) and the font was chosen to convey a simple and fun aura. It was also a great learning experience.

I found out quickly that not building a backend content management system was a bad idea. Since launch, I have spent many hours doing back end maintenance to the site that could easily be done by the wedding party. In the future, I will remember to build a backend interface so I don’t have to manage content.

I also used this site to experiment with a bunch of jQuery plugins like sliders, lightboxes and timers. I feel all the technology I used were pertinent and add functionality rather than adding superfluous design elements and interaction. Additionally, all the information on the site exists in a single page served by several backend php functions, so it has wonderful SEO!

Design: Mike Newell & Indonez

Backend: Custom

Hosting: Knownhost

High Mountain Garden Club

My girlfriend belongs to a local gardening club and they wanted to have a website for newsletters, information, blog posts about their gardens and bylaws. However, they didn’t have a budget for the website and didn’t know anyone who would make it and host it for free. I offered to make them a website as a way to help give back to the community.

The site is fairly straight forward and took minimal theme alteration to allow the garden club members to modify and update their site.

Design: Mladjo

Backend: WordPress

Hosting: Knownhost

Targhee Mobile Site

I had the opportunity to make a mobile site for Grand Targhee Ski Resort. The experience taught me not just about making site for mobile browsers, but about how our user experience of the web is changing. We expect to have information available to us in a friendly format almost anywhere!

I was prompted to make a mobile site after I had tried to access the regular Grand Targhee site from my phone as I was driving to work one day. I wanted to check how much snow we had to see whether it was worth turning around to go back and get my snowboard. After waiting a while for the regular Targhee site to load I gave up and just continued on to work. When I got to the hill, there was about a foot of light fluffy powder on the ground. I was so mad that I vowed Targhee would have a mobile version with the most valuable information on it within a week.

It turns out mobile sites are their own animals, I wrote Targhee’s mobile site without any javascript, and a very light version of valid xhtml + CSS. I paid extra attention to making the site as light and quick to load as possible, so even in the mountainous regions of the Tetons, one can still check Targhee’s powder scores!

Ride Habitat

I built this site for Grand Targhee Ski Resort after they acquired Habitat (a lifestyle gear company) in Driggs, Idaho. Habitat had recently moved to Driggs, ID from Victor, ID and the goal was to promote its new store location and to showcase their new selection of good.

The idea was to create a platform for the employees to blog about new gear, local town happenings and give advice on everything from ski repair to mountain bike maintenance. They needed a custom designed theme with a backend interface that would accommodate beginning bloggers.

Design: Maximus

Backend: WordPress

Grand Targhee Ski Resort

I worked for Grand Targhee Ski Resort as an Interactive Marketing Manager. Part of my responsibility was social media and interactive web campaigns. I also managed Targhee’s website, created media content and was in charge of development of a new content management system for the resort. One that would ultimately link Targhee’s POS System directly to the web in order to produce a more responsive web environment where users could book lodging and buy tickets directly on the site as well as write reviews and request information.

I even snapped a couple of the photos on their home page!