Slideshare.net (beta)

 
Post: 
Myspace Hi5 Friendster Xanga LiveJournal Facebook Blogger Tagged Typepad Freewebs BlackPlanet gigya icons



All comments

Add a comment on Slide 1

If you have a SlideShare account, login to comment; else you can comment as a guest


Showing 1-50 of 4 (more)

Simple Photo Processing and Web Display with Perl

From kcowgill, 5 months ago

I have a small photo gallery on my website and in this presentatio more

2811 views  |  0 comments  |  2 favorites  |  44 downloads
 
 
 

Privacy InfoNew!

This slideshow is Public

 
Embed in your blog
Embed (wordpress.com)
custom

Slideshow Statistics
Total Views: 2811
on Slideshare: 2811
from embeds: 0* * Views from embeds since 21 Aug, 07

Slideshow transcript

Slide 1: Simple Photo Processing and Web Display with Perl Kent Cowgill

Slide 2: Unfortunately, nothing super fancy.

Slide 3: The first camera I used

Slide 4: The pictures were ... ok

Slide 5: The phone stayed in my pocket. Along with pocket lint. A lot of pocket lint.

Slide 6: And a lot of lint got in the lens

Slide 7: And the pictures started in the blurry And a lot of lint got getting lens

Slide 8: Especially with all my daily activities.

Slide 9: Especially with all my daily activities.

Slide 10: And the pictures got blurrier

Slide 11: ... and blurrier ...

Slide 12: And practically unrecognizable.

Slide 13: So I got a new phone.

Slide 14: This has been my camera

Slide 15: The pictures were better

Slide 16: No lint problem, even in harsh conditions

Slide 17: Until my phone dropped out of my pocket and was picked up by Ricardo Signes at YAPC

Slide 18: But that blurriness was user error.

Slide 19: Thankfully, he returned my phone to me.

Slide 20: What a nice guy.

Slide 21: My RAZR has served me well.

Slide 22: Until Recently

Slide 23: More on that later.

Slide 24: Step 1: Get the picture from the phone to the server.

Slide 25: This is what I looked like

Slide 26: Taking pictures with my RAZR

Slide 27: The obligatory cat

Slide 34: www.kentcowgill.org

Slide 35: Step 2: Get the picture from the email to the filesystem.

Slide 36: strip image from mail cowgill motorola v551 2/9/05 1

Slide 37: use MIME::Parser; use MIME::Entity; use MIME::Base64 qw(decode_base64); use Image::Magick::Thumbnail;

Slide 38: my $parser = MIME::Parser->new(); $parser->output_dir( '/www/kentcowgill/photos/data' ); my $message = $parser->parse( *STDIN ) };

Slide 39: DFS? for my $part( $message->parts_DFS ){ if( $part->bodyhandle ){ if( $part->mime_type eq 'image/jpeg' ){ $filename = $part->bodyhandle->path; $data .= $part->as_string; $data =~ s/.*nn(.*)/$1/ms; } Ew. $data = decode_base64($data); ...

Slide 40: ... open ARCHIVE, '>', $archive_image; binmode ARCHIVE; Error checking print ARCHIVE $data; is left as an exercise close ARCHIVE; for the reader. my $src = new Image::Magick; $src->Read($archive_image); ...

Slide 41: ... my( $thumb, $x, $y ) =Image::Magick::Thumbnail::create( $src, 64 ); $thumb->Write($archive_thumb); } }

Slide 42: Worked great.

Slide 43: Flawlessly.

Slide 44: For a while.

Slide 45: Until I upgraded... Perl Image::Magick And with all their dependencies... God only knows what else.

Slide 46: WTF?

Slide 47: Then something broke. WTF?

Slide 48: It didn't make thisbroke. Then something blurry. WTF?

Slide 49: No, thatsomething broke. Then make this blurry. It didn'twas poor technique. WTF?

Slide 50: No, thatsomethingmy photos. It started make off broke. Then cutting this blurry. It didn'twas poor technique. WTF?

Slide 51: It was really annoying. WTF?

Slide 52: And inconsistent. WTF?

Slide 53: Cockroaches Termites (Not to scale) Bunnies (Also not to scale)

Slide 54: Tough to reliably reproduce.

Slide 55: So I spent my time and energy elsewhere.

Slide 56: Step 3: Get the picture from the filesystem to the web browser.

Slide 57: This is the site that I made

Slide 58: To view the pictures that I took

Slide 59: Click on a thumbnail...

Slide 60: And see the full image.

Slide 61: But not that one. ;-)

