731461c7b7cc9ea27ba65318bf4f234f0fadf818
[booh] / bin / booh
1 #!/usr/bin/ruby
2 #
3 #                         *  BOOH  *
4 #
5 # A.k.a `Best web-album Of the world, Or your money back, Humerus'.
6 #
7 # The acronyn sucks, however this is a tribute to Dragon Ball by
8 # Akira Toriyama, where the last enemy beaten by heroes of Dragon
9 # Ball is named "Boo". But there was already a free software project
10 # called Boo, so this one will be it "Booh". Or whatever.
11 #
12 #
13 # Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
14 #
15 # This software may be freely redistributed under the terms of the GNU
16 # public license version 2.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 require 'getoptlong'
23 require 'gettext'
24 include GetText
25 require 'rexml/document'
26 include REXML
27
28 require 'booh/booh-lib'
29 require 'booh/html-merges'
30
31 #- bind text domain as soon as possible because some _() functions are called early to build data structures
32 bindtextdomain("booh")
33
34 #- options
35 $options = [
36     [ '--help',          '-h', GetoptLong::NO_ARGUMENT,       _("Get help message") ],
37
38     [ '--no-check',      '-n', GetoptLong::NO_ARGUMENT,       _("Don't check for needed external programs at startup") ],
39
40     [ '--source',        '-s', GetoptLong::REQUIRED_ARGUMENT, _("Directory which contains original images/videos as files or subdirs") ],
41     [ '--destination',   '-d', GetoptLong::REQUIRED_ARGUMENT, _("Directory which will contain the web-album") ],
42 #    [ '--clean',         '-c', GetoptLong::NO_ARGUMENT,       _("Clean destination directory") ],
43
44     [ '--theme',         '-t', GetoptLong::REQUIRED_ARGUMENT, _("Select HTML theme to use") ],
45     [ '--config',        '-C', GetoptLong::REQUIRED_ARGUMENT, _("File containing config listing images and videos within directories with captions") ],
46     [ '--config-skel',   '-k', GetoptLong::REQUIRED_ARGUMENT, _("Filename where the script will output a config skeleton") ],
47     [ '--merge-config',  '-M', GetoptLong::REQUIRED_ARGUMENT, _("File containing config listing, where to merge new images/videos from --source") ],
48
49     [ '--mproc',         '-m', GetoptLong::REQUIRED_ARGUMENT, _("Specify the number of processors for multi-processors machines") ],
50
51     [ '--for-gui',       '-g', GetoptLong::NO_ARGUMENT,       _("Do the minimum work to be able to see the album under the GUI (don't generate all thumbnails)") ],
52
53     [ '--verbose-level', '-v', GetoptLong::REQUIRED_ARGUMENT, _("Set max verbosity level (0: errors, 1: warnings, 2: important messages, 3: other messages)") ],
54 ]
55
56 #- default values for some globals 
57 $switches = []
58 $stdout.sync = true
59
60 def usage
61     puts _("Usage: %s [OPTION]...") % File.basename($0)
62     $options.each { |ary|
63         printf " %3s, %-15s %s\n", ary[1], ary[0], ary[3]
64     }
65 end
66
67 def handle_options
68     parser = GetoptLong.new
69     parser.set_options(*$options.collect { |ary| ary[0..2] })
70     begin
71         parser.each_option do |name, arg|
72             case name
73             when '--help'
74                 usage
75                 exit(0)
76
77             when '--no-check'
78                 $no_check = true
79
80             when '--source'
81                 $source = arg.sub(%r|/$|, '')
82                 if !File.directory?($source)
83                     die _("Argument to --source must be a directory")
84                 end
85             when '--destination'
86                 $dest = arg.sub(%r|/$|, '')
87                 if File.exists?($dest) && !File.directory?($dest)
88                     die _("If --destination exists, it must be a directory")
89                 end
90                 if $dest != make_dest_filename($dest)
91                     die _("Sorry, destination directory can't contain non simple alphanumeric characters.")
92                 end
93 #            when '--clean'
94 #                system("rm -rf #{$dest}")
95
96             when '--theme'
97                 $theme = arg
98             when '--config'
99                 if File.readable?(arg)
100                     $xmldoc = REXML::Document.new File.new(arg)
101                     $mode = 'use_config'
102                 else
103                     die _('Config file does not exist or is unreadable.')
104                 end
105             when '--config-skel'
106                 if File.exists?(arg)
107                     msg 1, _("Config skeleton file already exists, backuping to %s.backup") % arg
108                     File.rename(arg, "#{arg}.backup")
109                 end
110                 $config_writeto = arg
111                 $mode = 'gen_config'
112             when '--merge-config'
113                 if File.readable?(arg)
114                     msg 2, _("Merge config notice: backuping current config file to %s.backup") % arg
115                     $xmldoc = REXML::Document.new File.new(arg)
116                     File.rename(arg, "#{arg}.backup")
117                     $config_writeto = arg
118                     $mode = 'merge_config'
119                 else
120                     die _('Config file does not exist or is unreadable.')
121                 end
122
123             when '--mproc'
124                 $mproc = arg.to_i
125                 $pids = []
126
127             when '--for-gui'
128                 $forgui = true
129
130             when '--verbose-level'
131                 $verbose_level = arg.to_i
132
133             end
134         end
135     rescue
136         puts $!
137         usage
138         exit(1)
139     end
140
141     if !$source && $xmldoc
142         $source = $xmldoc.root.attributes['source']
143         $dest = $xmldoc.root.attributes['destination']
144         $theme = $xmldoc.root.attributes['theme']
145     end
146
147     if !$source
148         die _("Missing --source or --config parameter.")
149     end
150     if !$dest
151         die _("Missing --destination parameter.")
152     end
153     if !$theme
154         $theme = 'simple'
155     end
156     select_theme($theme)
157
158     if !$xmldoc
159         $xmldoc = Document.new "<booh version='#{$VERSION}' source='#{utf8($source)}' destination='#{utf8($dest)}' theme='#{$theme}'/>"
160         $xmldoc << XMLDecl.new( XMLDecl::DEFAULT_VERSION, $CURRENT_CHARSET )
161         $mode = 'gen_config'
162     end
163
164 end
165
166 def read_config
167     $config = {}
168 end
169
170 def write_config
171 end
172
173 def check_installation
174     if $no_check
175         return
176     end
177     missing = %w(convert identify exif transcode mencoder).delete_if { |prg| system("which #{prg} >/dev/null 2>/dev/null") }
178     if missing != []
179         die _("The following programs are typically needed: `%s'. Re-run with --no-check if you're sure you're fine without them.") % missing.join(', ')
180     end
181 end
182
183 def replace_line(surround, keyword, line)
184     begin
185         contents = eval "$#{keyword}"
186         line.sub!(/#{surround}#{keyword}#{surround}/, contents)
187     rescue NameError
188         die _("No `%s' found for substitution") % keyword
189     end
190 end
191
192 def build_html_skeletons
193     $html_images     = File.open("#{$FPATH}/themes/#{$theme}/skeleton_image.html").readlines
194     $html_thumbnails = File.open("#{$FPATH}/themes/#{$theme}/skeleton_thumbnails.html").readlines
195     $html_index      = File.open("#{$FPATH}/themes/#{$theme}/skeleton_index.html").readlines
196     for line in $html_images + $html_thumbnails + $html_index
197         while line =~ /~~~(\w+)~~~/
198             replace_line('~~~', $1, line)
199         end
200     end
201 end
202
203 def find_caption_value(xmldir, filename)
204     xmldir.elements["[@filename='#{utf8(filename)}']"].attributes['caption']
205 end
206
207 def find_captions(xmldir, images)
208     return images.collect { |img| find_caption_value(xmldir, img) }
209 end
210
211 #- stolen from CVSspam
212 def urlencode(text)
213   text.sub(/[^a-zA-Z0-9\-,.*_\/]/) do
214     "%#{sprintf('%2X', $&[0])}"
215   end
216 end
217
218 def html_refresh(target)
219     return "<html><head><META http-equiv='refresh' content='0;URL=#{target}'></head><body></body></html>"
220 end
221
222 def discover_iterations(iterations, line)
223     if line =~ /~~iterate(\d)_open(_max(\d+))?~~/
224         for iter in iterations.values
225             if iter['open']
226                 iter['open'] = false
227                 iter['close_wait'] = $1.to_i
228             end
229         end
230         iterations[$1.to_i] = { 'open' => true, 'max' => $3, 'opening' => '', 'closing' => '' }
231         if $1.to_i == 1
232             line.sub!(/.*/, '~~thumbnails~~')
233         else
234             line.sub!(/.*/, '')
235         end
236     elsif line =~ /~~iterate(\d)_close~~/
237         iterations[$1.to_i]['open']  = false;
238         iterations[$1.to_i]['close'] = true;
239         line.sub!(/.*/, '')
240     else
241         for iter in iterations.values
242             if iter['open']
243                 iter['opening'] += line
244                 line.sub!(/.*/, '')
245             end
246             if !iter['close'] && iter['close_wait'] && iterations[iter['close_wait']]['close']
247                 iter['closing'] += line
248                 line.sub!(/.*/, '')
249             end
250         end
251     end
252 end
253
254 def reset_iterations(iterations)
255     for iter in iterations.values
256         iter['value'] = 1
257     end
258 end
259
260 def run_iterations(iterations)
261     html = ''
262     for level in iterations.keys.sort
263         if iterations[level]['value'] == 1 || level == iterations.keys.max
264             html += iterations[level]['opening']
265         end
266         iterations[level]['value'] += 1
267         if iterations[level]['max'] && iterations[level]['value'] > iterations[level]['max'].to_i
268             iterations[level]['value'] = 1
269             iterations[level-1]['value'] = 1
270             html += iterations[level-1]['closing']
271         end
272     end
273     return html
274 end
275
276 def close_iterations(iterations)
277     html = ''
278     for level in iterations.keys.sort.reverse
279         html += iterations[level]['closing']
280     end
281     return html
282 end
283
284 def img_element(fullpath)
285     if size = get_image_size(fullpath)
286         sizespec = 'width="${size[:x]}" height="${size[:y]}"'
287     else
288         sizespec = ''
289     end
290     return '<img src="' + File.basename(fullpath) + '" ' + sizespec + ' border="0"/>'
291 end
292
293 def walk_source_dir
294
295     `find #{$source} -type d`.sort.each { |dir|
296         dir.chomp!
297         if dir =~ /'/
298             die _("Source directory or sub-directories can't contain a single-quote character, sorry.")
299         end
300
301         #- place xml document on proper node if exists, else create
302         xmldir = $xmldoc.elements["//dir[@path='#{utf8(dir)}']"]
303         if $mode == 'use_config'
304             if !xmldir
305                 next
306             end
307         else
308             if $mode == 'gen_config' || ($mode == 'merge_config' && !xmldir)
309                 #- add the <dir..> element if necessary
310                 parent = File.dirname(dir)
311                 xmldir = $xmldoc.elements["//dir[@path='#{utf8(parent)}']"]
312                 if !xmldir
313                     xmldir = $xmldoc.root
314                 end
315                 xmldir = xmldir.add_element 'dir', { 'path' => utf8(dir) }
316             end
317         end
318
319         #- read images/videos entries from config or from directories depending on mode
320         entries = []
321         if $mode == 'use_config'
322             msg 2, _("Handling %s from config list...") % dir
323             xmldir.elements.each { |element|
324                 if %w(image video).include?(element.name)
325                     entries << from_utf8(element.attributes['filename'])
326                 end
327             }
328         else
329             msg 2, _("Examining %s...") % dir
330             entries = Dir.entries(dir).sort
331             #- populate config in case of gen_config, add new files in case of merge_config
332             for file in entries
333                 type = entry2type(file)
334                 if type && !xmldir.elements["#{type}[@filename='#{utf8(file)}']"]
335                     xmldir.add_element type, { "filename" => utf8(file), "caption" => utf8(file.sub(/\.[^\.]+$/, '')[0..17]) }
336                 end
337             end
338             if $mode == 'merge_config'
339                 #- cleanup removed files from config and reread entries from config to get proper ordering
340                 entries = []
341                 xmldir.elements.each { |element|
342                     fullpath = "#{dir}/#{from_utf8(element.attributes['filename'])}"
343                     if %w(image video).include?(element.name)
344                         if !File.readable?(fullpath)
345                             msg 1, _("Config merge: removing %s from config; use the backup file to retrieve caption info if this was a mistake") % fullpath
346                             xmldir.delete(element)
347                         else
348                             entries << from_utf8(element.attributes['filename'])
349                         end
350                     end
351                 }
352             end
353         end
354         images = entries.find_all { |e| entry2type(e) == 'image' }
355         msg 3, _("\t%s images") % images.length
356         videos = entries.find_all { |e| entry2type(e) == 'video' }
357         msg 3, _("\t%s videos") % videos.length
358
359         dest_dir = make_dest_filename(dir.sub(/^#{Regexp.quote($source)}/, $dest))
360         system("mkdir -p '#{dest_dir}'")
361
362         #- pass through if there are no images and videos
363         if images.size == 0 && videos.size == 0
364             next
365         end
366
367         msg 2, _("Outputting in %s...") % dest_dir
368
369         #- populate data structure with sizes from theme
370         for sizeobj in $images_size
371             fullscreen_images ||= {}
372             fullscreen_images[sizeobj['name']] = []
373             thumbnail_images ||= {}
374             thumbnail_images[sizeobj['name']] = []
375             thumbnail_videos ||= {}
376             thumbnail_videos[sizeobj['name']] = []
377         end
378
379         images.size >= 1 and msg 3, _("\tcreating images thumbnails...")
380
381         #- create thumbnails for images
382         images.each { |img|
383             base_dest_img = dest_dir + '/' + make_dest_filename(img.sub(/\.[^\.]+$/, ''))
384             if $forgui
385                 thumbnail_dest_img = base_dest_img + "-#{$default_size['thumbnails']}.jpg"
386                 gen_thumbnails_element("#{dir}/#{img}", xmldir, true, [ { 'filename' => thumbnail_dest_img, 'size' => $default_size['thumbnails'] } ])
387             else
388                 for sizeobj in $images_size
389                     size_fullscreen = sizeobj['fullscreen']
390                     size_thumbnails = sizeobj['thumbnails']
391                     fullscreen_dest_img = base_dest_img + "-#{size_fullscreen}.jpg"
392                     thumbnail_dest_img  = base_dest_img + "-#{size_thumbnails}.jpg"
393                     fullscreen_images[sizeobj['name']] << File.basename(fullscreen_dest_img)
394                     thumbnail_images[sizeobj['name']]  << File.basename(thumbnail_dest_img)
395                     gen_thumbnails_element("#{dir}/#{img}", xmldir, true, [ { 'filename' => fullscreen_dest_img, 'size' => size_fullscreen },
396                                                                             { 'filename' => thumbnail_dest_img, 'size' => size_thumbnails } ])
397                 end
398             end
399         }
400
401         videos.size >= 1 and msg 3, _("\tcreating videos thumbnails...")
402
403         #- create thumbnails for videos
404         videos.each { |video|
405             thumbnail_ok = true
406             if $forgui
407                 thumbnail_dest_img = dest_dir + '/' + make_dest_filename(video.sub(/\.[^\.]+$/, '')) + "-#{$default_size['thumbnails']}.jpg"
408                 thumbnail_ok &&= gen_thumbnails_element("#{dir}/#{video}", xmldir, true, [ { 'filename' => thumbnail_dest_img, 'size' => $default_size['thumbnails'] } ])
409             else
410                 for sizeobj in $images_size
411                     size_thumbnails = sizeobj['thumbnails']
412                     thumbnail_dest_img = dest_dir + '/' + make_dest_filename(video.sub(/\.[^\.]+$/, '')) + "-#{size_thumbnails}.jpg"
413                     thumbnail_videos[sizeobj['name']] << File.basename(thumbnail_dest_img)
414                     thumbnail_ok &&= gen_thumbnails_element("#{dir}/#{video}", xmldir, true, [ { 'filename' => thumbnail_dest_img, 'size' => size_thumbnails } ])
415                 end
416             end
417             destvideo = "#{dest_dir}/#{video}"
418             if !File.exists?(destvideo)
419                 psys("cp '#{dir}/#{video}' '#{destvideo}'")
420             end
421             #- cleanup temp
422             system("rm -f #{dest_dir}/screenshot.jpg000000.jpg")
423         }
424
425         if !$forgui
426             #- copy any resource file that goes with the theme (css, images..)
427             for entry in Dir.entries("#{$FPATH}/themes/#{$theme}")
428                 if !%w(. .. skeleton_image.html skeleton_thumbnails.html skeleton_index.html metadata CVS).include?(entry)
429                     if !File.exists?("#{dest_dir}/#{entry}")
430                         psys("cp '#{$FPATH}/themes/#{$theme}/#{entry}' '#{dest_dir}'")
431                     end
432                 end
433             end
434
435             #- fake for gettext to find these; if themes need more sizes, english name for them should be added here
436             sizenames = { 'small' => utf8(_("small")), 'medium' => utf8(_("medium")), 'large' => utf8(_("large")) }
437
438             msg 3, _("\tgenerating HTML pages...")
439             
440             #- generate thumbnails.html (page with thumbnails)
441             for sizeobj in $images_size
442                 html = $html_thumbnails.collect { |l| l.clone }
443                 iterations = {}
444                 for i in html
445                     i.sub!(/~~run_slideshow~~/, images.size <= 1 ? '' : '<a href="image-' + sizeobj['name'] + '.html?run_slideshow">' + utf8(_('Run slideshow!')) + '</a>')
446                     i.sub!(/~~title~~/, xmldir.attributes['thumbnails-caption'] || utf8(File.basename(dir)))
447                     for sizeobj2 in $images_size
448                         if sizeobj != sizeobj2
449                             i.sub!(/~~size_#{sizeobj2['name']}~~/, '<a href="thumbnails-' + sizeobj2['name'] + '.html">' + sizenames[sizeobj2['name']] + '</a>')
450                         else
451                             i.sub!(/~~size_#{sizeobj2['name']}~~/, sizenames[sizeobj2['name']])
452                         end
453                     end
454                     discover_iterations(iterations, i)
455                 end
456                 html_thumbnails = ''
457                 reset_iterations(iterations)
458                 for file in entries
459                     type = images.include?(file) ? 'image' : videos.include?(file) ? 'video' : nil
460                     if type
461                         html_thumbnails += run_iterations(iterations)
462                         if type == 'image'
463                             index = images.index(file)
464                             html_thumbnails.gsub!(/~~image_iteration~~/,
465                                                   '<a href="image-' + sizeobj['name'] + '.html?current=' + fullscreen_images[sizeobj['name']][index] + '">' +
466                                                       img_element("#{dest_dir}/#{thumbnail_images[sizeobj['name']][index]}") + '</a>')
467                             html_thumbnails.gsub!(/~~caption_iteration~~/,
468                                                   find_caption_value(xmldir, images[index]) || utf8(images[index]))
469                             html_thumbnails.gsub!(/~~ifimage\?~~(.+?)~~fi~~/) { $1 }
470                             html_thumbnails.gsub!(/~~ifvideo\?~~(.+?)~~fi~~/, '')
471                         elsif type == 'video'
472                             index = videos.index(file)
473                             if File.exists?("#{dest_dir}/#{thumbnail_videos[sizeobj['name']][index]}")
474                                 html_thumbnails.gsub!(/~~image_iteration~~/,
475                                                       '<a href="' + videos[index] + '">' + img_element("#{dest_dir}/#{thumbnail_videos[sizeobj['name']][index]}") + '</a>')
476                             else
477                                 html_thumbnails.gsub!(/~~image_iteration~~/,
478                                                       '<a href="' + videos[index] + '">' + utf8(_("(no preview)")) + '</a>')
479                             end
480                             html_thumbnails.gsub!(/~~caption_iteration~~/,
481                                                   find_caption_value(xmldir, videos[index]) || utf8(videos[index]))
482                             html_thumbnails.gsub!(/~~ifimage\?~~(.+?)~~fi~~/, '')
483                             html_thumbnails.gsub!(/~~ifvideo\?~~(.+?)~~fi~~/) { $1 }
484                         end
485                     end
486                 end
487                 html_thumbnails += close_iterations(iterations)
488                 for i in html
489                     i.sub!(/~~thumbnails~~/, html_thumbnails)
490                 end
491                 ios = File.open("#{dest_dir}/thumbnails-#{sizeobj['name']}.html", "w")
492                 ios.write(html)
493                 ios.close
494             end
495
496             #- generate image.html (page with fullscreen images)
497             if images.size > 0
498                 #- don't ask me why I need so many backslashes... the aim is to print \\\" for each " in the javascript source
499                 captions4js = find_captions(xmldir, images).collect { |e| e ? '"' + e.gsub('"', '\\\\\\\\\\\\\\\\\"' ) + '"' : '""' }.join(', ')
500                 for sizeobj in $images_size
501                     html = $html_images.collect { |l| l.clone }
502                     images4js = fullscreen_images[sizeobj['name']].collect { |e| "\"#{e}\"" }.join(', ')
503                     otherimages4js = ''
504                     othersizes = []
505                     for sizeobj2 in $images_size
506                         if sizeobj != sizeobj2
507                             otherimages4js += "var images_#{sizeobj2['name']} = new Array(" + fullscreen_images[sizeobj2['name']].collect { |e| "\"#{e}\"" }.join(', ') + ")\n"
508                             othersizes << "\"#{sizeobj2['name']}\""
509                         end
510                     end
511                     for i in html
512                         i.sub!(/~~images~~/, images4js)
513                         i.sub!(/~~other_images~~/, otherimages4js)
514                         i.sub!(/~~other_sizes~~/, othersizes.join(', '))
515                         i.sub!(/~~captions~~/, captions4js)
516                         i.sub!(/~~title~~/, xmldir.attributes['thumbnails-caption'] || utf8(File.basename(dir)))
517                         i.sub!(/~~thumbnails~~/, '<a href="thumbnails-' + sizeobj['name'] + '.html">' + utf8(_('Return to thumbnails')) + '</a>')
518                         for sizeobj2 in $images_size
519                             if sizeobj != sizeobj2
520                                 i.sub!(/~~size_#{sizeobj2['name']}~~/,
521                                        '<a href="image-' + sizeobj2['name'] + '.html" id="link' + sizeobj2['name'] + '">' + sizenames[sizeobj2['name']] + '</a>')
522                             else
523                                 i.sub!(/~~size_#{sizeobj2['name']}~~/,
524                                        sizenames[sizeobj2['name']])
525                             end
526                         end
527                     end
528                     ios = File.open("#{dest_dir}/image-#{sizeobj['name']}.html", "w")
529                     ios.write(html)
530                     ios.close
531                 end
532             end
533         end
534     }
535
536     msg 3, ''
537
538     #- add attributes to <dir..> elements needing so
539     if $mode != 'use_config'
540         msg 3, _("\tfixating configuration file...")
541         $xmldoc.elements.each('//dir') { |element|
542             path = captionpath = element.attributes['path']
543             child = element
544             captionfile = nil
545             while true
546                 child = child.elements[1]
547                 if !child
548                     element.remove  #- means we have a directory with nothing interesting in it
549                     break
550                 elsif child.name == 'dir'
551                     captionpath = child.attributes['path']
552                 else
553                     captionfile = "#{captionpath}/#{child.attributes['filename']}"
554                     break
555                 end
556             end
557             basename = File.basename(path)
558             if element.elements['dir']
559                 if !element.attributes['subdirs-caption']
560                     element.add_attribute('subdirs-caption', basename)
561                 end
562                 if !element.attributes['subdirs-captionfile']
563                     element.add_attribute('subdirs-captionfile', captionfile)
564                 end
565             end
566             if element.elements['image'] || element.elements['video']
567                 if !element.attributes['thumbnails-caption']
568                     element.add_attribute('thumbnails-caption', basename)
569                 end
570                 if !element.attributes['thumbnails-captionfile']
571                     element.add_attribute('thumbnails-captionfile', captionfile)
572                 end
573             end
574         }
575     end
576
577     #- write down to disk config if necessary
578     if $config_writeto
579         ios = File.open($config_writeto, "w")
580         $xmldoc.write(ios, 0)
581         ios.close
582     end
583
584     if $forgui
585         msg 3, _(" completed necessary stuff for GUI, exiting.")
586         return
587     end
588
589     #- second pass to create index.html files
590     default_thumbnails = $images_size.detect { |sizeobj| sizeobj['default'] }
591     if !default_thumbnails
592         die _("Theme `%s' has no default size.") % $theme
593     else
594         default_thumbnails = default_thumbnails['name']
595     end
596
597     msg 3, _("\trescanning directories to generate all `index.html' files...")
598
599     `find #{$source} -type d`.each { |dir|
600         dir.chomp!
601         xmldir = $xmldoc.elements["//dir[@path='#{utf8(dir)}']"]
602         if !xmldir
603             next
604         end
605         dest_dir = make_dest_filename(dir.sub(/^#{Regexp.quote($source)}/, $dest))
606
607         if xmldir.elements['dir']
608             html = $html_index.collect { |l| l.clone }
609             iterations = {}
610             for i in html
611                 caption = xmldir.attributes['subdirs-caption']
612                 i.gsub!(/~~title~~/, caption)
613                 if xmldir.parent.name == 'dir'
614                     nav = ''
615                     path = '..'
616                     parent = xmldir.parent
617                     while parent.name == 'dir'
618                         parentcaption = parent.attributes['subdirs-caption']
619                         nav = "<a href='#{path}/index.html'>#{parentcaption}</a> #{utf8(_(" > "))} #{nav}"
620                         path += '/..'
621                         parent = parent.parent
622                     end
623                     i.gsub!(/~~ifnavigation\?~~(.+?)~~fi~~/) { $1 }
624                     i.gsub!(/~~navigation~~/, nav + caption)
625                 else
626                     i.gsub!(/~~ifnavigation\?~~(.+?)~~fi~~/, '')
627                 end
628                 discover_iterations(iterations, i)
629             end
630             
631             html_index = ''
632             reset_iterations(iterations)
633             
634             #- deal with "current" album (directs to "thumbnails" page)
635             if xmldir.attributes['thumbnails-caption']
636                 thumbnail = "#{dest_dir}/thumbnails-thumbnail.jpg"
637                 gen_thumbnails_subdir(from_utf8(xmldir.attributes['thumbnails-captionfile']), xmldir, false,
638                                       [ { 'filename' => thumbnail, 'size' => $albums_thumbnail_size } ], 'thumbnails')
639                 html_index += run_iterations(iterations)
640                 html_index.gsub!(/~~image_iteration~~/, "<a href='thumbnails-#{default_thumbnails}.html'>" + img_element(thumbnail) + '</a>')
641                 html_index.gsub!(/~~caption_iteration~~/, xmldir.attributes['thumbnails-caption'])
642             end
643             #- cleanup temp for videos
644             system("rm -f #{dest_dir}/screenshot.jpg000000.jpg")
645
646             #- deal with sub-albums (direct to subdirs/index.html pages)
647             xmldir.elements.each('dir') { |child|
648                 subdir = make_dest_filename(from_utf8(File.basename(child.attributes['path'])))
649                 thumbnail = "#{dest_dir}/thumbnails-#{subdir}.jpg"
650                 html_index += run_iterations(iterations)
651                 captionfile, caption = find_subalbum_caption_info(child)
652                 gen_thumbnails_subdir(captionfile, child, false,
653                                       [ { 'filename' => thumbnail, 'size' => $albums_thumbnail_size } ], find_subalbum_info_type(child))
654                 html_index.gsub!(/~~caption_iteration~~/, caption)
655                 html_index.gsub!(/~~image_iteration~~/, "<a href='#{make_dest_filename(subdir)}/index.html'>" + img_element(thumbnail) + '</a>')
656                 #- cleanup temp for videos
657                 system("rm -f #{dest_dir}/screenshot.jpg000000.jpg")
658             }
659
660             html_index += close_iterations(iterations)
661
662             for i in html
663                 i.gsub!(/~~thumbnails~~/, html_index)
664             end
665             
666         else
667             html = html_refresh("thumbnails-#{default_thumbnails}.html")
668         end
669
670         ios = File.open("#{dest_dir}/index.html", "w")
671         ios.write(html)
672         ios.close
673
674         #- substitute "return to albums" correctly
675         `find '#{dest_dir}' -maxdepth 1 -name "thumbnails*.html"`.each { |thumbnails|
676             thumbnails.chomp!
677             contents = File.open(thumbnails.chomp).readlines
678             for i in contents
679                 if xmldir.elements['dir']
680                     i.sub!(/~~return_to_albums~~/, '<a href="index.html">' + utf8(_('Return to albums')) + '</a>')
681                 else
682                     if xmldir.parent.name == 'dir'
683                         i.sub!(/~~return_to_albums~~/, '<a href="../index.html">' + utf8(_('Return to albums')) + '</a>')
684                     else
685                         i.sub!(/~~return_to_albums~~/, '')
686                     end
687                 end
688             end
689             ios = File.open(thumbnails, "w")
690             ios.write(contents)
691             ios.close
692         }
693     }
694
695     msg 3, _(" all done.")
696 end
697
698 handle_options
699 read_config
700 check_installation
701
702 build_html_skeletons
703
704 walk_source_dir