07.13.10

Sysadmin: Automatically Compress Media Files Placed in a Directory

Posted in OS X Server, Sysadmin at 3:16 pm by cygnil

We do a lot of media conversion at my workplace, generally as part of a service for our clients. We’ve seen most formats come in, and we put them in the outgoing queue on most types of media (except for 8-tracks, so far as I know). It’s usually inconvenient to compress these media with our own workstations because most of us like to actually work on our workstations while this is going on.

To solve this, I wrote a Perl script to turn our underused OS X server into a media conversion workhorse. It’s a little underpowered by today’s standards (dual G5 processors), and more powerful computers are always better when it comes to compressing video, but the convenience of a networked drag’n'drop media compressor outweighs the speed issues.

This script requires FFMPEG for it to do its compressing, and also works best on a *NIX or OS X system (although it was developed and initially tested on a Windows machine, so everything except for email notices should work on that). It also requires the File::Find::Rule module for Perl, which can be downloaded from CPAN or the ActivePerl repository. One word of caution: the FFMPEG project isn’t composed of the user-friendliest of blokes and doesn’t do official releases, and while binaries are available for Windows the Linux and OS X users are going to have to download and compile their own versions. Novices be warned.

Before I show the code, here’s a brief feature list:

  • It can read and write a variety of codecs and formats. Some of them have to be compiled into FFMPEG separately, but almost everything is supported.
  • It can email a designated list of users when a job is completed, as well as the same list of users plus a separate list of admins if a job fails.
  • Options can be given on a per-file basis by placing a special text file alongside the media file to be converted.
  • Yes, this will work with DVDs; no, it won’t take things like subtitles or alternate language tracks into account. The default language and subtitles are what get put in the resulting media file.

Now, here’s the code:


#! /usr/bin/perl

# Media Converter 1.0 (C) 2009 Yale University
# Released under the Creative Commons-LGPL 2.1 License
# http://creativecommons.org/licenses/LGPL/2.1/

# Converts media files in a certain folder. Best when set to run automatically.
# It can email users when their media have been converted as well as sending
# an alert to those same users plus system administrators when a job fails for
# some reason. Encoding options can be given to each job by placing a text file
# with the name of the target media file plus ".txt" in the input directory
# (e.g. the file "in.mpg" would have a corresponding options file "in.mpg.txt").
# This script will continue to encode movies above a certain size threshold in
# the input directory from oldest to newest until there are no more files to
# encode. More than one copy of this script is prevented from running
# simultaneously by use of a lock file.

use strict;
use File::Copy;
use File::Find::Rule;

# You'll definitely want to change some of these to suit your own setup
#
use constant
{
  # If you have the nice command available, set an integer
  # here to make use of it (for the sake of other tasks
  # running on the server)
  NICE          => "",
  
  # Path your your FFMPEG binary
  FFMPEG_BINARY       => "/usr/local/bin/ffmpeg.exe",
  
  # Path to store the lockfile in
  LOCKFILE_PATH       => "/tmp/",
  
  # Name of the lockfile
  LOCKFILE         => "media_convert.lock",
  
  # Set to non-zero to delete source files after encoding
  DELETE_AFTER_DONE     => 0,
  
  # Set to non-zero to email admins on error
  EMAIL_ON_ERROR       => 1,
  
  # Addresses to email when errors occur
  EMAIL_ERROR_ADDRESSES   => 'your_email@example.com',
  
  # Directory to scan for source files
  INPUT_DIR         => "/share/media/in/",
  
  # Directory to move source files to during work
  WORKING_DIR       => "/share/media/working/",
  
  # Directory to move source files to after encoding
  # (assuming they don't get deleted)
  DONE_DIR         => "/share/media/done/",
  
  # Directory to place output in
  OUTPUT_DIR         => "/share/media/output/",
  
  # Minimum size of a file to be eligible for encoding
  SIZE_THRESHOLD      => "50Ki",
  
  # Network path for users to access the output files;
  # gets sent out in notification email
  OUT_SHARE        => "",
};

my %encopts;

# Check to make sure the environment is sane before these become issues
#
if ( ! -x FFMPEG_BINARY )
{
  error( FFMPEG_BINARY . " does not exist or is not executable." );
}
if ( ! -d INPUT_DIR || ! -r INPUT_DIR )
{
  error( "Input folder " . INPUT_DIR . " can't be read or does not exist." );
}
if ( ! -d WORKING_DIR || ! -r WORKING_DIR )
{
  error( "Working folder " . WORKING_DIR . " can't be read or does not exist." );
}
if ( ! -d WORKING_DIR || ! -w WORKING_DIR )
{
  error( "Working folder " . WORKING_DIR . " can't be written to." );
}
if ( ( ! -d DONE_DIR || ! -w DONE_DIR ) && !DELETE_AFTER_DONE )
{
  error( "Done folder " . DONE_DIR . " can't be written to or does not exist." );
}
if ( ! -d OUTPUT_DIR || ! -w OUTPUT_DIR )
{
  error( "Output folder " . OUTPUT_DIR . " can't be written to or does not exist." );
}

# Lock file; if this exists, another copy of the program is running (probably)
if ( -d LOCKFILE_PATH )
{
  if ( -e LOCKFILE_PATH . LOCKFILE )
  {
    print "Lock file detected at " . LOCKFILE_PATH . LOCKFILE . ". To get this to run, please remove the lock file.n";
    exit;
  }
  else
  {
    open( H_LOCK, ">", LOCKFILE_PATH . LOCKFILE ) or error( "Couldn't create lock at " . LOCKFILE_PATH . LOCKFILE );
    close( H_LOCK );
  }
}
else
{
  error( "Lockfile path " . LOCKFILE_PATH . " does not exist!" );
}

# Now start searching for files and encoding them
#
# "in" rule has to come last!
my @candidate_files = File::Find::Rule->file()
  ->size( ">=" . SIZE_THRESHOLD )
  ->maxdepth( 1 )
  ->in( INPUT_DIR );

