display size of videos in tooltip
[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     #- commify from http://pleac.sourceforge.net/ (pleac rulz)
149     def commify(n)
150         n.to_s =~ /([^\.]*)(\..*)?/
151         int, dec = $1.reverse, $2 ? $2 : ""
152         while int.gsub!(/(,|\.|^)(\d{3})(\d)/, '\1\2' + _(",") + '\3')
153         end
154         int.reverse + dec
155     end
156
157     def gen_thumbnails(orig, xmldir, allow_background, dests)
158         if !dests.detect { |dest| !File.exists?(dest['filename']) } 
159             return true
160         end
161
162         felem = xmldir.elements["[@filename='#{utf8(File.basename(orig))}']"]
163
164         if entry2type(orig) == 'image'
165             convert_options = ''
166             if felem
167                 rotate = felem.attributes['rotate']
168                 if !rotate
169                     orientation = `exif '#{orig}'`.detect { |line| line =~ /^Orientation/ }
170                     if orientation =~ /right - top/
171                         rotate = '90'
172                     end
173                     if orientation =~ /left - bottom/
174                         rotate = '-90'
175                     end
176                     rotate ||= '0'
177                     #- remove rotate if image is obviously already in portrait (situation can come from gthumb)
178                     size = get_image_size(orig)
179                     if size && size[:x] < size[:y]
180                         rotate = '0'
181                     end
182                     felem.add_attribute('rotate', rotate)
183                 end
184                 convert_options += "-rotate #{rotate} "
185             end
186             for dest in dests
187                 if !File.exists?(dest['filename'])
188                     cmd = "#{$convert} #{convert_options}-geometry #{dest['size']} '#{orig}' '#{dest['filename']}'"
189                     if allow_background
190                         psys(cmd)
191                     else
192                         sys(cmd)
193                     end
194                 end
195             end
196             return true
197
198         elsif entry2type(orig) == 'video'
199             dest_dir = make_dest_filename(File.dirname(dests[0]['filename']))
200             #- frame-offset is an attribute that allows to specify which frame to use for the thumbnail;
201             #- first try in dir to allow for: <dir .. subdirs-captionfile='/tmp/src2/people/pict0008.mov' pict0008.mov-frame-offset='100' ..>
202             frame_offset = xmldir.attributes["#{utf8(File.basename(orig))}-frame-offset"]
203             if !frame_offset
204                 #- then try in elements to allow for: <video filename='bourrique.mov' frame-offset='200'/>
205                 felem and frame_offset = felem.attributes['frame-offset']
206             end
207             frame_offset = (frame_offset || 5).to_i
208             for dest in dests
209                 if !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
210                     cmd = "transcode -a 0 -c #{frame_offset-5}-#{frame_offset} -i '#{orig}' -y jpg -o '#{dest_dir}/screenshot.jpg' 2>&1"
211                     msg 2, cmd
212                     if subproc_runaway_aware(cmd) =~ /V: import format.*unknown/ || !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
213                         msg 2, _("* could not extract first image of video %s with transcode, will try first converting with mencoder") % orig
214                         cmd = "mencoder '#{orig}' -nosound -ovc lavc -lavcopts vcodec=mjpeg -o '#{dest_dir}/foo.avi' -frames #{frame_offset} -fps 25 >/dev/null 2>/dev/null"
215                         msg 2, cmd
216                         system cmd
217                         if File.exists?("#{dest_dir}/foo.avi")
218                             cmd = "transcode -a 0 -c #{frame_offset-5}-#{frame_offset} -i '#{dest_dir}/foo.avi' -y jpg -o '#{dest_dir}/screenshot.jpg' 2>&1"
219                             msg 2, cmd
220                             results = subproc_runaway_aware(cmd)
221                             system("rm -f '#{dest_dir}/foo.avi'")
222                             if results =~ /V: import format.*unknown/ || !File.exists?("#{dest_dir}/screenshot.jpg000004.jpg")
223                                 msg 0, _("could not extract first image of video %s encoded by mencoder") % "#{dest_dir}/foo.avi"
224                                 return false
225                             end
226                         else
227                             msg 0, _("could not make mencoder to encode %s to mpeg4") % orig
228                             return false
229                         end
230                     end
231
232                 end
233                 sys("#{$convert} -geometry #{dest['size']} #{dest_dir}/screenshot.jpg000004.jpg '#{dest['filename']}'")
234             end
235             return true
236         end
237     end
238
239     def invornil(obj, methodname)
240         if obj == nil
241             return nil
242         else
243             return obj.method(methodname).call
244         end
245     end
246 end