display image counter
[booh] / 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 'html_merges'
24
25 #- install location
26 $FPATH = '.'
27
28 #- options
29 $options = [
30     [ '--help',          '-h', GetoptLong::NO_ARGUMENT,       "Get help message" ],
31
32     [ '--no-check',      '-n', GetoptLong::NO_ARGUMENT,       "Don't check for needed external programs at startup" ],
33
34     [ '--source',        '-s', GetoptLong::REQUIRED_ARGUMENT, "Directory which contains original images/videos as files or subdirs" ],
35     [ '--destination',   '-d', GetoptLong::REQUIRED_ARGUMENT, "Directory which will contain the web-album" ],
36     [ '--clean',         '-c', GetoptLong::NO_ARGUMENT,       "Clean destination directory" ],
37
38     [ '--theme',         '-t', GetoptLong::REQUIRED_ARGUMENT, "Select HTML theme to use" ],
39     [ '--size',          '-z', GetoptLong::REQUIRED_ARGUMENT, "Size of images for `fullscreen' slideshow" ],
40
41     [ '--mproc',         '-m', GetoptLong::REQUIRED_ARGUMENT, "Specify the number of processors for multi-processors machines" ],
42
43     [ '--verbose-level', '-v', GetoptLong::REQUIRED_ARGUMENT, "Set max verbosity level (0: warnings, 1: important messages, " +
44                                                               "2: other messages)" ],
45 ]
46
47 #- default values for some globals 
48 $convert = 'convert -interlace line +profile "*"'
49 $size = 'medium'
50 $verbose_level = 2
51
52 def usage
53     puts "Usage: #{File.basename($0)} [OPTION]..."
54     $options.each { |ary|
55         printf " %3s, %-15s %s\n", ary[1], ary[0], ary[3]
56     }
57 end
58
59 def msg(verbose_level, msg)
60     if verbose_level <= $verbose_level
61         if verbose_level == 0
62             warn "***ERROR***: #{msg}"
63         elsif verbose_level == 1
64             warn "Warning: #{msg}"
65         else
66             puts msg
67         end
68     end
69 end
70
71 def die(msg)
72     puts msg
73     exit 1
74 end
75
76 def handle_options
77     parser = GetoptLong.new
78     parser.set_options(*$options.collect { |ary| ary[0..2] })
79     begin
80         parser.each_option do |name, arg|
81             case name
82             when '--help'
83                 usage
84                 exit(0)
85
86             when '--no-check'
87                 $no_check = true
88
89             when '--source'
90                 $source = arg.sub(%r|/$|, '')
91                 if !File.directory?($source)
92                     die "Argument to --source must be a directory"
93                 end
94             when '--destination'
95                 $dest = arg.sub(%r|/$|, '')
96                 if File.exists?($dest) && !File.directory?($dest)
97                     die "If --destination exists, it must be a directory"
98                 end
99             when '--clean'
100                 system("rm -rf #{$dest}")
101
102             when '--theme'
103                 select_theme(arg)
104             when '--size'
105                 $size = arg
106
107             when '--mproc'
108                 $mproc = arg.to_i
109                 $pids = Array.new()
110
111             when '--verbose-level'
112                 $verbose_level = arg.to_i
113
114             end
115         end
116     rescue
117         usage
118         exit(1)
119     end
120
121     if !$source
122         die 'Missing --source parameter.'
123     end
124     if !$dest
125         die 'Missing --destination parameter.'
126     end
127
128     if !$theme
129         select_theme('simple')
130     end
131
132     size = $images_size.detect { |e| e['name'] == $size }
133     if !size
134         die "Size #{$size} not found in parameters of theme."
135     else
136         $sizeval = size['size']
137         msg 3, "Using size for fullscreen images of value: #{$sizeval}"
138     end
139 end
140
141 def select_theme(name)
142     $theme = name
143     themedir = "#{$FPATH}/themes/#{$theme}"
144     if !File.directory?(themedir)
145         die "Theme was not found (tried #{themedir} directory)."
146     end
147     require "#{themedir}/parameters.rb"
148 end
149
150 def check_installation
151     if $no_check
152         return
153     end
154     %w(convert transcode exif).each { |prg|
155         if !system("which #{prg} >/dev/null")
156             die "The `#{prg}' program is typically needed. Re-run with --no-check if you're sure you're fine without it."
157         end
158     }
159 end
160
161 def replace_line(surround, keyword, line)
162     begin
163         contents = eval "$#{keyword}"
164         line.sub!(/#{surround}#{keyword}#{surround}/, contents)
165     rescue NameError
166         die "No `#{keyword}' found for substitution"
167     end
168 end
169
170 def build_html_skeleton
171     theme_file = File.open("#{$FPATH}/themes/#{$theme}/skeleton.html").readlines
172     msg 2, "Read theme `#{$theme}'"
173     for line in theme_file
174         while line =~ /~~~(\w+)~~~/
175             replace_line('~~~', $1, line)
176         end
177     end
178     return theme_file
179 end
180
181 #- parallelizable sys
182 def psys(cmd)
183     if $mproc
184         if pid = fork
185             $pids << pid
186         else
187             msg 1, cmd + ' &'
188             system(cmd)
189             exit 0
190         end
191         if $pids.length == $mproc
192             finished = Process.wait2
193             $pids.delete(finished[0])
194             $pids = $pids.find_all { |pid| Process.waitpid(pid, Process::WNOHANG) == nil }
195         end
196     else
197         msg 1, cmd
198         system(cmd)
199     end
200 end
201
202 def walk_source_dir
203     `find #{$source} -type d`.each { |dir|
204         dir.chomp!
205         msg 2, "Examining #{dir}..."
206         images = Dir["#{dir}/*.{jpg,JPG,jpeg,JPEG,gif,GIF,bmp,BMP,png,PNG}"]
207         msg 2, "\t#{images.length} images"
208         videos = Dir["#{dir}/*.{mov,MOV,avi,AVI,mpg,MPG,mpeg,MPEG,wmv,WMV,asx,ASX}"]
209         msg 2, "\t#{videos.length} videos"
210         
211         dest_dir = dir.sub(/^#{Regexp.quote($source)}/, $dest)
212         system("mkdir -p '#{dest_dir}'")
213         msg 2, "Outputting in #{dest_dir}..."
214
215         final_images = []
216         images.each { |img|
217             dest_img = img.sub(/^#{Regexp.quote($source)}/, $dest).
218                            sub(/\.[^\.]+$/, '') + "-#{$sizeval}.jpg"
219             final_images << File.basename(dest_img)
220             if !File.exists?(dest_img)
221                 convert_options = ''
222                 orientation = `exif #{img}`.detect { |line| line =~ /^Orientation/ }
223                 if orientation =~ /right - top/
224                     convert_options += '-rotate 90 '
225                 end
226                 if orientation =~ /left - bottom/
227                     convert_options += '-rotate -90 '
228                 end
229                 psys("#{$convert} #{convert_options}-geometry #{$sizeval} '#{img}' '#{dest_img}'")
230             end
231         }
232
233         html = $skeleton.collect { |l| l.clone }
234         images4js = final_images.collect { |img| "\"#{img}\"" }.join(', ')
235         for i in html
236             i.sub!(/~~images~~/, images4js)
237             i.sub!(/~~title~~/, File.basename(dir))
238         end
239
240         ios = File.open("#{dest_dir}/index.html", "w")
241         ios.write(html)
242         ios.close
243     }
244 end
245
246 handle_options
247 check_installation
248
249 $skeleton = build_html_skeleton
250
251 walk_source_dir