I recently transferred all my photos to iPhoto. I share them on Flickr, but I've been unhappy with iPhoto's built-in Flickr support — it has an arbitrary 500 photo limit on web album size, it's crashy, it does weird synchronizations that take ages when combined with lots of large photos and a slow internet connection, it has multiple times failed to send all the full-resolution images — so I've been exploring alternatives. There's at least FlickrExport and Flickr's own Uploadr.
Although the tools work, there's a downside compared to iPhoto's built-in Flickr support. iPhoto has wonderful geotagging support, as does Flickr, but iPhoto doesn't write the data to EXIF tags and that poses a problem for the tools. Uploadr reads just the files and so never sees the data, and apparently iPhoto doesn't provide the data to FlickrExport, either. The result is that Flickr won't know the locations of the photos.
There's a way to work around this problem. The solution is AppleScript. iPhoto exports photo objects that can tell you their location as set inside iPhoto. The downside to this approach is that it's AppleScript, but apparently the alternatives like Python or JSTalk aren't quite up to tasks like these without application support.
This script will write the locations of the selected photos relies on ExifTool. It will litter your photo directory with files ending with _original that should contain the unmodified images. You should make sure the modified files are ok before deleting the originals. The usual caveats apply: I'm no AppleScript expert and this has not been tested particularly rigorously. I'd be careful especially if you don't live in the NE hemisphere. And you might want to reduce the number of dialogs. Do what you want with it.
-- This applescript will geotag the selected photos with the
-- location information set in iPhoto.
--
-- You must have exiftool installed; by default it's loaded from
-- /opt/local/bin, where MacPorts installs it from the package
-- p5-image-exiftool.
--
-- Author: Juri Pakaste (http://www.juripakaste.fi/)
--
-- Based on the Set Geo Data.scpt script by
-- Andrew Turner (http://highearthorbit.com)
--
property exifToolOriginal : "_original"
property exifToolPath : "/opt/local/bin/exiftool"
on extract_decimal(realnum)
log "extracting decimal from " & realnum
repeat while realnum > 1
log "realnum now " & realnum & ", subtracting 1"
set the realnum to realnum - 1
end repeat
realnum
end extract_decimal
on roundFloat(n, precision)
set x to 10 ^ precision
(((n * x) + 0.5) div 1) / x
end roundFloat
on d2s(degs)
log "enter d2s"
if the degs < 0 then
set the degs to degs * -1
end if
set the degrees to round degs rounding down
set the minssecs to extract_decimal(degs)
log "minssecs: " & minssecs
set the minssecs to minssecs * 60
set the mins to round minssecs rounding down
set the minssecs to extract_decimal(minssecs)
log "minssecs 2: " & minssecs
set the secs to minssecs * 60
"" & degrees & "," & mins & "," & roundFloat(secs, 2)
end d2s
on exportCoords(image_file, lat, lng, alt)
set the northSouth to "N"
set the eastWest to "E"
if the lat is less than 0 then
set the northSouth to "S"
set the lat to the lat * -1
end if
if the lng is less than 0 then
set the eastWest to "W"
set the lng to the lng * -1
end if
log "calling d2s on " & lat
set the latstr to my d2s(lat)
set the lngstr to my d2s(lng)
set exifCommand to exifToolPath & " -GPSMapDatum=WGS-84 -gps:GPSLatitude='" & latstr & "' -gps:GPSLatitudeRef='" & northSouth ¬
& "' -gps:GPSLongitude='" & lngstr & "' -gps:GPSLongitudeRef='" & eastWest ¬
& "' -xmp:GPSLatitude='" & latstr & northSouth & "' -xmp:GPSLongitude='" & lngstr & eastWest & "' -xmp:GPSMapDatum='WGS-84'" & " -xmp:GPSVersionID='2.2.0.0'" & " '" & image_file & "'"
display dialog of ("running: " & exifCommand)
set output to do shell script exifCommand
display dialog of output
--do shell script "rm '" & image_file & "'" & exifToolOriginal
end exportCoords
tell application "iPhoto"
activate
try
copy (my selected_images()) to these_images
if these_images is false or (the count of these_images) is 0 then ¬
error "Please select one or more images."
repeat with i from 1 to the count of these_images
set this_photo to item i of these_images
tell this_photo
set the image_file to the image path
set lat to the latitude
set lng to the longitude
set alt to the altitude
end tell
if lat < 90.1 and lng < 180.1 then
my exportCoords(image_file, lat, lng, alt)
else
display alert ("No location set for " & name of this_photo)
end if
log "read image, lat: " & lat & ", lng: " & lng
end repeat
display dialog "Geo Exif write complete."
on error error_message number error_number
if the error_number is not -128 then
display dialog error_message buttons {"Cancel"} default button 1
end if
end try
end tell
on selected_images()
tell application "iPhoto"
try
-- get selection
set these_items to the selection
-- check for single album selected
if the class of item 1 of these_items is album then error
-- return the list of selected photos
return these_items
on error
return false
end try
end tell
end selected_images
[ ] archived
I put up on Launchpad a backup utility I wrote called Chipmunk Backup. It's not extremely configurable nor does it have a huge set of features. It's a simple tool for maintaining a number of GnuPG encrypted full backups of a directory in a remote, rsync-accessible location.
There's no ready to download archive, but checking out lp:chipmunk-backup with bzr should give you a working version.
It's written in PLT Scheme and is known to work with version 4.1.4.
[ ] archived
Emacs tip #0: Always search EmacsWiki when you think you might need something.
Emacs tip #1: To navigate studlyCapped words, M-x c-subword-mode, as found on the CamelCase page. I had to add the following lines to my .emacs to get it work with C-left/C-right, M-b/M-f worked right out of the box:
(define-key global-map [(control right)] 'forward-word)
(define-key global-map [(control left)] 'backward-word)
Before that, they were bound to the -nomark variants.
[ ] archived
I have a somewhat difficult relationship with Django's admin site. It's a very useful feature, but I haven't really done enough with it to know when I'm going to hit a wall, if that wall's in the code or in my understanding, and how hard it's going to be to climb over the wall.
This time I wanted to have inline admin forms, except that I didn't actually want to have the forms there, I just wanted to have links to the objects — and not their views on the actual site, but on the admin site. As far as I can tell, there's no built-in support for this.
According to the admin docs, there are two subclasses of InlineModelAdmin: TabularInline and StackedInline. Looking at django/contrib/admin/options.py confirms this. And as the docs say, the only difference is the template they use. The stacked version comes pretty close when we add all the fields to an InlineModelAdmin subclass's exclude array, but it doesn't have the link.
To solve this we first create a new subclass:
class LinkedInline(admin.options.InlineModelAdmin):
template = "admin/edit_inline/linked.html"
When you want to create inline links to a model, you subclass this new LinkedInline class. So to use a slightly contrived example, if we have a Flight with Passengers:
class PassengerInline(LinkedInline):
model = models.Passenger
extra = 0
exclude = [ "name", "sex" ] # etc
class FlightAdmin(admin.ModelAdmin):
inlines = [ PassengerInline ]
And yes, we have to exclude all the fields explicitly: an empty fields tuple or list is ignored.
The new template is easiest to create by cutting down aggressively the stacked template. Like this:
{% load i18n %}
<div class="inline-group">
<h2>{{ inline_admin_formset.opts.verbose_name_plural|title}}</h2>
{{ inline_admin_formset.formset.management_form }}
{{ inline_admin_formset.formset.non_form_errors }}
{% for inline_admin_form in inline_admin_formset %}
<div class="inline-related {% if forloop.last %}last-related{% endif %}">
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b> {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %} #{{ forloop.counter }}{% endif %}
{% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
</h3>
{{ inline_admin_form.pk_field.field }}
{{ inline_admin_form.fk_field.field }}
</div>
{% endfor %}
</div>
The primary/foreign key fields are necessary to keep Django happy.
The result looks about right, it just lacks the links. It seems that Django doesn't give the template all the information we need to make them work: there's root_path that gives us /admin/, app_label contains the application's name and inline_admin_form.original.id contains the id of the inline object. What is lacking is the path component that names the model. I don't think it's available by default (is there a clean way to ask Django what's available in a template's context?), so we need to add it. Amend LinkedInline to look like this:
class LinkedInline(admin.options.InlineModelAdmin):
template = "admin/edit_inline/linked.html"
admin_model_path = None
def __init__(self, *args):
super(LinkedInline, self).__init__(*args)
if self.admin_model_path is None:
self.admin_model_path = self.model.__name__.lower()
Now inline_admin_formset.opts.admin_model_path will be bound to the lowercase name of the inline object's model, which is what the admin site uses in its paths.
With this, we can now replace the inline-related div in the template with this:
<div class="inline-related {% if forloop.last %}last-related{% endif %}">
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b> <a href="{{ root_path }}{{ app_label }}/{{ inline_admin_formset.opts.admin_model_path }}/{{ inline_admin_form.original.id }}/">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %} #{{ forloop.counter }}{% endif %}</a>
{% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
</h3>
{{ inline_admin_form.pk_field.field }}
{{ inline_admin_form.fk_field.field }}
</div>
That's it. Now Flights get links to Passengers without big forms cluttering up the page.
[ ] archived
These days I use an iPhone as my mobile music device. I have a bit over 1800 songs on it. I usually use shuffle and had it stuck in a weird state a couple of weeks ago — it was constantly playing me just a few tracks. I usually listen for just half an hour to an hour at a time, so I don't know if it would have started looping or what, but those were basically always there for a week's worth of commutes. I finally restarted the phone and that seemed to help, but what do you know, a couple of weeks, several restarts and one operating system upgrade later, it's again playing me exactly the same tracks.
As much as I love the The Roots, honestly, at this points Adrenaline!'s "Once a-again, once a-gain..." start makes me mostly think "once again indeed."
[ ] archived
Speaking of Cocoa (and iPhone) programming, for a change.
Having trouble with spurious EXC_BAD_ACCESS crashes when using NSURLConnection? NSZombie giving you not very clear messages about [Not A Type retain], pointing to an address that malloc_history says has been allocated somewhere with only framework code in the call stack? See Amro Mousa's blog entry about the subject.
In a nutshell, don't send the start message to a NSURLConnection object you've initialized with a +connectionWithRequest:delegate: or -initWithRequest:delegate:. It'll break stuff.
Apple, how about a warning about this in the docs? The docs for -start say "Causes the receiver to begin loading data, if it has not already.", not "Will break your program and make you waste uncounted hours debugging if receiver has already started." Or how about preventing this in the code? Is there some scenario where you'd want to call start after the object has already started?
[ ] archived
[ ] archived
On Saturday, it was raining cats and dogs. We were appropriately equipped and sniggered at the people in trainers etc trying to dodge the puddles. However, didn't see too many acts — saw a bit of Sébastien Tellier, but decided the crowds were too much and went to find some food (which was excellent and at 25 € for a three course vegetarian menu pretty good value.)
Next up on our schedule was CSS, which was pretty good. For some reason, it has never quite clicked for me, but still, they were busy as hell and obviously having fun.
And finally, The Roots. What a great show. No bling, no diva manners, just excellent hip hop with a surprising amount of jazz thrown in, just like on their early albums. And an incredibly diverse band, with Captain Kirk shredding his guitar and Tuba Gooding Jr on sousaphone.
Saturday was the only day it really felt like there were too many people stuffed into too small a space. Maybe it was the rain, maybe it was the fact that it was sold out, even though I don't think the other two days were that far behind.
On Sunday we were definitely starting to feel old and tired. It's surprising how tiring three days of festival gets, even without excessive drinking. Maybe I'm just too old. While eating on Saturday, I had the idea that they really should offer a show and dinner version of the festival; they already have an excellent restaurant on board, now just stretch out the dinner experience a bit and place the restaurant in a suitable location, and hey, the middle aged among us could be nice and comfy while checking out the gigs. And I really think they should have put the restaurant on the roof of the newer (if it is newer, the black one) gasometer.
We caught a glimpse of Astro Can Caravan, who had the weirdo jazz thing pretty well covered. Next up was The Five Corners Quintet, whose retro jazz was excellent as always. After that, we first checked out Plutonium 74, and had enough after one and a half songs. We listened to the first two or three songs from Señor Coconut and while their version of Daft Punk's Around the World wasn't horrible it wasn't nowhere near as good as Christian Prommer's on Friday, and by the time they hit Eurythmics' Sweet Dreams, we decided we had had enough of that too.
The next act we saw was José James who was one of the high points of the festival. He did a surprisingly jazzy gig and the crowd appreciated. After his gig, we listened for a couple of songs by Cut Copy, who really revealed themselves to be very summery party pop. Loud summery party pop. Their album In Ghost Colors was decent but I didn't get into it all that much, but they were better live. However, we were just too tired at that point and after a while headed home.
[ ] archived
Flow08 started off a whole lot better than last year. Everything worked smoothly despite the fact that there were twice as many people and twice as large an area as last year. In fact, the enlarged space felt better than the more constrained area of last year, maybe because we got to see more of the Suvilahti grounds.
Artists we saw:
Some mostly lousy pics on Flickr. Maybe Marko will post some better ones.
[ ] archived
The following isn't magic but it was unclear to me and required reading both documentation and source code and some additional Googling to get right. Maybe that's because I'm a Django newbie, but hey, I'm probably not the only one. By the way, the following applies to Django SVN revision 8068. That's roughly Django 1.0 alpha.
Anyway, I have in my model a field that's basically a time-stamped boolean: a field called deleted of type DateTimeField. If it's NULL, the thing, let's call it Foo, is not deleted; if it has a value, it tells us the Foo in question was marked as deleted back then. It's not the only way I could have implemented it but it meets my requirements and I didn't want to change it.
How to display that in admin? I could have left it as just a split datetime field, but that doesn't really communicate the intent. A checkbox with additional text telling the date is a much better representation.
I found some help in in Stuart Langridge's Overriding a single field in the Django admin, using newforms-admin, but it seems it's slightly outdated and didn't contain all the details.
Turns out for this to work well, I needed three bits: a widget class, a field class and a formfield_for_dbfield method in my ModelAdmin class.
Here's my widget class:
class BooleanDateWidget(forms.CheckboxInput):
def __init__(self, attrs=None, check_test=lambda v: v is not None):
super(BooleanDateWidget, self).__init__(attrs, check_test)
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
try:
result = self.check_test(value)
except: # Silently catch exceptions
result = False
if result:
final_attrs['checked'] = 'checked'
dt = ' <label for="%s" class="vCheckboxLabel">(%s)</label>' % (final_attrs["id"], value)
else:
dt = ""
if value not in ('', True, False, None):
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(value)
return mark_safe(u'<input%s />%s' % (flatatt(final_attrs), dt))
The render method has mostly been copied and pasted from django.forms.CheckboxInput.render, with a few modifications to create an additional label. When database value is rendered to the admin form, that method gets called. If deleted is NULL, it creates an empty checkbox; if there's a date there, it creates a checked checkbox with an additional label that contains the timestamp (not very prettily formatted, though.)
Next, the field class:
class BooleanDateField(fields.BooleanField):
widget = BooleanDateWidget
def clean(self, value):
v = super(BooleanDateField, self).clean(value)
if v:
return datetime.datetime.now()
return None
That's pretty simple. I just specify the widget to use and make the clean method which is called when the value sent by the browser is validated for storage in the model return either None if the checkbox was checked or None otherwise.
And finally the last piece is formfield_for_dbfield method in the ModelAdmin class.
class FooAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'deleted':
field = db_field.formfield(form_class=booleandate.BooleanDateField)
else:
field = super(FooAdmin,self).formfield_for_dbfield(db_field,**kwargs)
return field
That method is invoked when the newforms-admin interface needs the field object for displaying and handling the deleted value. It checks the field name to specialize the interface. For the deleted class, we call the db_field object's formfield method, otherwise we delegate to ModelAdmin. ModelAdmin's formfield_for_dbfield is pretty simple itself, it mostly just sets the widget type for a few field types and calls the formfield method of the db_field object. That's what we could have done here, too; instead of specifying the widget type in the Field class, we could have done it here.
That's all it takes! With those modifications, the split datetime field disappears and instead you get a datetime-labeled checkbox.
[ ] archived
Apologies, I'm going to indulge myself for a moment. If you aren't interested in C++ ranting, skip this.
I'm in the process of converting some C++ code to heap allocate objects instead of putting them in the stack, because I need to use them in Objective-C++ and stack-allocated objects aren't the best idea there.
Who in their right mind wants to spend programming time worrying about where to allocate objects? Why do I have to care? There's an actual problem domain here with business objects, and I'm twiddling object allocation. I feel like bashing my head against the keyboard.
[ ] archived
I just released version 0.2 (and 0.2.1, now with a NEWS file!) of JUGC, the unit conversion library generator. It allows you to specify a set of measuring units and translations in XML files and generate a conversion library from them, thus avoiding parsing a definition file at startup. The generator portion is still written in Java, but now in addition to Java, it can also generate Python code. There are small examples on the Examples wiki page. There isn't much documentation, but the example data files and tests should be clear enough.
[ ] archived
I put up pgrok ("Project Grokking for Emacs"), a simple Elisp package for project settings and tools, on Launchpad. It's a tidied up version of a couple of things I've been using for ages, basically for loading project (a source tree) settings, functions or whatever you need from files that are looked up in your directory hierarchy. There are also two (at the moment) functions for locating stuff in your project with find and grep.
There are no releases yet, but it's just one file in the bzr branch hosted on Launchpad. I use GNU Emacs 23.0.60, no guarantees about any other Emacsen.
[ ] archived
They keep on asking their listeners to plug them, so here goes. I've been listening to podcasts quite a bit lately, as I got a phone that can do them and my commute is too short to really concentrate on reading articles, and the best of the ones I regularly follow is Software Engineering Radio. There's a lot of backlog to go through and at least the interviews they've done with Joe Armstrong, Galen Hunt and Dave "not PragDave" Thomas have been really nice.
[ ] archived
Waking the project up, at least momentarily, from a nine-month slumber, I released version 0.6 of AudioFormat, the friendly audio file format converter for the GNOME desktop.
I've again found more use for the software and discovered that it was in several ways still embarassingly broken, from refusing to install on Python versions with micro version numbers (like 2.5.1) to still continuing conversions after clicking cancel, even without the window, when using as a Nautilus extension.
It should be at least a bit better now.
[ ] archived