aboutsummaryrefslogtreecommitdiff
path: root/doc/users/smcv/gallery.mdwn
blob: 5b4c6fe00e844f8d1981e7caa9c1f3e244ad4a8b (plain)
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
[[!template id=plugin name=smcvgallery author="[[Simon_McVittie|smcv]]"]]
[[!tag type/chrome]]

This plugin has not yet been written; this page is an experiment in
design-by-documentation :-)

## Requirements

This plugin formats a collection of images into a photo gallery,
in the same way as many websites: good examples include the
PHP application [Gallery](http://gallery.menalto.com/), Flickr,
and Facebook's Photos "application".

The web UI I'm trying to achieve consists of one
[HTML page of thumbnails](http://www.pseudorandom.co.uk/2008/2008-03-08-panic-cell-gig/)
as an entry point to the gallery, where each thumbnail links to
[a "viewer" HTML page](http://www.pseudorandom.co.uk/2008/2008-03-08-panic-cell-gig/img_0068/)
with a full size image, next/previous thumbnail links, and
[[plugins/comments]].

(The Summer of Code [[plugins/contrib/gallery]] plugin does the
next/previous UI in Javascript using Lightbox, which means that
individual photos can't be bookmarked in a meaningful way, and
the best it can do as a fallback for non-Javascript browsers
is to provide a direct link to the image.)

Other features that would be good to have:

* minimizing the number of separate operations needed to make a gallery -
  editing one source file per gallery is acceptable, editing one
  source file per photo is not

* keeping photos outside source code control, for instance in an
  underlay

* assigning [[tags|ikiwiki/directive/tag]] to photos, providing a
  superset of Facebook's "show tagged photos of this person" functionality

* constructing galleries entirely via the web by uploading attachments

* inserting grouping (section headings) within a gallery; as in the example
  linked above, I'd like this to split up the thumbnails but not the
  next/previous trail

* rendering an `<object>/<embed>` arrangement to display videos, and possibly
  thumbnailing them in the same way as totem-video-thumbnailer
  (my camera can record short videos, so some of my web photo galleries
  contain them)

My plan is to have these directives:

* \[[!gallery]] registers the page it's on as a gallery, and displays all
  photos that are part of this gallery but not part of a \[[!gallerysection]]
  (below).

  All images (i.e. `*.png *.jpg *.gif`) that are attachments to the gallery page
  or its subpages are considered to be part of the gallery.

  Optional arguments:

  * filter="[[ikiwiki/PageSpec]]": only consider images to be part of the
    gallery if they also match this filter

  * sort="date|filename": order in which to sort the images

* \[[!gallerysection filter="[[ikiwiki/PageSpec]]"]] displays all photos in the
  gallery that match the filter

So,
[the gallery I'm using as an example](http://www.pseudorandom.co.uk/2008/2008-03-08-panic-cell-gig/)
could look something like this:

    \[[!gallery]]
    <!-- replaced with one uncategorized photo -->

    # Gamarra

    \[[!gallerysection filter="link(sometag)"]]
    <!-- all the Gamarra photos -->

    # Smokescreen

    \[[!gallerysection filter="link(someothertag)"]]
    <!-- all the Smokescreen photos -->

    <!-- ... -->

## Implementation ideas

The next/previous part this plugin overlaps with [[todo/wikitrails]].

A \[[!galleryimg]] directive to assign metadata to images is probably necessary, so
the gallery page can contain something like:

    \[[!galleryimg p1010001.jpg title="..." caption="..." tags="foo"]]
    \[[!galleryimg p1010002.jpg title="..." caption="..." tags="foo bar"]]

Making the viewer pages could be rather tricky. Here are some options:
"synthesize source pages for viewers" is the one I'm leaning towards at the
moment.

### Viewers' source page is the gallery

One possibility is to write out the viewer pages as a side-effect of
preprocessing the \[[!gallery]] directive. The proof-of-concept implementation
below does this.  However, this does mean the viewer pages can't have tags or
metadata of their own and can't be matched by [[pagespecs|ikiwiki/pagespec]] or
[[wikilinks|ikiwiki/wikilink]]. It might be possible to implement tagging by
using \[[!galleryimg]] to assign the metadata to the *images* instead of their
viewers.

### Synthesize source pages for viewers

Another is to synthesize source pages for the viewers. This means they can have
tags and metadata, but trying to arrange for them to be scanned etc. correctly
without needing another refresh run is somewhat terrifying.
[[plugins/autoindex]] can safely create source pages because it runs in
the refresh hook, but I don't really like the idea of a refresh hook that scans
all source pages to see if they contain \[[!gallery]]...

The photo galleries I have at the moment, like the Panic Cell example above,
are made by using an external script to parse XML gallery descriptions (lists
of image filenames, with metadata such as titles), and using this to write
IkiWiki markup into a directory which is then used as an underlay. This is a
hack, but it works. The use of XML is left over from a previous attempt at
solving the same problem using Django.

Perhaps a better approach would be to have a setupfile option that names a
particular underlay directory (meeting the objective of not having large
photos under source code control) and generates a source page for each file
in that directory during the refresh hook. The source pages could be in the
underlay until they are edited (e.g. tagged), at which point they would be
copied into the source-code-controlled version in the usual way.

The synthetic source pages can be very simple, using the same trick as my
[[plugins/comments]] plugin (a dedicated [[directive|ikiwiki/directives]]
encapsulating everything the plugin needs). If the plugin automatically
gathers information like file size, pixel size, date etc. from the images, then
only the human-edited information and a filename reference need to be present
in the source page; with some clever lookup rules based on the filename of
the source page, not even the photo's filename is necessarily needed.

    \[[!meta title="..."]]
    \[[!meta date="..."]]
    \[[!meta copyright="..."]]
    \[[!tag ...]]

    \[[!galleryimageviewer p1010001.jpg]]

However, this would mean that editing tags and other metadata would require
editing pages individually. Rather than trying to "fix" that, perhaps it would
be better to have a special CGI interface for bulk tagging/metadata editing.
This could even be combined with a bulk upload form (a reasonable number of
file upload controls - maybe 20 - with metadata alongside each).

Uploading multiple images is necessarily awkward due to restrictions placed on
file upload controls by browsers for security reasons - sites like Facebook
allow whole directories to be uploaded at the same time, but they achieve this
by using a signed Java applet with privileged access to the user's filesystem.

I've found that it's often useful to be able to force the creation time of
photos (my camera's battery isn't very reliable, and it frequently decides that
the date is 0000-00-00 00:00:00), so treating the \[[!meta date]] of the source
page and the creation date of the photo as synonymous would be useful.

### Images are the viewer's source - special filename extension

Making the image be the source page (and generate HTML itself) would be
possible, but I wouldn't want to generate a HTML viewer for every `.jpg` on a
site, so either the images would have to have a special extension (awkward for
uploads from Windows users) or the plugin would have to be able to change
whether HTML was generated in some way (not currently possible).

### Images are the viewer's source - alter `ispage()`

It might be possible to hack up `ispage()` so some, but not all, images are
considered to "be a page":

* srcdir/not-a-photo.jpg → destdir/not-a-photo.jpg
* srcdir/gallery/photo.jpg → destdir/gallery/photo/index.html

Perhaps one way to do this would be for the photos to appear in a particular
underlay directory, which would also fulfil the objective of having photos not
be version-controlled:

* srcdir/not-a-photo.jpg → destdir/not-a-photo.jpg
* underlay/gallery/photo.jpg → destdir/gallery/photo/index.html

## Proof-of-concept implementation of "viewers' source page is the gallery"

    #!/usr/bin/perl
    package IkiWiki::Plugin::gallery;
    
    use warnings;
    use strict;
    use IkiWiki 2.00;
    
    sub import {
    	hook(type => "getsetup", id => "gallery",  call => \&getsetup);
    	hook(type => "checkconfig", id => "gallery", call => \&checkconfig);
    	hook(type => "preprocess", id => "gallery",
    		call => \&preprocess_gallery, scan => 1);
    	hook(type => "preprocess", id => "gallerysection",
    		call => \&preprocess_gallerysection, scan => 1);
    	hook(type => "preprocess", id => "galleryimg",
    		call => \&preprocess_galleryimg, scan => 1);
    }
    
    sub getsetup () {
    	return
    		plugin => {
    			safe => 1,
    			rebuild => undef,
    		},
    }
    
    sub checkconfig () {
    }
    
    # page that is a gallery => array of images
    my %galleries;
    # page that is a gallery => array of filters
    my %sections;
    # page that is an image => page name of generated "viewer"
    my %viewers;
    
    sub preprocess_gallery {
    	# \[[!gallery filter="!*/cover.jpg"]]
    	my %params=@_;
    
    	my $subpage = qr/^\Q$params{page}\E\//;
    
    	my @images;
    
    	foreach my $page (keys %pagesources) {
    		# Reject anything not a subpage or attachment of this page
    		next unless $page =~ $subpage;
    
    		# Reject non-images
    		# FIXME: hard-coded list of extensions
    		next unless $page =~ /\.(jpg|gif|png|mov)$/;
    
    		# Reject according to the filter, if any
    		next if (exists $params{filter} &&
    			!pagespec_match($page, $params{filter},
    				location => $params{page}));
    
    		# OK, we'll have that one
    		push @images, $page;
    
    		my $viewername = $page;
    		$viewername =~ s/\.[^.]+$//;
    		$viewers{$page} = $viewername;
    
    		my $filename = htmlpage($viewername);
    		will_render($params{page}, $filename);
    	}
    
    	$galleries{$params{page}} = \@images;
    
    	# If we're just scanning, don't bother producing output
    	return unless defined wantarray;
    
    	# actually render the viewers
    	foreach my $img (@images) {
    		my $filename = htmlpage($viewers{$img});
    		debug("rendering image viewer $filename for $img");
    		writefile($filename, $config{destdir}, "# placeholder");
    	}
    
    	# display a list of "loose" images (those that are in no section);
    	# this works because we collected the sections' filters during the
    	# scan stage
    
    	my @loose = @images;
    
    	foreach my $filter (@{$sections{$params{page}}}) {
    		my $_;
    		@loose = grep { !pagespec_match($_, $filter,
    				location => $params{page}) } @loose;
    	}
    
    	my $_;
    	my $ret = "<ul>\n";
    	foreach my $img (@loose) {
    		$ret .= "<li>";
    		$ret .= "<a href=\"" . urlto($viewers{$img}, $params{page});
    		$ret .= "\">$img</a></li>\n"
    	}
    	return "$ret</ul>\n";
    }
    
    sub preprocess_gallerysection {
    	# \[[!gallerysection filter="friday/*"]]
    	my %params=@_;
    
    	# remember the filter for this section so the "loose images" section
    	# won't include these images
    	push @{$sections{$params{page}}}, $params{filter};
    
    	# If we're just scanning, don't bother producing output
    	return unless defined wantarray;
    
    	# this relies on the fact that we ran preprocess_gallery once
    	# already, during the scan stage
    	my @images = @{$galleries{$params{page}}};
    	@images = grep { pagespec_match($_, $params{filter},
    			location => $params{page}) } @images;
    
    	my $_;
    	my $ret = "<ul>\n";
    	foreach my $img (@images) {
    		$ret .= "<li>";
    		$ret .= htmllink($params{page}, $params{destpage},
    			$viewers{$img});
    		$ret .= "</li>";
    	}
    	return "$ret</ul>\n";
    }
    
    sub preprocess_galleryimg {
    	# \[[!galleryimg p1010001.jpg title="" caption="" tags=""]]
    	my $file = $_[0];
    	my %params=@_;
    
    	return "";
    }
    
    1