while ( @candidate_files > 0 )
{
  # Make sure customized options from one file don't persist to others
  #
  reset_encoding_defaults();
  
  # Use a FIFO method for determining which item to encode
  #
  # Start determining which file is the oldest
  my @older_files = ( $candidate_files[ 0 ], $candidate_files[ 0 ] );
  while ( @older_files > 1 )
  {
    my $first_file = $older_files[ 0 ];
    my @stat = stat( $first_file );
    @older_files = File::Find::Rule  ->file()
      ->size( ">=" . SIZE_THRESHOLD )
      ->mtime( $stat[ 9 ] )
      ->maxdepth( 1 )
      ->in( INPUT_DIR );
    if ( @older_files == 0 ) { @older_files = ( $first_file ); }
  }
  my $work_file = $older_files[ 0 ];
  $work_file =~ s|${INPUT_DIR}|&WORKING_DIR|ei;
  print "Moving '" . $older_files[ 0 ] . "' to '" . $work_file . "'...";
  move( $older_files[ 0 ], $work_file ) or error( "Coudn't move " . $older_files[ 0 ] . " to working folder." );
  print " done.n";

  # Check to see if there's an attached file of encoding options;
  # if so, read it in. Code from Recipe 8.16 of the Perl Cookbook
  #
  if ( -f $older_files[ 0 ] . ".txt" )
  {
    print "Found config file, reading it in...";
    open( H_CONF, $older_files[ 0 ] . ".txt" );
    while( <H_CONF> )
    {
      chomp;                    # no newline
      s/#.*//;                  # no comments
      s/^s+//;                 # no leading white
      s/s+$//;              # no trailing white
      s/r//;      # Remove Windows line endings
      next unless length;  # anything left?
      my ($var, $value) = split(/s*=s*/, $_, 2);
      $encopts{$var} = $value;
    }
    close( H_CONF );
    print " done.n";
  }
  
  # Do the conversion
  #
  my $out_file = $work_file;
  $out_file =~ s|${WORKING_DIR}|&OUTPUT_DIR|ei;
  $out_file =~ s/..*$/.$encopts{ 'out_extension' }/;
  my $ffmpeg_string = '"' . FFMPEG_BINARY . "" -i "$work_file" -y -r " . $encopts{ 'framerate' }
    . " -acodec " . $encopts{ 'acodec' } . " -vcodec " . $encopts{ 'vcodec' }
    . " -qscale " . $encopts{ 'qscale' } . " -b " . $encopts{ 'b' }
    . ( $encopts{ "size" } ? " -s $encopts{ 'size' }" : "" )
    . ( $encopts{ "time" } ? " -t $encopts{ 'time' }" : "" )
    . " -bt " . $encopts{ 'bt' } . " "$out_file"";
  if ( NICE ) { $ffmpeg_string = NICE . " " . $ffmpeg_string; }
  `$ffmpeg_string`;
  
  # Check that the conversion went all right and dispose of the file appropriately
  #
  if ( ! -f $out_file ) { error( "Output file wasn't created! FFmpeg string was:n" . $ffmpeg_string ); }
  if ( DELETE_AFTER_DONE )
  {
    unlink $work_file or error( "Couldn't delete '$work_file' after encoding." );
    unlink $older_files[ 0 ] . ".txt";
  }
  else
  {
    my $done_file = $work_file;
    $done_file =~ s|${WORKING_DIR}|&DONE_DIR|ei;
    move( $work_file, $done_file ) or error( "Couldn't move '$work_file' to '$done_file'." );
  }
  
  # If there's anybody who signed up to be alerted when the job is done,
  # email them now
  #
  if ( $encopts{ 'done_addresses' } )
  {
    my $filename = $out_file;
    $filename =~ s|.*/||;
    my $message = "Encoding of your file '$filename' has finished, and it can now be found at '" . OUT_SHARE . "$filename'.";
    `echo $message | mail -s "Encoding of $filename completed!" $encopts{ 'done_addresses' }`;
  }
  
  @candidate_files = File::Find::Rule->file()
    ->size( ">=" . SIZE_THRESHOLD )
    ->maxdepth( 1 )
    ->in( INPUT_DIR );
}

# Now clean up
#
unlink LOCKFILE_PATH . LOCKFILE;

#
# END OF PROGRAM
#

# Subroutines
#

# Report an error to the console and optionally through email
#
sub error
{
  my $error = shift();
  
  # Notify the people who should be alerted on error as well as the users
  #
  if ( EMAIL_ON_ERROR )
  {
    my $message = "Encoding of an item failed. The following message was given:nn" . $error;
    $encopts{ 'done_addresses' } .= EMAIL_ERROR_ADDRESSES;
    `echo $message | mail -s "Encoding failure!" $encopts{ 'done_addresses' }`;
  }
  if ( -e LOCKFILE_PATH . LOCKFILE )
  {
    unlink LOCKFILE_PATH . LOCKFILE;
  }
  die( "An error occurred: '" . $error . "'" );
}

# This is where you change the defaults. In particular, if
# you have any extra libraries compiled into FFMPEG then
# you'll probably want to set those here (e.g. libx264, libfaac)
#
sub reset_encoding_defaults
{
  $encopts{ 'acodec' }       = "vorbis";
  $encopts{ 'vcodec' }       = "h263";
  $encopts{ 'framerate' }     = "30";
  $encopts{ 'qscale' }       = "8";
  $encopts{ 'b' }         = "96k";
  $encopts{ 'bt' }         = "256k";
  $encopts{ 'size' }        = "";
  $encopts{ 'time' }      = "";
  $encopts{ 'other_opts' }     = "";
  $encopts{ 'out_extension' }     = "mov";
  $encopts{ 'done_addresses' }   = "";
}

At runtime, the program searches a directory for media files and encodes any it finds in FIFO fashion according to file modification date, i.e. it encodes the oldest file first, then the next-oldest, and so on. If it finds a text file bearing the same name as the media file (e.g. “MyMovie.mov” would have a text file “MyMovie.mov.txt”) then it reads in options from that text file and replaces the defaults with them. This allows for granular control over formats and encoding options without having to invoke a command line. Here’s a sample text file:


 # This is a file for defining options for a particular file.
 # For example, if you wanted to use mp3 compression instead
 # of the default, you could put that here. If you're familiar
 # with the FFMPEG binary, you can also give it special
 # parameters (such as setting the size or aspect ratio)
 # from within this file.
 #
 # To use this file, simply copy it and rename it using the
 # name of the media file it's attached to, plus the extension
 # ".txt". For example, if you have a media file with the name
 # "test.mpg" then the options file would be "test.mpg.txt".
 # You can then set the below options according to your needs.

 # Audio codec to use. Valid options are libfaac, libmp3lame,
 # ac3, mp2, adpcm_ms, pcm_s8, flac, vorbis, and many others.
 # See "Valid Encoders.txt" for a full list.
 # The default is libfaac.
