Latest posts for tag ruby

I've always wanted to tag my image collection, but it's big and a pain to do by hand. Today I found out that digikam has got to a point in which it can be used as a nice interface to categorise images, so I now have something cool for the tagging work.

Now, I'd like to play with the tags using my debtags toolchain. Digikam stores data in a SQLite3 database, so it's easy to convert it into the text format used by tagcoll. Here's the script to do it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/usr/bin/ruby

# gettags - Extract a tagged collection out of the digikam database
#
# Copyright (C) 2006  Enrico Zini <enrico@debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# Usage: gettags [path/to/digikam3.db]
# If the database file is not given, use the one in the current directory

require 'sqlite3'

db = SQLite3::Database.new( ARGV[0] || 'digikam3.db' )

# Build a table of id => complete tag name
tagByID = {}
db.execute( "select id, pid, name from Tags" ) do |row|
        if row[1] != 0 and tagByID[row[1]] then
                tagByID[row[0]] = tagByID[row[1]]+'::'+row[2]
        else
                tagByID[row[0]] = row[2];
        end
end

# Why does it work with instant queries but not with precompiled queries?
#db.execute("select tagid from ImageTags where imageid = ?", 36887) { |r| print r[0], "\n" }
#gettags = db.prepare("select tagid from ImageTags where imageid = ?")
#gettags.execute(36887) { |r| print r[0], "\n" }

lastname = nil
lastdir = nil
ids = []
db.execute( "select i.name, t.tagid, a.url from Images i, ImageTags t, Albums a where i.id = t.imageid and i.dirid = a.id" ) do |row|
        if row[0] != lastname then
                if lastname != nil then
                        print lastdir, "/", lastname, ": ",
                              ids.collect { |id| tagByID[id] }.join(', '), "\n"
                end
                lastname = row[0]
                lastdir = row[2]
                ids = []
        end
        ids <<= row[1]
end
if ! ids.empty? then
        print lastdir, "/", lastname, ": ", ids.collect { |id| tagByID[id] }.join(', '), "\n"
end

exit 0

I didn't understand why if I perform a precompiled query I get a row object that cannot be indexed, while if I perform an instant query then everything is fine (see the comment in the script).

I'm still on the learning side of Ruby, so I welcome people telling me of better ways to write this script, and I'll be glad to update this entries with what I receive.

Done the little script, now I can plug my images collection into tagcoll and have a bit of fun::

./gettags | tagcoll related /2005/12-15-03-Taiwan-Newcamera/dsci0051.jpg ./gettags | tagcoll hierarchy ./gettags | tagcoll implications ./gettags | tagcoll findspecials

Cute! And this is another little step to somehow connect pieces of the debtags toolchain to the web, which is something I'd like to explore both for the web interface of the central database, my blog and my picture gallery.

I've been having three wishes since some time:

  1. Lean Ruby
  2. Find an easy and fast way to find digital camera shoots that need an invocation of exiftran, and invoke it
  3. Find a comfy way to code user interfaces. In particular, I've always dreamed of sticking closures into event handlers.

I've recently managed to fulfill these three wishes in a single shot by coding drézza, a minimal picture browser with quick links to invoke exiftran. If anyone is wondering, "drézza" means "make it straight" in Bolognese.

It is simple, it is easy, it does all I need: you invoke it and it will show all the pictures in the current directory. If you invoke it with a directory as commandline argument, it will show all the pictures in that directory.

It will display the pictures in a list together with their thumbnail. The thumbnail is taken from the EXIF thumbnail. If a JPEG file has no EXIF thumbnail, it invokes exiftran to generate it.

Clicking on an image in the list shows it in detail. In the toolbar there are buttons to rotate the image with exiftran, which uses a lossless transform and respects exif data.

