Video and Image Moblogging with a (video enabled) Camera Phone
Shawn Van Every

Many mobile phones now have cameras and several higher end models now have the ability to capture and send images and video. I believe there are now more camera phones sold world wide than cameras themselves, I would imagine that in the near future this will be true of video enabled phones outselling video cameras. This hack will guide you through the steps necessary to use your video enabled camera phone to post directly to your vlog.

Before starting, you should be aware of some possible gotcha's. First, this hack relies upon using MMS messaging from your phone to send the images and video. Due to the sheer number of phones on the market and the differences between each one you will have to figure out how to send an MMS message yourself. You should also be aware that mobile providers charge for MMS messaging and if you are going to be doing this a lot, best to get a plan that encompasses it.

Enough with the downers, let's get started with a definition of MMS:

MMS stands for Multimedia Message Service which like SMS (Short Message Service, IE: texting) is a standard employed by mobile phone service providers that allows users to send images, text, sound and video to each-other's mobile phones. Different providers market this capability slightly differently under different names. Verizon calls it "Video & PIX Messaging" while T-Mobile calls it Picture/Video Messaging.

Most of the US providers also offer the ability to send MMS messages to regular internet email. Unfortunately, they don't all do this in the same manner. Some providers send the MMS message in an email including HTML so as to display the content right in the recipients mail program, other providers will simply attach the content of the message as a MIME email. Fortunately, most of these situations can be accounted for and since you will probably only be posting from your phone through your provider to your vlog, once you get it right you won't have to change it.