acodec = libfaac

 # Video codec to use. Valid options are libx264, flv, h263,
 # mjpeg, mpeg1video, mpeg2video, mpeg4, rv10, rv20, wmv2,
 # and many others. See "Valid Encoders.txt" for a full list.
 # The default is libx264.
vcodec = libx264

 # Extension to give the output file, e.g. MOV, WMV, AVI.
 # The default is mov.
out_extension = mov

 # Addresses to email when the video is finished encoding.
 # Separate them with a comma, and it's up to the user to
 # make sure the addresses are valid. No default
done_addresses =

 # Framerate of the output video. Default is 30
framerate = 30

 # Qscale factor. This has nothing to do with the physical
 # dimensions; rather, it has to do with PSNR quality.
 # The default is 8.
qscale = 8

 # Video bitrate in bits/second. Use "k" for thousands.
 # The default is 96k.
b = 96k

 # Video bitrate tolerance. Use "k" for thousands. Too
 # low and quality will suffer dramatically. The default
 # is 256k.
bt = 256k

 # Other options to pass to FFMPEG. These should be given
 # just as they would be on the command line. The full list
 # is at http://www.ffmpeg.org/ffmpeg-doc.html
 # Some useful ones:
 # -s WxH       Defines size in width by height, e.g. 640x480
 # -t seconds   Encodes only the first part of a video,
 #                e.g. 30 for the first thirty seconds
 # -aspect x:y  Sets the aspect ratio, e.g. 16:9
 # -ac X        Sets the number of audio channels
 # Others can be found at http://www.ffmpeg.org/ffmpeg-doc.html
 #
 # As an example, to encode the first minute of a video at
 # 320x240 resolution, the options would be set to
 # "-s 320x240 -t 60"
 #
 # There is no default
other_opts =

Now for the final part: making the script run repeatedly. It will scan the target directory once when it’s invoked, but if you want to have it run on anything dropped in a directory you’ll need to run it periodically, sort of like OS X folder actions except capable of working on remotely-added files (folder actions will only work when something is dropped in from the server’s console). To do that in OS X, create a .plist file in a convenient system directory (I used /Library/LaunchDaemons):


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/
PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>Media_Convert</string>
    <key>Program</key>
    <string>/usr/local/bin/media_convert.pl</string>
    <key>StartInterval</key>
    <integer>300</integer>
  </dict>
</plist>

(This sets the script to run every five minutes, thereby constantly checking the target directory for media.)

Finally, set the script to launch when the OS X machine is started by issuing the command:

launchctl -w load /Library/LauchDaemons/your_script.plist

Linux users have it easier this time around: don’t muck about with the plist file, and instead simply set the Perl script to be executed every five minutes from cron.

Windows users: sorry, you’ll either have to deal with the awkward Windows task scheduler or download something like CRONw. You’ll also be devoid of email capability unless you set up sendmail on your Windows machine (as well as cat) or alter the lines in the script which send email out.

I’ve found this script to be very useful so far, and I hope that it can help someone else, too.

03.09.09

Sysadmin: Update OS X Leopard Automatically by Running Software Update

Posted in OS X, Programming, Sysadmin at 2:14 pm by cygnil

I’ve had occasion to spend quite a bit of time managing Macs in a lab environment (that is to say, lots of Macs used by nobody-knows-how-many users), and updating the OS efficiently across several machines has always been a problem.

Radmind is certainly one option, except that the issue of dynamic libraries makes it similarly time-intensive. That is, some updates will force dynamic libraries (.dylib) to be recompiled, and libraries compiled on one machine have sometimes broken when copied over to another machine (Quicktime is an especially frequent case of this). Knowing which libraries are okay to copy over and when it’s okay to do so requires a bit of voodoo and vigilance, which doesn’t make Radmind quite the best solution in this case.

It’s also not hard to schedule Apple’s softwareupdate command-line tool to run every so often. However, it’s fairly dumb: if run via launchd, it won’t tell you when an update recommends that you reboot. Even if the output gets piped out to a file somewhere, the computer has no way of acting on it.

Because of this, I’ve written a quick Perl script to be run periodically to check for updates, download them if present, and install them. If a user is currently logged in and the updates require rebooting the machine, the updates will be deferred until either the next time the update check is scheduled or, if the updates have been installed and a user logged in during the process, the next five-minute block of time that a user isn’t logged in.

To start with, save this script somewhere on your system; the rest of this article assumes it’s saved as /usr/local/bin/swupdate-weekly.pl:


#!/usr/bin/perl

# Apparently it took until Leopard to have a way to reliably detect
# whether a user was logged into the system. Be aware that this might
# not work as intended or at all under OS 10.4.

use strict;

my $verbose = $ARGV[0] eq "-v";
if ($verbose) {print "Checking list...n";}
my $check = `/usr/sbin/softwareupdate --list`;
if ($check =~ /No new software available/ ||
    ($check =~ /restart/ && `who` ne '')) { exit; }

if ($verbose) {print "Installing updates...n";}
`/usr/sbin/softwareupdate --install --all`;
if ($check =~ /restart/)
{
    while ( `who` ne '' ) { sleep 300; }
    if ($verbose) {print "Rebooting...n";}
    `/sbin/reboot -n`;
}

Now add this property list file to your system, preferably somewhere like /Library/LaunchDeamons/swupdate-weekly.plist:


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>swupdate-weekly</string>
        <key>ProgramArguments</key>
        <array>
            <string>/usr/bin/perl</string>
            <string>/usr/local/bin/swupdate-weekly.pl</string>
        </array>
        <key>LowPriorityIO</key>
        <true/>
        <key>Nice</key>
        <integer>1</integer>
        <key>startCalendarInterval</key>
        <dict>
            <key>Hour</key>
            <integer>5</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>1</integer>
        </dict>
    </dict>
