Tuesday, October 19, 2010

VMWare and Virtual PC: Playing Age of Empires on Windows 7

Recently, I decided to install Age of Empires on my computers.  The game came up in a conversation with someone who periodically hosts AOE parties.  Anyway, it was one of the few PC games I played growing up, and also one that my wife used to play.  When I got home, I discovered that, indeed, I did still have the original discs for Age of Empires, the Age of Kings Expansion, and Age of Emprires II.  I believe my sister salvaged them for me from the boxes left when my parents moved from a house to an apartment.

In any case, I had the software that I needed, so I installed it on my wife's PC running Windows XP.  That done, I had a couple of old PCs running Windows 2000, but those are on the same KVM with my wife's PC, so they couldn't be used for multi-player.  The problem was Windows 7.  I had heard that it wouldn't be pretty (you have to shut down Explorer to play), so I decided to virtualize.  That way, I wouldn't need to give up any part of Windows 7, even Aero, and it would run seamlessly.

I had used VMWare before, so that's what I started with.  VMWare Player is free to download, and so I did.  The installation went pretty smoothly.  I chose to install the option to install the OS using the VMWare wizard, which turned out to be a problem later on when I had to manually eject the virtual floppy drive in order to be able to install VMWare Tools (VMWare thought that the OS installation wasn't complete, when it was.)  Next time, I'll choose the option to install the OS after creating a blank virtual machine.

After installing the OS (I used the original XP that came with my laptop), I updated to SP3, and then had Windows Update install all of the latest patches.  Once everything was updated, I installed the Age of Empires games and applied the appropriate patches.  I also created a small subset in my list of software suitable for a minimalist virtual machine. 

After that, I was good to go, so my wife and I fired up the game and played a few matches.  We had to brush up on our skills first, but it didn't take us long to get back into the swing of things.

I used Bridged mode for networking, but even so, I had to disable Windows Firewall on the XP VM in order to host an AOE game, even after creating a firewall exception, and expanding it to the whole subnet.


Of course, it wasn't perfect.  Even though I had VMWare Tools installed, the mouse was a bit unresponsive, and VMWare Player tends to release the mouse if you cross the edge of the screen.  For this reason, and because I also wanted to try another option for virtualization that I hadn't used before, I decided to also try out Virtual PC. 

It took some doing to find the download link for the latest version of Virtual PC.  I think that Microsoft doesn't want anyone running Windows 7 Home Premium (which is what I have on my laptop) to find the file.  I kept being redirected to Microsoft Virtual PC 2007, which is the appropriate version if your host operating system is Windows XP or Vista, or to upgrade to Windows 7 Professional or Ultimate.  I finally found the right link for Windows Virtual PC, which only supports Windows 7 as a host OS.  This is also the basis of Windows 7's Windows XP Mode.  Indeed, when I installed Windows Virtual PC on my Windows 7 Home Premium, it created a link in the Start Menu for Windows XP Mode.

The link doesn't work (it only displays a message that it won't work in this edition of Windows), and the only other item in the Windows Virtual PC start menu folder opens a folder.  At first, I couldn't figure out how to create a virtual machine in this folder, but then I noticed the bar at the top of the folder window.  When I created a new virtual machine, it stored only a small data file in that folder, with the virtual disk files buried out of sight in my hidden AppData folder.  This approach is different from that of VMWare, and it reflects the fact that Microsoft does not expect me to move this VM, back it up, or access its underlying files.  It's supposed to "just work", and I'm supposed to treat this small VMCX file as a proxy for the whole VM.  With VMWare, I can easily move or back up the VM by moving or copying the folder containing all of its files: to a different drive, or even a different machine. 

Windows Virtual PC with the Integration Tools installed has almost perfect mouse movement, which is essential for playing a real-time strategy game such as AOE.  It wasn't difficult to get used to hitting Ctrl+Alt+Left to escape input capture, instead of VMWare's Ctrl+Alt.


