Fri, 12 Feb 2010

Quicktime Blog Autoposter

One of the difficulties busy people face with technology is sharing their digital content. Camcorders and digital cameras typically require some kind of digital workflow. Capturing movies in iMovie, editing them down, exporting them into web-optimized files, all of this is time consuming and busy people simply don't make time to do all of that.

This is a key reason I'm in love with my iPhone 3GS. It's been said that "the best camera is the one you have with you" and considering I generally keep my phone with me at all times, it's a great platform for capturing both pictures and video.

A while back I posted a script I use to pull a feed of images from Flickr to post photos to my blog. Today I'm sharing a script that does the same but for movies.

I approached the problem by considering what the iPhone offers out of the Camera app. Specifically, it allows the export of movies you capture with your iPhone via YouTube and via Email.

Accessing the content from YouTube seemed a bit closed and non-portable, and I wasn't sure whether the video quality would be compromised if YouTube were to re-encode the video from Quicktime to a FLV.

So I figured I would set up a free email box and simply email movies from Camera.app to that mailbox. Now the challenge was to poll that email address for messages, parse the MIME content for video/quicktime parts, save the movie to a path on my blog host and finally create a post pointing to that asset. To prevent anyone from sending images to your blog, you should keep the address you choose a secret and make it hard to guess what it is.

I'm sharing that script here for anyone who'd like to use it.
#!/usr/bin/perl # Author: Khan Klatt # Released under the GNU GPL. (http://www.gnu.org/copyleft/gpl.html) # http://www.khan.org/blog/cronmovies use strict; use DateTime; use Image::ExifTool; use IO::Socket::SSL; use Mail::POP3Client; use MIME::Parser; # Date Constants my $dt= DateTime->now(time_zone => "America/Los_Angeles"); #my $global_date = time() - 36000000; # (to kickstart first post when no videos recently posted) my $global_date = time() - 3600; # One Hour Ago # POP Constants my $username = 'SECRET@SOMEMAILHOST.COM'; # Needs to support POP3 access. my $password = 'P@$$\/\/0Rd'; my $mailhost = 'POP.MAILHOST.COM'; my $port = '995'; # Path Constants my $homedir = '/home/YOU/'; my $blogdir = "$homedir" . ".blog"; my $webhome = "public_html"; my $moviepath = "movies"; my $moviedir = "$homedir" . "$webhome/$moviepath"; my $webmoviepath = "/$moviepath"; # Post Constants my $postfilename = $blogdir . "/qtmov" . $global_date . ".txt"; my $prettydate = $dt->month_abbr. " " . $dt->day . ", " . $dt->year . " " . $dt->hour_12 . lc($dt->am_or_pm) . "\n"; my $debug = 0; my $pop = new Mail::POP3Client( USER => $username, PASSWORD => $password, HOST => $mailhost, PORT => $port, USESSL => 'true', DEBUG => 0 ); if (($pop->Count()) < 1) { print "No messages...\n" if $debug; exit; } print $pop->Count() . " messages found!\n\n" if $debug; my $movieposted; my $post = qq[My Recent Videos, as of $prettydate\n<div class="movies">\n]; for (my $i = 1; $i <= $pop->Count(); $i++) { my $msg = $pop->HeadAndBody($i) || die "Couldn't do HeadAndBody $!\n"; my $parser = new MIME::Parser; $parser->output_to_core(1); $parser->tmp_to_core(1); my $entity = $parser->parse_data(\$msg); $entity->dump_skeleton if $debug; foreach my $part ($entity->parts_DFS) { if ($part->mime_type eq 'video/quicktime') { # Write the file to a web-accessible qtpath my $filename = $part->head->recommended_filename; print "I found a quicktime movie: $filename\n\n" if $debug; open (MOVIE, ">$moviedir/$filename") || die "Can't open $moviedir/$filename"; select MOVIE; $part->bodyhandle->print; close(MOVIE); select STDOUT; print "Saved quicktime movie to $moviedir/$filename\n\n" if $debug; # Read image dimensions print "Preparing to read image dimensions for $moviedir/$filename \n\n" if $debug; my $info = Image::ExifTool::ImageInfo("$moviedir/$filename", 'ImageWidth','ImageHeight') || die "Couldn't read $moviedir/$filename"; my $width = $info->{ImageWidth}; my $height = $info->{ImageHeight} + 16; print "Read image of dimensions $width x $height \n\n" if $debug; # Add to the HTML for the entry for each new message $post .= <<EOF; <div class="movie"><embed src="$webmoviepath/$filename" width="$width" height="$height" autoplay="false"></embed></div><br /><br /> EOF $movieposted++; } } } $pop->Close(); $post .= "</div>\n"; print "Preparing to write post for $movieposted movies\n\n" if $debug; # Write the post for each run if ($movieposted) { # Set file permissions and replace whatever was in the file umask(002); open(OUTFILE, ">$postfilename") or die "Couldn't open $postfilename for writing."; print OUTFILE $post; close(OUTFILE); print "Wrote blog post $postfilename\n\n" if $debug; } exit;

Caveat: There is a bug in the script as it is written in that the iPhone 3GS can shoot video in both landscape and portrait mode. I generally shoot in portrait mode since that's the format that, when shown on a TV, is going to fit the screen the best. However, if you shoot images in portrait mode, then the Quicktime movie should be depicted using the proper $width/$height values in the <embed> tag. The code that does the Image::ExifTool image dimension detection returns landscape values for portrait videos, and no other EXIF tags seem to contain metadata about capture orientation.

Video::Info::Quicktime_PL and Video::Info::Quicktime/Video::OpenQuicktime wouldn't compile/install on my system. Apple lists and developer documentation seemed to indicate I could look at GetTrackMatrix, and look for -1.0 in [0,1].

As far as I know there's no programmatic way of getting access to orientation information. If anyone has a way to detect the orientation in portrait images so my code to properly set the width/height of the movies, those tips would be appreciated.

Of course, if you only shoot in landscape, or choose to only email landscape videos to your special address, this script will work just fine.





Colophon

Written using MacVim
Published by Blosxom
Layout: Blueprint CSS