</plist>

Finally, set the .plist file to load when the machine starts up by using launchctl:

launchctl load -w /Library/LaunchDaemons/swupdate-weekly.plist

Voilà! Your machines should update automatically now. One thing I’d like to do is add a clause to check whether there are any further updates before or after rebooting and handle those appropriately, but for the moment this has been getting us by.

Edit: Thanks to Mike O. for catching some errors. Should be fixed now.

12.18.08

User Interface: When Good Design Meets Bad UX

Posted in User Interface, donts at 1:02 am by cygnil

Whew, it’s really been over half a year since anything was written here. I’ve got a good excuse for that (I’ve decided to become one of the devoted few making a RepRap from scratch), but that’s not why I come to you today. No, it’s something much more nefarious that brings me here. Hang on, because it’s a doozy….

A co-worker pointed me to an article posted at Cynergy Systems which outlined taking one aspect of a program written in Flash and converting it to Silverlight. I’ve been following both AIR (formerly Apollo) and Silverlight since they were first announced, and I think this sort of cross-conversion is a good way to discover the strengths and weaknesses of each platform.

The article itself could use a hefty bucket of red ink from an editor, but the information is there. What I’m boggling at, however, is the user experience (UX) at the site linked to in the article: the main page for Chromazone, a British print graphics firm.

The whole site is a huge Flash app. The jury’s still debating whether that’s a sin or not, so we’ll just pass by that for now (I notice they have an XHTML version of the site as well, so that earns them some design points). There’s a categorical navigation menu at the top, but if the user wants any actual information then he or she will have to click on one of the little buttons that appear scattered around the main pane.

Chromazone

Now, let’s take a moment here to admire the site. The whole thing is rather pleasing to the eye, the music in the background is suitably ambient, modern, and generally pleasant. The colors and shapes in the background which Mr. Fajardo at Cynergy Systems fell in love with are nicely combined, and the parallax effect (which has been used in video games since at least 1990) is well-executed. Windows have soft, round corners, and whoosh in and out of frame instead of appearing jarringly from nowhere. Beautiful graphic design all around.

But wait! As soon as the user moves the mouse to click on a link, the whole tableau moves in exactly the opposite direction! That’s highly distracting — in fact, it took me two or three times before I was able to position the cursor on the thing I wanted to click. The swirling, roundabout motions of the user’s cursor are reflected in the little navigation map in the lower left corner — that’s something which is fine in video games, but here it’s an absurd, ugly thing which should under no circumstances be required to find all the menu options in a web site.

Once the user succeeds in clicking on an item, a window whooshes into view with the content. The links, now shoved to the background, rotate and go all screwy for no defined reason. A few bleeps and clicks later and the user is looking at whatever the selected option was. For several things on the “About Us” page, this is fine. But hold on, how does one exit the window? Clicking anywhere on the background (still largely in evidence) does nothing. There are no obvious controls, just the same shape next to the title which got us into this window. Failing all else, I guess we have to click that….

Fortunately that works; the user is taken back to the main site, which skews back to a less head-tilting angle and lets the user zoom around with the cursor again. Now assume that the user clicks on “Introduction” or “People”….

Chromazone, Detail Text

Well, I’m not sure what to make of that. Just soak that in for a minute. There’s text going off the bottom of the page — for the “People” link, there’s a lot of text going off the bottom. Yet look at that tiny, tiny window. I mean, it’s minuscule! It’s the sort of window that probably has a Napolean complex and spends its evenings responding to spam messages about cheap Viagra in the hope that it might somehow, someday get big enough to hold all of the text. Some quick measurements show that even with a browser window set to 640 x 480, the text window — you know, the one with the information the user wanted — takes up less than a quarter of the screen real estate.

And guess what? Expand the browser window. Go ahead, make it as large as possible. Maximize it. That’s right, the text window stays the same size.

The coup de grace in this particular flurry of design abuse is that, since the scroll bar is clearly going to be required no matter what we do, the mouse scroll wheel doesn’t even work.

Sigh.

Okay, so having made peace with these particular visual vagaries, the user navigates to another section. Click, for example, on “Applications” in the top nav bar. Take a moment to appreciate the visual elements all over again, regardless of their function. Everything is so well put together, so calming, and the music and design elements have changed…. Just feel… the… calm….

All right, that’s enough. Time to get worked up about something again.

Click on any one of these elements, and the user is taken to a display of the talents this particular company can bring to bear on any project. Which would be nice, except, well… you can’t really see it.

Chromazone, Slide Display

I mean, look at that. It’s the tiny window syndrome all over again. This time there’s one window for text, and another for the tiniest, most Liliputian thumbnail the world has ever seen. Fortunately there’s not much text, but there’s also not much to be seen on the one thing which is really supposed to be selling clients on Chromazone’s services: the graphics depicting their graphic skills. Clicking the pictures doesn’t even zoom in on any larger versions — what you see here is what you get, and you have to make the decision to plop down several thousand British pounds on a couple of squints. Not an encouraging prospect.

Okay, so that’s pretty infuriating. But what really makes me livid is the same thing I’ve complained about time and again. Click between the top-bar nav items a few times and you’ll notice something pretty quickly: the menu items get completely rearranged. Nothing drives me battier than things changing around right when I want to find them quickly. I mean, being placed randomly on a page is one thing, but having the menu items change location every time you visit? How many people can there be in the world who think this can possibly be a good idea? More importantly, how many kneecaps do we, the mob of still-sane users, need to break on these people before the entire group will learn not to do this? I and my baseball bat, “Kneebiter,” are prepared to do whatever it takes.

Chromazone, Random Menus

As a quick side note, the randomization algorithm is pure and utter crap. Take a look at this arrangement: there are at least six menu items which are stacked upon one another, two of them such that you can’t even select the lower link.

Chromazone, Unecessary Options

The final insult is in the last menu option’s screen: “Contact.” This screen consists of a total of one — yes, one — link option. One option which was randomly placed off-screen when I visited it. It’s like Chromazone enjoys making users leap through unnecessary hoops.