The tool comes in a single file, no need to install. The source code is clean and commented, and is also a nice demo of various Ruby/GTK2 and Ruby/Gnome2 features and programming tricks, including:

  • subclassing GTK widgets
  • creating custom signals
  • reacting to widget resize
  • processing GTK events during long computations
  • updating the Gnome status bar
  • embedding pixmaps in the source code
  • using custom pixmaps for the toolbar

You can get it from http://www.enricozini.org/galleries/drezza.

If you play with it, let me know and I'll keep it up to date.

How to create a custom signal in Ruby-GTK2

I'm playing with the GTK2 bindings for Ruby. When coding GUI things, I usually like to build my own higher level widgets by subclassing containers and populating them with other widgets.

For example, I'm creating an image browser and I like to make an ImageList class that subclasses a Gtk::ScrolledWindow and builds the TreeView inside it:

class ImageList < Gtk::ScrolledWindow
    def initialize
        super

        @files = Gtk::ListStore.new(String, String, Gdk::Pixbuf)

        @fileList = Gtk::TreeView.new(@files)

        renderer = Gtk::CellRendererPixbuf.new
        col = Gtk::TreeViewColumn.new("Thumb", renderer, :pixbuf => 2)
        @fileList.append_column(col)

        renderer = Gtk::CellRendererText.new
        col = Gtk::TreeViewColumn.new("File name", renderer, :text => 1)
        @fileList.append_column(col)

        self.add @fileList
    end

    def readDir (root)
        Dir.foreach root do |dir|
            file = root+'/'+dir
            if !FileTest.directory?(file) && dir =~ /\.jpe?g$/ && FileTest.readable?(file)
                thumb = getThumbnail(file)
                iter = @files.append
                iter[0] = file
                iter[1] = dir
                iter[2] = Gdk::Pixbuf.new thumb
                File.delete(thumb)
            end
        end
    end
end

When working like this, I find myself very soon in need of creating custom signals: in this case I want to make a signal that ImageList emits when the user select an image from the TreeView, like this:

fileList.signal_connect("selected") do |filename|
      imageDetails.load(filename)
end

Now, how do I implement that extra 'selected' signal in my ImageList? Ruby documentation is usually very good and easy to find, but this time Google had little to say to me.

When the going gets tough, the tough gets to IRC::

enrico> Hi.  http://ruby-gnome2.sourceforge.jp/?News_20031116_1 says that it is
        possible to create custom signals, however I could not find any
        documentation on how to do it.  Anyone has a link or a short explanation?
pterjan> glib/sample in the source IIRC
pterjan>   # define new signal "hoge"
pterjan>   signal_new("hoge",                  # name
pterjan>              GLib::Signal::RUN_FIRST, # flags
pterjan>              nil,                     # accumulator (XXX: not supported yet)
pterjan>              nil,                     # return type (void == nil)
pterjan>              Integer, Integer         # parameter types
pterjan>              )
pterjan> in glib/sample/type-register.rb
enrico> ooh!
enrico> pterjan: thanks!
enrico> I'll blog about it, it might make it easier for others to google this answer

Thanks pterjan, and oh, coolness! So there are nice examples that I was silly enough to miss. Are they included in the Debian packages? Oh, yes! Right in /usr/share/doc/libglib2-ruby/examples/type-register.rb.

Here's the ImageList class with the new signal::

