I've written something that's about 60% of what you're looking for, and it's in Perl, and while I hadn't considered licensing issues, I think I'd tend towards the PAL. I've kind of stalled with moving forward with refactoring and other changes, because it pretty much does everything I want (so far!).
Here's why it could likely be adapted to what you're looking for:
- All the files are in a big directory, with thumbnails in another directory, with filenames based on timestamps.
- All pictures (for which I've logged in and added an entry ;) have an entry in a mysql database.
I could easily see modifying my database schema to allow for 'tags', and the indexing CGI to query the database for a given set of tags.
Here's a link to it in action, and the code for the indexing CGI is included here in a should you be interested. (There's also some of the modules used listed [href://http://www.kentcowgill.org/photos/colophon.cgi|here] which I won't place here for (some!) brevity)
#!/usr/bin/perl
use strict;
use warnings;
use CGI qw/:standard/;
use CGI::Cookie;
use CGI::Carp qw(fatalsToBrowser);
use File::Basename;
use Skin;
use FileReader;
my $cellheight = 76;
my $cellwidth = 95;
my $cellspc = 4;
my $cellpad = 4;
my $pwidth = 700;
my $pheight = 540;
my $year = (localtime)[5] + 1900;
my $author = "kent cowgill";
my $title = "$author :: photo archive";
my $next = "previous >";
my $prev = "< next";
my $first = "|< last";
my $last = "first >|";
my $basedir = "/www/kentcowgill";
my $archdir = "/photos/arch";
my $thmbdir = "/photos/thumbs";
my $maindir = $basedir . $archdir;
my( $cols, $rows, $header, $footer, $stylesheet, $javascript,
$picdir, $start, $skin, $scheme, $end, $picture, $picbase,
$datelabel, $hour, $minute, $table );
my @datearray;
my @files;
my $abscounter = 0;
my $colcounter = 0;
my $counter = 0;
my $rowcounter = 0;
my $meridian = "am";
my $filepath = $0;
my $basename = basename( $filepath );
$start = ( param( 'start' ) and param( 'start' ) =~ /^\d{1,4}$/ )
? param( 'start' ) : 0;
my %cookies = fetch CGI::Cookie;
$scheme = $cookies{'skin'}
? ( ( param( 'scheme' ) and param( 'scheme' ) =~ /^\w{1,6}$/ )
? param( 'scheme' ) : $cookies{'skin'}->value )
: ( ( param( 'scheme' ) and param( 'scheme' ) =~ /^\w{1,6}$/ )
? param( 'scheme' ) : 'default' );
$rows = $cookies{'rows'}
? ( ( param( 'rows' ) and param( 'rows' ) =~ /^\d{1,2}$/ )
? param( 'rows' ) : $cookies{'rows'}->value )
: ( ( param( 'rows' ) and param( 'rows' ) =~ /^\d{1,2}$/ )
? param( 'rows' ) : 5 );
$cols = $cookies{'cols'}
? ( ( param( 'cols' ) and param( 'cols' ) =~ /^\d{1,2}$/ )
? param( 'cols' ) : $cookies{'cols'}->value )
: ( ( param( 'cols' ) and param( 'cols' ) =~ /^\d{1,2}$/ )
? param( 'cols' ) : 5 );
my $skincookie = new CGI::Cookie( -name => 'skin',
-value => $scheme,
-expires => '+3M',
);
my $rowscookie = new CGI::Cookie( -name => 'rows',
-value => $rows,
-expires => '+3M',
);
my $colscookie = new CGI::Cookie( -name => 'cols',
-value => $cols,
-expires => '+3M',
);
my $TWIDTH = $cellwidth * $cols + $cellspc * $cols - 1
+ $cellpad * $cols * 2;
my $THEIGHT = $cellheight * $rows + $cellspc * $cols - 1
+ $cellspc * $cols * 2;
$skin = Skin->new(
SKIN => "$scheme",
);
my $bodybg = $skin->body_background();
my $mainbg = $skin->main_background();
my $tblebg = $skin->table_background();
my $cellfont = $skin->cell_font();
my $filereader = FileReader->new(
PICDIR => $picdir,
MAINDIR => $maindir,
FILEARRAY => @files,
);
my $files = $filereader->get_files();
foreach $picture( @$files ){
if( $start > $colcounter ){ $abscounter++; $colcounter++; next; }
if( $rowcounter >= $rows ){ $abscounter++; next; }
$picture =~ m/(.*)\.jpg/;
$picbase = $1;
SWITCH: for( $counter ){
$_ == 0 && do { $table .= "| \n"; last SWITCH; };
$_ == ($cols) && do { $table .= " |
\n"; last SWITCH; };
$table .= " \n";
}
@datearray = split( /\s+/, scalar localtime( $picbase ) ),
$datelabel = join ' ', splice( @datearray, 1, 5 );
( $hour, $minute ) = ( split /:/, ( split /\s+/, $datelabel )[2] )[0,1];
if( $hour > 12 ){ $hour -= 12; $meridian = "pm" }
else { $meridian = "am" }
$datelabel =~ s/(\w+) (\d+) .* (\d+)/$1-$2-$3 $hour:$minute$meridian/;
$table .= " ";
$table .= " "
. "$datelabel\n | \n";
$counter++;
$abscounter++;
if( $counter == $cols ){ $counter = 0; $rowcounter++; }
}
$table .="| ";
if( $start > 0 ){
$table .= build_link( $basename, 0, $scheme, $rows, $cols, $first )
. " " . build_link( $basename, ($start-$rows*$cols), $scheme,
$rows, $cols, $prev );
}
else {
$table .= " ";
}
$table .= " | | ";
if( $rowcounter > ($rows-1)
&& $abscounter > $colcounter
&& ( $start + $rows * $cols ) < $abscounter ){
$table .= build_link( $basename, ($start+$rows*$cols), $scheme,
$rows, $cols, $next )
. " " . build_link( $basename, ($abscounter-$rows*$cols), $scheme,
$rows, $cols, $last );
}
else {
$table .= " ";
}
$table .= " |
\n\n";
$end = ( $start + ($rows*$cols) > $abscounter )
? $abscounter : ( $start + ($rows*$cols) );
$table .= "pictures ".($colcounter+1)
. " through $end of $abscounter
\n";
$table .= "\nview in |\n";
for( 'default', 'yellow', 'green', 'blue', 'teal',
'purple', 'pink', 'red', 'orange' ){
$table .= scheme_link( $_ ) . " |\n";
}
$table .= "
\n";
$table .= "view in |\n";
for( [3,3], [4,4], [5,5], [6,6], [7,7], [8,8] ){
$table .= size_link( $_->[0], $_->[1] ) . " |\n";
}
$table .= "
\n";
set_content();
print header(-cookie=>[$skincookie,$rowscookie,$colscookie]);
print $header, $table, $footer;
sub scheme_link {
my $color = shift;
return build_link( $basename, $start, $color, $rows, $cols, $color );
}
sub size_link {
my ( $r, $c ) = @_;
return build_link( $basename, $start, $scheme, $r, $c, "${r}x$c" );
}
sub build_link {
my( $bas, $sta, $sch, $row, $col, $nam ) = @_;
return "$nam";
}
sub set_content {
($stylesheet=<<" EOSTYLESHEET") =~ s/ //m;
td { font-family: $cellfont; font-size: 7pt;
width: ${cellwidth}px;
height: ${cellheight}px;
}
td.validateright{ font-family: $cellfont; font-size: 7pt;
width: ${cellwidth}px;
height: ${cellheight}px;
text-align: right;
}
td.validateleft{ font-family: $cellfont; font-size: 7pt;
width: ${cellwidth}px;
height: ${cellheight}px;
text-align: left;
}
#counttext { font-family: $cellfont; font-size: 8pt; font-style: italic;
text-align: center;
}
#copyright { font-family: $cellfont; font-size: 6pt; color: #292929;
}
#copyright2{ font-family: $cellfont; font-size: 6pt; color: #292929;
}
#scheme { font-family: $cellfont; font-size: 6pt; color: #292929;
text-align: center;
}
#scheme2 { font-family: $cellfont; font-size: 6pt; color: #292929;
text-align: center;
}
body { background: $bodybg;
}
#main { border: solid 1px #000000; padding: 25px;
background: $mainbg
}
table { border:0 ;
height: ${THEIGHT}px; width: ${TWIDTH}px;
background-color: $tblebg;
}
table.validator { border:0 ;
height: 12px; width: ${TWIDTH}px;
background-color: $bodybg;
}
EOSTYLESHEET
($javascript=<<" EOJAVASCRIPT") =~ s/ //m;
function OpenWindow(url,wid,hig,txt){
var myhight=Number(hig)+60
newWindow=window.open('viewer.cgi?scheme=$scheme&'
+'url='+url,'Photo','width='+wid+','
+'height='+myhight+',scrollbars=no,resizable=no,toolbar=no,location'
+'=no,menubar=no,status=no')
newWindow.focus()
}
EOJAVASCRIPT
($header=<<" EOHEADER") =~ s/ //gm;
$title
EOHEADER
($footer=<<" EOFOOTER") =~ s/ //gm;
all photographs, images, javascript, css and html
copyright © $year $author
EOFOOTER
}
I could actually be persuaded to add this for myself, as I find that I have 'sets' of pictures of my own that it might be nice to 'sort' by - house pictures, construction pictures, party pictures, dog pictures, etc. :)
--chargrill
$,=42;for(34,0,-3,9,-11,11,-17,7,-5){$*.=pack'c'=>$,+=$_}for(reverse split//=>$*
){$%++?$ %%2?push@C,$_,$":push@c,$_,$":(push@C,$_,$")&&push@c,$"}$C[$#C]=$/;($#C
>$#c)?($ c=\@C)&&($ C=\@c):($ c=\@c)&&($C=\@C);$%=$|;for(@$c){print$_^$$C[$%++]}
Re: Photo Database Application
Well, the filesystem gives you hierarchy and an RDBMS can give you the tagging you need. To start, I would
mock this up with a CGI that has access to a predefined set of folders. You can get a quick tagging system going
by making the md5sum of the file be the key in a single table sqlitedb and create entries like (md5sum,tag) each time
a tag is applied to a file. there are a million good directory strategies, research them. For thumb presentation try mirroring the full size photos from the top of the tree like: root->photos and root->thumbs, so when someone clicks the thumb, just change the root. this is just a start but i think you'll see how easy it can be. the hardest part will be presentation. you might also want to look at INTERCHANGE, since it is good at cataloguing, you might get most of the functionality you need there.
humbly,
novitiate
"...goodnight you princes of main(e)"  --The Cider House Rules
Re^2: Photo Database Application
I havn't used it, but there is CGI::Application::PhotoGallery. I would also check the C::A archives and wiki to see if there is anything else out there.
Re: Photo Database Application
I haven't tried it myself, largely because (like far too many other people) I've been working on-and-off on a similar project of my own, but it looks like FotoBilder might be exactly what you need. Fairly strictly web-based, and requires mod_perl (unless I'm very much mistaken), but if you don't mind those constraints, it seems like it could work pretty well for you.
If God had meant us to fly, he would *never* have given us the railroads.
--Michael Flanders
Re: Photo Database Application
You might be able to adapt Rubric to do what you want.
http://search.cpan.org/~rjbs/Rubric-0.12/lib/Rubric.pm
Re: Photo Database Application
I've been playing with something similar (havent' we all?). My solution is based on Win32 but I think the same idea will work for *nix. What I wanted was to have a system that in the end, regular applications could access the pictures through there tags. This meant either hacking into the windows file system (which seemed scary and dangerous) or abusing the current system. So I set out to abuse the current system! ;) It turns out that you can do Hard links in windows if you download the right tools. So i have a script monitor a directory, and when a picture is added, it takes all the directories and uses those as tags. For instance if i put a picture in 2005/March/Eric/Mountains it would be tagged with all four of those. Then hard links are distributed out into the other approriate directories. In this case a hard linke would be put in 2005, March, Eric, Mountains, 2005/March, 2005/Eric, 2005/Mountings, March/2005, March/Eric, etc. I'm still working on getting the monitoring right, but right now it looks very promising and exciting. I get the heirarchial access method of starting with broad tags and then as you enter directories you get pictures more and more related to what you want. In the end I want to then tack a web based interface to allow easier tagging and searching of pictures.
I know this isn't quite what you want sense i don't have sub-tags likey are thinking, but maybe there is some way to weld the two ideas together ;). Of the top of my head sub categories could be folders prefixed with something like "." so that the two systems could be merged. Then when storing the categories in a DB you concat the main category with the sub category and store them that way. /me stops thinking out loud.