|
|
|
|
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:
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:
Thanks to: Comments:If you would like to submit a comment, click here. Submit a Comment:You must login to post comments |
|
|
Hosting by Tony Wills Website Design by Jethro Carr |
Legal Information | Contacts |