Before I wrap up, I’ve got to finish by congratulating Chromazone. The site really does look and sound amazing, and they deserve kudos for that; it’s not an easy thing to make something so appealing and novel. It’s just a shame that the user experience is so abysmal. Next time how about putting a UX/UI expert on your design team, guys?

So, here’s the summarized list of what we’ve learned:

Lesson #1: Don’t play games of chase with your users if you want them to click on something. Especially with a business site: it’s supposed to be easy for the users to get the info they want, and taking them out of the comfort zone isn’t a good way to ingratiate anybody to your company.

Lesson #2: If you need a mini-map on your site to show the user where your links are, you’re doing site design very, very badly. Turn in your mouse and designer badge at the door and don’t come back.

Lesson #3: Give users an obvious exit option. If the user has to guess as to what closes a window — especially if nothing else can get done until then — it’s going to be frustrating and leave a bad impression of your site (and by extension, business).

Lesson #4: Make good use of screen real estate. There was no reason whatsoever for the background to take up over 3/4 of the space on any of these text items, and not making use of all that available space is like shooting yourself in the foot… with a Howitzer.

Lesson#5: Make scroll bars functional. If I’m going to have a postage stamp-sized view of the site, I’d like to at least be able to scroll the part with information on it rather than seeing more of the background (however delightful it may be).

Lesson #6: You’re a graphic company, for the love of all that’s holy! Get some full-sized, detailed graphics on the web! C’mon, guys, I mean… you just failed at the internet. All of you. (This goes for other companies too — if you have something to show, make sure people can see it well — but a graphic design company not doing this is like an airline grounding all their planes and then wondering why nobody flys them anymore.)

Lesson #7: Do! Not! Use! Random! Menus! Actually, you know what, guys? I’m just going to go in a corner and cry instead. Go ahead, use all the random menus you like! Just know that I’ve got another needle to jam in some hapless designer’s eye for every random item you code. It could be yours.

Corollary to Lesson #7: If you have any elements randomly placed, for love’s sake, at least make sure they won’t overlap one another. That’s just sloppy.

Lesson #8: If a menu has only one item, it’s hardly worth going through the whole display thing, is it? Just use that menu item as the actual link. Take us directly to the contact page in this case, and don’t make us use your terrible cat-mouse-game interface to hunt for and click on that one precious item.

Lesson #9: Hire a copy editor for your public site. This goes more for Cynergy Systems than Chromazone, but even Chromazone had a lot of mistakes in their grammar. Yes, Chromazone is British. Some things (like possessive apostrophes) are universal throughout English dialects, though. Sloppiness is unprofessional.

05.09.08

Sysadmin: Customizing Time Machine in a Managed Environment

Posted in OS X Server, Sysadmin, dos at 12:55 pm by cygnil

I’ve been having a lot of fun with Leopard Server recently.

No, really.

Even though the Server edition seems in many ways like a couple of tools were slapped onto OS X as an afterthought, it does have a lot of extras that I find useful in managing disparate Mac systems. For example Leopard’s new Time Machine, while not a full-featured backup solution, provides a largely self-managed solution that involves relatively little administrator overhead. The users can connect to the “backup” server (we’ll leave that in euphemism quotes for now), navigate through and restore their own items, and all I have to do is make sure the terabytes of space don’t run dry.

Well, hm, about that….

Say there are ten users running Time Machine, and a one terabyte drive being used for backups. Minus filesystem overhead, that’s conservatively 90 gigabytes per user — definitely less than the typical drive capacity on a new Mac. Now, it’s true that most users won’t use the full capacity of their drives. Still, I’d rather do what I can to reduce needless waste of our precious space.

On an unmanaged computer, Time Machine will give the user a fairly simplistic way to exclude folders. Workgroup Manager on OS X Server, however, will only give the administrator a paucity of options in its basic state: the path, whether to skip system files (good!), whether to back up automatically, and an optional quota. Nowhere is there an option for finer control over what gets backed up; it’s either “everything on the drive” or “everything on the drive minus system files.” “System files” is an odd designation, too, because it still backs up the Applications, Library, and some System folders.

Fortunately, there is a more advanced mode in the Workgroup Manager. It’s going to require a bit of work, though. Here’s what I did:

First, I created a directory in /Library called “Client Preferences” on the speculation that I’ll be doing more of this sort of thing. Managed clients have a similar directory called “Managed Preferences” so I thought this would be a nice match.

With that directory created, I copied the /Library/Preferences/com.apple.TimeMachine.plist file into the Client Preferences directory. (That’s not to be confused with /Library/Preferences/com.apple.MCX.TimeMachine.plist which is, in fact, completely different.)

Now for the hack-and-slash. Just about everything except for the excluded directories entry can be managed through Workgroup Manager, so delete those keys and leave only the excluded directories. From there it’s a matter of simply putting in what you wish to exclude, each one in a separate string. For example, if I wanted to only let the User directory be backed up on a default Leopard install, I’d have:

<dict>
    <key>ExcludeByPath</key>
    <array>
        <string>/System</string>
        <string>/Applications</string>
        <string>/Library</string>
        <string>/Desktop</string>
    </array>
</dict>

Finally, go into the Workgroup Manager’s preferences for the computer or computer group you want to set the exclusions for (note that you can’t set Time Machine preferences of any sort for users or user groups). Click over to “Details” instead of “Overview” and you’ll see a list of what you’ve already set. Add a set of preferences by clicking on the “+” button at the bottom, navigate to the new Time Machine .plist file in the Client Preferences directory, and click “Add”. You should now have a more granular Time Machine backup strategy!

Note that if you wanted to set different directories for different computer groups, you still only need the one file; you can add it and then edit the keys directly in the Workgroup Manager without altering the file itself — WGM only gets its initial preferences from the file.

I’ve used this strategy to shave tens of gigabytes off of each client’s backup. Three cheers for efficiency!

04.28.08

User Interface: Menus, Revisited

Posted in User Interface, donts at 10:34 pm by cygnil

