Wellington Linux Users Group (WellyLUG) #

User: Logged Out

User:

Password:

New users, click here to join

Main Pages:
Home
Meetings
Get Linux
Articles
Resources
Discussion


Installfests:
InstallFest Archive

New Posts:

Newest Articles:

Useful apps... and hi!

Linux file system benchmarks

A bootable CD for the paranoid


Newest Resources:

NY Times article about Ubuntu

Wellington Python Users

A Virtual Box on OpenSuse 11.1 tutorial

Title: Using KDE's context menus to manage photos
About:

An efficient way to shrink, rotate and e-mail photos from the desktop.

Written By: jumbophut
Date Published: 18 November 04

Like many people, I have a digital camera. My Debian machine recognises it as a USB storage device, so I have no trouble transfering the files to Linux. Up until now, I've used ImageMagick from the command line to manipulate the files so that, for example, I can e-mail scaled down copies to friends and family. This works, but it's not efficient. I looked around for a more efficient solution that wouldn't have too much overhead, and I found one. (It's not efficient code, but it makes me more efficient :-).

The solution comes in two parts:

  1. Additions to the context (right-click) menu in Konqueror when it is being used as a file manager.
  2. A Python script that items in the context menu call.

Additions to the context menu

To add items to the context menu for all users, add files with a .desktop suffix to /usr/share/apps/konqueror/servicemenus (you will need to be root).

First, I added jpeg.desktop to deal with operations on JPEGs:

[Desktop Entry]
ServiceTypes=image/jpeg

Actions=shrink;shrinkrot90;shrinkrot270;shrinkmail

[Desktop Action shrink]
Name=Shrink to new folder
Exec=python /home/user/args.py shrink 0 0 %F

[Desktop Action shrinkrot90]
Name=Shrink to new folder + rotate clockwise
Exec=python /home/user/args.py shrink 90 0 %F

[Desktop Action shrinkrot270]
Name=Shrink to new folder + rotate counter-clockwise
Exec=python /home/user/args.py shrink 270 0 %F

[Desktop Action shrinkmail]
Name=Shrink and send as mail attachment
Exec=python /home/user/args.py shrinkmail 0 0 %F

The ServiceTypes field tells KDE that it should add the items in the Action field to the context menu, but only when the files that are right-clicked are JPEG images. Any MIME type should work. There are also some special types. In KDE2, ServiceTypes=allfiles means the context menu entry/entries will appear for any files selected (including a directory). In KDE3, it is apparently possible to exclude directories using ServiceTypes=all/allfiles.

The Actions field, as already indicated, tells KDE which actions to add to the context menu.

Each action has an associated [Desktop Action] section which specifies what text to put in the context menu and what to do when the text is clicked. In this case, I have KDE run a Python script. KDE will replace the '%F' you see in the argument list of the function, with the path of the file (or files) selected. So right-clicking on /tmp/test.jpg and selecting shrinkmail would be the equivalent of running: python /home/user/args.py shrinkmail 0 0 '/tmp/test.jpg'

I added a second file, which is more general. Unlike the first, it will show a conext menu item no matter what file type is selected:

[Desktop Entry]
ServiceTypes=allfiles

Actions=mailto

[Desktop Action mailto]
Name=Mail as attachment
Exec=python /home/users/args.py mail %U

The Python script

The Python script which KDE ends up calling should work on any machine which has ImageMagick and a recent version of Mozilla (including the mailer) installed. If you want to change the mailer to your preferred client, you will need to change the arguments of execlp/execvp in the mailFiles function.

# /home/users/args.py
# Usage python args.py command rotate overwrite file [...]
# command = shrink | shrinkmail | mail
# rotate = 90 | 180 | 270
# overwrite = 0 | 1 (0 = don't overwrite existing files)

def mailFiles(files):
    """Will mail files as attachments using Mozilla"""

    import urllib,os

    # Make filenames safe for use in a url
    files = [("file://" + urllib.pathname2url(i)) for i in files]

    pid = os.fork()
    if pid == 0:
        # Child process should see whether Mozilla is running or not
        os.execlp("mozilla", "mozilla", "-remote",  "ping()")
    else:
        (pid,status) = os.waitpid(pid, 0)
        if (status >> 8) == 0: # return status of Mozilla is in high byte
            # Mozilla is already running, so use the running instance
            cmdarg1 = "-remote"
            cmdarg2 = """xfeDoCommand(composeMessage,attachment='%s') """ % (",".join(files))
        else:
            # Mozilla is not running, so open a new instance
            cmdarg1 = "--compose"
            cmdarg2 = """attachment='%s'""" % (",".join(files))

    pid = os.fork()
    if pid == 0:
        # Child process should run bring up a compose window with attachments
        os.execvp("mozilla", ["mozilla", cmdarg1, cmdarg2])
    else:
        # Our job here is done
        (pid, status) = os.waitpid(pid, 0)
        if (status >> 8) == 0:
          return 0
        else:
          return 1