I do have a license for Windows 7 Ultimate, so I would like to check out Windows XP Mode.  However, this license is currently installed on our living room media PC.  It will take a few hours to set up, so it will probably have to be a free afternoon on a weekend.  If I installed the key currently on my laptop on the media PC, I might be able to use "Anytime Upgrade" to install the newly-unused Ultimate key to my laptop without doing a re-install.  We'll see.

Tuesday, October 05, 2010

My Take on Windows Live Essientials

Microsoft just released their Live Essentials suite of software downloads for Vista and Windows 7 machines.  I've been using them since January 2009.  Here are my thoughts.

You shouldn't install them all, first of all.  When you download and run the installer, you get the choice to install everything, or pick and choose.  Make the latter selection.  If you have a previous version of something, they won't give you the choice not to upgrade, so if there's something you don't want to install, quit the installer and uninstall it first.
Which programs in particular to install will be an individual choice.  I already had Mail, Writer, Photo Gallery, and Windows Live Mesh installed from the beta.  I had installed Microsoft Office since last updating the software, and so the installer offered me the "Outlook Connecter Pack".  I'm not sure what it is, but it probably won't hurt.

Speaking of hurt, though, unless you really, really want it, don't install the Bing Bar.  It's just a bad idea.  It will try to take over all of your browsers, and seriously, who needs a toolbar in their browser? 
 
I've never tried the updated Messenger, Messenger Companion, or Family Safety.  I hardly ever use my hotmail account to chat, and I use Pidgin when I do, so I don't really have a use for the Messenger enhancements.

Writer is apparently a very good blogging tool that works with a lot of popular blogging sites (like Blogger, which hosts this blog), but so far, I've stuck with the web interface for composition.
By far the most useful tool is Windows Live Mesh.  If you're like me, you have a bunch of pictures, music, files, and other documents on various computers.  The file sets are simply too large to fit into a free Dropbox account, and you don't really need access to them over the web, at least not most of them, you just want them on your various computers.  It's a hassle to keep all of photos or music organized in more than one place, so you don't.  You keep them organized in one place, and (hopefully) make periodic backups to another computer just in case. Well, Live Mesh allows you to keep it organized the way you want it, everywhere you want it, and it doesn't matter how big the files are, because Microsoft isn't going to store any of them (except for a special 5GB folder, which it will store in the cloud and allow you to access from anywhere on the web.)

Microsoft doesn't upload your files to its servers, but it does keep track of them for you.  Any change you make to your shared folders gets copied to the other computers where that folder is synced, and the copying is peer-to-peer, so if you're at home, it happens at the speed of your home network.  It will also keep your files in sync even if you're not at home, directly from your other computer, not through their servers.

The management interface is pretty simple, though it's easy to miss the "Remote" settings, which allow you to connect to your computer over the Internet if you have enabled it on that device.  Connect is a lot like Remote Desktop, if you're familiar with that.  Basically, it's just like you're sitting at the other computer.  You have to be running MSIE on the computer you're connecting from.

The web interface is a lot like the desktop interface, except in addition to your shared folders, you also have access to all of your devices as well, and you can see which devices sync to each of your folders.

Update:  After installing  Windows Live Mesh on my wife's new netbook, she experienced extremely slow performance.  Her netbook has a 2GHz x64 processor and 2GB of ram, so it wasn't simply the fact that it was a netbook that was making it slow.  I opened Task Manager, and found that the MOE process ("Mesh Operating Environment") was consistently taking up 40 - 60% of the CPU.  I shut down the process, and deleted the "Run" entry from the registry to disable it starting up automatically.  Any syncing that happens will need Live Mesh to be started manually.  I also observed similar behavior on my laptop, but the media PC (which is on all the time) has the MOE process taking only 3 - 5% of the CPU.  It's probably checking the synced files for updates every time it starts up.

Anyway, be warned: Windows Live Mesh is a resource hog on machines that need to turn on and off all the time.

Friday, September 10, 2010

The Cure For Vista Media Center's Insomnia