A little less than a year ago I pointed to the atrocity of the FUNiGIRLS website’s menu and its exasperating refusal to perform the one main function of a menu: providing a clear, easy way to navigate through the rest of the site. It turns out Vincent Flanders of Web Pages that Suck (which we may have to look at, too) beat me to it by a year with an equally stunning example of horrible menu design: unlabeled, animated menus. On the one hand, the FUNiGIRLS site has inactive elements in its menu that look exactly the same as active ones. On the other hand, the Qualcomm menu’s unlabeled elements actually move around! I see that some even cross each other, trying their best to evade recollection and gleefully frustrating the user. Qualcomm gets bonus points for the fact that their menu looks like a poorly-designed Flash ad.

In their favor, both Qualcomm and FUNiGIRLS have fixed their navigation issues. This won’t stop the old sites from being used as negative examples, of course, but at least the design managers in each case finally came to their senses.

04.22.08

User Interface: Sliders, localization, and user expectations

Posted in User Interface, donts at 12:27 pm by cygnil

I recently had the task of evaluating several different applications for recording video calls in Skype, a class of software which is surprisingly meager. The few programs I could scrounge up ran from the one-man show to corporate essays into Skype plugins. One of these in particular caught my eye, however. Actually, “caught my eye” is probably a little more euphemistic than it should be: the image I want to create here is really one of the program leaping out of the monitor, grabbing ahold of my eyeball like a crazed cowboy riding a rabid bronco, and headbutting me repeatedly with its design flaws.

Let’s start with the main screen of today’s subject, SkypeCap:

SkypeCap main screen

There are already some things causing me problems here, and I’m not even talking about the dull, flat, 1995 look common to most Windows applications.

The first thing most people notice, of course, are the colors. I can’t even pretend to quote research on this, but numbers granted to me by proctological extraction say that it’s about a fifty/fifty chance whether the eyes are pulled upwards towards the buttons or rightwards towards the status boxes. Either way, let’s look at what’s going on here:

The buttons along the top don’t win any points for original or cohesive style in the first place (they’re recycled Windows XP icons), but the furthest to the left is rather confusing. It has the exact same icon as the widely-known Windows Media Player, so most users would expect it to launch that program (indeed, it would even make sense in this context — if you want to play a recording made in SkypeCap, it will launch your default media application and play that file externally). But no! That would be sensible! Instead the WMP icon cleverly performs… a filesystem operation! That’s right, it’s the button you click to choose the output folder for your Skype recordings. A folder icon of some sort clearly wouldn’t have done the trick. Note also that the exact same function is reproduced elsewhere in the window with a different look — the ellipses button to the right of the text input does precisely the same thing.

Speaking of that text input, here’s another thing that seems completely wrong to me: you would expect to be able to type in a path there, wouldn’t you? It looks exactly like every other text input where you’re expected to enter something. But no, sorry, even though you get a text cursor by clicking in the box, actually typing anything meets with zero success; the text box actually has special code whose sole responsibility is to ignore any key typed by the user and reposition the cursor at the beginning of the box! The only way to change the entry is by clicking one of the aforementioned selection buttons.

Let’s move on to the colored indicators on the far right. Again, no awards for style, but here’s the thing: they look exactly like stoplights. Color, arrangement, everything. What they actually do is turn a slightly brighter shade when there’s a video stream or audio stream detected in Skype, or when Skype is launched at all. There’s no exclusive state information shared between the three as one would expect from a traffic light arrangement, and they really don’t indicate whether something is stopped, stopping/starting, or operating normally (as most people would think). They are, in fact, wholly separate from one another. It would have been more sensible to have a column of three buttons which all have a similar look when deactivated; in lieu of that, I would have been content with buttons in any color arrangement but this one, which has a loaded connotation that easily 99% of the audience would call to mind.

Those are all fairly minor complaints, though they are all worth bearing in mind. Bad design, sure, but nothing we haven’t seen before. However, there is one particular feature of this program that made me decide to write this article…

Standard encoding quality

Several layers deep into the options (we won’t even discuss that) lies a settings box for the mp3 encoding quality. Most of it is pretty good, assuming that the user is advanced enough to know what the settings mean. However, do you see that slider at the bottom? Given its label, most people would expect that moving the slider towards its maximum point would increase the quality of the file. So let’s see what happens when it gets moved all the way to the right:

Slider maximum

“Fast mode”? That sounds kind of like the opposite of quality; “you can either have it done well or done quickly” is a maxim of engineering.* But most English-speakers would expect that the right side of the slider would be the maximum value. If the slider gets moved all the way to the left, however, sure enough:

Slider minimum

“Encoding quality: high.” Well, something is clearly not right here. I sympathize with the programmer’s dilemma: the tradeoff between quality and encoding speed is something people sometimes fail to grasp, and it should be explicitly stated for the benefit of new users. However, setting up your slider to work in the reverse direction is — and these are the kindest words I can muster for this — a piss-poor solution.

Before we get to any lessons, let me point out something about knowing your audience. This slider behavior can be excused under one circumstance: internationalization. Some languages actually do read from right to left; two of the better-known among these are Arabic and Hebrew. This reversal leads to most other things flowing similarly — for example, the label for the slider above would be on the right side instead of the left, and the slider would be expected to have its maximum point at the left instead of the right. Everything would be good. (Labels for the other controls, of course, would be switched similarly, as would the positions of the “OK”, “Cancel”, and “Apply” buttons, and even the window-close widget would be moved to the left side of the menu bar instead of the right in order to make room for the title.)

However, that’s Hebrew (or Arabic, or…). There’s still no excuse for the slider to behave that way when the interface has been localized to English, since no English-speaker reads the language from right to left. A quick check on GeoVid, the company responsible for this product, reveals no obvious ties to any foreign cultures, so my inclination is to blame this on clueless user interface design rather than shoddy localization. The rest of the application only reinforces that hypothesis.

So, what have we learned? A whole bunch, hopefully.

Lesson #1: Make sure your icons are indicative of the function they serve.

Lesson #2: Lesson #1 goes doubly so if you’re using an icon with a loaded meaning, e.g. one that most users will be used to seeing in another context. If there’s any sort of widely-held prior association, whether with other applications, traffic lights, or anything else, make very sure to label your widgets. I can’t stress enough how important it is to have the UI act according to most user expectations.