def shrinkImages(files, dir, rotate, overwrite):
    """Will take a list of jpeg file names, make a smaller copy
    of each file and put it in the directory dir.  Optionally, it
    will also rotate the image.  If overwrite is 0, existing
    files will not be overwritten; otherwise, they will be overwritten.
    Note that files will not be shrunk if there are fewer than    
    400x300=12000 pixels in it."""

    import os, sys, re

    # Get actual image size and decide shrunken size

    (topipe, frompipe) = os.pipe()

    pid = os.fork()

    if pid == 0:
        # We are the child
        os.close(topipe)
        os.dup2(frompipe, sys.stdout.fileno())
        os.execvp("identify", ["identify"] + files)
    else:
        # We are the parent
        file = os.fdopen(topipe)
        os.close(frompipe)
        imageinfo = file.readlines()
        file.close()
        os.waitpid(pid, 0)

    # Extract image dimensions
    pattern = re.compile(r'^.*\ JPEG (\d+)x(\d+).*$')
    imagedims = [pattern.search(i).groups() for i in imageinfo]

    rng = range(len(files))
    dests = [dir + os.path.basename(i) for i in files]

    for i in rng:

        src = files[i]
        dest = dests[i]
        origx, origy = int(imagedims[i][0]), int(imagedims[i][1])

        if origx > origy:
            # Landscape
            newx = (origx > 400) and 400 or origx
            newy = int((newx + 0.0) / origx * origy)
        else:
            # Portrait
            newy = (origy > 400) and 400 or origy
            newx = int((newy + 0.0) / origy * origx)

        size = str(newx) + 'x' + str(newy)

        rotargs = (rotate in [90, 180, 270]) and ["-rotate", str(rotate)] \
                                              or None
        if rotate in [90, 270]:
            # Reverse dimensions for target size
            newx, newy = newy, newx

        resize = str(newx) + 'x' + str(newy)

        sizeargs = ["-size", size, "-resize", resize]

        cmdargs =  rotargs and ["convert"] + rotargs + sizeargs + [src, dest] \
                           or ["convert"] + sizeargs + [src, dest]

        if not(os.path.exists(dest)) or overwrite:

            pid = os.fork()

            if pid == 0:
                #os.execvp("echo", ["echo"] + cmdargs)
                os.execvp("convert", cmdargs)
            else:
                (pid, status) = os.waitpid(pid, 0)
                if status >> 8:
                    sys.stderr.write("Failed to resize %s\n" % (src))

        else:
            sys.stderr.write("File %s exists.  Not overwriting.\n" % (dest) )


def openFolder(dir):
    import os
    pid = os.fork()
    if pid == 0:
        os.execvp("kfmclient", ["kfmclient", "exec", dir])
    else:
        (pid, status) = os.waitpid(pid, 0)
        if status >> 8 == 0:
            return 0
        else:
            return 1

def mkTempDir():
    name = os.tempnam("/tmp")
    os.mkdir(name)
    return name

if __name__=="__main__":
    import sys,os

    cmd = str(sys.argv[1]).lower()
    if cmd == "shrink" or cmd == "shrinkmail":
        rotate = int(sys.argv[2])
        overwrite = int(sys.argv[3])
        files = sys.argv[4:]
        dir = mkTempDir() + "/"
        shrinkImages(files, dir, rotate, overwrite)
        if cmd == "shrinkmail":
            mailFiles([(dir + os.path.basename(i)) for i in files])
        else:
            openFolder(dir)
    elif cmd == "mail":
        files = sys.argv[2:]
        mailFiles(files)

This general technique (.desktop files plus scripts) can easily be adapted to perform many powerful and useful functions, such as writing selected files to CD, opening files using a non-default application, and counting words in text files. The only drawback I can see is that in KDE2 the context menu quickly gets clogged with entries. The KDE folks came up with at least a partial solution for that in KDE3, where sub-menus can be used. In my case, all the JPEG functions could have been grouped under a single, top-level context item. It just keeps getting better!

System information:

  • Debian 3.0r1
  • KDE 2.2
  • ImageMagick 5.4.4
  • Python 2.1

Thanks to:







Comments:

If you would like to submit a comment, click here.


Submit a Comment:

You must login to post comments

Upcoming Meetings:

Wellington:
Mon 16 August 10

Search:


Quick Poll:

There is no current poll.












Legal Information | Contacts