The first step in setting up a system that automatically posts these messages to your vlog is to setup a dedicated email account for this purpose. The code below is written to utilize a standard POP3 email account as offered by most web hosting companies (but not by most webmail providers such as Google's Gmail or the free version of Yahoo! Mail). An enterprising hacker could port this code to utilize IMAP or even to use one of the free webmail provider accounts but this is beyond the scope of this book (we hope those who do will share ;-) ).

There are several reasons to use a dedicated account, first and foremost is that you don't want to be accidentially publishing personal email to your blog. You also don't want things mucked up with spam from an account that you have previously used or have this application delete important email before you have a chance to read it.

The next step is to gain access to a unix shell account from which you can execute a program to check your newly setup email account. I typically use web hosting companies that offer SSH (secure shell) access to their webservers and recommend that you do as well for these purposes. Of course, if your hosting company only offers telnet (an insecure remote shell application) or offers none at all or you are hosted on a Windows server, the below instructions may be modified to work with your setup but you are going to be more or less on your own.

The following code is a for a Perl script that retreives and parses email from a dedicated email account. If it is desired, this script is setup to retreive text, images, video and sound from email messages (or MMS messages) and automatically post them to your blog.

Here is the script:

#!/usr/bin/perl 

###
# No Copyright.  Use as you wish.  Don't blame me if it doesn't work as it should.
# Send me a note if you feel like it.  Bug fixes and enhancements appreciated.
# Shawn Van Every: vanevery@walking-productions.com
# 
# To Do:
# Make Category Posting Work
# Get Spring Messages Working (need a tester)
# Test latest fixes on providers other than T-Mobile
# Post videos as a screenshot or perhaps as a flash video but still with enclosure
# Post images sized correctly for display instead of full size
###

###
# Perl modules that need to be installed
###
use MIME::Parser;
use MIME::Entity;
use MIME::Base64;
use Net::POP3;
use XMLRPC::Lite;

###
# User Configurable Variables
###
my $username = "xxx"; ## CHANGE THIS LINE
my $password = "xxx";  ## CHANGE THIS LINE
my $mailserver = "mail.xxx.com"; ## CHANGE THIS LINE
my $temp_folder = "/home/xxx/xxx/temp/"; ## CHANGE THIS LINE
my $attachment_output_folder = "/home/xxx/xxx/posts/"; ## CHANGE THIS LINE
my $attachment_output_folder_relative = "http://xxx.xxx.com/posts/"; ## CHANGE THIS LINE
my $blog_xmlrpc_url = "http://xx.xx.com/xmlrpc.php"; ## CHANGE THIS LINE
my $blog_id = 0; ## CHANGE THIS LINE
my $blog_username = "xxx"; ## CHANGE THIS LINE
my $blog_password = "xxx"; ## CHANGE THIS LINE
## THIS DOESN'T WORK YET
my $post_category = "xxxx"; ## CHANGE THIS LINE 
## NEW
my $image_width = 320;
my $use_wp_plugin = 1; ## If you are using this with WordPress and 
## you are using my Word Press Plugin: 
## http://www.walking-productions.com/QuickTimePostWPP/

###
# Other Variables that *may be* changed
###
my $pop = Net::POP3->new($mailserver, Timeout => 15);
my $max_chars = 70;  # Number of chars to allow in a line of text from an incoming message
my $delete_messages = 1; # Delete messages from your inbox as they are processed
my $delete_temp_files = 0; # Delete temporary files that are created
my $print_output = 1; # Print output
my $post_to_blog = 1; # Post to your blog?
my $use_gif = 0; # 1 to allow GIF's as attachments, 0 to not allow them (T-Mobile issues)
my @bad_attachments = ("masthead.jpg","dottedLine_600.gif","spacer.gif","video.gif","dottedLine_350.gif"); # A list of attachment filename regular expressions that you don't want included 
my @allowed_domains = ("mobile.att.net","cingularme.com","messaging.sprintpcs.com","tmomail.net","vtext.com","mmode.com","alltel.net","vzwpix.com","mms.mycingular.com"); # A list of domains that are allowed to post to this site

my @bad_text = ("nothingatall",
"PIX.*FLIX.*Messaging",
"To.*learn.*how.*you.*can.*snap.*pictures.*with.*your.*wireless.*phone",
"To.*learn.*how.*you.*can",
"www\.verizonwireless\.com",
"To.*play.*video.*messages.*sent.*to.*email",
"process.*when.*asked.*to.*choose.*an.*installation.*type.*Minimum.*Recommended.*or.*Custom.*select",
"If.*you.*can.*read.*this.*text",
"^T-Mobile\$",
"If.*you.*are.*having.*trouble.*playing.*this.*attachment",
"This.*Video.*Message.*was.*sent.*from.*a.*T-Mobile.*video.*phone",
"\.footer.*{",
"font-family:.*Arial,.*;",
"font-size.*;",
"color:.*;",
"text-decoration:.*;",
"normal.*{",
"This message was sent using service from Verizon Wireless!",
"visit /getitnow/getflix.",
", QuickTime 6.5 or higher is required. Visit www.apple.com/quicktime/download to download the free player or upgrade your existing QuickTime Player. Note: During the download",
"Minimum for faster download."
); # a list of text regular expressions that you don't want included
# NEED TO WORK ON THIS

my $umask = '0002';  # File creation to 775, 0022 would be 755

my %mime_types = ("image\/jpeg", "jpg",
                    "image\/jpg", "jpg",
                    "image\/gif", "gif",
                    "video\/3gpp", "3gp",
                    "audio\/x-wav", "wav",
                    "video\/mp4", "mp4",
                    "video\/3gpp2", "3g2",
                    "video\/mpeg", "mpg",
                    "video\/quicktime", "mov",
                    "video\/x-quicktime", "mov",
                    "video/x-msvideo",  "avi"
                ); # A list of the attachment mime types to extract

###
# Parsing Subroutine
###
sub parseMessageParts
{
    my @messageParts = @_;
    
    my $partnum = 0;
    my $is_mime_message = 0;

    while(my $part = shift(@messageParts)) 
    {          
        $is_mime_message = 1;  # Yes we have a mime message
        my $known_type = 0;  # Did we find the type yet?

        # Get the Mime type of the part
        my $type=$part->head->mime_type || $part->head->effective_type; 
        
        ## Loop through the types we understand
        foreach $valid_type (keys %mime_types)
        {
            if ($type =~ $valid_type)
            {
                $known_type = 1;
                
                for (my $i = 0; $i <= $#bad_attachments; $i++)
                {          
                    if ($part->head->recommended_filename =~ $bad_attachments[$i])
                    {
                        $skip = 1;
                    }
                }

                if (!$skip)
                {
                    my $attachment = $part->bodyhandle->as_string;
                    my $file_name = "attachment_" . time() . "_" . int(rand(1000)) . "\." . $mime_types{$valid_type};
                    my $image_file = $attachment_output_folder . $file_name;
                    my $fh = new FileHandle "> $image_file";
                    if (defined $fh) {
                            print $fh $attachment;
                            $fh->close;
                    }
                
                    $attachments[++$#attachments] = $file_name;
                    $attachments_type[++$#attachments_type] = $mime_types{$valid_type};
                    $attachments_relative[++$#attachments_relative] = $attachment_output_folder_relative . $file_name;
                }    
            }
        }
        
        ## Not in our list, let's check for text or multipart messages
        if ($known_type == 0)
        {
            if ($type =~ /text\/plain/i)
            {
                my $message_bodyhandle = $part->bodyhandle;
                $mime_message_body = $message_bodyhandle->as_string;

		$mime_message_body =~ s/<.*>//sgi; # Strip out any HTML tags

		foreach $badline (@bad_text)
		{
			$mime_message_body =~ s/$badline//sgi;
		}
                
                @mime_message_array = split('\n',$mime_message_body);
                
                foreach $message_line (@mime_message_array)
                {
                    if ($message_line =~ /^\n/ ||
                            $message_line =~ /^\s*\n/ ||
                            $message_line !~ /\w/)
                    {
                       # Ignore blank lines
		       if ($print_output)
		       {
		       	print "skipping" . $message_line;
		       }
                    }
                    else
                    {
		    	$message_line =~ s/^\s//;
                        $body .= $message_line . "\n";
                    }                                
                }                
            }                       
            elsif ($type =~ /text\/html/i)
            {
                # Plain Text portions or attachments
                my $message_bodyhandle = $part->bodyhandle;
                $mime_message_body = $message_bodyhandle->as_string;
                
		$mime_message_body =~ s/<.*>//sgi; # Strip out any HTML tags

		foreach $badline (@bad_text)
		{
			$mime_message_body =~ s/$badline//sgi;
		}

                @mime_message_array = split('\n',$mime_message_body);

                foreach $message_line (@mime_message_array)
                {
                    if ($message_line =~ /^\n/ ||
                            $message_line =~ /^\s*\n/ ||
                            $message_line !~ /\w/)
                    {
                       # Ignore blank lines
		       if ($print_output)
		       {
			print "skipping " . $message_line . "\n";
		       }
                    }
                    else
                    {
		    	$message_line =~ s/^\s//;
                        $body .= $message_line . "\n"; # Only grabbing first line
                    }                                
                }
            }
            elsif ($type =~ /multipart\/.*/i || $type =~ /message\/.*/i)
            {
                # Multipart Message, Parse This Again
                # Thanks again T-Mobile
                my @otherparts=$part->parts;
                &parseMessageParts(@otherparts);
            }
            else
            {
                if ($print_output)
                {
                    print "Other Type: " . $type . "\n\n"; # OUTPUT
                }
            }
        }
        
        $partnum++;
    }
    return $is_mime_message;
}

###
# Main Program Execution
###
if ($print_output)
{
    print("Running at: " . localtime() . "\n");
}

if ($pop->login($username, $password)) 
{
        $umask = oct($umask) if $umask =~ /^0/;
        umask $umask;
        
        if ($print_output)
        {
            print("Logged into mailserver\n");
        }    
        
        # Create the parser object
        my $parser = MIME::Parser->new();
        $parser->output_dir($temp_folder);
        
        my $msgnums = $pop->list; # hashref of msgnum => size
        foreach my $msgnum (keys %$msgnums) 
        {
                my $msg = $pop->get($msgnum); 
                
                my $entity = $parser->parse_data($msg);
                
                ###
                # GET MESSAGE HEADER
                ###
                my $msg_head = $entity->head;
                my $subject = "";
                my $to = "";
                my $from = "";
                
                ###
                # MESSAGE Related Vars
                ###
                $body = "";
                @attachments = ();
                @attachments_type = ();
                @attachments_relative = ();
                
                ##
                # GET MESSAGE SUBJECT
                ##
                if ($msg_head->count('Subject') > 0)
                {
                    $subject = $msg_head->get('Subject');            
                }
                
                ##
                # GET MESSAGE FROM
                ##
                if ($msg_head->count('From') > 0)
                {
                    $from = $msg_head->get('From');
                    if ($from =~ /<(.*)>/)
                    {
                        $from = $1;
                    }
                }

		my $posting_allowed = 0;
		foreach $allowed_domain (@allowed_domains)
		{
			if ($from =~ /.*$allowed_domain.*/)
			{
				$posting_allowed = 1;
				if ($print_output)
				{
					print "Matched: $allowed_domain\n";
				}
			}
		}
                
                ##
                # GET MESSAGE TO
                ##
                if ($msg_head->count('To') > 0)
                {
                    $to = $msg_head->get('To');
                    if ($to =~ /<(.*)>/)
                        {
                            $to = $1;
                        }                   
                }
                
                ###
                # GET MESSAGE PARTS (BODY AND ATTACHMENTS)
                ###
                my @parts=$entity->parts;
                my $is_mime = &parseMessageParts(@parts);  ## Calling our parse subroutine
                                
                ##
                # IF IT ISN'T A MIME MESSAGE (NO ATTACHMENTS)
                ##
                if (!$is_mime)
                {
                    ###
                    # GET MESSAGE BODY LINES
                    ###
                    $msg_body = $entity->body;
                    foreach $message_line (@$msg_body)
                    {
		    	## Skip bad lines
			$skip = 0;
		    	foreach $badline (@bad_text)
		    	{
				if ($message_line =~ /$badline/)
				{
					$skip = 1;
					if ($print_output)
					{
						print "should be skipping\n";
					}
				}
		    	}
			
                        if ($message_line =~ /^\n/ || $skip == 1)
                        {
                      		if ($print_output)
				{
					print "skipping " . $message_line . "\n";
				}
                       	}
                       	else
                       	{
                       		$body .= $message_line;
                       	}
                    }
                }
               
	       	# IF SUBJECT IS BLANK TAKE FIRST LINE OF BODY
		if ($subject eq "")
		{
			@body_array = split('\n',$body);
			$subject = $body_array[0];
		}
		
                chomp($to); # REMOVE TRAILING LINE BREAKS
                chomp($from);
                chomp($subject);
                chomp($body);
                
                if ($post_to_blog && $posting_allowed)
                {
                    my $attachment_html = "";
                    for (my $i = 0; $i <= $#attachments; $i++)
                    {
                        my $attachment = $attachments[$i];
                        my $attachment_type = $attachments_type[$i];
                        my $attachment_relative = $attachments_relative[$i];
                        
                        if ($attachment_type eq "jpg" || $attachment_type eq "gif")
                        {
			    # Maybe height will be relative? Guess it depends on the browser..
                            $attachment_html .= "<img src=\"$attachment_relative\" width=\"$image_width\">\n";
                        }
                        elsif (
                                $attachment_type eq "3gp" || $attachment_type eq "wav" || $attachment_type eq "mp4" ||
                                $attachment_type eq "3g2" || $attachment_type eq "mpg" || $attachment_type eq "mov" || 
                                $attachment_type eq "avi"
                                )
                        {
                            if ($use_wp_plugin)
                            {
                                $attachment_html .= "[QUICKTIME $attachment_relative 320 257]";
                            }
                            else
                            {
                                $attachment_html .= "
            <OBJECT CLASSID=\"clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B\" WIDTH=\"320\" HEIGHT=\"257\" CODEBASE=\"http://www.apple.com/qtactivex/qtplugin.cab\">
                <PARAM name=\"SRC\" VALUE=\"$attachment_relative\">
                <PARAM name=\"AUTOPLAY\" VALUE=\"false\">
                <PARAM name=\"CONTROLLER\" VALUE=\"true\">
                <EMBED SRC=\"$attachment_relative\" WIDTH=\"320\" HEIGHT=\"257\" AUTOPLAY=\"false\" CONTROLLER=\"true\" PLUGINSPAGE=\"http://www.apple.com/quicktime/download/\"></EMBED>
            </OBJECT>"; 
                           }
                        }
                    }
                    
                    my $postresult=XMLRPC::Lite
                               ->proxy($blog_xmlrpc_url)
                               ->call('metaWeblog.newPost',$blog_id,$blog_username,$blog_password,
                                {
                                    'title'=>$subject,
                                    'description'=>$body . "\n" . $attachment_html,
                                    'mt_allow_comments'=>1,
                                    'mt_allow_pings'=>1,
				    'cateory'=>$post_category
                                },
                                1
                    )
                    ->result;

                   for (my $i = 0; $i <= $#attachments; $i++)
		   {
		   	my $attachment = $attachment_output_folder . $attachments[$i];
		        my $attachment_type = $attachments_type[$i];
		        my $attachment_relative = $attachments_relative[$i];

                        my $filesize = -s $attachment;
                        if ($print_output)
                        {
                                print $filesize;
                        }

		        my $addencresult = XMLRPC::Lite
                          ->proxy($blog_xmlrpc_url)
                          ->call('demo.addEnclosure',$postresult,$blog_username,$blog_password,
                             $attachment_relative . "\n" 
			     . $filesize . "\n"
                             . $attachment_type)
                            ->result;

                      if ($print_output)
                      {
                             print $addencresult;
                      }
                   }
                    
                    if ($print_output)
                    {
                        print "Message To: $to\n";
                        print "Message From $from\n";
                        print "Message Subject $subject\n";
                        print "Message Body $body\n";
                        for (my $i = 0; $i <= $#attachments; $i++)
                        {
                            print "Message Attachment $attachments[$i]  -  $attachments_type[$i]  -  $attachments_relative[$i]\n";
                        }
                        print "Post to Blog Result: " . $postresult . "\n";
                        print "--------------------------\n";
                    }
                    
                }
                elsif ($print_output)
                {
                    print "Message To: $to\n";
                    print "Message From $from\n";
                    print "Message Subject $subject\n";
                    print "Message Body $body\n";
                    for (my $i = 0; $i <= $#attachments; $i++)
                    {
                        print "Message Attachment $attachments[$i]  -  $attachments_type[$i]  -  $attachments_relative[$i]\n";
                    }
                    print "Not Posted to Blog";
		    print "Result of Posting Allowed: " . $posting_allowed . "\n";
                    print "--------------------------\n";
                }
            
                if ($delete_messages)
                {
                        $pop->delete($msgnum);
                }
                     
        }

        if ($delete_temp_files)
        {       
            $parser->filer->purge;
        }  
}
$pop->quit;
(The latest version of this script has been updated it can will always be available for download from the following URL: http://www.walking-productions.com/parseMailScript/parseMailScript.zip)

Let's go through it so you can customize it for your needs.

Most of the lines that need to be changed are located near the top and are demarked with "## CHANGE THIS LINE".

The first item is: my $username = 'xxxx'; ## CHANGE THIS LINE
You will simply edit this line and change the xxxx's between the quotes to your email account username. Some hosting providers force you to use 'yourusername@yourdomain.com' and some just require you to use your username to retreive email. Check with your hosting provider for the correct way to do this.

The next line is: my $password = 'xxxx'; ## CHANGE THIS LINE
This is your email account password, simply change the xxxx to your email password.

Following that you need to specify your mailserver (for retrieving email, sometimes referred to as your "POP server"):
my $mailserver = 'xxx.yourdomain.com"; ## CHANGE THIS LINE

The next line, specifies a folder to use as a temp folder for the parsing. You should create a directory for this purpose.
my $temp_folder = "/home/username/temp/"; ## CHANGE THIS LINE

Following that is a folder that is web accessible to save the video and image files into:
my $attachment_output_folder = "/home/username/public_html/attachments/"; ## CHANGE THIS LINE

Next is the relative path to that folder for accessing it via the web:
my $attachment_output_folder_relative = "http//servername.com/~username/attachments/"; ## CHANGE THIS LINE

This script relies upon your blog supporting XML-RPC with the metaWeblog API. This is the path to your XML-RPC application:
my $blog_xmlrpc_url = "http://servername.com/~username/blogname/xmlrpc.cgi"; ## CHANGE THIS LINE

Each blog on a web blogging system as a unique id. This should be set to the one you want to post to:
my $blog_id = 2; ## CHANGE THIS LINE

Your blog username:
my $blog_username = "xxx"; ## CHANGE THIS LINE

Your blog password:
my $blog_password = "xxx"; ## CHANGE THIS LINE

The script has a couple of defaults that are built-in for text parsing. If your MMS messages come through email with a subject line that will be used for the title of your blog post. In this case, any text found in the message will be utilized for the body of the post. On the other-hand, if you would rather specify a title separate from the subject line you can use the following syntax within the body of the MMS message itself: Title: Your custom title Body: Your body text

Using this manner of specifying body and title text decreases the chances that text you don't want to be included in the post will be. Specifically, any text that your provider might add onto the message when sending it through email (such as marketing or advertising related text).

Once you have the script edited and pasted into a plain text document (such as those created by notepad on windows or SubEthaEdit on the Mac) you should save it with the extension ".pl".

The next step is to upload it to your hosting provider. You will want to put the script somewhere in your user directory that is not publicly accessible (not in your web documents directory (public_html) or in your cgi-bin). This way you are protecting your email account and your blog's username and password.

On my server, I use a utility called cron to execute this script every so often.
I set this script up via cron to run every minute on my webserver. Here is the crontab entry that I use:

* * * * * perl /path/to/my/script/parseMessages.pl >> /path/to/my/output/log/output.log
This means to run this command every minute of every hour of every day of every month and every day of the week (* * * * *). The command that is run is 'perl perl /path/to/my/script/parseMessages.pl' with output redirected to '/path/to/my/output/log/output.log'

Viola.. You should now be able to send MMS messages via email and have them show up on your vlog.

DEBUGGING

Problems with XML-RPC.. (more to come)

Problems viewing the attachments in your blog.. (more to come)

Problems with RSS and 3GP files as enclosures
To finish up, we will need to make sure that your webserver recognizes video from phones (3GP, MP4 and so on) so you may need add an htaccess file.

Problems with WordPress:
QuickTime Embedding WordPress Plugin

If you use this script, let me know: shawn@mobvcasting.com.. I use it at: MobVCasting and Open Vlog