b0d5d506e3e3b2b5fbfa2f17c2a5a252b53bd2ee
[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 #- options
26 $options = [
27     [ '--help',          '-h', GetoptLong::NO_ARGUMENT,       "Get help message" ],
28                          
29     [ '--no-check',      '-n', GetoptLong::NO_ARGUMENT,       "Don't check for needed external programs at startup" ],
30                          
31     [ '--source',        '-s', GetoptLong::REQUIRED_ARGUMENT, "Directory which contains original images/videos as files or subdirs" ],
32     [ '--destination',   '-d', GetoptLong::REQUIRED_ARGUMENT, "Directory which will contain the web-album" ],
33     [ '--clean',         '-c', GetoptLong::NO_ARGUMENT,       "Clean destination directory" ],
34                          
35     [ '--theme',         '-t', GetoptLong::REQUIRED_ARGUMENT, "Select HTML theme to use" ],
36
37     [ '--verbose-level', '-v', GetoptLong::REQUIRED_ARGUMENT, "Set max verbosity level (0: warnings, 1: important messages, " +
38                                                               "2: other messages)" ],
39 ]
40
41 #- default values for some globals 
42 $theme = 'simple'
43 $convert = 'convert -interlace line +profile "*"'
44 $verbose_level = 2
45
46 def usage
47     puts "Usage: #{File.basename($0)} [OPTION]..."
48     $options.each { |ary|
49         printf " %3s, %-15s %s\n", ary[1], ary[0], ary[3]
50     }
51 end
52
53 def msg(verbose_level, msg)
54     if verbose_level <= $verbose_level
55         if verbose_level == 0
56             warn "Warning: #{msg}"
57         else
58             puts msg
59         end
60     end
61 end
62
63 def die(msg)
64     puts msg
65     exit 1
66 end
67
68 def handle_options
69     parser = GetoptLong.new
70     parser.set_options(*$options.collect { |ary| ary[0..2] })
71     begin
72         parser.each_option do |name, arg|
73             case name
74             when '--help'
75                 usage
76                 exit(0)
77
78             when '--no-check'
79                 $no_check = true
80
81             when '--source'
82                 $source = arg.sub(%r|/$|, '')
83                 if !File.directory?($source)
84                     die "Argument to --source must be a directory"
85                 end
86             when '--destination'
87                 $dest = arg.sub(%r|/$|, '')
88                 if File.exists?($dest) && !File.directory?($dest)
89                     die "If --destination exists, it must be a directory"
90                 end
91             when '--clean'
92                 system("rm -rf #{$dest}")
93
94             when '--theme'
95                 $theme = arg
96
97             when '--verbose-level'
98                 $verbose_level = arg.to_i
99
100             end
101         end
102     rescue
103         usage
104         exit(1)
105     end
106
107     if !$source
108         die 'Missing --source parameter.'
109     end
110     if !$dest
111         die 'Missing --destination parameter.'
112     end
113 end
114
115 def check_installation
116     if $no_check
117         return
118     end
119     %w(convert transcode exif).each { |prg|
120         if !system("which #{prg} >/dev/null")
121             die "The `#{prg}' program is typically needed. Re-run with --no-check if you're sure you're fine without it."
122         end
123     }
124 end
125
126 def replace_line(surround, keyword, line)
127     begin
128         contents = eval "$#{keyword}"
129         line.sub!(/#{surround}#{keyword}#{surround}/, contents)
130     rescue NameError
131         die "No `#{keyword}' found for substitution"
132     end
133 end
134
135 def build_html_skeleton
136     theme_file = File.open("themes/#{$theme}/skeleton.html").readlines
137     msg 2, "Read theme `#{$theme}'"
138     for line in theme_file
139         while line =~ /~~~(\w+)~~~/
140             replace_line('~~~', $1, line)
141         end
142     end
143     return theme_file
144 end
145
146 def sys(cmd)
147     msg 1, cmd
148     system(cmd)
149 end
150
151 def walk_source_dir
152     `find #{$source} -type d`.each { |dir|
153         dir.chomp!
154         msg 2, "Examining #{dir}..."
155         images = Dir["#{dir}/*.{jpg,JPG,jpeg,JPEG,gif,GIF,bmp,BMP,png,PNG}"]
156         msg 2, "\t#{images.length} images"
157         videos = Dir["#{dir}/*.{mov,MOV,avi,AVI,mpg,MPG,mpeg,MPEG,wmv,WMV,asx,ASX}"]
158         msg 2, "\t#{videos.length} videos"
159         
160         dest_dir = dir.sub(/^#{Regexp.quote($source)}/, $dest)
161         system("mkdir -p #{dest_dir}")
162         msg 2, "Outputting in #{dest_dir}..."
163
164         final_images = []
165         images.each { |img|
166             dest_img = img.sub(/^#{Regexp.quote($source)}/, $dest).
167                            sub(/\.[^\.]+$/, '') + '-704.jpg'
168             final_images << File.basename(dest_img)
169             if !File.exists?(dest_img)
170                 convert_options = ''
171                 orientation = `exif #{img}`.detect { |line| line =~ /^Orientation/ }
172                 if orientation =~ /right - top/
173                     convert_options += '-rotate 90 '
174                 end
175                 if orientation =~ /left - bottom/
176                     convert_options += '-rotate -90 '
177                 end
178                 sys "#{$convert} #{convert_options}-geometry 704x528 #{img} #{dest_img}"
179             end
180         }
181
182         html = $skeleton.collect { |l| l.clone }
183         images4js = final_images.collect { |img| "\"#{img}\"" }.join(', ')
184         for i in html
185             i.sub!(/~~images~~/, images4js)
186             i.sub!(/~~title~~/, File.basename(dir))
187         end
188             
189         File.open("#{dest_dir}/index.html", "w").write(html)
190     }
191 end
192
193 handle_options
194 check_installation
195
196 $skeleton = build_html_skeleton
197
198 walk_source_dir