Slide 62: <?php $start = $_GET["start"] ? $_GET["start"] : "0"; $filearray = array(); $dir = "/www/kentcowgill/photos/arch"; $mydir = "/photos/arch"; $thumbdir = "/photos/thumbs"; if( $handle = opendir( $dir ) ){ while( false !== ( $file = readdir( $handle ) ) ){ if( $file != "." && $file != ".."){ array_push( $filearray, $file ); } } closedir( $handle ); } ...

Slide 63: PHP?

Slide 64: Didn't I say Perl at the beginning?

Slide 66: $_ == 0 && do { $table .= "<tr><td align=center height=$CELLHEIGHT " . "width=$CELLWIDTH valign=bottom>"; last SWITCH; }

Slide 67: $_ == 0 && do { $table .= "<tr><td align=center height=$CELLHEIGHT " . "width=$CELLWIDTH valign=bottom>"; last SWITCH; } My only excuse is that I wrote it a really long time ago

Slide 68: :-p~

Slide 73: The picture viewer was updated as well.

Slide 74: I also got a dog. Cat Dog

Slide 75: And I stored the users' preferences...

Slide 76: User Preferences

Slide 77: Step 4: Fix the image stripping bug.

Slide 78: strip image from mail cowgill motorola RAZR v3 7/25/06 2

Slide 79: Add module version requirements.

Slide 80: use MIME::Parser; use MIME::Entity; use MIME::Base64 qw(decode_base64); use Image::Magick::Thumbnail;

Slide 81: use MIME::Parser 5.415; use MIME::Entity 5.415; use MIME::Base64 3.07 qw(decode_base64); use Image::Magick::Thumbnail;

Slide 82: Add debugging and logging.

Slide 83: my $parser = MIME::Parser->new(); $parser->output_dir( '/www/kentcowgill/photos/data' ); my $message = $parser->parse( *STDIN ) };

Slide 84: my $parser = MIME::Parser->new(); $parser->output_to_core(1); $parser->output_dir( '/www/kentcowgill/photos/data' ); my $message; eval { $message = $parser->parse( *STDIN ) }; if( $@ ){ my $results = $parser->results; open ERR, '>', '/www/kentcowgill/photos/err.txt'; print ERR $results; Additional error close ERR; } checking is left as an exercise for the reader.

Slide 85: for my $part( $message->parts_DFS ){ if( $part->bodyhandle ){ if( $part->mime_type eq 'image/jpeg' ){ $filename = $part->bodyhandle->path; $data .= $part->as_string; $data =~ s/.*nn(.*)/$1/ms; } $data = decode_base64($data); ...

Slide 86: for my $part( $message->parts_DFS ){ if( $part->bodyhandle ){ if( $part->mime_type eq 'image/jpeg' ){ $filename = $part->bodyhandle->path; $data .= $part->as_string; print LOG2 $data; print LOG3 $filename; my @raw_part = split( /n/, $data ); my @edited_part; for my $line( @raw_part ){ if( $line =~ m/^$/ ){ $found++; next; } next unless $found; push @edited_part, $line; } $data =~ s/.*nn(.*)/$1/ms; $data = join( "n", @edited_part ); print LOG $data; } $data = decode_base64($data);

Slide 87: ... my $src = new Image::Magick; $src->Read($archive_image); ...

Slide 88: ... my $src = new Image::Magick; my $debug = $src->Read($archive_image); print LOG "WARNING: ImageMagick::Read " . "had problem: $debugn" if $debug; ...

Slide 89: ... my( $thumb, $x, $y ) =Image::Magick::Thumbnail::create( $src, 64 ); $thumb->Write($archive_thumb); } }

Slide 90: No debugging here. ... my( $thumb, $x, $y ) =Image::Magick::Thumbnail::create( $src, 64 ); $thumb->Write($archive_thumb); } }

Slide 91: No problem with thumbnails.  The cut-off images scaled fine.

Slide 92: The result?

Slide 93: No hints.

Slide 94: No clues.

Slide 95: No fix.

