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:
- Lean Ruby
- Find an easy and fast way to find digital camera shoots that need an invocation of exiftran, and invoke it
- 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:
- I only want to maintain information once, without copying around and information getting outdated.
- I need translations, and I want to translate using smart tools.
Here's how it works now:
- It all starts with informations scattered around the file system.
- Some Ruby scripts go around digging for them and generate some XML files.
- The XML files have tags with special markers so that intltool can extract the strings inside and generate .pot files with them.
- I translate .pot files with all the tools one normally uses with them.
- Intltool generates multiple XML versions with clean tags and translated strings.
- 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:
gdk-pixbuf-csource --raw --name=antani image.png > /tmp/antani
-
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"+ [...]
-
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.