Lesson #3: It’s bad form to have two differently-shaped and -labeled buttons perform the same function. Repeat this a thousand times to yourself like a Zen mantra for buttons which lie on the same screen/frame/pane.

Lesson #4: Use UI elements for the purposes for which they were intended. For example, if a text input field is present, the user should be able to type something there. If it’s being used simply for a non-interactive display of data, it’s being used incorrectly.

Lesson #5: Make sure the label on an adjustable widget corresponds to what it’s adjusting. For example, if the label says “quality,” the widget should be used to adjust quality, not speed.

Lesson #6 is a tricky one. “Know your audience” is what it boils down to, but it carries nuances of cultural localization that run so deeply into the fundamentals of how an application is built that several books have been written entirely on just this subject**. For now we’ll leave it at “know what your audience expects, and don’t violate those expectations unnecessarily.”

Corollary to lesson #6: If you must violate your audience’s expectations, make sure it’s obvious what you’re doing. One solution to the slider problem above is to label each endpoint of the slider, one with “Higher quality” and the other with “Faster encoding” (or ideally one with “Higher quality/slower encoding” and one with “Lower quality/faster encoding”). That would have made the dichotomy explicit while only sacrificing another text line of screen real estate for the sake of the users’ sanity.

Epilogue: We evaluated three PC products for the task of recording Skype video conversations (as well as one Mac product, the excellent Call Recorder from Ecamm). Of the three, Pamela cost between $25 and $37 depending on edition and SuperTintin cost $25. And SkypeCap? It cost $50. Twice as much as the other packages so that we could inflict this upon our users.

Needless to say, we decided against it.

———

* All right, all right, typically it’s more of a triangle: “You can have it done well, done quickly, or done cheaply; choose any two.”

** Books on internationalization/localization: Do a search on Abe, Amazon, or Barnes & Noble for the topics, or do a Google search for “i18n” (techie code for internationalization, because it is a fingerful to type) and “L10n” (techie code for localization).

03.20.08

The Simplicity of Design (David Pogue)

Posted in User Interface at 11:10 pm by cygnil

This is about two years after the fact, but I’ve just discovered the archives of talks from the last few years’ worth of TED conferences and came across a nice talk from the New York Times’ David Pogue on simplicity and technology design which definitely strikes home. His parting words are perhaps the most poignant of all: making simple interfaces is really difficult. Anticipating all the different ways users can possibly navigate through an application is tedious and painfully open-ended, and collapsing the range of possible options for a piece of software into something that isn’t overwhelming can be like choosing which limb to cut off. Nevertheless, the success of the iPhone, iPod, and Wii show that simplicity done right is a strong selling point.

11.14.07

Programming: Safari Javascript rewrites innerHTML properties

Posted in Broken, Javascript, Safari at 4:21 pm by cygnil

Any web developer will tell you that quality assurance across platforms is one giant headache. In fact, it may be impossible to get any web developer to stop telling you about it. Allow me to add my voice to the clamor.

I’ve long abhorred Javascript as a tool. It’s frequently necessary, but the range of different and often incompatible implementations creates a barrier to efficient web design. By the time a Javascript application of any significant size is finished, all the browser tests and workarounds have mutated an otherwise healthy, beautiful code base into an ugly, lumbering monster of Frankensteinian design.

Throughout all this, one thing developers usually like to count on in their fight to maintain sanity and working applications is Tenet #1: “The interpreter shall not touch thine stuff inappropriately”. If you give the browser variable containing a string, “foo”, it would be extremely distressing to find that what it returns is actually “bar”.

Well, guess what? That’s exactly what Safari does.

Specifically, within Safari’s innerHTML properties it checks to make sure that the input it’s been given conforms to what it thinks is valid HTML. The more benign manifestation is changing all element names to upper case (for example, passing “<a>” into an innerHTML property results in “<A>”). Slightly less benign is enclosing any attribute value in double quotes, even if it was passed in as single quotes to begin with. More malicious still is the complete eradication of any comments, and worst of all is the automatic closing of any tags that Safari doesn’t see in the first value passed to it. Yes, even if you append the proper closing tags later on, they’ll count for nothing.

This means that none of the following items will work properly when assigned to an innerHTML property in Safari:

item.innerHTML = "<span onclick='alert("Hi there!");'>Click me</span>"; will result in <span onclick="alert("Hi there!");">Click me</span>, which fails because the quote marks are all wrong.

item.innerHTML = "<!-- DO NOT REMOVE THIS COMMENT OR YOUR APPLICATION WILL BREAK --> This application's license requires all comments be included. Failure to comply will result in an inoperable application" will fail because, of course, the comment will get stripped out.

And worst of all, item.innerHTML = "<a href='http://'>" + n + " Review"; if ( n > 1 ) { item.innerHTML += "s"; } item.innerHTML += "</a>"; results in (if n is equal to 2): <A href="http://">2 Review</A>s (oh yes, did I also mention that an ending tag with no corresponding start tag is stripped out? There should have been a spare </A> at the end of that, but never fear — Safari takes care of you!)

This is a serious flaw in Safari, and is not replicated in any other browser nor, to my knowledge, is it part of any Javascript specification. While the idea is certainly tempting to help “sanitize” the web and make sure that all code delivered to the browser is well-formed, this is a clumsy and harmful implementation that should be done away with as quickly as possible. (This is still accurate as of Safari 2.0.4 build 419.3.)

I also had difficulty finding anything directly related to this using several web searches, so it’s my hope that this helps somebody else avoid the wasted work day I just experienced because of Apple’s excessive cleverness.

07.27.07

User Interface: Menus, Part II

Posted in User Interface, donts at 1:37 pm by cygnil

Let us, for the moment, assume that the internet really is a series of tubes. Let’s even go so far as to say it’s a habitrail with you, the user, scurrying back and forth and up and down trying to find the rare piece of cheese that is actual content on the web. I think we’ve all had days where we feel like that.

Now assume that there’s some Pavlovian schmuck who’s messing with you, putting switches and electrical plates all over the tubes. Press the right switch, and you get the cheese. Press the wrong one and you get a shock, or worse: redirected to goatse.

