The family album has been growing and coping with the volume of images, stored under Ubuntu is becoming a problem. With the attitude – “waste time to save time” – started exploring the options for some kind of digital workflow to automate whatever I can or at least keep mouse clicks to a minimum.

One particular problem is adding comments to images; most archiving software (like gthumb) has you store stuff seperately from the images, maintaining their own databases (in the case of gthumb, under ~/.gnome2/gthumb). But I’d rather have comments stay with the image (e.g. using Exif – there are other ways but Exif wins on being widely supported).

Scripting Nautilus

By chance ran into g-scripts and the notion that you can extend Nautilus (the Ubuntu file manager GUI) with your own scripts. In fact there’s a short tutorial here. The examples on g-scripts use bash scripts but figured this was a good opportunity to mis-use PHP.


In addition to extending Nautilus, also took advantage of Zenity (I’m running Ubuntu as in Gnome, not Kbuntu), which allows you to pop dialogs out of shell scripts. For example if you run $ zenity --question --text "Feeling OK?", up pops a dialog like…

Zenity Dialog

There’s more on Zenity here. In fact it is a bad idea to use PHP for this given that Perl has this nailed with UI::Dialog, which supports multiple backends including Zenity and KDialog (meaning your scripts could then also work under Kbuntu) but for fun will continue with PHP here.


Anyway – some quick tips (which is pretty much covered in above tutorials) then the code.

  • You store you scripts under ~/.gnome2/nautilus-scripts
  • Make sure you’ve got the php5-cli package installed ($ sudo apt-get install php5-cli)
  • You need to add the PHP shebang line at the top of your script (I omitted the .php extension) e.g. #!/usr/bin/php
  • Make sure you make them executable ($ chmod +x ~/.gnome2/nautilus-scripts/myscript)
  • To activate the scripts, you need to point Nautilus at the scripts directory (menu: Go > Location). You may need to reload if you create additional scripts
  • To run the scripts, right click on a file and select the scripts menu

Exif Manipulation

Anyway – succeeded in throwing together two scripts, one to view Exif comments (for which it was easiest just to use the exif_read_data() function) in a Zenity info dialog and another to edit comments using a Zenity text entry dialog. For the second I used Martin Geisler’s excellent PEL library, which has an example that I could pretty much copy and paste, replacing the command line interface with Zenity (side note: PEL 0.9 isn’t quite stable it seems – if you get error messages about redefining constants, edit the script and comment the second constant assignment). So now right clicking on an image and selecting my exif_comment_edit script I get a dialog like…

Editing an Exif comment

The code…


function ohDearMe($msg) {
    `zenity --error --text="$msg" --title="Exif Info Error"`;

if ( !isset($argv[1]) ) {
    ohDearMe("Image filename required");

$file = getcwd().'/'.$argv[1];

if (!is_readable($file)) {
  ohDearMe(sprintf("Unable to read: %s", $file));

$title = 'Exif Info for '.addslashes(basename($file));

$data = exif_read_data($file);

if ( !$data ) {
    ohDearMe('Unrecognized image format!');

if ( !isset($data['ImageDescription']) || trim($data['ImageDescription']) == '' ) {
    ohDearMe('Image contains no comment');

$out = $data['ImageDescription'];

`zenity --info --text="$out" --title="$title"`;



function ohDearMe($msg) {
    `zenity --error --text="$msg" --title="Exif Info Error"`;

if ( !isset($argv[1]) ) {
    ohDearMe("Image filename required");

$in = getcwd().'/'.$argv[1];

if (!is_readable($in)) {
  ohDearMe(sprintf("Unable to read: %s", $in));

$title = 'Exif Info for '.addslashes(basename($in));

$data = new PelDataWindow(file_get_contents($in));

if (PelJpeg::isValid($data)) {
    $jpeg = $file = new PelJpeg();
    $app1 = $jpeg->getSection(PelJpegMarker::APP1);
    if ($app1 == null) {
        $app1 = new PelExif();
        $jpeg->insertSection(PelJpegMarker::APP1, $app1, 2);
        $tiff = new PelTiff();
    } else {
        $tiff = $app1->getTiff();

} elseif (PelTiff::isValid($data)) {

    $tiff = $file = new PelTiff();
} else {
    ohDearMe('Unrecognized image format!');

$ifd0 = $tiff->getIfd();

if ($ifd0 == null) {

    $ifd0 = new PelIfd();

$desc = $ifd0->getEntry(PelTag::IMAGE_DESCRIPTION);

if ($desc == null) {
    $description = exec(
        'zenity --entry --title="Exif New Comment" --text="Enter comment" --width=800 2>&1'
    $desc = new PelEntryAscii(PelTag::IMAGE_DESCRIPTION, $description);
} else {
    $entry_text = addslashes($desc->getValue());
    $description = exec('zenity --entry --title="Exif Existing Comment" --entry-text="'.
        $entry_text.'" --text="Enter comment" --width=800 2>&1');

file_put_contents($in, $file->getBytes());

It goes without saying but will say anyway – use at your own risk!

  • Tobias Beruf

    But you have to pay for it so i can use the time for you.

  • dv

    Hey Harry,

    Thanks for the inspiration.

    Thinking there must be a nicer way than to it then through nautilus-scripts I discovered Nautilus Actions. Look for “Nautilus Actions Configuration” under system->preferences.

    By going that route you can have a menu-item show up in the main menu based on filename or mimetype


  • akshay

    Very useful technology to find the most effective way of imaging.
    Thanks for this wonderful post.

Special Offer
Free course!

Git into it! Bonus course Introduction to Git is yours when you take up a free 14 day SitePoint Premium trial.