class ImageList < Gtk::ScrolledWindow
    type_register

    signal_new("selected",              # name
           GLib::Signal::RUN_FIRST, # flags
           nil,                     # accumulator (XXX: not supported yet)
           nil,                     # return type (void == nil)
           String           # parameter types
           )

    def initialize
        super

        @files = Gtk::ListStore.new(String, String, Gdk::Pixbuf)

        @fileList = Gtk::TreeView.new(@files)

        renderer = Gtk::CellRendererPixbuf.new
        col = Gtk::TreeViewColumn.new("Thumb", renderer, :pixbuf => 2)
        @fileList.append_column(col)

        renderer = Gtk::CellRendererText.new
        col = Gtk::TreeViewColumn.new("File name", renderer, :text => 1)
        @fileList.append_column(col)

        self.add @fileList

        sel = @fileList.selection

        sel.signal_connect("changed") do |sel|
            if iter = sel.selected
                puts "Double-clicked row contains name #{iter[0]}!"
                self.signal_emit("selected", iter[0])
            end
        end
    end

    def signal_do_selected(file)
          puts "Selected " + file
          #p caller
    end

    def readDir (root)
        Dir.foreach root do |dir|
            file = root+'/'+dir
            if !FileTest.directory?(file) && dir =~ /\.jpe?g$/ && FileTest.readable?(file)
                thumb = getThumbnail(file)
                iter = @files.append
                iter[0] = file
                iter[1] = dir
                iter[2] = Gdk::Pixbuf.new thumb
                File.delete(thumb)
            end
        end
    end
end

And here's the controller code that binds the signal to some effect::

imageList.signal_connect("selected") do |imagelist, filename|
      puts "File #{filename} was clicked!"
      image.load(filename)
end

It's quite easy, and (finally!) it does what I want without needing to write thousands of lines of code. YAY! And I finally fulfull my dream of connecting closures instead of callbacks to GTK signals.

Yes, I could have done it in Perl. Yes I could have done it in Python. But Ruby is soo cute!

I finally finished restructuring my curriculum. It needed to have two properties:

  1. I only want to maintain information once, without copying around and information getting outdated.
  2. I need translations, and I want to translate using smart tools.

Here's how it works now:

  1. It all starts with informations scattered around the file system.
  2. Some Ruby scripts go around digging for them and generate some XML files.
  3. The XML files have tags with special markers so that intltool can extract the strings inside and generate .pot files with them.
  4. I translate .pot files with all the tools one normally uses with them.
  5. Intltool generates multiple XML versions with clean tags and translated strings.
  6. At this point, I have XSLT sheets that can generate:
    • My CV
    • The Talks page
    • A page with a summary of the free software projects I'm involved in

...and it's all driven by Makefiles.

The day I feel like it, I'll add an XSLT to generate a CV in OpenDocument format.

Javier Candeira called this "Rube Goldberg Hacking", but then noone could figure out simpler ways of doing it with the properties I wanted.

Scripts available on request if someone is crazy enough to walk along a similar path.

Interesting links:

Including inline images in Ruby GTK2

I'm making a simple application and I'd like to keep it self-contained into a single file; however, I'd like to use some custom icons in it.

I tried storing a pixbuf in a string::

ICON = <<EOT
/* XPM */
[...]
};
EOT

icon = Gdk::Pixbuf.new ICON

but it tried to open a file called "/* XPM */\n....".

I tried with an array::

icon = Gdk::Pixbuf.new ICON.split("\n")

But I kept getting GdkPixbuf-WARNING **:Inline XPM data is broken: Invalid XPM header. Note that trying to write that data to a file and loading the file, worked.

This worked:

icon = Gdk::Pixbuf.new "pippo.xpm"

This didn't::

tmp = IO.readlines "pippo.xpm"
icon = Gdk::Pixbuf.new tmp

I finally managed using csource:

  1. gdk-pixbuf-csource --raw --name=antani image.png > /tmp/antani
  2. insert /tmp/antani in the Ruby source code, strip away the comments, concat the various strings:

    ICON = ""+
      "GdkP"+
      "\0\0\4\30"+
      "\1\1\0\2"+
      "\0\0\0@"+
      "\0\0\0\20"+
      "\0\0\0\20"+
      "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"+
    [...]
    
  3. load the data with icon = Gdk::Pixbuf.new ICON.unpack("C*"), true

A possible improvement could be using --rle in gdk-pixbuf-csource: raw is good for avoiding to copy the image data, but I don't feel comfortable in doing it since the image data is an integer array temporarily generated by unpack.

As usual, if you know of better ways please send me a mail and I'll update the blog entry.