At home we have a computer hooked up to our TV.  It has two TV tuners, and is set up with Windows 7 Media Center to record our favorite television shows (and automatically detect and skip commercials).  It's wonderful.  We can also watch Blu-ray movies, access Netflix, Hulu, Amazon Unbox, and any number of other streaming services right from our living room TV. 

This post is not about that computer.  This post is about my laptop.  Occasionally, when traveling, or when simply in another room of the house, I like to use Windows Media Center on my laptop.  Currently, it's running Windows Vista, so in order to be able to view shows in the .wtv format used by Windows 7's Media Center, I have installed the "TV Pack" unofficially leaked by Microsoft.

It works great.  Basically, I browse to the file I want to watch on a shared drive (I have a shortcut to the Recorded TV folder on the desktop), double-click it, and it plays on the laptop.  The commercial scan files are automatically synced. When traveling, I usually copy what I want to watch to my Laptop's hard drive, but there's also this.

So, what's the problem?  My laptop won't sleep.  Or hibernate.  At least not all night.  It wakes up at 3:30 AM to download the latest TV listings, even though I never configured it to work with a tuner, so it has no listings to download.  Needless to say, this is annoying.  It drains my battery unnecessarily, and if it's in its case, there's a danger that it will overheat.

After living with this problem, usually dealing with it by shutting the laptop down every time I stop using it--which means a cold boot every time I start using it, and it takes a while to load everything up--I finally found the solution to my problem.  Step 9 on this page points you in the right direction, but here's how you do it:

Launch the Task Scheduler.  You can do this by opening the Start Menu, typing "Task Scheduler", and pressing Enter.  You will get a UAC prompt, which you should authorize. 

In the left pane, click on the arrows left of the text to expand down to the following item: Task Scheduler (Local) -> Task Scheduler Library -> Microsoft -> Windows -> Media Center


Once you have selected Media Center, look on the top middle pane for a task named mcupdate_scheduled.  Double-click this task to load the Properties window.


In the Properties window, click the Conditions tab, and uncheck the box next to "Wake the computer to run this task".

Click OK, and close Task Scheduler.  That's it.  No more waking up from hibernate or sleep in the middle of the night!

Friday, July 16, 2010

TwitPic to Posterous Export Script: Update

In the time since I wrote my script which downloads all of a user's TwitPic posts (text included) and uploads them as Posterous posts, Posterous has come out with their own import tool.

However, as noted in their blog post, TwitPic is currently blocking Posterous' servers, so someone came along and tried to use my script.  It turned out that the TwitPic site had been updated, and my script no longer worked.

Well, I updated the script so that it works again.  The script can be found here.

Wednesday, March 17, 2010

TwitPic to Posterous Export Script

Note: This post (and the script it contains) has been updated as of December 14, 2010.  (v1.4.0) The script can also be downloaded from my server here.
Also, Posterous has done a lot of work on solving this problem since I wrote my script.   You can see their latest solutions here.

Recently, I switched from TwitPic to Posterous as my method of posting phone pictures (and now video) to the Internet.  But since I switched, I didn't want to have my data history split in two, so I decided to write a script to download each of my TwitPic images with their associated text and date, and upload them to Posterous with the same information.

Initially, I wanted to make one long post with all of the images, and their text below.  However, with the Posterous API, it isn't possible to refer to a specific image in your body text, so individual posts is the way I went.

Along the way, I became familiar with yet another Linux command: curl.

I love that Posterous has an API that (once you figure out curl) is pretty easy to use.  TwitPic, on the other hand, has absolutely zero support for exporting anything.  The fact that they're so non-user-centric and out-dated was a driving force in my switching.  The only reason I hadn't switched to img.ly already was because img.ly has a bug that prevents images sent from my phone from being posted, since my phone sends them without a file extension.  I worked with their tech support for a while, but they didn't fix it.  I got a new phone, but it was also a Samsung, and it did the same thing with images.  Oh, well.  Posterous is better.

Anyway, here is the script:

First run it with just the first two arguments, and it will download all of your TwitPic data, including thumbnail images.  Once you're satisfied, supply your Posterous User ID, Password, and Site ID.  (If you don't know your Site ID, run the script with your Posterous User ID, Password, and no Site ID, and it will query your Posterous site info as long as your Posterous credentials are valid.)

Note: if you want to run this from Windows, you should install Cygwin (with, at a mimum, curl and sed) and run it from there.

./twitpic-to-posterous.sh [twitpic-id] [working-dir] [postrous-id] [posterous-password] [posterous-site-id] [skip-number]
#!/bin/sh

# Copyright 2010 Tim "burndive" of http://burndive.blogspot.com/ and http://tuxbox.blogspot.com/
# This software is licensed under the Creative Commons GNU GPL version 2.0 or later.
# License informattion: http://creativecommons.org/licenses/GPL/2.0/

# This script was obtained from here:
# http://tuxbox.blogspot.com/2010/03/twitpic-to-posterous-export-script.html

RUN_DATE=`date +%F--%H-%m-%S`
SCRIPT_VERSION_STRING="v1.4.0"

TP_NAME=$1
WORKING_DIR=$2
P_ID=$3
P_PW=$4
P_SITE_ID=$5
UPLOAD_SKIP=$6

# Comma separated list of tags to apply to your posts
P_TAGS="twitpic"
# Whether or not to auto-post from Posterous
P_AUTOPOST=0
# Whether or not the Posterous posts are marked private
P_PRIVATE=0

# This is the default limit of the number of posts that can be uploaded per day
P_API_LIMIT=50

DOWNLOAD_FULL=1
DOWNLOAD_SCALED=0
DOWNLOAD_THUMB=0
PREFIX=twitpic-$TP_NAME
HTML_OUT=$PREFIX-all-$RUN_DATE.html
UPLOAD_OUT=posterous-upload-$P_SITE_ID-$RUN_DATE.xml

if [ -z "$TP_NAME" ]; then
  echo "You must supply a TP_NAME."
  exit
fi
if [ ! -d "$WORKING_DIR" ]; then
  echo "You must supply a WORKING_DIR."
  exit
fi
if [ -z "$UPLOAD_SKIP" ]; then
  UPLOAD_SKIP=0
fi
UPLOAD_SKIP_DIGITS=`echo $UPLOAD_SKIP | sed -e 's/[^0-9]//g'`
if [ "$UPLOAD_SKIP" != "$UPLOAD_SKIP_DIGITS" ]; then
  echo "Invalid UPLOAD_SKIP: $UPLOAD_SKIP"
  exit
fi

cd $WORKING_DIR

if [ -f "$HTML_OUT" ]; then
  rm -v $HTML_OUT
fi

# If Posterous username and password were supplied, but not site ID, query the server and exit.
P_SITE_INFO_FILE=posterous-$P_SITE_ID.out
if [ ! -z "$P_ID" ] && [ ! -z "$P_PW" ] && [ -z "$P_SITE_ID" ]; then
  echo "Getting Posterous account info..."
  curl -u "$P_ID:$P_PW" "http://posterous.com/api/getsites" -o $P_SITE_INFO_FILE
  SITE_ID_RET=`grep "<id>$P_SITE_ID</id>" $P_SITE_INFO_FILE`
  if [ -z "$SITE_ID_RET" ]; then
    echo "Please supply your Posterous Site ID as the fifth argument."
    echo "Here is the response from the Posterous server.  If you entered correct credentials, you should see your Site ID(s):"
    cat $P_SITE_INFO_FILE | tee -a $UPLOAD_OUT
    exit
  fi
fi

# Confirm that we have a valid Posterous Site ID
if [ ! -z "$P_SITE_ID" ]; then
  echo "Getting Posterous account info..."
  curl -u "$P_ID:$P_PW" "http://posterous.com/api/getsites" -o $P_SITE_INFO_FILE
  SITE_ID_RET=`grep "<id>$P_SITE_ID</id>" $P_SITE_INFO_FILE`
  if [ -z "$SITE_ID_RET" ]; then
    echo "Make sure that you have supplied a valid Posterous Site ID as the fifth parameter.  If you don't know your Site ID, leave it out, and this script will query the server."
    echo "Here is the response from the Posterous server.  If you entered correct credentials, you should see your site ID(s):"
    cat $P_SITE_INFO_FILE | tee -a $UPLOAD_OUT
    exit
  fi
