Adding a proof-of-concept .wps based on LucidLore
[rbmoodbar:rbmoodbar.git] / rbmoodbar.rb
1 #!/usr/bin/env ruby
2 #****************************************************************************************
3 #* Copyright (c) 2005 Gav Wood <gav@kde.org>                                            *
4 #* Copyright (c) 2006 Joseph Rabinoff <rabinoff@post.harvard.edu>                       *
5 #* Copyright (c) 2009 Nikolaj Hald Nielsen <nhn@kde.org>                                *
6 #* Copyright (c) 2009 Mark Kretschmann <kretschmann@kde.org>                            *
7 #* Copyright (c) 2010 Nick Tryon <dhraak@gmail.com>                                     *
8 #*                                                                                      *
9 #* This program is free software; you can redistribute it and/or modify it under        *
10 #* the terms of the GNU General Public License as published by the Free Software        *
11 #* Foundation; either version 2 of the License, or (at your option) any later           *
12 #* version.                                                                             *
13 #*                                                                                      *
14 #* This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
15 #* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
16 #* PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
17 #*                                                                                      *
18 #* You should have received a copy of the GNU General Public License along with         *
19 #* this program.  If not, see <http://www.gnu.org/licenses/>.                           *
20 #****************************************************************************************/
21
22 # The mood file loading and rendering code is based on the Amarok 1.4 moodbar implementation
23 # by Gav Wood and Joseph Rabinoff, ported to Qt 4 with only a few modifications by me.
24 #
25 # The moodbar generator seems to be running just fine on modern systems if gstreamer is
26 # installed, but it could none the less do with a major update, perhaps to use Phonon or
27 # even porting to qtscript so it could be run, as needed, by Amarok.
28
29 # - Nikolaj
30
31 # I pretty much just retyped the code straight from the Amarok codebase with alterations for
32 # Ruby/Ruby-Qt.  The copyright block is copied straight from moodbarManager.cpp.  I don't know
33 # exactly whether all the names apply to just these two functions (readMoodFile and
34 # drawMoodbar), but hey, erring on the side of caution
35 # - Nick
36
37 # RockBox/Ruby Moodbar utility
38 #   for rendering .mood files to viewable images[1] for eventual use in a Rockbox while-playing
39 #   screen
40 #
41 #   [1] by which I (currently) mean windows bitmap, since that's the only lossless format Rockbox
42 #       (currently?) supports for albumart
43 # TODO:
44 #   - Make it cli friendly, seriously.
45 #               - works from cli, if not very robustly
46 #   - remove hardcoded values for things (eg. width, height)
47 #               - yep, that too
48 #   - Add in the Make Moodier stuff, as it might be nice for progress bar stuff in Rockbox
49 #   - Conquer the world or, lacking the motivation for that, simply make a Rockbox .wps to fit
50 #               - basic proof of concept with LucidLore/Fuze is in progress
51
52 require 'Qt4'
53
54
55 class Moodbar
56   attr_reader :pixmap
57
58   def initialize(filename, width=1000, height=10)
59     @data = Array.new
60     @filename = filename
61     @drawn = Qt::Image.new
62     readMoodFile()
63     drawMoodbar(width, height)
64   end
65   
66   # Since qBound, which the Amarok code uses, doesn't seem to be available
67   def bound(min, mine, max)
68     if mine < min
69       return min
70     elsif max < mine
71       return max
72     else
73       return mine
74     end
75   end
76   
77   def readMoodFile()
78     samples = File.size?(@filename).to_i / 3
79     
80     # This would be bad
81     if samples == 0
82       puts "Moodbar file " + @filename + " is corrupt. skipping."
83     end
84     
85     moodFile = File.new(@filename, "r")
86     
87     for i in 0...samples
88       r = moodFile.getc.abs
89       g = moodFile.getc.abs
90       b = moodFile.getc.abs
91       
92       @data << Qt::Color.new( bound( 0, r, 255 ),
93                              bound( 0, g, 255 ),
94                              bound( 0, b, 255 ))
95     end
96     
97     moodFile.close
98     return @data
99   end
100   
101   def drawMoodbar(width, height)
102     # First average the moodbar samples that will go into each
103     # vertical bar on the screen.
104     
105     # Play it safe -- see below
106     if (@data.length() == 0) or (width == 0) 
107       return Qt::Image.new
108     end
109     
110     if width < 0
111       width.abs!
112     end
113     
114     screenColors = Array.new
115     bar = Qt::Color.new
116     r, g, b = 0.0, 0.0, 0.0
117     h = Qt::Integer.new(0)
118     s = Qt::Integer.new(0)
119     v = Qt::Integer.new(0)
120     
121     for i in 0...width
122       r, g, b = 0.0, 0.0, 0.0
123       
124       # @data.length needs to be at least 1 (see above)
125       start = i * @data.length / width
126       finish = (i+1) * @data.length / width
127       
128       if start == finish
129         finish = start + 1
130       end
131       
132       for j in start...finish
133         r += @data[j].red
134         g += @data[j].green
135         b += @data[j].blue
136       end
137       
138       n = (finish - start).abs.to_f
139       bar = Qt::Color.new( (r / n).to_i,
140                            (g / n).to_i,
141                            (b / n).to_i )
142       
143       # Snap to the HSV values for later
144       bar.getHsv(h, s, v)
145       bar.setHsv(h, s, v)
146       
147       
148       screenColors << bar
149     end
150     
151     # Paint the bars.  This is Gav's painting code -- it breaks up the monotony of solid-color vertical bars by playing with the saturation and value.
152
153     
154     @drawn = Qt::Image.new(width, height, Qt::Image.Format_RGB888)
155     paint = Qt::Painter.new(@drawn)
156     
157     for x in 0...width
158       screenColors[x].getHsv(h, s, v)
159       
160       if height == 1
161         paint.setPen(screenColors[x])
162         paint.drawPoint( x, 0 )
163       else
164         if (height % 2 == 0) # height is even
165             for y in 0...(height / 2) 
166             coeff = y.to_f / (height / 2).to_f
167             coeff2 = 1.0 - ( (1.0 - coeff) * (1.0 - coeff) )
168             coeff = 1.0 - (1.0 - coeff) / 2.0
169             coeff2 = 1.0 - (1.0 - coeff2) / 2.0
170           
171             hsvColor = Qt::Color.new
172             hsvColor.setHsv( h.to_i,
173                              bound( 0, (s.to_f * coeff).to_i, 255 ),
174                              bound( 0, (255.0 - ( 255.0 - v.to_f ) * coeff2).to_i, 255 ) )
175             paint.setPen( hsvColor )
176             paint.drawPoint( x, y )
177             paint.drawPoint( x, height - 1 - y )
178           end
179         else
180           # change to inclusive in an attempt to rm the black line when height is odd
181           # Perhaps consider doing some stuff with blocks/lambdas here to cover both cases w/o code dupe?
182           # or, alternatively, as less of a shave, just draw the odd center line separately?
183           for y in 0..(height / 2) 
184             coeff = y.to_f / (height / 2).to_f
185             coeff2 = 1.0 - ( (1.0 - coeff) * (1.0 - coeff) )
186             coeff = 1.0 - (1.0 - coeff) / 2.0
187             coeff2 = 1.0 - (1.0 - coeff2) / 2.0
188           
189             hsvColor = Qt::Color.new
190             hsvColor.setHsv( h.to_i,
191                              bound( 0, (s.to_f * coeff).to_i, 255 ),
192                              bound( 0, (255.0 - ( 255.0 - v.to_f ) * coeff2).to_i, 255 ) )
193             paint.setPen( hsvColor )
194             paint.drawPoint( x, y )
195             paint.drawPoint( x, height - 1 - y )
196           end
197         end
198       end
199     end
200     paint.end
201     
202   end
203   
204   def save
205     path = @filename.sub(/#{Qt::FileInfo.new(@filename).suffix}$/, 'bmp')
206
207     # unhide the bitmap if the moodfile was hidden
208     fn = Qt::FileInfo.new(path).fileName
209     if fn.start_with? '.' 
210       path.sub!( fn, fn.sub(/^\./, '') )
211     end
212
213     @drawn.save path
214   end
215     
216 end
217
218 length, width = ARGV.shift 2
219
220 ARGV.each do |filename| # no, not robust at all.  Why do you ask?
221   puts "Rendering #{filename}"
222   moody = Moodbar.new(filename, length.to_i, width.to_i)
223   moody.save
224 end