With stakes this high, you would be forgiven for asking for a user interface that was consistent, usable, maybe even… informative? It’s a stretch, I know, but I hear such things exist out there. Somewhere.

I’ll tell you one place you won’t find something like that, though: FUNiGIRLS, home of the downright worst menu I’ve ever seen in my life. Let’s take a look here, making sure to keep hammers and sharp objects well away from the area for the duration of our tour.

When you visit the page, you see the menu in the upper right corner. Quite helpfully, it’s labeled “MENU”. This, at least, is better than Windows Live managed. However, there’s something to be learned here:

Lesson #1: If you need to label your menu as such, it might be a good idea to think about a small redesign.

Funigirls menu, blank

Okaaay , that’s kind of a lot of bubbles there. Since something is so helpfully labeled “Menu”, how about if we start there and see what happens? We’ll just mouse over and…

Funigirls menu, menu activated once

Oh. Well, um, okay. I guess that’ll do for an option. So the circles are where the menu items really are, huh? That’s cool. But the circle just below and to the right of the menu doesn’t do anything. So it’s not all the circles, just some. That’s… really quite annoying. I think I feel my blood pressure increasing a bit.

Well, since not all of the circles do something, let’s go back to the “Menu” option so we can at least find the one it gave us before. Ah, there we go. Now we’re… wait, what?

Funigirls menu, activated twice

That’s… that’s not what we got last time. So you’re telling me that I have to keep moving my mouse cursor on and off the “Menu” item to find the thing I got last time? What kind of mad scientist devised that? Is this some kind of experiment? Am I being watched? Is… do I get cheese at random intervals or something? That’s… I… Ohhhhh!

Fine. Be a dick. I’ll play your little game.

No, wait, that time I got the freebies.

Now back to FUNiFans.

Oh! FUNiFacts! Not what I wanted.

Freebies again. I wonder if there’s free Advil behind that link? Or a poke in the brain? That would be more expedient than the slow torture of this menu.

Okay, you know what? Screw you, miss or mister web developer. I’ll just mouse over the menu as if I know what I’m doing. Time to play hide and seek with options!

The report: The uppermost left bubble does nothing. The one just below and to the right is the fans area. Down and right from that, facts. Down and right from that, freebies. Up from that, nothing, and then going straight to the right is something called Focus, a plea to join, and then in possibly the most prominent circle, separated from the rest… nothing.

So not only does the “Menu” option choose something for you completely at random, there’s no indication which items in the bubbly mish-mash of user confusion are actually viable options! I’m trying to navigate here, people! But not only are the menu items hidden or chosen at random, they’re also completely uninformative when you finally get to one. GRAAAAAAGH!

You know what? Forget about what I said earlier, leaving hammers and sharp things aside. I don’t think there’s a legal body in the whole world that would have anything but sympathy for you in hunting down the accursed designers for this one. I feel a bit of a Hulk moment coming on myself, in fact.

Smash. Smash it all.

Okay, so here’s the rundown:

Lesson #2: If a menu item is selectable, give it the same result every time. If you can’t manage that, at least give it some sense of logic. Giving a result at random? Why… what… I mean, who thinks that’s a good idea?

Lesson #3: If your menu is a nonstandard size or shape, have the items labeled.

Lesson #4: If your menu has some things that look like buttons, for the love of all that’s holy and good in this world, don’t throw in inactive ones among the active ones!

There is one more thing about this site. It’s something I’ve avoided showing you in the above screencaps. It’s kind of like the cherry on top of a mountainous sundae of crap. Here, right below the menu in prime screen real estate, written in the largest font on the page that isn’t a product logo, is this gem:

Funigirls Sitemap link

Lesson #5: If you need to prominently display a link to your sitemap — larger even than the main menu, in text larger than any other text on the page — then it’s time to trash your site design and start over. No, you aren’t allowed to argue. Put that in the waste bin right this instant.

I can’t believe that actually cleared any design team. I can’t believe that somebody actually worked so hard to make it look polished. I… just can’t believe anything about this site. I think I need to lie down.

And you know, I think I almost wish that link had taken me to goatse. At least there the pain, however intense, is short-lived. This is just downright torture.

07.26.07

User Interface: Menus, Part I

Posted in User Interface, donts at 12:47 pm by cygnil

Ah, I remember when I was youthful and brash, joining the hue and cry of open-source demagogues against the evils visited upon this world by the likes of Microsoft. The world would clearly be a better place without such commercial wankery, and open-source software wasn’t just another way of releasing a product, it was a way of life.

Actually, that was just last year. Things have changed, I’ve seen that Microsoft (like anyone) has both good and evil sides, and I’ve realized that it’s a much trickier and nastier business of visiting the grey areas between beneficence and skulduggery and trying to separate the two.

However, that’s mainly ideological — but it does explain why I have habitually stayed away from Live search, and why I’ve therefore completely missed this prominently-placed menuing faux pas. Let’s take a look at the screen at live.com and the second worst menu I’ve ever seen, shall we?

Live Menu, Closed

It’s very clean and simple, not confusing the user with any extraneous information except for a few choices tidbits the marketing ninjas snuck in there in the dead of night. But wait! This is also supposed to be a portal page to all of Microsoft’s services, isn’t it? You know, Hotmail, MSN, black-market baby trade… (oops, sorry, I think there’s still some GNU toxin running through my system). Where are all of those?

Turns out they’re easily available in a menu that’s hiding in plain sight — probably also the work of ninjas. There’s no indication that anything will happen if you mouse over the Windows logo (and only the Windows logo — even placing the cursor over “Windows Live” does nothing), and the only way a user could possibly find out the menu was there is by flailing around wildly, passing the Windows logo by accident, and suddenly getting swarmed by a flock of new options.

Live Menu, Open

Since Microsoft went through all the trouble of developing each of those services, I’m going to take a leap of faith here and say they want people using them. But if users can’t find out how to get to the services, that might hinder the goal, huh?

Lesson: If you have a menu, make sure it’s marked as such.

Tomorrow my blood will boil and my teeth will gnash as we look at the worst menu I’ve ever seen.

« Previous entries Next Page » Next Page »