Slide 96: :-(

Slide 97: Step 5: Replace the image stripping program.

Slide 98: strip image from mail cowgill motorola RAZR v3 1/5/07 3

Slide 99: Replace the modules used.

Slide 100: use MIME::Parser 5.415; use MIME::Entity 5.415; use MIME::Base64 3.07 qw(decode_base64); use Image::Magick::Thumbnail;

Slide 101: use Email::MIME; use Email::MIME::Attachment::Stripper; use MIME::Base64 qw(decode_base64); use Imager; Still Used

Slide 102: my $parser = MIME::Parser->new(); $parser->output_to_core(1); $parser->output_dir( '/www/kentcowgill/photos/data' ); my $message; eval { $message = $parser->parse( *STDIN ) }; if( $@ ){ my $results = $parser->results; open ERR, '>', '/www/kentcowgill/photos/err.txt'; print ERR $results; close ERR; }

Slide 103: for my $part( $message->parts_DFS ){ if( $part->bodyhandle ){ if( $part->mime_type eq 'image/jpeg' ){ $filename = $part->bodyhandle->path; $data .= $part->as_string; print LOG2 $data; print LOG3 $filename; my @raw_part = split( /n/, $data ); my @edited_part; for my $line( @raw_part ){ if( $line =~ m/^$/ ){ $found++; next; } next unless $found; push @edited_part, $line; } $data =~ s/.*nn(.*)/$1/ms; $data = join( "n", @edited_part ); print LOG $data; } $data = decode_base64($data);

Slide 104: my $email = Email::MIME->new( $msg_text ); my $stripper = Email::MIME::Attachment::Stripper->new( $email ); my @files = $stripper->attachments; @files = grep { $_->{content_type} eq 'image/jpeg' } @files; my $image_data = @files ? $files[0]->{payload} : decode_base64( $email->{parts}[0]->{body} );

Slide 105: ... my $src = new Image::Magick; my $debug = $src->Read($archive_image); print LOG "WARNING: ImageMagick::Read " . "had problem: $debugn" if $debug; ...

Slide 106: my $src = Imager->new; $src->read( file => $archive_image, type => 'jpeg' );

Slide 107: ... my( $thumb, $x, $y ) =Image::Magick::Thumbnail::create( $src, 64 ); $thumb->Write($archive_thumb); } }

Slide 108: my $tmbimg = $src->copy(); my $thumb = $tmbimg->scale( xpixels => 64, ypixels => 48, type => 'min' ); $thumb->write( file => $archive_thumb );

Slide 110: The result?

Slide 111: OMG!! IT WORKS!@$!

Slide 112: No one was happier than my dog.

Slide 113: Step 6: Add display options.

Slide 114: When was the picture taken?

Slide 115: The pictures arrive at my server within minutes.

Slide 116: [~]$ perldoc -f time time Returns the number of non-leap seconds since whatever time the system considers to be the epoch, suitable for feeding to "gmtime" and "localtime".

Slide 117: my $archivedir = '/www/kentcowgill/photos/arch'; my $thumbdir = '/www/kentcowgill/photos/thumbs'; my $pic_time = time; my $archive_image = $archivedir . '/' . $pic_time . '.jpg'; my $archive_thumb = $thumbdir . '/' . $pic_time .'_thumb.jpg';

Slide 118: sub pretty_date { my( $stamp ) = shift; # $stamp = 1195004723; my @date = split /s+/, scalar localtime $stamp; # @date = qw(Tue Nov 13 19:45:23 2007); my $date = join q{ }, splice @date, 1, 5; # $date = 'Nov 13 19:45:23 2007'; my ( $hr, $mn ) = ( split /:/, ( split /s+/, $date)[2] )[0,1]; # $hr = 19; $mn = 45; my $merid = $hr > 12 ? do { $hr -= 12; 'pm' } : $hr == 12 ? 'pm' : 'am'; # $hr = 7; $merid = 'pm'; $date =~ s/(w+) (d+) .* (d+)/$1 $2, $3: $hr:$mn$merid/; # $date = 'Nov 13, 2007: 7:45pm'; return $date; }

Slide 119: SIDE BAR my $merid = $hr > 12 ? do { $hr -= 12; 'pm' } : $hr == 12 ? 'pm' : 'am'; # $hr = 7; $merid = 'pm'; Concise and efficient? OR Obfuscation?

Slide 120: SIDE BAR <australian accent> That's not an obfuscation... <australian accent>

Slide 121: $_='`$t` `.=lc for<>;u($_` )for` 3..``6;%t=qw ` `(a 82 b 15 c 28 d 43 e ` `127 f 22 ` g 20 h 61 i 70 ` `j 2 k 8 ```````l 40 m 24 n 67 ` o 75` ` p 19 q 1` `r` 60 s 63 t ` 91 ` u 28 v 1 `0 w ` 24 x 2 y ` `20 ` ` z 1);$k= k() ` ``;$d+=$t{$` `_}f o``r keys%t;$l =$d` /in` ``t(`length($t)/ $k)/100 ` ;map{%n=f(t($_));@g=b(1,` `%n);$y.= i(@g)}0..$k-1;@_=(a..z); map{@$_= @_;if($;++){for$"(2..$;){ pu ` sh` `` @$_,shift@$_} `` `` `}` }@_;map{$p=i` n` d`ex `((join'',` ` ` `@`{(sp `lit//,$y)[$c ` ]}),$_);` `$o```.=$p>=0?$`a` `` [ $p]: $_;$c+=$c<$k-1?1 ````: `-$` ``k+1}split//,$t;s ``ub 'b{my($e,$s `,@g)=@_;p ` ``ush@ `g`,[$_,(s pli` `` ``t//,'#' ``x in` `` `t($$s{`$_}*$e )`)]for ` `+sort+keys%$s;retur ```n@g}s` ub'c{my$x=shift;$x=g($x,shift ```)while@_; return$x}sub'f{my%d;$d{$_}++f` or grep/[a-z]/ ,split//,shift;$d{$_}||=0for a..z;return%d}su b'g{my($x,$y)=@_;($x,$y)=($y,$x%$y)while$y;r eturn$x}sub'i{my($g,@c)=@_;map{push@c,o(v($g),` `` ` $$g[0][0]);w($g)}0..25;return(map{$_->[1]}sort{$` b-`` >[0]<=>$a->[0]}@c)[0]} sub'k{my@g;for(sort{$s{` `$b}`` <=>$s{$a}}keys%s){last ``if$s{$_}<3;next unless y `/a-``` z//>2;my@f ;push@f,(pos `($t)-3)while$t=~/$_/g;m` ````````y$g=c(n(@f) );`$g```` >2&&push@g,$g}return c(@` g)}sub'n{my$o= shift;return map{$_-$o}@_ }sub'o{my($g,$w) =@_;my$c=0;map{map{/+/&&` $c++;/-/&&$c--}@ $_}@$g;return[$c,$w]}sub' `t{my($o)=@_;my$c= 0;my$r;map{$r.=$_ unless( `$k-$o+$c)%$k;$c++} split//,$t;$r=~s/[^a-z]/ /g;return$r}sub'u{ my$l=$_[0];$s{substr($t` ,$_,$l)}++for 0..(le ngth($t)-$l)}sub'v{my($ `m)=@_;my@g=b($l,%t );$s=@g;$z=0;map{$x=0;ma `p{$$s[$z][$x]=$$m` [$z][$x]eq'#'&&$$s[$z][ `$x]eq'#'?'+` ':'-';$x++}@$_;$z++}@$m `;return$s}sub 'w{$R=shift;push@$R,shif` `t@$R}print" Key: $ynPlaintext:n$o`` `n";';s-s s+--gmx;s&`&&gm;eval#;` #etur#`` `#my($x($v());$y=$z#`#` ##```` ``# charles #`` #`````` ````# babbage #`

Slide 122: $_='`$t` `.=lc for<>;u($_` )for` 3..``6;%t=qw ` `(a 82 b 15 c 28 d 43 e ` `127 f 22 ` g 20 h 61 i 70 ` `j 2 k 8 ```````l 40 m 24 n 67 ` o 75` ` p 19 q 1` `r` 60 s 63 t ` 91 ` u 28 v 1 `0 w ` 24 x 2 y ` `20 ` ` z 1);$k= k() ` ``;$d+=$t{$` `_}f o``r keys%t;$l =$d` /in` ``t(`length($t)/ $k)/100 ` ;map{%n=f(t($_));@g=b(1,` `%n);$y.= i(@g)}0..$k-1;@_=(a..z); map{@$_= @_;if($;++){for$"(2..$;){ pu ` sh` `` @$_,shift@$_} `` `` `}` }@_;map{$p=i` n` d`ex `((join'',` ` ` `@`{(sp `lit//,$y)[$c ` ]}),$_);` `$o```.=$p>=0?$`a` `` [ $p]: $_;$c+=$c<$k-1?1 ````: `-$` ``k+1}split//,$t;s ``ub 'b{my($e,$s `,@g)=@_;p ` ``ush@ `g`,[$_,(s pli` `` ``t//,'#' ``x in` `` `t($$s{`$_}*$e )`)]for ` `+sort+keys%$s;retur ```n@g}s` ub'c{my$x=shift;$x=g($x,shift ```)while@_; return$x}sub'f{my%d;$d{$_}++f` or grep/[a-z]/ ,split//,shift;$d{$_}||=0for a..z;return%d}su b'g{my($x,$y)=@_;($x,$y)=($y,$x%$y)while$y;r eturn$x}sub'i{my($g,@c)=@_;map{push@c,o(v($g),` `` ` $$g[0][0]);w($g)}0..25;return(map{$_->[1]}sort{$` b-`` >[0]<=>$a->[0]}@c)[0]} sub'k{my@g;for(sort{$s{` `$b}`` <=>$s{$a}}keys%s){last ``if$s{$_}<3;next unless y `/a-``` z//>2;my@f ;push@f,(pos `($t)-3)while$t=~/$_/g;m` ````````y$g=c(n(@f) );`$g```` >2&&push@g,$g}return c(@` g)}sub'n{my$o= shift;return map{$_-$o}@_ }sub'o{my($g,$w) =@_;my$c=0;map{map{/+/&&` $c++;/-/&&$c--}@ $_}@$g;return[$c,$w]}sub' `t{my($o)=@_;my$c= 0;my$r;map{$r.=$_ unless( `$k-$o+$c)%$k;$c++} split//,$t;$r=~s/[^a-z]/ /g;return$r}sub'u{ my$l=$_[0];$s{substr($t` ,$_,$l)}++for 0..(le ngth($t)-$l)}sub'v{my($ `m)=@_;my@g=b($l,%t );$s=@g;$z=0;map{$x=0;ma `p{$$s[$z][$x]=$$m` [$z][$x]eq'#'&&$$s[$z][ `$x]eq'#'?'+` ':'-';$x++}@$_;$z++}@$m `;return$s}sub 'w{$R=shift;push@$R,shif` `t@$R}print" Key: $ynPlaintext:n$o`` `n";';s-s s+--gmx;s&`&&gm;eval#;` #etur#`` `#my($x($v());$y=$z#`#` ##```` ``# charles #`` #`````` ````# babbage #`

Slide 123: Or with Date::Calc: use Date::Calc qw( Today Month_to_Text Localtime ); my $datestamp = shift; my( $year, $month, $day, $hour, $minute ) = ( Localtime( $datestamp ) )[ 0 .. 4 ]; my $meridian = $hour > 12 ? do { $hour -= 12; 'pm' } : $hour == 12 ? 'pm' : 'am'; my $date = sprintf( '%.3s %02d, %d: %d:%d%s', Month_to_Text( $month ), $day, $year, $hour, $minute, $meridian, ); # $date = 'Nov 13, 2007: 7:45pm';

Slide 124: Or with a regex: Believe it use strict; or not use warnings; my $date = scalar localtime $stamp; $date =~ s{^w{3}s(w{3} )(?{$^N})s+(d+)(?{$,=$^R.$". $^N})s(d+)(?{$.=$^N;$@=$.>12?do{$.-=12;'pm' }:$ .==12?'pm':'am'}):( d+)(?{$ /="$ .:$ ^N$ @"}):d+s(d{4})(?{$==$ ^N})}{$,$"$=:$"$/}x; # $date = 'Nov 13, 2007: 7:45pm';

Slide 125: Adding captions to the photos.

Slide 126: Use a database.

Slide 127: create database photoblog; use photoblog; create table captions ( caption_id int( 11 ) not null auto_increment, caption_photo varchar( 32 ) not null, caption_text text, primary key( caption_id ), unique caption_id( caption_id ) );

Slide 128: Create a row in the database when an email arrives.

Slide 129: Why? • Makes later CRUD implementation easier.

Slide 130: CRUD The four basic functions of persistent storage. Create Retrieve Update Delete

Slide 131: Why? • Makes later CRUD implementation easier. • In the editing interface, just implement: • Retrieve and Update • Don't worry about Create • Delete is just updating a record to nothing.

Slide 132: What is CRUD without the Create and Delete? CRUD

Slide 133: RU

Slide 134: IN SOVIET RUSSIA CRUD WRITES YOU!

Slide 135: my $dbh = Mysql->connect( 'localhost', 'photoblog', $username, $password, ); my $sth = $dbh->query( qq{ insert into captions (caption_photo) values ('$new_filename')} ); Bindinginput parameters is left $sth->finish; as an exercise for the reader.

Slide 136: Create an interface to update captions.

Slide 137: my $caption = param( 'caption' ); my $url = param( 'url' ); Binding the $url =~ m/.*/(.*).jpg/; input parameter my $picture = $1; would've helped here. $caption =~ s/'/''/g; $caption =~ s/<.*?>//g; Just stripping out HTML tags. my $dbh = Mysql->connect( 'localhost', 'photoblog', $username, $password, ); my $query = qq{ update captions set caption_text='$caption' where caption_photo='$picture'}; my $sth = $dbh->query( $query );

Slide 138: Fetch the captions when showing the thumbnails.

Slide 139: my $query = qq{ select caption_text from captions where caption_photo=$picbase}; my $dbh = Mysql->connect( 'localhost', 'photoblog', $username, $password, ); my $sth = $dbh->query( $query ); my $caption = $sth->fetchrow; $caption = $caption || '';

Slide 140: Step 7: Redesign.

Slide 141: Fit into the design for the rest of the site

Slide 143: ($header=<<" EOHEADER") =~ s/ //gm; <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head><title>$title</title> <style type="text/css"> EOHEADER ... SWITCH: for( $counter ){ $_ == 0 && do { $table .= "<tr><td align="center" " . "valign="bottom">n"; last SWITCH; }; $_ == ($cols) && do { $table .= "</tr>n"; last SWITCH; }; $table .= qq(<td align="center" valign="bottom">n); }

Slide 144: Templates++

Slide 145: use Template; ... my %pic = ( picture => q{/photos/arch/} . $base . '.jpg', timestamp => pretty_date( $base ), thumb => q{/photos/thumbs/} . $base . q{_thumb.jpg}, caption => $caption, );

Slide 146: <table border="0" class="gallery"> [% FOREACH row IN pictures -%] <tr> [% FOREACH pic IN row -%] <td align="center" valign="bottom" class="pic"> <a href="javascript:OpenWindow('[% pic.picture %]','700','540')"><img src ="[% pic.thumb %]" alt ="[% pic.caption %]" title ="[% pic.caption %]"/></a> <p class="timestamp">[% pic.timestamp %]</p> </td> [% END -%] </tr> [% END -%] </table>

Slide 148: Add a "current image". i.e. most recently taken

Slide 149: my $src = Imager->new; $src->read( file => $archive_image, type => 'jpeg' ); my $newimg = $src->copy(); my $curimg = $newimg->scale( xpixels => 320, ypixels => 240, type => 'min', ); my $gryimg = $curimg->convert( preset => 'grey' ); $gryimg->write( file => $current_image );

Slide 150: my ( $firstbase ) = $firstfile =~ m{^.+/(d+).jpg$}; $vars->{ mostrecent } = get_caption( $firstbase ); $vars->{ randomizer } .= int rand 9 for 1 .. 8;

Slide 151: Why? • Browsers cache the image • appending a random number (i.e. "?12345678") prevents caching • It's an actual image, but it could be a CGI • It could be dynamically generated • Your web browser and my server won't know the difference • No caching, fresh request each time

Slide 152: [% IF mostrecent -%] <table border="0" class="mostrecent"> <tr><td class="mostrecent"> <img src="/photos/mostrecent.jpg?[% randomizer %]"/> </td></tr> </table> [% END -%]

Slide 154: Use it elsewhere.

Slide 155: Organize the photos.

Slide 156: Why? • Having a chronological list is OK • By assigning tags to photos, you won't have to remember which page of 60 has: • that picture of the dog from 3 months ago • the picture of the bed you bought last year • or...

Slide 157: The picture of the video chat with your crazy in-laws.

Slide 158: Use the same database.

Slide 159: use photoblog; create table tags ( tag_id int( 11 ) not null auto_increment, tag_photo varchar( 32 ) not null, tag_name text, primary key( tag_id ), unique tag_id( tag_id ) );

Slide 160: Create an interface to add tags.

Slide 161: my $tags = param( 'tags' ); my @tags = split( /(?:s+|,)/, $tags ); my $dbh = Mysql->connect( "localhost", "photoblog", $username, $password ); my $delete = qq{ delete from tags where tag_photo = '$picture'}; $sth = $dbh->query( $delete ); for my $tag( @tags ){ my $ins = qq{ insert into tags( tag_photo, tag_name ) values( '$picture', '$tag' )}; $sth = $dbh->query( $ins ); }

Slide 162: Fetch the tags when showing the pictures.

Slide 163: my $query = q{ select tag_name from tags where tag_photo=$picbase}; $sth = $dbh->query( $query ); my @tags; while( my $tag = $sth->fetchrow ){ push @tags, $tag; } my $tags = join( ' ', @tags ); $tags ||= ''; $vars->{ tags } = [ $tags ];

Slide 164: Create a "tag cloud".

Slide 165: $query = qq{ select count(*) as num, tag_name from tags group by tag_name having num > $MIN_TAGS }; $sth = $dbh->prepare( $query ); $sth->execute; my @tags; while( my $hashrow = $sth->fetchrow_hashref ){ push @tags, $hashrow } $vars->{ phototags } = [ @tags ];

Slide 166: <table style="background: #ccc; border: 1px solid #555"> <tr><th align="left">Photos filed under...</th></tr> <tr><td align="center"> [% FOREACH tag = phototags -%] <span style="font-size: [% ( tag.num + 80 ) / 8 %]px"> <a href="/photos.cgi?tagged=[% tag.tag_name %]" title="[% tag.num %] photo[% IF tag.num > 1 %]s[% END %] filed under [% tag.tag_name %]">[% tag.tag_name %]</a> </span> Highly complicated [% END -%] and sophisticated formula derived by </td></tr></table> minutes and minutes of trial and error

Slide 168: Jazz it up with a little AJAX

Slide 170: AJAX (Asynchronous JavaScript and XML), or Ajax, is a group of inter-related web development techniques used for creating interactive web applications. A primary characteristic is the increased responsiveness and interactiveness of web pages achieved by exchanging small amounts of data with the server "behind the scenes" so that the entire web page does not have to be reloaded each time the user performs an action. This is intended to increase the web page's interactivity, speed, functionality, and usability. http://en.wikipedia.org/wiki/Ajax_(programming)

Slide 171: AJAX (Asynchronous JavaScript and XML)

Slide 172: AJ

Slide 174: AJ

Slide 175: http://jquery.com

Slide 176: Load JQuery in your template <script type = "text/javascript" src = "/script/jquery.js"></script> Right here

Slide 177: Create a Javascript Function to switch the pictures function swapem( pic ){ $('#caption').fadeOut( 'slow' ); $('#display').fadeOut( 'slow', function(){ getData( pic ); $('#display').html( '<img src="' + pic + '" border="0">'); $('#display').show; $('#display').fadeIn( 1500 ); Callback }); }

Slide 178: Callback A callback is executable code that is passed as an argument to other code. Because of the asynchronous nature of calls in the JQuery library, using certain code as callbacks helps ensure specific timing for events.

Slide 179: Create the AJAX request function getData( pic ){ $.ajax( { url : '/caption.cgi', Callback type : 'POST', dataType : 'html', data : 'pic=' + pic, success : function( retVal ){ $('#caption').html( retVal ); }, complete : function(){ $('#caption').fadeIn( 'slow' ); } }); } Callback

Slide 180: Call the AJAX code <td align="center" valign="bottom" class="pic"> <a href="javascript:swapem('[% pic.picture %]');"><img src ="[% pic.thumb %]" alt ="[% pic.caption %]" title ="[% pic.caption %]"/></a> <p class="timestamp">[% pic.timestamp %]</p> </td> Right here

Slide 181: Create a CGI to handle the AJAX call #!/usr/bin/perl use strict; use warnings; use DBI; use CGI qw/:standard/; Saved a lot of copying use PhotoLib; and pasting ...

Slide 182: Damian said it best: • Place original code inline. • Place duplicated code in a subroutine. • Place duplicated subroutines in a module. - Perl Best Practices, page 401

Slide 183: Create a CGI to handle the AJAX call ... my $pic = param('pic') || exit; # $pic = '/photos/arch/1197770265.jpg'; $pic =~ s/[^d]+(d+)..*/$1/; # $pic = '1197770265'; my $caption = get_caption( $pic ); $caption =~ s/''/'/g; # stoopid mysql my @tags = get_tags( $pic ); ...

Slide 184: Create a CGI to handle the AJAX call ... my $out = header(); $out .= qq{<span style="font-family: tahoma; }; $out .= qq{"><p>$caption</p><p>More pictures: }; for my $tag( @tags ){ $out .= qq{<a href="photos.cgi?tagged=$tag">$tag</a> }; } $out .= "</p>"; print $out; Should I have used a template?

Slide 185: Please visit http://www.kentcowgill.org/photos for the Ajax demonstration

Slide 186: Then something wonderful happened

Slide 187: 8-D

Slide 189: The pictures arrive at my server within minutes.

Slide 190: www.kentcowgill.org

Slide 193: Why?

Slide 194: I got a real* camera * For some values of real.

Slide 196: My new camera stores images on one of these...

Slide 197: www.kentcowgill.org Also happens in batches Happens much later, all at once

Slide 199: They would all get the same timestamp: Nov 13, 2007: 8:15pm www.kentcowgill.org

Slide 200: What to do?

Slide 201: Read The Fine Manual

Slide 204: I want to keep the filename standard

Slide 205: I want to keep the filename standard I've already got over 700 images and their tags in my database

Slide 206: exif_date_time_original 2007:10:28 18:03:54 ≠ 1193612634

Slide 207: Time::Local(3) User Contributed Perl Documentation Time::Local(3) NAME Time::Local - efficiently compute time from local and GMT time SYNOPSIS $time = timelocal($sec,$min,$hour,$mday,$mon,$year); $time = timegm($sec,$min,$hour,$mday,$mon,$year); DESCRIPTION These routines are the inverse of built-in perl functions localtime() and gmtime(). They accept a date as a six-element array, and return the corresponding time(2) value in seconds since the system epoch (Mid- night, January 1, 1970 GMT on Unix, for example).

Slide 208: my $filebase = 'tempfilename'; my $archive_image = qq($arch_dir/${filebase}.jpg); # ... my $date_time = $src->tags( name => 'exif_date_time_original' ); my( $y, $mo, $d, $h, $m, $s ) = ( split /(?::| |T|-)/, $date_time )[ 0 .. 5 ]; $y -= 1900; $mo -= 1;

Slide 209: my $filebase = 'tempfilename'; my $archive_image = qq($arch_dir/${filebase}.jpg); # ... my $date_time = $src->tags( name => 'exif_date_time_original' ); my( $y, $mo, $d, $h, $m, $s ) = ( split /(?::| |T|-)/, $date_time )[ 0 .. 5 ]; $y -= 1900; $mo -= 1;

Slide 210: my $filebase = 'tempfilename'; my $archive_image = qq($arch_dir/${filebase}.jpg); # ... my $date_time = $src->tags( name => 'exif_date_time_original' ); my( $y, $mo, $d, $h, $m, $s ) = ( split /(?::| |T|-)/, $date_time )[ 0 .. 5 ]; $y -= 1900; $mo -= 1; my $new_filename = timelocal( $s, $m, $h, $d, $mo, $y ); my $new_image = qq($arch_dir/${new_filename}.jpg); rename $archive_image, $new_image;

Slide 211: my $filebase = 'tempfilename'; my $archive_image = qq($arch_dir/${filebase}.jpg); # ... my $date_time = $src->tags( name => 'exif_date_time_original' ); my( $y, $mo, $d, $h, $m, $s ) = ( split /(?::| |T|-)/, $date_time )[ 0 .. 5 ]; $y -= 1900; $mo -= 1; my $new_filename = timelocal( $s, $m, $h, $d, $mo, $y ); my $new_image = qq($arch_dir/${new_filename}.jpg); rename $archive_image, $new_image;

Slide 212: exif_date_time_original 2007:10:28 18:03:54

Slide 213: exif_date_time_original 2007:10:28 18:03:54 2007-10-28T18:03:54

Slide 214: =)

Slide 215: 7.2 MEGA PIXELS 3,072px x 2,304px

Slide 216: 1 2 3,072 pixels x 2,304 pixels 12,288 1 921,600 + 6,144,000 7,077,888 pixels

Slide 217: 445 54 7,077,888 pixels x 16 bits per pixel 42,467,328 + 70,778,880 113,246,208 bits

Slide 218: 113,246,208 bits 14,155,776 bytes 13,824 kilobytes 13.5 megabytes

Slide 219: TOO

Slide 220: BIG

Slide 221: Wouldn't it be great if I could get these 7 MP images automatically scaled to 640x480?

Slide 223: [Kent-Cowgills-Computer ~]$ locate Constrain /Applications/Adobe ImageReady CS/Samples/Droplets/ ImageReady Droplets/Constrain 350, Make JPG 30.exe /Applications/Adobe ImageReady CS/Samples/Droplets/ ImageReady Droplets/Constrain to 200x200 pixels.exe /Applications/Adobe ImageReady CS/Samples/Droplets/ ImageReady Droplets/Constrain to 64X64 pixels.exe /Applications/Adobe ImageReady CS/Samples/Droplets/ Photoshop Droplets/Constrain to 300 pixels.exe

Slide 224: Now for a tutorial on creating contextual menu droplets in... Adobe ImageReady™

Slide 225: Just kidding.

Slide 227: But that's not Perl.

Slide 228: No Perl ≠ Fun

Slide 229: Perl === Fun (wait - that's PHP)

Slide 230: Perl == Fun

Slide 231:

Slide 232: Use Imager.pm to scale images to 640x480

Slide 235: my $src = Imager->new; $src->read( file => $archive_image, type => 'jpeg' ); my $newimg = $src->scale( xpixels => 640, ypixels => 480, type => 'min', qtype => 'mixing', ); $newimg->write( file => $new_name, type => 'jpeg', jpegquality => 85, );

Slide 236: What about when you rotate the camera?

Slide 239: Exif Orientation Tag Values Value 0th Row 0th Column 1 top left side 2 top right side 3 bottom right side 4 bottom left side 5 left side top 6 right side top 7 right side bottom 8 left side bottom

Slide 243: Here is what the letter F would look like if it were displayed by a program that ignores the orientation tag: 1 2 3 4 888888 888888 88 88 88 88 88 88 8888 8888 8888 8888 88 88 88 88 88 88 888888 888888 5 6 7 8 8888888888 88 88 8888888888 88 88 88 88 88 88 88 88 88 8888888888 8888888888 88 From: http://sylvana.net/jpegcrop/exif_orientation.html

Slide 244: 8 8888888888 88 88 88

Slide 245: 8 8888888888 88 88 88

Slide 246: 8 8888888888 88 88 88

Slide 247: 8 8888888888 88 88 88

Slide 248: my $src = Imager->new; $src->read( file => $archive_image, type => 'jpeg' ); my $orientation_tag = $src->tags( name => 'exif_orientation' ); my %degrees_for = ( 6 => 90, 8 => -90 ); my $newimg; if( exists $degrees_for{ $orientation_tag } ){ $src = $src->rotate( degrees => $degrees_for{ $orientation_tag } ); }

Slide 250:  <