remove rotate if image is obviously already in portrait (situation can come from...
[booh] / lib / booh / booh-lib.rb
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 'iconv'
23 require 'timeout'
24
25 require 'gettext'
26 include GetText
27 bindtextdomain("booh")
28
29 require 'booh/config.rb'
30 require 'booh/version.rb'
31
32 module Booh
33     $verbose_level = 2
34     $CURRENT_CHARSET = `locale charmap`.chomp
35     $convert = 'convert -interlace line +profile "*"'
36
37     def utf8(string)
38         return Iconv::iconv("UTF-8", $CURRENT_CHARSET, string).to_s
39     end
40     
41     def from_utf8(string)
42         return Iconv::iconv($CURRENT_CHARSET, "UTF-8", string).to_s
43     end
44
45     def make_dest_filename(orig_filename)
46         #- we remove non alphanumeric characters but need to do that
47         #- cleverly to not end up with two similar dest filenames. we won't
48         #- urlencode because urldecode might happen in the browser.
49         return orig_filename.unpack("C*").collect { |v| v.chr =~ /[a-zA-Z\-_0-9\.\/]/ ? v.chr : sprintf("%2X", v) }.to_s
50     end
51
52     def msg(verbose_level, msg)
53         if verbose_level <= $verbose_level
54             if verbose_level == 0
55                 warn _("\t***ERROR***: %s\n") % msg
56             elsif verbose_level == 1
57                 warn _("\tWarning: %s\n") % msg
58             else
59                 puts msg
60             end
61         end
62     end
63
64     def msg_(verbose_level, msg)
65         if verbose_level <= $verbose_level
66             if verbose_level == 0
67                 warn _("\t***ERROR***: %s") % msg
68             elsif verbose_level == 1
69                 warn _("\tWarning: %s") % msg
70             else
71                 print msg
72             end
73         end
74     end
75
76     def die(msg)
77         puts msg
78         exit 1
79     end
80
81     def select_theme(name)
82         $theme = name
83         msg 3, _("Selecting theme `%s'") % $theme
84         themedir = "#{$FPATH}/themes/#{$theme}"
85         if !File.directory?(themedir)
86             die _("Theme was not found (tried %s directory).") % themedir
87         end
88         require "#{themedir}/parameters.rb"
89     end
90
91     def entry2type(entry)
92         if entry =~ /\.(jpg|jpeg|jpe|gif|bmp|png)$/i
93             return 'image'
94         elsif entry =~ /\.(mov|avi|mpg|mpeg|mpe|wmv|asx)$/i
95             #- might consider using file magic later..
96             return 'video'
97         else
98             return nil
99         end
100     end
101
102     def sys(cmd)
103         msg 2, cmd
104         system(cmd)
105     end
106
107     #- parallelizable sys
108     def psys(cmd)
109         if $mproc
110             if pid = fork
111                 $pids << pid
112             else
113                 msg 2, cmd + ' &'
114                 system(cmd)
115                 exit 0
116             end
117             if $pids.length == $mproc
118                 finished = Process.wait2
119                 $pids.delete(finished[0])
120                 $pids = $pids.find_all { |pid| Process.waitpid(pid, Process::WNOHANG) == nil }
121             end
122         else
123             sys(cmd)
124         end
125     end
126
127     #- grab the results of a command but don't sleep forever on a runaway process
128     def subproc_runaway_aware(command)
129         begin
130             timeout(5) {
131                 return `#{command}`
132             }
133         rescue Timeout::Error    
134             msg 1, _("forgetting runaway process (transcode sucks again?)")
135             #- todo should slay transcode but dunno how to do that
136             return nil
137         end
138     end
139
140     def get_image_size(fullpath)
141         if `identify '#{fullpath}'` =~ / JPEG (\d+)x(\d+) /
142             return { :x => $1, :y => $2 }
143         else
144             return nil
145         end
146     end
147
148     def gen_thumbnails(orig, xmldir, allow_background, dests)
149         if !dests.detect { |dest| !File.exists?(dest['filename']) } 
150             return true
151         end
152
153         felem = xmldir.elements["[@filename='#{utf8(File.basename(orig))}']"]
154
155         if entry2type(orig) == 'image'
156             convert_options = ''
157             if felem
158                 rotate = felem.attributes['rotate']
159                 if !rotate
160                     orientation = `exif '#{orig}'`.detect { |line| line =~ /^Orientation/ }
161                     if orientation =~ /right - top/
162                         rotate = '90'
163                     end
164                     if orientation =~ /left - bottom/
165                         rotate = '-90'
166                     end
167                     rotate ||= '0'
168                     #- remove rotate if image is obviously already in portrait (situation can come from gthumb)
169                     size = get_image_size(orig)
170                     if size && size[:x] < size[:y]
171                         rotate = '0'
172                     end
173                     felem.add_attribute('rotate', rotate)
174                 end
175                 convert_options += "-rotate #{rotate} "
176             end
177             for dest in dests
178                 if !File.exists?(dest['filename'])
179                     cmd = "#{$convert} #{convert_options}-geometry #{dest['size']} '#{orig}' '#{dest['filename']}'"
180                     if allow_background
181                         psys(cmd)
182                     else
183                         sys(cmd)
184                     end
185                 end
186             end
187             return true
188
189         elsif entry2type(orig) == 'video'
190             dest_dir = make_dest_filename(File.dirname(dests[0]['filename']))
191             #- frame-offset is an attribute that allows to specify which frame to use for the thumbnail;
192             #- first try in dir to allow for: <dir .. subdirs-captionfile='/tmp/src2/people/pict0008.mov' pict0008.mov-frame-offset='100' ..>
193             frame_offset = xmldir.attributes["#{utf8(File.basename(orig))}-frame-offset"]
194             if !frame_offset
195                 #- then try in elements to allow for: <video filename='bourrique.mov' frame-offset='200'/>
196                 felem and frame_offset = felem.attributes['frame-offset']
197             end
198             frame_offset = (frame_offset || 5).to_i
199             for dest in dests
200                 if !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
201                     cmd = "transcode -a 0 -c #{frame_offset-5}-#{frame_offset} -i '#{orig}' -y jpg -o '#{dest_dir}/screenshot.jpg' 2>&1"
202                     msg 2, cmd
203                     if subproc_runaway_aware(cmd) =~ /V: import format.*unknown/ || !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
204                         msg 2, _("* could not extract first image of video %s with transcode, will try first converting with mencoder") % orig
205                         cmd = "mencoder '#{orig}' -nosound -ovc lavc -lavcopts vcodec=mjpeg -o '#{dest_dir}/foo.avi' -frames #{frame_offset} -fps 25 >/dev/null 2>/dev/null"
206                         msg 2, cmd
207                         system cmd
208                         if File.exists?("#{dest_dir}/foo.avi")
209                             cmd = "transcode -a 0 -c #{frame_offset-5}-#{frame_offset} -i '#{dest_dir}/foo.avi' -y jpg -o '#{dest_dir}/screenshot.jpg' 2>&1"
210                             msg 2, cmd
211                             results = subproc_runaway_aware(cmd)
212                             system("rm -f '#{dest_dir}/foo.avi'")
213                             if results =~ /V: import format.*unknown/ || !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
214                                 msg 0, _("could not extract first image of video %s encoded by mencoder") % "#{dest_dir}/foo.avi"
215                                 return false
216                             end
217                         else
218                             msg 0, _("could not make mencoder to encode %s to mpeg4") % orig
219                             return false
220                         end
221                     end
222
223                 end
224                 sys("#{$convert} -geometry #{dest['size']} #{dest_dir}/screenshot.jpg000004.jpg '#{dest['filename']}'")
225             end
226             return true
227         end
228     end
229
230     def invornil(obj, methodname)
231         if obj == nil
232             return nil
233         else
234             return obj.method(methodname).call
235         end
236     end
237 end