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.
Zenity
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…
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.
Tips
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). With this, you should be able to right click on an image and select my exif_comment_edit
script.
The code…
exif_view_comment
#!/usr/bin/php
<?php
function ohDearMe($msg) {
`zenity --error --text="$msg" --title="Exif Info Error"`;
exit(1);
}
if ( !isset($argv[1]) ) {
ohDearMe("Image filename required");
exit(1);
}
$file = getcwd().'/'.$argv[1];
if (!is_readable($file)) {
ohDearMe(sprintf("Unable to read: %s", $file));
exit(1);
}
$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"`;
exif_comment_edit
#!/usr/bin/php
<?php
require_once('pel/PelDataWindow.php');
require_once('pel/PelJpeg.php');
require_once('pel/PelTiff.php');
function ohDearMe($msg) {
`zenity --error --text="$msg" --title="Exif Info Error"`;
exit(1);
}
if ( !isset($argv[1]) ) {
ohDearMe("Image filename required");
exit(1);
}
$in = getcwd().'/'.$argv[1];
if (!is_readable($in)) {
ohDearMe(sprintf("Unable to read: %s", $in));
exit(1);
}
$title = 'Exif Info for '.addslashes(basename($in));
$data = new PelDataWindow(file_get_contents($in));
if (PelJpeg::isValid($data)) {
$jpeg = $file = new PelJpeg();
$jpeg->load($data);
$app1 = $jpeg->getSection(PelJpegMarker::APP1);
if ($app1 == null) {
$app1 = new PelExif();
$jpeg->insertSection(PelJpegMarker::APP1, $app1, 2);
$tiff = new PelTiff();
$app1->setTiff($tiff);
} else {
$tiff = $app1->getTiff();
}
} elseif (PelTiff::isValid($data)) {
$tiff = $file = new PelTiff();
$tiff->load($data);
} else {
ohDearMe('Unrecognized image format!');
}
$ifd0 = $tiff->getIfd();
if ($ifd0 == null) {
$ifd0 = new PelIfd();
$tiff->setIfd($ifd0);
}
$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);
$ifd0->addEntry($desc);
} 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');
$desc->setValue($description);
}
file_put_contents($in, $file->getBytes());
It goes without saying but will say anyway – use at your own risk!
Harry Fuecks is the Engineering Project Lead at Tamedia and formerly the Head of Engineering at Squirro. He is a data-driven facilitator, leader, coach and specializes in line management, hiring software engineers, analytics, mobile, and marketing. Harry also enjoys writing and you can read his articles on SitePoint and Medium.