BT Nexus + Aframe = fibre-powered video collaboration

BT are today launching their new fibreoptic gigabit ethernet network designed for companies who need to send large video files over the web.

What's exciting for us is that they've chose to partner with Aframe for the launch. This is a big deal. In the past BT spent a long time building their own asset management system so they know a thing or two about selecting the best tools for the job!

So the idea is simple - take a BT Nexus gigabit line, add an Aframe account and you can start storing, transcoding, sharing and moving large broadcast video files from place to place and with any member of your production or archive team.

We've got big plans ahead particularly when gigabit connections become more prevalent - we will see TV productions happening entirely over distributed networks, so producers won't be tethered to the edit station at a particular time. This could be a huge thing for documentaries, reality TV, advertising, in fact it's hard to think of someone who works in TV that this wouldn't be useful for.

I think it's safe to say that when you add the power of a really fast connection to the simplicity and ease of use of Aframe exciting things start happening.

"Log it for me", "Tag it for me" - give us video and we give you metadata

One of the killer features we've been working on at Aframe is for people who are working on film or TV projects where there's just far too much media being produced to be able to make sense of it in any reasonable amount of time. 

We're working on a couple of interesting high profile projects at the moment, and in particular one where the client is producing several hours of rushes each day, more than could be watched by one person on the following day, let alone what could be reasonably digested and annotated by them in any reasonable amount of time. All productions employ loggers to add metadata to the media as it is produced. "At this timecode, the main character picks up a sword", or "Exterior. Night. A taxi passes by".

Unfortunately there's no way for a computer programme to 'watch' a piece of video and tell you what it contains (I did a fair bit of Artificial Intelligence at university). It's a human job, particularly when it comes to adding metadata about plot-lines, 'good shot / bad shot' and anything emotional.

Over the last year we've built a very slick tagging and logging service for anyone who has a lot of video and just wants it logged or annotated so they can search their video by text use it in the edit.

It's pretty neat:

  • Drop off some media at Aframe on tape, disk, drive, or upload it if you have a suitable connection
  • We securely store the media in its original format in two of our data centres
  • We transcode it so it can be viewed over a standard web connection
  • Every hour of footage is sent to a team of crack broadcast loggers in the North East of England
  • In _realtime_ they log the footage and also in realtime the logs are viewable on aframe.com. 1hr of media takes 1hr realtime to log, sometimes quicker if we are roughly tagging or summarising clips, so that's a guide.
  • You can then sort, arrange, search and share your footage using Aframe
  • Once you've made a collection of media you want to use in an edit, you select it and request an "Edit" resolution version be transcoded for you
  • You download these "edit proxies" (this can be set up to happen automatically) to your edit station
  • You edit as you would normally on these low resolution files using the logs that we've added to your footage as a guide
  • Once you're done, you upload the cut and download just the high res "Edit" media.
  • It magically relinks in your Avid and you render out the finished media
  • You can then drop that final version into Aframe and use it to send a large video file to someone via email

You can watch a demo video of this workflow as done by David Peto.

The core of this, aside from the brute force computer power we're putting behind this, is the quality of the logging, tagging and transcription we do. If you can find media then you can use it. If you can't even find it because you're generating too much (like on a reality TV show or on a multi-camera shoot) or it's poorly logged/tagged/indexed, it's going to be a time-consuming process to do the edit in the end, or find that particular moment you need to licence to a third party.

In effect, we're making media findable which when you think about how much video is floating around nowadays is no small task!

We're releasing this workflow to our first handful of clients at the moment. If you'd like to try it out drop me a line on Twitter.

Make sense of both sides of the argument - introducing Wrangl

Wrangl is a new service I've built that helps you visualise debates in an easy to read and navigate style.

In May, the UK will be having a referendum on the potential use of the Alternative Vote for choosing our MPs. I wasn't impressed with the level of the debate elsewhere that I found online, and I found it difficult to make sense of the various one-sided (and in some cases factually incorrect) arguments.

So, rather than having to follow hundreds of links, I used a little of my paternity leave (whilst looking after Max in a baby sling) to put together a tool that shows these arguments graphically. Like this:

Wrangl-av

Each of these arguments and counter-arguments is clickable, and displays the argument with a link and some text. Quite simple really, but for getting a quick overview of an argument it turns out it's quite useful. See it in action.

