Looks like you've figured that out on your own. But you failed to include
a hack in your message (merely being able to post here is not an
acceptable hack, but most anything else is).
ObHack:
I use flickr a lot, and I noticed some people used third parties to
organize automatic sets based on various criteria. Sets based on which
items people had commented on or favorited appealed to me. But I didn't
like the idea of trusting some random code on some random site to
access my photos. So I got a API key and learned the interface, and
now I have a script triggered by automatic email notices of activity
on my stream that can parse the activity message to get the item and
type of activity (photo or set, comment or fave) and sort the item
automatically. (Sets with comments at the set level go into a
"collection" (ie, set of sets), doing that was a whole different layer
of hack, since the collection API is not yet public.)
Elijah
------
can be found at the obvious name on flickr
That's not a hack. That's just SOP.
ObHack: Wrapping your Xbox 360 in a blanket and running it until the
thermal protection circuit shuts it down to reflow the solder on the
mainboard. No, I don't recommend doing this unless you want to risk
permanently destroying your '360.
You need a better class of problems to keep life interesting if Google
can fix them so easily. I don't know what the hell was wrong with
siege (web load tester) under Solaris that caused it to core dump
for me. Well, actually I do know that it was only doing it under
certain conditions, and I did enough forensics on the core to identify
that the *printf for the "Content-Type" header was involved. I replaced
the %s in the source with the value I wanted and it was working.
> ObHack: Wrapping your Xbox 360 in a blanket and running it until the
> thermal protection circuit shuts it down to reflow the solder on the
> mainboard. No, I don't recommend doing this unless you want to risk
> permanently destroying your '360.
Yeah, sounds like a risky way to achieve the goal.
ObSubjectLine: A long time Kirk Israel was a regular here. He's lost his
Usenet connection, but he's still around. Here's his latest, an Advent
calendar of Java apps:
http://kisrael.com/java/advent2009/
ObHack: Another flickr related item. In some circles on flickr it's
amost cliche to "Add a note" to an image showing a suggested photo
crop. So it occurred to me, why not implement that? Thus I now have
a command line script to take a photo id, the text of a note box,
and then use that to download a photo, crop it (requires scaling
note box to original image size and taking image rotation into
account), copy the Exif tags from the original to the new, and
re-upload it, optionally using the 'replace photo' method. For
good measure I delete the note if the photo was replaced.
Example usage.
Original image:
http://www.flickr.com/photos/elithebearded/4022799281/
Cropped image:
http://www.flickr.com/photos/elithebearded/4027864066/
$ flickr-cropper 4022799281 'The sign.' new
After upload, I edited the text on the 'The sign.' note to be a
hyperlink to the new image.
Next up is generalizing this to a perform some operation on this
area script. I'm thinking of adding inset images, selective blurring,
and anything else I can script by command line.
Elijah
------
:r $FR/flickr-cropper
#!/usr/bin/env perl
# Using a special note as a guide, crop and replace a photo.
# usage:
# cropper photoid "target note contents" [ newflag ]
#
# If newflag is set ("1", "y", "new", etc) then the cropped version
# will be uploaded as a new image instead of replacing the original.
#
# After a photo replacement the target note will be deleted.
#
# Jul 2009 Eli the Bearded
use strict;
use warnings;
use Flickr::API;
use Data::Dumper;
use vars qw( $flickr_api_key $flickr_api_secret $flickr_token $api $apiconf
$photoid $target $url $verbose
$flickrtool $uploadtool $replacetool
$exiftool $dltool $rottool $croptool
@notebox @sizes $sizes_cache $getinfo_cache
);
$apiconf = (defined($ENV{FLICKR_API_RC})? $ENV{FLICKR_API_RC} :
(defined($ENV{HOME})? "$ENV{HOME}/.flickrapirc" : "flickrapirc"));
do $apiconf || die "$0: can't load rc $apiconf: $!.\n";
########## external tool setup
# external tools, with %X escapes:
# %% %
# %c name of cropped file
# %d rotation to perform (clockwise degrees)
# %D rotation to perform (counterclockwise degrees)
# %h height of crop
# %i original photo id
# %l left column of crop
# %o name of original file
# %r rotate tool in pipeline (only substitues if rotation is non-zero)
# %t top row of crop
# %u url of orignal file
# %w width of crop
# flickr-upload is part of the Flickr::Upload perl package on CPAN
$uploadtool = 'flickr-upload %c';
# flickr-replace is based on flickr-upload and needs Flickr::Upload
$replacetool = 'flickr-replace --id=%i %c';
# bget is a basic HTTP fetch tool I wrote, it is on CPAN in the scripts
# section, or you can use 'lynx --dump %u > %o' or similar.
$dltool = 'bget -f %u > %o';
# rotation command, will be substituted into $croptool if needed
$rottool = 'pamflip -r%D |';
# this needs to accept a jpeg on standard in, and save a jpeg to %c
$croptool = 'djpeg %o | %r pamcut %l %t %w %h | cjpeg -q 90 > %c';
# copy Exif tags from original to new file, you can use /bin/true if
# if you don't care.
$exiftool = 'exiftool -TagsFromFile %o %c';
#
#
########## end tool setup
$api = new Flickr::API({ 'key' => $flickr_api_key,
'secret' => $flickr_api_secret });
$photoid = shift;
$target = shift;
if(!defined($target) or !defined($photoid)) {
die "$0: usage: cropper photoid 'target note contents' [ newflag ]\n";
}
if(defined($ARGV[0]) and $ARGV[0]) {
$flickrtool = $uploadtool;
print "Will upload new. \n";
} else {
$flickrtool = $replacetool;
print "Will upload replacement. \n";
}
sub getinfo($) {
my $id = shift;
my $response;
my $content;
if($id !~ /^\d+$/) {
die "$0: photoid $id is not numerical\n";
} else {
$response = $api->execute_method('flickr.photos.getInfo', {
'photo_id' => $id,
'auth_token' => $flickr_token,
});
$content = $$response{_content};
}
return $content; # might be undef
} # sub getinfo
sub getsizes($) {
my $id = shift;
my $response;
my $content;
if($id !~ /^\d+$/) {
die "$0: photoid $id is not numerical\n";
} else {
$response = $api->execute_method('flickr.photos.getSizes', {
'photo_id' => $id,
'auth_token' => $flickr_token,
});
$content = $$response{_content};
}
return $content; # might be undef
} # sub getsizes
sub geturl($$) {
my $sizes = shift; # response from flickr.photos.getSizes
my $size = shift; # name of size to find URL
my $url;
while($sizes =~ /<size ([^>]*)/g) {
my $hold = $1;
# label="Original" width="640" height="480"
# url="http://www.flickr.com/photos/stewart/567229075/sizes/o/"
# source="http://farm2.static.flickr.com/1103/567229075_6dc09dc6da_o.jpg"
if($hold =~ /label="$size"/i) {
if($hold =~ /source="([^"]+)"/) {
$url = $1
}
}
}
return $url; # may be undef
} # sub geturl
sub gettarget($$) {
my $info = shift;
my $tar = shift;
my $noteid;
my $text;
my $note_w;
my $note_h;
my $note_x;
my $note_y;
if(!defined($info)) {
die "$0: failed to get photo info\n";
} else {
while ($info =~ m: <note (.*?) </note>:gx) {
my $thisbit = $1;
if($thisbit =~ /\bid="([^"]+)"/) { $noteid = $1; }
if($thisbit =~ /\bx="(\d+)"/) { $note_x = $1; }
if($thisbit =~ /\by="(\d+)"/) { $note_y = $1; }
if($thisbit =~ /\bw="(\d+)"/) { $note_w = $1; }
if($thisbit =~ /\bh="(\d+)"/) { $note_h = $1; }
if($thisbit =~ />\s*(.+)$/) { $text = $1; }
$text =~ s/\s+$//;
if($text eq $tar) {
return ($noteid, $note_x, $note_y, $note_w, $note_h);
}
# print "Skipping text: '$text'\n";
}
}
return (undef, undef, undef, undef, undef);
} # sub gettarget
sub deletenote($){
my $noteid = shift;
my $resp;
my $cont;
$resp = $api->execute_method('flickr.photos.notes.delete', {
'note_id' => $noteid,
'auth_token' => $flickr_token,
});
$cont = $$resp{_content};
if($cont !~ /<rsp\s*stat="ok"/) {
print "Error removing $noteid:\n$cont\n";
} else {
print "removed note $noteid\n";
}
} # sub deletenote
sub calccropsize($$$) {
my $sizes = shift;
my $getinfo = shift;
my $note_r = shift;
my $orig_rot;
my $orig_wide;
my $orig_height;
my $scaled_wide;
my $scaled_height;
my $new_top;
my $new_left;
my $new_wide;
my $new_height;
while($sizes =~ /<size ([^>]*)/g) {
my $hold = $1;
my $w;
my $h;
# label="Original" width="640" height="480"
if($hold =~ /width="(\d+)"/) {
$w = $1;
}
if($hold =~ /height="(\d+)"/) {
$h = $1;
}
if($hold =~ /label="Original"/i) {
$orig_wide = $w;
$orig_height = $h;
}
if($hold =~ /label="Medium"/i) {
$scaled_wide = $w;
$scaled_height = $h;
}
}
if(!defined($orig_wide) or !defined($orig_height)) {
die "$0: cannot get original size\n";
}
if(!defined($scaled_wide) or !defined($scaled_height)) {
die "$0: cannot get scaled size\n";
}
if($getinfo =~ /rotation="(90|180|270)"/) {
$orig_rot = $1;
if($orig_rot != 180) {
# swap width / height if rotated 90 left or 90 right.
($orig_wide, $orig_height) = ($orig_height, $orig_wide)
}
} else {
$orig_rot = 0;
}
# $note_r = [$noteid, $note_x, $note_y, $note_w, $note_h]
$new_left = int( ($$note_r[1] / $scaled_wide ) * $orig_wide );
$new_top = int( ($$note_r[2] / $scaled_height) * $orig_height );
$new_wide = int( ($$note_r[3] / $scaled_wide ) * $orig_wide );
$new_height = int( ($$note_r[4] / $scaled_height) * $orig_height );
print "orignal image is ${orig_wide}x$orig_height, scaled is ${scaled_wide}x$scaled_height\n";
print "target box at ($$note_r[1],$$note_r[2]) is $$note_r[3]x$$note_r[4]\n";
print "rotate: $orig_rot, crop at $new_top,$new_left to ${new_wide}x$new_height\n";
return($orig_rot, $new_top, $new_left, $new_wide, $new_height);
} # sub calccropsize
sub dotoolsubs($$) {
my $tool = shift;
my $sub_r = shift;
$tool =~ s/%(.)/(defined($$sub_r{$1})? $$sub_r{$1} : '')/eg;
return $tool;
} # sub dotoolsubs
sub runtool($) {
my $cmd = shift;
if ($verbose) { print "system($cmd);\n" }
system($cmd);
} # sub runtool
sub docrop($$$) {
my $siz_r = shift;
my $url = shift;
my $id = shift;
my $tool;
my %subs = (
'%' => '%',
# %c name of cropped file
'c' => "$ENV{HOME}/tmp/fc-$id-c",
# %d rotation to perform (clockwise degrees)
'd' => $$siz_r[0],
# %D rotation to perform (counterclockwise degrees)
'D' => (360 - $$siz_r[0]),
# %h height of crop
'h' => $$siz_r[4],
# %i original photo id
'i' => $id,
# %l left column of crop
'l' => $$siz_r[2],
# %o name of original file
'o' => "$ENV{HOME}/tmp/fc-$id-o",
# %r rotate tool in pipeline
'r' => '', # defined later if needed
# %t top row of crop
't' => $$siz_r[1],
# %u url of orignal file
'u' => $url,
# %w width of crop
'w' => $$siz_r[3],
);
if($$siz_r[0]) {
$tool = dotoolsubs($rottool, \%subs);
$subs{r} = $tool;
}
$tool = dotoolsubs($dltool, \%subs);
runtool($tool);
$tool = dotoolsubs($croptool, \%subs);
runtool($tool);
$tool = dotoolsubs($exiftool, \%subs);
runtool($tool);
$tool = dotoolsubs($flickrtool, \%subs);
runtool($tool);
unlink($subs{o}); # original file
unlink($subs{c}); # cropped file
} # sub docrop
# This will give us note information and rotation of original image
$getinfo_cache = getinfo($photoid);
if(!defined($getinfo_cache)) {
die "$0: could not get info on $photoid\n";
}
# extract the note information
@notebox = gettarget($getinfo_cache, $target);
if(!defined($notebox[0])) {
die "$0: no target note found on $photoid\n";
}
# get urls and dimensions of available sizes
$sizes_cache = getsizes($photoid);
if(!defined($sizes_cache)) {
die "$0: can't get sizes for $photoid\n";
}
$url = geturl($sizes_cache, "Original");
if(!defined($url)) {
die "$0: can't get orignal url $photoid\n";
}
@sizes = calccropsize($sizes_cache, $getinfo_cache, \@notebox);
# docrop uses a number of global $*tool variables
docrop(\@sizes,$url,$photoid);
# only delete note if we are doing a replacement.
if(!(defined($ARGV[0]) and $ARGV[0])) {
deletenote($notebox[0]);
}
__END__