don't background thumbnail creation when we'll be using "identify" to know the exact...
[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 gen_thumbnails(orig, xmldir, allow_background, dests)
141         if !dests.detect { |dest| !File.exists?(dest['filename']) } 
142             return true
143         end
144
145         felem = xmldir.elements["[@filename='#{utf8(File.basename(orig))}']"]
146
147         if entry2type(orig) == 'image'
148             convert_options = ''
149             if felem
150                 rotate = felem.attributes['rotate']
151                 if !rotate
152                     orientation = `exif '#{orig}'`.detect { |line| line =~ /^Orientation/ }
153                     if orientation =~ /right - top/
154                         rotate = '90'
155                     end
156                     if orientation =~ /left - bottom/
157                         rotate = '-90'
158                     end
159                     rotate ||= '0'
160                     felem.add_attribute('rotate', rotate)
161                 end
162                 convert_options += "-rotate #{rotate} "
163             end
164             for dest in dests
165                 if !File.exists?(dest['filename'])
166                     cmd = "#{$convert} #{convert_options}-geometry #{dest['size']} '#{orig}' '#{dest['filename']}'"
167                     if allow_background
168                         psys(cmd)
169                     else
170                         sys(cmd)
171                     end
172                 end
173             end
174             return true
175
176         elsif entry2type(orig) == 'video'
177             dest_dir = make_dest_filename(File.dirname(dests[0]['filename']))
178             #- frame-offset is an attribute that allows to specify which frame to use for the thumbnail;
179             #- first try in dir to allow for: <dir .. subdirs-captionfile='/tmp/src2/people/pict0008.mov' pict0008.mov-frame-offset='100' ..>
180             frame_offset = xmldir.attributes["#{utf8(File.basename(orig))}-frame-offset"]
181             if !frame_offset
182                 #- then try in elements to allow for: <video filename='bourrique.mov' frame-offset='200'/>
183                 felem and frame_offset = felem.attributes['frame-offset']
184             end
185             frame_offset = (frame_offset || 5).to_i
186             for dest in dests
187                 if !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
188                     cmd = "transcode -a 0 -c #{frame_offset-5}-#{frame_offset} -i '#{orig}' -y jpg -o '#{dest_dir}/screenshot.jpg' 2>&1"
189                     msg 2, cmd
190                     if subproc_runaway_aware(cmd) =~ /V: import format.*unknown/ || !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
191                         msg 2, _("* could not extract first image of video %s with transcode, will try first converting with mencoder") % orig
192                         cmd = "mencoder '#{orig}' -nosound -ovc lavc -lavcopts vcodec=mjpeg -o '#{dest_dir}/foo.avi' -frames #{frame_offset} -fps 25 >/dev/null 2>/dev/null"
193                         msg 2, cmd
194                         system cmd
195                         if File.exists?("#{dest_dir}/foo.avi")
196                             cmd = "transcode -a 0 -c #{frame_offset-5}-#{frame_offset} -i '#{dest_dir}/foo.avi' -y jpg -o '#{dest_dir}/screenshot.jpg' 2>&1"
197                             msg 2, cmd
198                             results = subproc_runaway_aware(cmd)
199                             system("rm -f '#{dest_dir}/foo.avi'")
200                             if results =~ /V: import format.*unknown/ || !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
201                                 msg 0, _("could not extract first image of video %s encoded by mencoder") % "#{dest_dir}/foo.avi"
202                                 return false
203                             end
204                         else
205                             msg 0, _("could not make mencoder to encode %s to mpeg4") % orig
206                             return false
207                         end
208                     end
209
210                 end
211                 sys("#{$convert} -geometry #{dest['size']} #{dest_dir}/screenshot.jpg000004.jpg '#{dest['filename']}'")
212             end
213             return true
214         end
215     end
216
217     def invornil(obj, methodname)
218         if obj == nil
219             return nil
220         else
221             return obj.method(methodname).call
222         end
223     end
224 end