Anyone can create one of these using Wrangl. You sign in with one of your social profiles (twitter, facebook, etc.) and start a debate. You then fill in the arguments as best you know them for yes and no, and other people can contribute by adding arguments and counter arguments.

As a caveat - I build this in a weekend whilst the kids were sleeping, so in many places it's bare bones and very simple, but in many ways that adds to its usefulness. It just does one thing.

For those interested in the how to, this is a Sinatra application, hosted on Heroku. Markup is in HAML and styling in SCSS with Compass. The graphical display uses JQuery and Raphael.js

I'm hoping to apply this to other prominent debates and I've had a few conversations with people looking at democracy, local decision-making, government policy and for just working out what to do in a team ("Should we use open source or Windows for our new project?", "Should we relocate to San Francisco?", etc.)

I'm looking for people to collaborate with on this. If you have an idea of how it could be used, perhaps for a project you are working on, email or tweet me. If you have a debate you'd like to see visualised, either start your own debate, or let me know in the comments below and I'll see what I can do.

What debate would you visualise?

Culture Hack Day: CultureGrid hacks - A mobile app, JSONP API and a large image scraper

For the recent Culture Hack Day, I did a handful of small hacks around CultureGrid. It's an aggregated search on 80+ UK cultural institutions' archives of cultural artefacts, so you can use it to search across museums for 1.2m things - paintings, drawings, photographs, objects, locations, and so on. Quite the treasure trove!

They have an XML API and I wanted to do something using JQuery mobile aimed at providing a mobile interface to the database. So step 1 was to find a way to represent their XML as JSON for Backbone.js. I since found out that there's a way to retrieve JSON directly from their API but it's undocumented. So I wrote an XML parser and made it available as a JSONP proxy.

Try it: JSONP from the CultureGrid

Then I could start using that JSON in a mobile app. I've been playing with CoffeeScript recently (I've got a spare time mobile app I'm working on), and I'm loving it so far.

So, representing each of the results (they are called 'docs' in the XML) as a Backbone Model, adding a Backbone Controller to handle listing and showing the results, I quickly had a way to search the database on a mobile device.

Try it: Culturegrid mobile app

But one problem I found was that Culturegrid would supply me lots of information about something, but only give me a very small thumbnail of the result. If I wanted to see the painting that I was searching for, I'd have to follow a link to it and view it on the website of the institution itself. Not great for a mobile app. 

So, how do you scrape those images into the mobile app? All the sites are different, the resulting pages contain lots of other images and I don't want to write a scraper for every single site.

I came up with this solution - a generic scraper that learns over time. It has an ultra-simple API:

  • Fire it a web page.
  • It grabs the page and looks for images within the HTML
  • It weights the results by how many times it has seen each of the images (logos fall to the bottom of the rankings quite quickly)
  • It gets the outlier or if it can't find one, strips out the best five and then weights them by how many bytes long they are by doing a HEAD request against them.
  • It gets the best one it can find.
  • Then it caches the result for next time and stores the URLs of the images it has seen.
  • Then it serves the cached image via Varnish for subsequent requests.

Try it: Culture scraper

So, plugging in the scraper, I could add full size images to the mobile app. Total time probably 16 hours. All the code for these examples are available via the links above or on my Github.

What next? I'd love to apply some of this to a particular collection or archive, maybe look at what you can do with geolocation. Oh hang on - there's a hack day on February 18th to take some of these things and run with them...

Did I mention I'm a photographer? Anatomy of an HTML5/JQuery Image gallery

For the last few years I've been helping Emily develop her wedding photography business - doing the website, doing the graphics for the brand, helping sort out backups and online tools to make things work efficiently. She's got very successful all of a sudden and was on Channel 5's How to Take Stunning Pictures show at the end of last year.

I've also been behind the lens at a fair few weddings as 'second shooter', and Emily convinced me I should put up a portfolio of some of my work. It's not something I've really talked about online, just something I've been doing on the side, but when I went back through the thousands (literally - I often take 20-30GB of photos at a wedding) of images I thought I should pull a few out and put them online.