fi

MORE=1
PAGE=1
while [ $MORE -ne 0 ]; do
  echo PAGE: $PAGE
  FILENAME=$PREFIX-page-$PAGE.html
  if [ ! -s $FILENAME ]; then
    wget http://twitpic.com/photos/${TP_NAME}?page=$PAGE -O $FILENAME
    if [ ! -s "$FILENAME" ]; then
      echo "ERROR: could not get $FILENAME" | tee -a $LOG_FILE
      sleep 5
    fi
  fi
  if [ -z "`grep "More photos &gt;" $FILENAME`" ]; then
    MORE=0
  else
    PAGE=`expr $PAGE + 1`
  fi
done

ALL_IDS=`cat $PREFIX-page-* | grep -Eo "<a href=\"/[a-zA-Z0-9]+\">" | grep -Eo "/[a-zA-Z0-9]+" | grep -Eo "[a-zA-Z0-9]+" | sort -r | xargs`

# For Testing
#ALL_IDS="1kdjc"

COUNT=0
LOG_FILE=$PREFIX-log-$RUN_DATE.txt

echo $ALL_IDS | tee -a $LOG_FILE

for ID in $ALL_IDS; do
  COUNT=`expr $COUNT + 1`
  echo $ID: $COUNT | tee -a $LOG_FILE

  echo "Processing $ID..."
  FULL_HTML=$PREFIX-$ID-full.html
  if [ ! -s "$FULL_HTML" ]; then
    wget http://twitpic.com/$ID/full -O $FULL_HTML
    if [ ! -s "$FULL_HTML" ]; then
      echo "ERROR: could not get FULL_HTML for $ID" | tee -a $LOG_FILE
      sleep 5
    fi
  fi
  TEXT=`grep "<img src=" $FULL_HTML | tail -n1 | grep -oE "alt=\"[^\"]*\"" | sed \
        -e 's/^alt="//'\
        -e 's/"$//'\
        -e "s/&#039;/'/g"\
        -e 's/&quot;/"/g'\
        `
  if [ "$TEXT" = "" ]; then
    TEXT="Untitled"
  fi
  echo "TEXT: $TEXT" | tee -a $LOG_FILE
  # Recognize hashtags and username references in the tweet
  TEXT_RICH=`echo "$TEXT" | sed \
        -e 's/\B\@\([0-9A-Za-z_]\+\)/\@<a href="http:\/\/twitter.com\/\1">\1<\/a>/g' \
        -e 's/\#\([0-9A-Za-z_-]*[A-Za-z_-]\+[0-9A-Za-z_-]*\)/<a href="http:\/\/twitter.com\/search\?q\=%23\1">\#\1<\/a>/g' \
        `
  echo "TEXT_RICH: $TEXT_RICH" | tee -a $LOG_FILE

  # Convert hashtags into post tags
  P_TAGS_POST=$P_TAGS`echo "$TEXT" | sed \
        -e 's/\#\([^A-Za-z_-]\)*\B//g' \
        -e 's/^[^\#]*$//g' \
        -e 's/[^\#]*\(\#\([0-9A-Za-z_-]*[A-Za-z_-]\+[0-9A-Za-z_-]*\)\)[^\#]*\(\#[0-9]*\B\)*/,\2/g' \
        `
  # Uncomment if you don't want hashtags converted into post tags
  #P_TAGS_POST=$P_TAGS

  # Add custom tags from a file (optional).  The file is formatted like this:
  # ,tag1,tag2,tag3
  TAGS_FILE=$PREFIX-$ID-tags-extra.txt
  if [ -s "$TAGS_FILE" ]; then
    P_TAGS_POST=$P_TAGS_POST`cat $TAGS_FILE`
  fi
  echo "P_TAGS_POST: $P_TAGS_POST" | tee -a $LOG_FILE

  TEXT_FILE=$PREFIX-$ID-text.txt
  if [ ! -s $TEXT_FILE ]; then
    echo "$TEXT" > $TEXT_FILE
  fi
  FULL_URL=`grep "<img src=" $FULL_HTML | grep -Eo "src=\"[^\"]*\"" | grep -Eo "http://[^\"]*"`
  echo "FULL_URL: $FULL_URL" | tee -a $LOG_FILE

  SCALED_HTML=$PREFIX-$ID-scaled.html
  if [ ! -s "$SCALED_HTML" ]; then
    wget http://twitpic.com/$ID -O $SCALED_HTML
    if [ ! -s "$SCALED_HTML" ]; then
      echo "ERROR: could not get SCALED_HTML for $ID" | tee -a $LOG_FILE
      sleep 5
    fi
  fi
  SCALED_URL=`grep "id=\"photo-display\"" $SCALED_HTML | grep -Eo "http://[^\"]*" | head -n1`
  echo "SCALED_URL: $SCALED_URL" | tee -a $LOG_FILE
  POST_DATE=`grep -Eo "Posted on [a-zA-Z0-9 ,]*" $SCALED_HTML | sed -e 's/Posted on //'`
  echo "POST_DATE: $POST_DATE" | tee -a $LOG_FILE

  THUMB_URL=`cat $PREFIX-page-* | grep -E "<a href=\"/$ID\">" | grep -Eo "src=\"[^\"]*\"" | head -n1 | sed -e 's/src=\"//' -e 's/\"$//'`
  echo "THUMB_URL: $THUMB_URL" | tee -a $LOG_FILE

  EXT=`echo "$FULL_URL" | grep -Eo "[a-zA-Z0-9]+\.[a-zA-Z0-9]+\?" | head -n1 | grep -Eo "\.[a-zA-Z0-9]+"`
  if [ -z "$EXT" ]; then
    EXT=`echo "$FULL_URL" | grep -Eo "\.[a-zA-Z0-9]+$"`
  fi
  echo "EXT: $EXT"
  if [ "$DOWNLOAD_FULL" -eq 1 ]; then
    FULL_FILE="$PREFIX-$ID-full$EXT"
    if [ ! -s $FULL_FILE ]; then
      wget "$FULL_URL" -O $FULL_FILE
      if [ ! -s "$FULL_FILE" ]; then
        echo "ERROR: could not get FULL_URL for $ID: $FULL_URL" | tee -a $LOG_FILE
        sleep 5
      fi
    fi
  fi
  if [ "$DOWNLOAD_SCALED" -eq 1 ]; then
    SCALED_FILE=$PREFIX-$ID-scaled$EXT
    if [ ! -s $SCALED_FILE ]; then
      wget "$SCALED_URL" -O $SCALED_FILE
      if [ ! -s "$SCALED_FILE" ]; then
        echo "ERROR: could not get SCALED_URL for $ID: $SCALED_URL" | tee -a $LOG_FILE
        sleep 5
      fi
    fi
  fi
  if [ "$DOWNLOAD_THUMB" -eq 1 ]; then
    THUMB_FILE=$PREFIX-$ID-thumb$EXT
    if [ ! -s $THUMB_FILE ]; then
      wget "$THUMB_URL" -O $THUMB_FILE
      if [ ! -s "$THUMB_FILE" ]; then
        echo "ERROR: could not get THUMB_URL for $ID: $THUMB_URL" | tee -a $LOG_FILE
        sleep 5
      fi
    fi
  fi

  BODY_TEXT="$TEXT_RICH <p>[<a href=http://twitpic.com/$ID>Twitpic</a>]</p>"

  # Format the post date correctly
  YEAR=`echo "$POST_DATE" | sed -e 's/[A-Z][a-z]* [0-9]*, //'`
  DAY=`echo "$POST_DATE" | sed -e 's/[A-Z][a-z]* //' -e 's/, [0-9]*//'`
  MONTH=`echo "$POST_DATE" | sed -e 's/ [0-9]*, [0-9]*//' | sed \
    -e 's/January/01/' \
    -e 's/February/02/' \
    -e 's/March/03/' \
    -e 's/April/04/' \
    -e 's/May/05/' \
    -e 's/June/06/' \
    -e 's/July/07/' \
    -e 's/August/08/' \
    -e 's/September/09/' \
    -e 's/October/10/' \
    -e 's/November/11/' \
    -e 's/December/12/' \
    `
  # Adjust the time to local midnight when west of GMT
  HOURS_LOC=`date | grep -Eo " [0-9]{2}:" | sed -e 's/://' -e 's/ //'`
  HOURS_UTC=`date -u | grep -Eo " [0-9]{2}:" | sed -e 's/://' -e 's/ //'`
  HOURS_OFF=`expr $HOURS_UTC - $HOURS_LOC + 7`
  echo "HOURS_LOC: $HOURS_LOC"
  echo "HOURS_UTC: $HOURS_UTC"
  echo "HOURS_OFF: $HOURS_OFF"
  if [ "$HOURS_OFF" -lt 0 ]; then
    # We're east of GMT, do not adjust
    HOURS_OFF=0
  fi
  if [ "$HOURS_OFF" -lt 10 ]; then
    HOURS_OFF=0$HOURS_OFF
  fi
  if [ "$DAY" != "" ] && [ "$DAY" -lt 10 ]; then
    DAY=0$DAY
  fi
  DATE_FORMATTED="$YEAR-$MONTH-$DAY-$HOURS_OFF:00"
  echo "DATE_FORMATTED: $DATE_FORMATTED" | tee -a $LOG_FILE

  echo "<p><img src='$FULL_FILE' alt='$TEXT' title='$TEXT' /></p>" >> $HTML_OUT
  echo "$BODY_TEXT" >> $HTML_OUT
  echo "  Post date: $DATE_FORMATTED; Count: $COUNT" >> $HTML_OUT

  # Upload this Twitpic data to Posterous
  if [ ! -z "$P_SITE_ID" ]; then

    # First make sure we're under the API upload limit
    if [ "$COUNT" -le "$UPLOAD_SKIP" ]; then
      echo Skipping upload...
      continue
    fi
    if [ "$COUNT" -gt "`expr $UPLOAD_SKIP + $P_API_LIMIT`" ]; then
      echo "Skipping upload due to daily Posterous API upload limit of $P_API_LIMIT."
      echo "To resume uploading where we left off today, supply UPLOAD_SKIP parameter of `expr $UPLOAD_SKIP + $P_API_LIMIT`."
      continue
    fi

    P_OUT_FILE="posterous-$P_SITE_ID-$ID.out"
    if [ -s "$P_OUT_FILE" ]; then
      rm "$P_OUT_FILE"
    fi
    echo "Uploading Twitpic image..."
    curl -u "$P_ID:$P_PW" "http://posterous.com/api/newpost" -o "$P_OUT_FILE" \
      -F "site_id=$P_SITE_ID" \
      -F "title=$TEXT" \
      -F "autopost=$P_AUTOPOST" \
      -F "private=$P_PRIVATE" \
      -F "date=$DATE_FORMATTED" \
      -F "tags=$P_TAGS_POST" \
      -F "source=burndive's Twitpic-to-Posterous script $SCRIPT_VERSION_STRING" \
      -F "sourceLink=http://tuxbox.blogspot.com/2010/03/twitpic-to-posterous-export-script.html" \
      -F "body=$BODY_TEXT" \
      -F "media=@$FULL_FILE"
    cat $P_OUT_FILE  | tee -a $UPLOAD_OUT
  fi
done
echo Done.
CC-GNU GPL
This software is licensed under the CC-GNU GPL version 2.0 or later.

PS: If you use my code, I appreciate comments to let me know, and any feedback you may have, especially if it's not working right for you, but also just to say thanks.

For convenience, you can download this script from my server.