5 # A.k.a `Best web-album Of the world, Or your money back, Humerus'.
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.
13 # Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
15 # This software may be freely redistributed under the terms of the GNU
16 # public license version 2.
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.
25 require 'rexml/document'
32 #- bind text domain as soon as possible because some _() functions are called early to build data structures
33 bindtextdomain("booh")
37 [ '--help', '-h', GetoptLong::NO_ARGUMENT, _("Get help message") ],
39 [ '--no-check', '-n', GetoptLong::NO_ARGUMENT, _("Don't check for needed external programs at startup") ],
41 [ '--source', '-s', GetoptLong::REQUIRED_ARGUMENT, _("Directory which contains original images/videos as files or subdirs") ],
42 [ '--destination', '-d', GetoptLong::REQUIRED_ARGUMENT, _("Directory which will contain the web-album") ],
43 [ '--clean', '-c', GetoptLong::NO_ARGUMENT, _("Clean destination directory") ],
45 [ '--theme', '-t', GetoptLong::REQUIRED_ARGUMENT, _("Select HTML theme to use") ],
46 [ '--config', '-C', GetoptLong::REQUIRED_ARGUMENT, _("File containing config listing images and videos within directories with captions") ],
47 [ '--config-skel', '-k', GetoptLong::REQUIRED_ARGUMENT, _("Filename where the script will output a config skeleton") ],
48 [ '--merge-config', '-M', GetoptLong::REQUIRED_ARGUMENT, _("File containing config listing, where to merge new images/videos from --source") ],
50 [ '--mproc', '-m', GetoptLong::REQUIRED_ARGUMENT, _("Specify the number of processors for multi-processors machines") ],
52 [ '--verbose-level', '-v', GetoptLong::REQUIRED_ARGUMENT, _("Set max verbosity level (0: errors, 1: warnings, 2: important messages, 3: other messages)") ],
55 #- default values for some globals
56 $convert = 'convert -interlace line +profile "*"'
61 puts __("Usage: %s [OPTION]...", File.basename($0))
63 printf " %3s, %-15s %s\n", ary[1], ary[0], ary[3]
68 parser = GetoptLong.new
69 parser.set_options(*$options.collect { |ary| ary[0..2] })
71 parser.each_option do |name, arg|
81 $source = arg.sub(%r|/$|, '')
82 if !File.directory?($source)
83 die __("Argument to --source must be a directory")
86 $dest = arg.sub(%r|/$|, '')
87 if File.exists?($dest) && !File.directory?($dest)
88 die __("If --destination exists, it must be a directory")
90 if $dest != make_dest_filename($dest)
91 die __("Sorry, destination directory can't contain non simple alphanumeric characters.")
94 system("rm -rf #{$dest}")
99 if File.readable?(arg)
100 $xmldoc = REXML::Document.new File.new(arg)
103 die __('Config file does not exist or is unreadable.')
107 msg 1, __("Config skeleton file already exists, backuping to #{arg}.backup")
108 File.rename(arg, "#{arg}.backup")
110 $config_writeto = arg
112 when '--merge-config'
113 if File.readable?(arg)
114 msg 2, __("Merge config notice: backuping current config file to #{arg}.backup")
115 $xmldoc = REXML::Document.new File.new(arg)
116 File.rename(arg, "#{arg}.backup")
117 $config_writeto = arg
118 $mode = 'merge_config'
120 die __('Config file does not exist or is unreadable.')
127 when '--verbose-level'
128 $verbose_level = arg.to_i
138 if !$source && $xmldoc
139 $source = $xmldoc.root.attributes['source']
140 $dest = $xmldoc.root.attributes['destination']
144 die __("Missing --source parameter.")
147 die __("Missing --destination parameter.")
150 $xmldoc = Document.new "<booh version='#{$VERSION}' source='#{utf8($source)}' destination='#{utf8($dest)}'/>"
151 $xmldoc << XMLDecl.new( XMLDecl::DEFAULT_VERSION, $CURRENT_CHARSET )
156 select_theme('simple')
161 def check_installation
165 %w(convert identify exif transcode mencoder).each { |prg|
166 if !system("which #{prg} >/dev/null")
167 die __("The `%s' program is typically needed. Re-run with --no-check if you're sure you're fine without it.", prg)
172 def replace_line(surround, keyword, line)
174 contents = eval "$#{keyword}"
175 line.sub!(/#{surround}#{keyword}#{surround}/, contents)
177 die __("No `%s' found for substitution", keyword)
181 def build_html_skeletons
182 $html_images = File.open("#{$FPATH}/themes/#{$theme}/skeleton_image.html").readlines
183 $html_thumbnails = File.open("#{$FPATH}/themes/#{$theme}/skeleton_thumbnails.html").readlines
184 $html_index = File.open("#{$FPATH}/themes/#{$theme}/skeleton_index.html").readlines
185 for line in $html_images + $html_thumbnails + $html_index
186 while line =~ /~~~(\w+)~~~/
187 replace_line('~~~', $1, line)
197 #- parallelizable sys
207 if $pids.length == $mproc
208 finished = Process.wait2
209 $pids.delete(finished[0])
210 $pids = $pids.find_all { |pid| Process.waitpid(pid, Process::WNOHANG) == nil }
217 def find_caption_value(xmldir, filename)
218 xmldir.elements["[@filename='#{utf8(filename)}']"].attributes['caption']
221 def find_captions(xmldir, images)
222 return images.collect { |img| find_caption_value(xmldir, img) }
225 def entry2type(entry)
226 if entry =~ /\.(jpg|jpeg|jpe|gif|bmp|png)$/i
228 elsif entry =~ /\.(mov|avi|mpg|mpeg|mpe|wmv|asx)$/i
229 #- might consider using file magic later..
236 #- grab the results of a command but don't sleep forever on a runaway process
237 def subproc_runaway_aware(command)
242 rescue Timeout::Error
243 msg 1, _("forgetting runaway process (transcode sucks again?)")
244 #- todo should slay transcode but dunno how to do that
249 def gen_thumbnails(orig, xmldir, dests)
250 if !dests.detect { |dest| !File.exists?(dest['filename']) }
254 felem = xmldir.elements["[@filename='#{utf8(File.basename(orig))}']"]
256 if entry2type(orig) == 'image'
259 rotate = felem.attributes['rotate']
261 orientation = `exif '#{orig}'`.detect { |line| line =~ /^Orientation/ }
262 if orientation =~ /right - top/
265 if orientation =~ /left - bottom/
269 felem.add_attribute('rotate', rotate)
271 convert_options += "-rotate #{rotate} "
274 if !File.exists?(dest['filename'])
275 psys("#{$convert} #{convert_options}-geometry #{dest['size']} '#{orig}' '#{dest['filename']}'")
280 elsif entry2type(orig) == 'video'
281 dest_dir = make_dest_filename(File.dirname(dests[0]['filename']))
282 #- frame-offset is an attribute that allows to specify which frame to use for the thumbnail;
283 #- first try in dir to allow for: <dir .. subdirs-captionfile='/tmp/src2/people/pict0008.mov' pict0008.mov-frame-offset='100' ..>
284 frame_offset = xmldir.attributes["#{utf8(File.basename(orig))}-frame-offset"]
286 #- then try in elements to allow for: <video filename='bourrique.mov' frame-offset='200'/>
287 felem and frame_offset = felem.attributes['frame-offset']
289 frame_offset = (frame_offset || 5).to_i
291 if !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
292 cmd = "transcode -a 0 -c #{frame_offset-5}-#{frame_offset} -i '#{orig}' -y jpg -o '#{dest_dir}/screenshot.jpg' 2>&1"
294 if subproc_runaway_aware(cmd) =~ /V: import format.*unknown/ || !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
295 msg 2, __("* could not extract first image of video %s with transcode, will try first converting with mencoder", orig)
296 cmd = "mencoder '#{orig}' -nosound -ovc lavc -lavcopts vcodec=mjpeg -o '#{dest_dir}/foo.avi' -frames #{frame_offset} -fps 25 >/dev/null 2>/dev/null"
299 if File.exists?("#{dest_dir}/foo.avi")
300 cmd = "transcode -a 0 -c #{frame_offset-5}-#{frame_offset} -i '#{dest_dir}/foo.avi' -y jpg -o '#{dest_dir}/screenshot.jpg' 2>&1"
302 results = subproc_runaway_aware(cmd)
303 system("rm -f '#{dest_dir}/foo.avi'")
304 if results =~ /V: import format.*unknown/ || !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
305 msg 0, __("could not extract first image of video %s encoded by mencoder", "#{dest_dir}/foo.avi")
309 msg 0, __("could not make mencoder to encode %s to mpeg4", "#{orig}")
315 sys("#{$convert} -geometry #{dest['size']} #{dest_dir}/screenshot.jpg000004.jpg '#{dest['filename']}'")
321 #- stolen from CVSspam
323 text.sub(/[^a-zA-Z0-9\-,.*_\/]/) do
324 "%#{sprintf('%2X', $&[0])}"
328 def html_refresh(target)
329 return "<html><head><META http-equiv='refresh' content='0;URL=#{target}'></head><body></body><html>"
332 def discover_iterations(iterations, line)
333 if line =~ /~~iterate(\d)_open(_max(\d+))?~~/
334 for iter in iterations.values
337 iter['close_wait'] = $1.to_i
340 iterations[$1.to_i] = { 'open' => true, 'max' => $3, 'opening' => '', 'closing' => '' }
342 line.sub!(/.*/, '~~thumbnails~~')
346 elsif line =~ /~~iterate(\d)_close~~/
347 iterations[$1.to_i]['open'] = false;
348 iterations[$1.to_i]['close'] = true;
351 for iter in iterations.values
353 iter['opening'] += line
356 if !iter['close'] && iter['close_wait'] && iterations[iter['close_wait']]['close']
357 iter['closing'] += line
364 def reset_iterations(iterations)
365 for iter in iterations.values
370 def run_iterations(iterations)
372 for level in iterations.keys.sort
373 if iterations[level]['value'] == 1 || level == iterations.keys.max
374 html += iterations[level]['opening']
376 iterations[level]['value'] += 1
377 if iterations[level]['max'] && iterations[level]['value'] > iterations[level]['max'].to_i
378 iterations[level]['value'] = 1
379 iterations[level-1]['value'] = 1
380 html += iterations[level-1]['closing']
386 def close_iterations(iterations)
388 for level in iterations.keys.sort.reverse
389 html += iterations[level]['closing']
394 def img_element(fullpath)
395 sizespec = `identify '#{fullpath}'` =~ / JPEG (\d+)x(\d+) / ? 'width="' + $1 + '" height="' + $2 + '"' : ''
396 return '<img src="' + File.basename(fullpath) + '" ' + sizespec + ' border="0"/>'
401 `find #{$source} -type d`.sort.each { |dir|
404 #- place xml document on proper node if exists, else create
405 xmldir = $xmldoc.elements["//dir[@path='#{utf8(dir)}']"]
406 if $mode == 'use_config'
411 if $mode == 'gen_config' || ($mode == 'merge_config' && !xmldir)
412 #- add the <dir..> element if necessary
413 parent = File.dirname(dir)
414 xmldir = $xmldoc.elements["//dir[@path='#{utf8(parent)}']"]
416 xmldir = $xmldoc.root
418 xmldir = xmldir.add_element 'dir', { 'path' => utf8(dir), 'new' => 1 }
422 #- read images/videos entries from config or from directories depending on mode
424 if $mode == 'use_config'
425 msg 2, __("Handling %s from config list...", dir)
426 xmldir.elements.each { |element|
427 if %w(image video).include?(element.name)
428 entries << from_utf8(element.attributes['filename'])
432 msg 2, __("Examining %s...", dir)
433 entries = Dir.entries(dir).sort
434 #- populate config in case of gen_config, add new files in case of merge_config
436 type = entry2type(file)
437 if type && !xmldir.elements["#{type}[@filename='#{utf8(file)}']"]
438 xmldir.add_element type, { "filename" => utf8(file), "caption" => utf8(file.sub(/\.[^\.]+$/, '')[0..17]) }
441 if $mode == 'merge_config'
442 #- cleanup removed files from config and reread entries from config to get proper ordering
444 xmldir.elements.each { |element|
445 fullpath = "#{dir}/#{from_utf8(element.attributes['filename'])}"
446 if %w(image video).include?(element.name)
447 if !File.readable?(fullpath)
448 msg 1, __("Config merge: removing #{fullpath} from config; use the backup file to retrieve caption info if this was a mistake")
449 xmldir.delete(element)
451 entries << from_utf8(element.attributes['filename'])
457 images = entries.find_all { |e| entry2type(e) == 'image' }
458 msg 3, __("\t%s images", images.length)
459 videos = entries.find_all { |e| entry2type(e) == 'video' }
460 msg 3, __("\t%s videos", videos.length)
462 dest_dir = make_dest_filename(dir.sub(/^#{Regexp.quote($source)}/, $dest))
463 system("mkdir -p '#{dest_dir}'")
465 #- copy any resource file that goes with the theme (css, images..)
466 for entry in Dir.entries("#{$FPATH}/themes/#{$theme}")
467 if !%w(. .. skeleton_image.html skeleton_thumbnails.html skeleton_index.html parameters.rb CVS).include?(entry)
468 if !File.exists?("#{dest_dir}/#{entry}")
469 psys("cp '#{$FPATH}/themes/#{$theme}/#{entry}' '#{dest_dir}'")
474 #- pass through if there are no images and videos
475 if images.size == 0 && videos.size == 0
479 msg 2, __("Outputting in %s...", dest_dir)
481 #- populate data structure with sizes from theme
482 for sizeobj in $images_size
483 fullscreen_images ||= {}
484 fullscreen_images[sizeobj['name']] = []
485 thumbnail_images ||= {}
486 thumbnail_images[sizeobj['name']] = []
487 thumbnail_videos ||= {}
488 thumbnail_videos[sizeobj['name']] = []
491 images.size >= 1 and msg 3, __("\tcreating images thumbnails...")
493 #- create thumbnails for images
495 base_dest_img = dest_dir + '/' + make_dest_filename(img.sub(/\.[^\.]+$/, ''))
496 for sizeobj in $images_size
497 size_fullscreen = sizeobj['fullscreen']
498 size_thumbnails = sizeobj['thumbnails']
499 fullscreen_dest_img = base_dest_img + "-#{size_fullscreen}.jpg"
500 thumbnail_dest_img = base_dest_img + "-#{size_thumbnails}.jpg"
501 fullscreen_images[sizeobj['name']] << File.basename(fullscreen_dest_img)
502 thumbnail_images[sizeobj['name']] << File.basename(thumbnail_dest_img)
503 gen_thumbnails("#{dir}/#{img}", xmldir, [ { 'filename' => fullscreen_dest_img, 'size' => size_fullscreen },
504 { 'filename' => thumbnail_dest_img, 'size' => size_thumbnails } ])
508 videos.size >= 1 and msg 3, __("\tcreating videos thumbnails...")
510 #- create thumbnails for videos
511 videos.each { |video|
513 for sizeobj in $images_size
514 size_thumbnails = sizeobj['thumbnails']
515 thumbnail_dest_img = dest_dir + '/' + make_dest_filename(video.sub(/\.[^\.]+$/, '')) + "-#{size_thumbnails}.jpg"
516 thumbnail_videos[sizeobj['name']] << File.basename(thumbnail_dest_img)
517 thumbnail_ok &&= gen_thumbnails("#{dir}/#{video}", xmldir, [ { 'filename' => thumbnail_dest_img, 'size' => size_thumbnails } ])
519 destvideo = "#{dest_dir}/#{video}"
520 if !File.exists?(destvideo)
521 psys("cp '#{dir}/#{video}' '#{destvideo}'")
524 system("rm -f #{dest_dir}/screenshot.jpg00000*")
527 #- fake for gettext to find these; if themes need more sizes, english name for them should be added here
528 sizenames = { 'small' => utf8(_("small")), 'medium' => utf8(_("medium")), 'large' => utf8(_("large")) }
530 msg 3, __("\tgenerating HTML pages...")
532 #- generate thumbnails.html (page with thumbnails)
533 for sizeobj in $images_size
534 html = $html_thumbnails.collect { |l| l.clone }
537 i.sub!(/~~run_slideshow~~/, images.size <= 1 ? '' : '<a href="image-' + sizeobj['name'] + '.html?run_slideshow">' + utf8(_('Run slideshow!')) + '</a>')
538 i.sub!(/~~title~~/, xmldir.attributes['thumbnails-caption'] || utf8(File.basename(dir)))
539 for sizeobj2 in $images_size
540 if sizeobj != sizeobj2
541 i.sub!(/~~size_#{sizeobj2['name']}~~/, '<a href="thumbnails-' + sizeobj2['name'] + '.html">' + sizenames[sizeobj2['name']] + '</a>')
543 i.sub!(/~~size_#{sizeobj2['name']}~~/, sizenames[sizeobj2['name']])
546 discover_iterations(iterations, i)
549 reset_iterations(iterations)
551 type = images.include?(file) ? 'image' : videos.include?(file) ? 'video' : nil
553 html_thumbnails += run_iterations(iterations)
555 index = images.index(file)
556 html_thumbnails.gsub!(/~~image_iteration~~/,
557 '<a href="image-' + sizeobj['name'] + '.html?current=' + fullscreen_images[sizeobj['name']][index] + '">' +
558 img_element("#{dest_dir}/#{thumbnail_images[sizeobj['name']][index]}") + '</a>')
559 html_thumbnails.gsub!(/~~caption_iteration~~/,
560 find_caption_value(xmldir, images[index]) || utf8(images[index]))
561 html_thumbnails.gsub!(/~~ifimage\?~~(.+?)~~fi~~/) { $1 }
562 html_thumbnails.gsub!(/~~ifvideo\?~~(.+?)~~fi~~/, '')
563 elsif type == 'video'
564 index = videos.index(file)
565 if File.exists?("#{dest_dir}/#{thumbnail_videos[sizeobj['name']][index]}")
566 html_thumbnails.gsub!(/~~image_iteration~~/,
567 '<a href="' + videos[index] + '">' + img_element("#{dest_dir}/#{thumbnail_videos[sizeobj['name']][index]}") + '</a>')
569 html_thumbnails.gsub!(/~~image_iteration~~/,
570 '<a href="' + videos[index] + '">' + utf8(_("(no preview)")) + '</a>')
572 html_thumbnails.gsub!(/~~caption_iteration~~/,
573 find_caption_value(xmldir, videos[index]) || utf8(videos[index]))
574 html_thumbnails.gsub!(/~~ifimage\?~~(.+?)~~fi~~/, '')
575 html_thumbnails.gsub!(/~~ifvideo\?~~(.+?)~~fi~~/) { $1 }
579 html_thumbnails += close_iterations(iterations)
581 i.sub!(/~~thumbnails~~/, html_thumbnails)
583 ios = File.open("#{dest_dir}/thumbnails-#{sizeobj['name']}.html", "w")
588 #- generate image.html (page with fullscreen images)
590 #- don't ask me why I need so many backslashes... the aim is to print \\\" for each " in the javascript source
591 captions4js = find_captions(xmldir, images).collect { |e| e ? '"' + e.gsub('"', '\\\\\\\\\\\\\\\\\"' ) + '"' : '""' }.join(', ')
592 for sizeobj in $images_size
593 html = $html_images.collect { |l| l.clone }
594 images4js = fullscreen_images[sizeobj['name']].collect { |e| "\"#{e}\"" }.join(', ')
597 for sizeobj2 in $images_size
598 if sizeobj != sizeobj2
599 otherimages4js += "var images_#{sizeobj2['name']} = new Array(" + fullscreen_images[sizeobj2['name']].collect { |e| "\"#{e}\"" }.join(', ') + ")\n"
600 othersizes << "\"#{sizeobj2['name']}\""
604 i.sub!(/~~images~~/, images4js)
605 i.sub!(/~~other_images~~/, otherimages4js)
606 i.sub!(/~~other_sizes~~/, othersizes.join(', '))
607 i.sub!(/~~captions~~/, captions4js)
608 i.sub!(/~~title~~/, xmldir.attributes['thumbnails-caption'] || utf8(File.basename(dir)))
609 i.sub!(/~~thumbnails~~/, '<a href="thumbnails-' + sizeobj['name'] + '.html">' + utf8(_('Return to thumbnails')) + '</a>')
610 for sizeobj2 in $images_size
611 if sizeobj != sizeobj2
612 i.sub!(/~~size_#{sizeobj2['name']}~~/,
613 '<a href="image-' + sizeobj2['name'] + '.html" id="link' + sizeobj2['name'] + '">' + sizenames[sizeobj2['name']] + '</a>')
615 i.sub!(/~~size_#{sizeobj2['name']}~~/,
616 sizenames[sizeobj2['name']])
620 ios = File.open("#{dest_dir}/image-#{sizeobj['name']}.html", "w")
629 #- add attributes to <dir..> elements needing so
630 if $mode != 'use_config'
631 msg 3, __("\tfixating configuration file...")
632 $xmldoc.elements.each('//dir[@new]') { |element|
633 path = captionpath = element.attributes['path']
637 child = child.elements[1]
639 element.remove #- means we have a directory with nothing interesting in it
641 elsif child.name == 'dir'
642 captionpath = child.attributes['path']
644 captionfile = "#{captionpath}/#{child.attributes['filename']}"
648 basename = File.basename(path)
649 if element.elements['dir']
650 element.add_attribute('subdirs-caption', basename)
651 element.add_attribute('subdirs-captionfile', captionfile)
653 if element.elements['image'] || element.elements['video']
654 element.add_attribute('thumbnails-caption', basename)
655 element.add_attribute('thumbnails-captionfile', captionfile)
657 element.delete_attribute('new')
661 #- write down to disk config if necessary
663 ios = File.open($config_writeto, "w")
664 $xmldoc.write(ios, 0)
668 #- second pass to create index.html files
669 default_thumbnails = $images_size.detect { |sizeobj| sizeobj['default'] }
670 if !default_thumbnails
671 die __("Theme `%s' has no default size.", $theme)
673 default_thumbnails = default_thumbnails['name']
676 msg 3, __("\trescanning directories to generate all `index.html' files...")
678 `find #{$source} -type d`.each { |dir|
680 xmldir = $xmldoc.elements["//dir[@path='#{utf8(dir)}']"]
684 dest_dir = make_dest_filename(dir.sub(/^#{Regexp.quote($source)}/, $dest))
686 html = $html_index.collect { |l| l.clone }
689 caption = xmldir.attributes['subdirs-caption'] || xmldir.attributes['thumbnails-caption'] || utf8(File.basename(dir))
690 i.gsub!(/~~title~~/, caption)
691 if xmldir.parent.name == 'dir'
694 parent = xmldir.parent
695 while parent.name == 'dir'
696 parentcaption = parent.attributes['subdirs-caption'] || parent.attributes['thumbnails-caption'] || File.basename(parent.attributes['path'])
697 nav = "<a href='#{path}/index.html'>#{parentcaption}</a> #{utf8(_(" > "))} #{nav}"
699 parent = parent.parent
701 i.gsub!(/~~ifnavigation\?~~(.+?)~~fi~~/) { $1 }
702 i.gsub!(/~~navigation~~/, nav + caption)
704 i.gsub!(/~~ifnavigation\?~~(.+?)~~fi~~/, '')
706 discover_iterations(iterations, i)
710 reset_iterations(iterations)
712 if xmldir.elements['dir']
713 #- deal with "current" album (directs to "thumbnails" page)
714 if xmldir.attributes['thumbnails-caption']
715 thumbnail = "#{dest_dir}/thumbnails-thumbnail.jpg"
716 gen_thumbnails(from_utf8(xmldir.attributes['thumbnails-captionfile']), xmldir, [ { 'filename' => thumbnail, 'size' => $albums_thumbnail_size } ])
717 html_index += run_iterations(iterations)
718 html_index.gsub!(/~~image_iteration~~/, "<a href='thumbnails-#{default_thumbnails}.html'>" + img_element(thumbnail) + '</a>')
719 html_index.gsub!(/~~caption_iteration~~/, xmldir.attributes['thumbnails-caption'])
721 #- cleanup temp for videos
722 system("rm -f #{dest_dir}/screenshot.jpg00000*")
724 #- deal with sub-albums (direct to subdirs/index.html pages)
725 xmldir.elements.each('dir') { |child|
726 subdir = make_dest_filename(from_utf8(File.basename(child.attributes['path'])))
727 thumbnail = "#{dest_dir}/thumbnails-#{subdir}.jpg"
728 html_index += run_iterations(iterations)
729 #- first look for subdirs info; if not, means there is no subdir
730 caption = child.attributes['subdirs-caption']
732 gen_thumbnails(from_utf8(child.attributes['subdirs-captionfile']), child, [ { 'filename' => thumbnail, 'size' => $albums_thumbnail_size } ])
733 html_index.gsub!(/~~caption_iteration~~/, caption)
735 gen_thumbnails(from_utf8(child.attributes['thumbnails-captionfile']), child, [ { 'filename' => thumbnail, 'size' => $albums_thumbnail_size } ])
736 html_index.gsub!(/~~caption_iteration~~/, child.attributes['thumbnails-caption'])
738 html_index.gsub!(/~~image_iteration~~/, "<a href='#{make_dest_filename(subdir)}/index.html'>" + img_element(thumbnail) + '</a>')
739 #- cleanup temp for videos
740 system("rm -f #{dest_dir}/screenshot.jpg00000*")
744 html = html_refresh("thumbnails-#{default_thumbnails}.html")
747 html_index += close_iterations(iterations)
749 i.gsub!(/~~thumbnails~~/, html_index)
752 ios = File.open("#{dest_dir}/index.html", "w")
756 #- substitute "return to albums" correctly
757 `find '#{dest_dir}' -maxdepth 1 -name "thumbnails*.html"`.each { |thumbnails|
759 contents = File.open(thumbnails.chomp).readlines
761 if xmldir.elements['dir']
762 i.sub!(/~~return_to_albums~~/, '<a href="index.html">' + utf8(_('Return to albums')) + '</a>')
764 if xmldir.parent.name == 'dir'
765 i.sub!(/~~return_to_albums~~/, '<a href="../index.html">' + utf8(_('Return to albums')) + '</a>')
767 i.sub!(/~~return_to_albums~~/, '')
771 ios = File.open(thumbnails, "w")
777 msg 3, _(" all done.")