I spent a sleepy Sunday putting a site together, and I got a beta online in about 6 hours (I'll get a separate domain name for it once I can think of one):

View the site

My wedding photography portfolio. 

This is what it looks like:

Stefoto

My spec

I spent a long time looking around for a gallery system that did what I wanted. Here was my spec:

  • I don't want to host any images. Someone else can handle that. 
  • I want to be able to upload a folder of images and get their URLS back as JSON.
  • I want the hosting service to resize the images automatically to the same dimensions - about 1000 pixels on one side. I don't want to write code for this.
  • I don't want any Flash.
  • I want the images to appear full-screen, with no cluttery interface around the place.
  • I want each image to have its own URL on my web page.
  • I want to be able to scroll through the images using left/right clicking, arrow keys, space bar and mouse gestures.
  • It's got to look slick - nice big images, nice fade transitions.
  • No ads.
  • No licence fees to pay for the slideshow.
  • No thumbnails - I'm not bothered about them at the moment.
  • I want to work in HTML5 and Javascript with not very much server side.
  • If there's any server side it needs to be Ruby, not PHP or Coldfusion, etc.
  • I'd like JQuery because I know it.
  • I'd like the images to preload ad you browse through the gallery, so it feels nippy.
  • I don't want all the images to load at once on the home page.
  • It should look great on a small device as well as on the desktop, without requiring another website.

I looked around and found hundreds of articles on "Top 25 Jquery Image Galleries", and I tried a fair few out.

No luck with the existing galleries

Sorry to say, but I couldn't find anything that fitted my spec. They were either too complicated, too clicky-buttony, too ugly or too confusing.

So I threw out my initial testing code and started from scratch.

Image storage

I chose 23hq because it has a very simple API (none of the usual OAuth token dance here). You upload a bunch of photos to it, and then you can get 500 images back with one request as XML.

Ruby

Some server-side was required, so here's my Ruby code.

require 'open-uri'
                      require 'nokogiri'
                      require 'dalli'

# set ENV["TWENTY_THREE_HQ_USERNAME"], ENV["TWENTY_THREE_HQ_USERID"] and ENV["TWENTY_THREE_HQ_PASSWORD"]


class TwentyThreeHQ
                        
                        def self.photos
                          d = Dalli::Client.new
                          p = d.get("photos")
                          return p unless p.nil?
                          r = Nokogiri::XML(open("http://www.23hq.com/services/rest/?api_key=mydemo&method=people.getPublicPhotos&user_id=#{ENV["TWENTY_THREE_HQ_USERID"]}&per_page=500&extras=url_o&username=#{ENV["TWENTY_THREE_HQ_USERNAME"]}&password=#{ENV["TWENTY_THREE_HQ_PASSWORD"]}")).css("photo").collect{|a| "http://www.23hq.com/23666/#{a.attributes["id"]}_#{a.attributes["secret"]}_large1k.jpg" }
                          d.set("photos",r)
                          r
                        end
                        
                      end


So, what's this doing?

I use Heroku for my hosting, and it comes with a free Memcache server. This means you can store arbitrary stuff in a key value store that gets reset when you restart or redeploy the server. What I didn't want is to be hitting 23hq for a set of data that wouldn't change that often. So it stores an array of the photo URLS from the XML returned by 23HQ and returns it without having to hit the API every page load.

Serving the JSON

The next part is to serve this via JSON. I use Sinatra:

$:.unshift(File.expand_path(File.join(File.dirname(__FILE__),"lib")))
                      require 'sinatra'
                      require 'sinatra/jsonp'
                      require '23hq'

get '/' do @photos = TwentyThreeHQ.photos.shuffle haml :home end

get '/photos' do content_type :json jsonp TwentyThreeHQ.photos.shuffle end


So, after saving my class as lib/23hq I now I have two routes - a home page and a /photos route. The home page will be my gallery, and /photos will give me the URLs of the photos to display as JSON (or JSONP if I want it later).

Display the images

This is all one HTML5 page with some javascript. Here's what's in the toolbox:

  • I used Backbone.js, which gives me a Controller for the #!/photos/12345 routes. This means photos are bookmarkable, and navigation is performed by changing the hash in the URL.]
  • Underscore.js gives me access to some useful array and collection sorting utilities.
  • One top of that JQuery for animations and transitions, as well as a couple of plugins for mouse gestures and resizing images to the full available space.

Javascript

The resulting code:

// Array Remove - By John Resig (MIT Licensed)
                      Array.prototype.remove = function(from, to) {
                        var rest = this.slice((to || from) + 1 || this.length);
                        this.length = from < 0 ? this.length + from : from;
                        return this.push.apply(this, rest);
                      };

var Photos, Pages;

$(document).ready(function() { Photos = { urls: [], element: $("#photos"), preload_element: $("#preload"), frame: 0, img: null, animating: false, setup: function(data) { this.urls = data; this.element.css("text-align","center") this.img = $("").css("opacity",0).css("margin","auto").css("display","block"); this.element.append(this.img); this.preload_element.css("display", "none"); $(".pager").bind('mousewheel', function(event, delta) { Photos.scroll(delta); }); $("#left").bind('click', function(event) { Photos.prev(); }); $("#left").bind('mousemove', function(event) { Photos.prevIndicator(); }); $("#right").bind('mousemove', function(event) { Photos.nextIndicator(); }); $("#right").bind('click', function(event) { Photos.next(); }); $("nav").bind('mouseenter', function(event) { $(this).stop().animate({opacity: 0.9}); }); $("nav").bind('mouseleave', function(event) { $(this).stop().animate({opacity: 0.5}); }); $(document).bind('keyup',function(event) { switch(event.which) { case 8: // backspace case 33: // page up case 37: // left case 38: // up Photos.prev(); event.stopPropagation(); break; case 32: // space case 34: // page down case 39: // right case 40: // down Photos.next(); event.stopPropagation(); break; } }); this.element.disableTextSelect(); this.animate(); }, redirect: function(num) { parent.location.hash = '#!/images/' + this.urls[num].split("/").pop().split("_")[0]; $(".page").animate({opacity: 0},250); this.animate(); }, goto: function(id) { var image_id = id; var image_url = _.detect(this.urls, function(url) { return(url.indexOf(image_id + "_") != -1); }); this.frame = this.urls.indexOf(image_url); this.redirect(this.frame); }, next: function() { if(this.frame < this.urls.length-1) { this.preload(1); this.frame = this.frame + 1; this.redirect(this.frame); } else { this.first(); } }, prev: function() { if(this.frame > 0) { this.preload(-1); this.frame = this.frame - 1; this.redirect(this.frame); } else { this.last(); } }, last: function() { this.frame = this.urls.length-1; this.redirect(this.frame); }, first: function() { this.frame = 0; this.redirect(this.frame); }, preload: function(offset) { i = $("").attr("src", this.urls[this.frame+offset]); this.preload_element.append(i); }, scroll: function(delta) { if(!this.animating) { if(delta < 0) { Photos.next(); } if(delta > 0) { Photos.prev(); } } } , nextIndicator: function() { $("#next_indicator").stop().animate({opacity: 0.75},250, function(){ $(this).animate({opacity: 0},5000)}); }, prevIndicator: function() { $("#prev_indicator").stop().animate({opacity: 0.75},250, function(){ $(this).animate({opacity: 0},5000)}); }, resize: function() { this.img.aeImageResize({ height: $(window).height() - $("nav").height(), width: $(window).width() }); }, animate: function() { this.animating = true; this.img.stop(true) .animate({opacity: 0}, 250, function() { $(this).attr("src", Photos.urls[Photos.frame]) .aeImageResize({ height: $(window).height() - $("nav").height(), width: $(window).width() }) .error(function() { console.log("rescuing from 404"); Photos.urls.remove(Photos.frame); Photos.animate(); }) .load(function() { $(this).animate({opacity: 1},250, function() { Photos.animating = false; }) }); }); } }; $(window).resize(function() { Photos.resize(); }); Pages = Backbone.Controller.extend({

routes: { "!/home": "home", "!/about": "about", "!/pricing": "pricing", "!/where": "where", "!/contact": "contact", "!/philosophy": "philosophy", "!/images/:id": "image", }, image: function(id) { var i = Photos.goto(id); }, hide: function(callback) { $(".page").animate({opacity: 0},250, callback).css({display: "none", left: -20000}); }, swap: function(page_id) { var p = page_id; $("nav ul li a").removeClass("active"); $(page_id + "_show").addClass("active"); this.hide( function() { $(p).css({display: "block", left: "50%"}).animate({opacity: 1}, 250); }); }, home: function() { $("nav ul li a").removeClass("active"); this.hide(); }, about: function() { this.swap("#about"); }, pricing: function() { this.swap("#pricing"); }, contact: function() { this.swap("#contact"); }, where: function() { this.swap("#where"); }, philosophy: function() { this.swap("#philosophy"); } }); $.getJSON("/photos", function(data) { Photos.setup(data); Backbone.history.start(); }); new Pages(); });


I'm sure there are bugs in there, but this gives you a page with a clickable menu that shows overlaid pages of content over a clickable slideshow of images drawn from the /photos JSON. 

HTML5 and HAML

I use HAML for all of my HTML wrangling. It's much harder to do invalid HTML output using it, and it's very readable. Here's the main layout:

!!! 5
                      %html
                        %head
                          %meta{:content => "text/html; charset=utf-8", "http-equiv" => "Content-Type"}/
                          %meta{:name=>"viewport",:content=>"width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"}/
                          %meta{:name=>"apple-mobile-web-app-capable",:content=>"yes"}/
                          %meta{:name=>"apple-mobile-web-app-status-bar-style", :content=>"black-translucent"}/
                          %title Stef Lewandowski Photography
                          
                          \

%script{:src => "https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"} %script{:src => "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"} %script{:src => "/js/jquery.easing.1.3.js", :type => "text/javascript"} %script{:src => "/js/jquery.mousewheel.min.js", :type => "text/javascript"} %script{:src => "/js/jquery.disableselect.js", :type => "text/javascript"} %script{:src => "/js/underscore-min.js", :type => "text/javascript"} %script{:src => "/js/backbone-min.js", :type => "text/javascript"} %script{:src => "/js/jquery.ae.image.resize.min.js", :type => "text/javascript"} %link{:href=>'http://fonts.googleapis.com/css?family=Raleway:100', :rel=>'stylesheet', :type=>'text/css'}/ %link{:href=>"/stylesheets/screen.css", :media=>"screen, projection", :rel=>"stylesheet", :type=>"text/css"}/ %link{:href=>"/stylesheets/print.css", :media=>"print", :rel=>"stylesheet", :type=>"text/css"}/ \ \ %link{:media=>"only screen and (max-device-width: 480px)", :rel=>"stylesheet", :type=>"text/css", :href=>"/stylesheets/iphone.css"}/ \ %body %nav#main_navigation %h1 %a{:href=>"#!/home"} Stef Lewandowski %h2 Wedding photography %ul %li %a#about_show{:href=>"#!/about"} About %li %a#philosophy_show{:href=>"#!/philosophy"} Philosophy %li %a#pricing_show{:href=>"#!/pricing"} Pricing %li %a#where_show{:href=>"#!/where"} Where? %li %a#contact_show{:href=>"#!/contact"} Contact #photos #preload #left.pager #prev_indicator #right.pager #next_indicator #where.page %h1 Where %p Stef has a home in Harpenden, Hertfordshire near the Bedfordshire border, and works in Central London at Leicester Square tube. %p If you'd like to meet up for a chat, these two locations are a good starting point. %p He is available for weddings in the London and the South East of England. %p Thinking further afield? Please = succeed(".") do %a{:href=>"#!/contact"}= "get in touch" #pricing.page %h1 Simple pricing %h2 6 hours of photography %br A set of beautiful digital images %br £1500 %ul %li Your date is secured once a 20% deposit is paid, with the balance due the week before the wedding. %li I upload a selection of images from the day to your own online photo storage account, and/or give you a disk. %li Images are copyright free, so you are free to do with them as you please. %li Additional hours charged at £150 per hour. %li 1 hour's travel from London is included, additional travel charged at 40p/mile. %li Post-production, prints, cards, framing, canvas, albums, DVDs, photo books and lots more as additional options through = succeed(".") do %a{:href=>"http://emilyquinton.com"}= "Emily Quinton Photography" #contact.page %h1 Yes! I'd like to hear from you %p The best way to reach me is by email or Twitter. %h2 %a{:href=>"mailto:stef@emilyquinton.com"} stef@emilyquinton.com %h2 %a{:href=>"http://twitter.com/stef",:rel=>"me"}= "@Stef" on Twitter %p For existing clients, you can also contact %a{:href=>"http://emilyquinton.com"}="Emily Quinton" who handles post-production and after care for special purchases. #about.page %h1 Award-winning creativity %img{:src=>"/images/stef_square.png", :width=>100, :height=>100, :id=>"profile_photo"} %p Stef has won a clutch of awards for his creative work, and took to photography out of the love of making beautiful images. %p{:style=>"clear: both;"} When his parter, Emily Quinton's wedding business took off he found himself increasingly being involved as 'second shooter' at the larger weddings she was asked to photograph. It seemed to come naturally! %p Stef is represented by %a{:href=>"http://emilyquinton.com"} Emily Quinton Photography and available for bookings in 2011. #philosophy.page %h1 Philosophy? %br It's all about capturing the spirit of the event.

%p You'll hear people talking about 'reportage' and 'vintage' as what's currently in fashion, but the real thing that matters is that the feeling of the big day is captured for posterity. %p That's what Stef focusses on. Sometimes it's the little details that do it, sometimes it's an unexpected joke between friends, a teary eye or a look from father to daughter.

%p Stef tries to be there, anticipating when these small, fleeting things happen and to open and close the camera shutter just in time. %script{:src=> "/js/script.js", :type=>"text/javascript"}


The idea is that the page has all of the content in it, so I could have this as a static web application using a cache manifest if I wanted to.

Mobile and desktop

So, the site looked great in Safari on the OS X, but I also wanted to target handheld devices. You'll see in the code that if the device has a max size of 480px it gets a different stylesheet. Overriding my Compass SCSS sheet with an iphone one gives me a nice little app that works well in the browser and well in mobile Safari. Not so sure it looks good on a PC yet, but I'll try that out today.

Coffeescript?

Recently I've become a fan of Coffeescript, but I decided against it for this experiment. It would be easy to refactor this back into Coffeescript but for such a simple app it's probably not worth it.

Try it yourself

You can view and download all the code for this at https://github.com/stefl/stefoto

Culture Hack Day: Filmflexicon

Culture Hack Day ( http://culturehackday.org.uk ) was a weekend where people from the cultural industries with data to mash got together with software developers who wanted some data to mash into useful things. I did a few hacks - this is one that's actually quite useful.

Filmflexicon - it's a mobile web application that helps you choose what to watch on Virgin Media's on-demand movie service Filmflex. 

The great thing about Filmflex is that they have some great films on there. The bad thing is that the on-screen interface is slow to use, is structure in a way that doesn't help the user, and you have to choose each film to get a rough synopsis, which is repetitive and annoying. I usually sit there choosing a film and compare what's on offer with online ratings services like IMDB and Rotten Tomatoes.

So, Filmflexicon scrapes the listings from the Virgin Media website and then combines the films on offer with the ratings and reviews that Rotten Tomatoes has given each of them. It colorizes the results so you can see at a glance what's a good film and what isn't (I'll leave the definition of 'good' out of this...) and if you want to, you can view the full Rotten Tomatoes review for each one.

It's already saved me some time and I've found a couple of films I would otherwise have missed.

Try it out yourself on iPhone, Android and (probably) modern Blackberry, as well as just with a normal web browser. On iPhone you can save the app to your home screen using the Bookmark button and choose "Add to Home Screen".

My resolution: answer some questions on Stack Overflow

Stack-overflow

I can't count how many times I've pasted an error message into Google and arrived at Stack Overflow where some kind person has written a solution to the problem I'm having. It (and sites like it) have helped me gain an understanding of Ruby and helped me build all of the things I do in the language.

So - time to stop being a leech and start contributing. This year you'll find me answering questions about Ruby, Sinatra, maybe a bit of Rails, probably some jQuery, amongst other things.

There's a new stack overflow page on this site so you can all hold me to account if I don't do this :-)

Today's monster

Todays-monster

I regularly start the day with my daughter (4) asking me to draw a monster on the computer with her. 

She gets to decide shapes, arrangement, colours, scariness, "bigger eyes" and so on, and I put it all together in Illustrator. She's getting pretty good at Illustrator herself actually, and we did our christmas cards together this year.

Anyway - I might start putting a few of our monsters here for want of a better place! 

Here's today's - "Glump".