Automatically put OMAs where they belong.
[amarok_sonynw.git] / walkgirl / Walkman.rb
blobf3f836ac501c57032b1fd9d2f6d5297a4db2ef78
1 ############################################################################
2 #    Copyright (C) 2008 by RafaƂ Rzepecki   #
3 #    divided.mind@gmail.com   #
4 #                                                                          #
5 #    This program is free software; you can redistribute it and#or modify  #
6 #    it under the terms of the GNU General Public License as published by  #
7 #    the Free Software Foundation; either version 2 of the License, or     #
8 #    (at your option) any later version.                                   #
9 #                                                                          #
10 #    This program is distributed in the hope that it will be useful,       #
11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of        #
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
13 #    GNU General Public License for more details.                          #
14 #                                                                          #
15 #    You should have received a copy of the GNU General Public License     #
16 #    along with this program; if not, write to the                         #
17 #    Free Software Foundation, Inc.,                                       #
18 #    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             #
19 ############################################################################
21 require 'iconv'
22 require 'id3'
23 module Walkman
24   class Mp3
25     MPEG_VERSION = [:v2_5, :reserved, :v2, :v1]
26     LAYER = [:reserved, :layer3, :layer2, :layer1]
27     BITRATE = [
28         # v1l1 v1l2 v1l3 v2l1 v2l*
29 [        0, 0, 0, 0, 0],
30 [        32, 32, 32, 32, 8],
31 [        64, 48, 40, 48, 16],
32 [        96, 56, 48, 56, 24],
33 [        128, 64, 56, 64, 32],
34 [        160, 80, 64, 80, 40],
35 [        192, 96, 80, 96, 48],
36 [        224, 112, 96, 112, 56],
37 [        256, 128, 112, 128, 64],
38 [        288, 160, 128, 144, 80],
39 [        320, 192, 160, 160, 96],
40 [        352, 224, 192, 176, 112],
41 [        384, 256, 224, 192, 128],
42 [        416, 320, 256, 224, 144],
43 [        448, 384, 320, 256, 160],
44 [        -1, -1, -1, -1, -1]].transpose
46     SAMPLING_RATE = { :v1 => [44100, 48000, 32000, -1],
47     :v2 => [22050, 24000, 16000, -1],
48     :v2_5 => [11025, 12000, 8000, -1] }
49     SAMPLE_PER_FRAME = { :v1 => [0, 1152, 1152, 384],
50     :v2 => [0, 576, 1152, 384],
51     :v2_5 => [0, 576, 1152, 384]}
53     DvId = 0x08F63DCA
54     CRC_POLY = 0x8005
55     
56     def bitrates
57       if version == :v1
58         case layer
59         when :layer1:
60           BITRATE[0]
61         when :layer2
62           BITRATE[1]
63         when :layer3
64           BITRATE[2]
65         end
66       elsif layer == :layer1
67         BITRATE[3]
68       else
69         BITRATE[4]
70       end
71     end
73     attr_reader :version, :layer, :tags, :bitrate
74     def initialize(filename)
75       @tags = ID3::AudioFile.new(filename)
76       @file = ::File.new(filename)
77       @file.seek(@tags.audioStartX)
79       # find first frame
80       loop do
81         loop do
82           if @file.readchar == 0xFF
83             @tags.audioStartX = @file.pos
84             if (byte = @file.readchar) & 0xE0 == 0xE0
85               @version = MPEG_VERSION[(byte & 0x18) >> 3]
86               @layer = LAYER[(byte & 0x06) >> 1]
87               @has_crc = (byte & 1) == 0
88               @encoding = (byte & 0x1e) << 3
89               break
90             end
91           end
92         end
94         byte = @file.readchar
95         @bitrate = bitrates[(byte & 0xF0) >> 4]
96         if @bitrate == 0
97           next
98         end
99         @sampling_rate = SAMPLING_RATE[version][(byte & 0x0C) >> 2]
100         @is_padded = (byte & 2) == 2
101         @length = (tags.audioEndX - tags.audioStartX) * 8 / bitrate
102         @sample_per_frame = SAMPLE_PER_FRAME[version][LAYER.index(layer)]
103         @frames = @length * @sampling_rate / 1000 / @sample_per_frame
104         @framelen = bitrate * 1000 * @sample_per_frame / 8 / @sampling_rate 
105         @encoding |= (byte & 0xF0) >> 4
106         if @has_crc
107           @crc = @file.read(2).unpack("n")
108         end
109         if @sampling_rate > 0
110           break
111         end
112       end
113       
114     end
116     def write(outfile, track, dvid)
117 #      outfile << ["ea3", 3, 0x1776].pack("a3c@8n@3072")
118       tag = ID3::Tag2.new
119       tags = @tags.tagID3v2 || @tags.tagID3v1
120       if tags
121         tags.each { |key, value|
122         begin
123           tag[key] = value
124           tag[key] = tag[key].recode(2)
125         rescue
126         end
127         }
128       end
129 #      p tag
130       tag = tag.dump
131       tag[0...10] = ["ea3", 3, 0x1776].pack("a3c@8n")
132       outfile << [tag].pack("a3072")
133 #      tag = ID3::Tag2.new.dump
134 #      tag[0..2] = "ea3"
135 #      outfile << tag
136       outfile << "EA3"
137       outfile << [2, 0, 0x60, 0xff, 0xfe, 1, 0x0f, 0x50, 0x00].pack("c5@9c4")
138       outfile << [0, 4, 0, 0, 0, 1, 2, 3, 0xc8, 0xd8, 0x36, 0xd8, 0x11, 0x22, 0x33, 0x44].pack("c*")
139       outfile << [0x03, 0x80, @encoding, 10, @length, @frames, 0].pack("c4N3")
140       outfile << [0,0,0,0].pack("N4")
141       outfile << [0,0,0,0].pack("N4")
142       outfile << [0,0,0,0].pack("N4")
143       scramble(outfile, track, dvid)
144     end
145     def scramble(outfile, track, dvid)
146       key = ( 0x2465 + track * 0x5296E435 ) ^ dvid;
147       @file.seek(tags.audioStartX)
148 #      printf "Please remember to put the file at\nOMGAUDIO\\10F%02x\\1%07x.OMA in the device root.\n\nScrambling...", track >> 8, track & 0xFF
149       left = tags.audioEndX - tags.audioStartX
150       while left > 0 and block = @file.read([8, left].min)
151         if block.length != 8
152           outfile << block
153         else
154           outfile << block.unpack("N2").map{|x|x^key}.pack("N2")
155         end
156         left -= block.length
157 #        print "\rBytes left: #{left}"
158       end
159     end
160   end
161   
162   module Id3
163     FRAME2SYMBOL = ID3::Framename2symbol["2.4.0"]
164     SYMBOL2FRAME = ID3::SUPPORTED_SYMBOLS["2.4.0"]
166     class Frame < Hash
167       def initialize(*a)
168         if a[0].is_a?(Hash)
169           self.merge! a[0]
170         end
171       end
172       def to_s(*a)
173         limit = ""
174         if a[0]
175           limit = a[0] - 6
176         end
177         [SYMBOL2FRAME[self[:tag]], self[:encoding], Id3.encode(self[:encoding], self[:content])].
178         pack "a4nZ#{limit}"
179       end
180     end
181     
182     def Id3.frame(tag, content, encoding)
183       tag = FRAME2SYMBOL[tag] || tag
184       content = content
185       Frame.new :tag => tag, :content => Id3.decode(encoding, content), :encoding => encoding
186     end
187     def Id3.decode(encoding, content)
188       case encoding
189       when 2: # UTF-16BE
190         Iconv.conv('utf8', 'utf16be', content)
191       else
192         raise "Unknown id3 encoding (#{encoding})"
193       end
194     end
195     def Id3.encode(encoding, content)
196       case encoding
197       when 2: # UTF-16BE
198         Iconv.conv('utf16be', 'utf8', content)
199       else
200         raise "Unknown id3 encoding (#{encoding})"
201       end
202     end
203   end
204   
205   module Record
206     class Cnfb
207       attr_reader :track_length, :frame_count, :tags, :content, :track_type
208       OBFUSCATED_MP3 = [ 0, 0, 0xFF, 0xFE ]
209       PLAIN_MP3 = [ 0, 0, 0xFF, 0xFF ]
210       TAG_LEN = 128
211       def initialize(*a)
212         if a.length == 1 and a[0].is_a?(String)
213           parse a[0]
214         end
215       end
216       def parse(contents)
217         track_type, @track_length, @frame_count, num_tags, tag_size, contents =
218         contents.unpack("a4NNnna*")
219         
220         @track_type = track_type.unpack("C4")
221         @track_type.instance_eval do
222           def inspect
223             case self
224             when OBFUSCATED_MP3:
225               "OBFUSCATED_MP3"
226             when PLAIN_MP3:
227               "PLAIN_MP3"
228             else
229               super
230             end
231           end
232           def to_s
233             pack("C4")
234           end
235         end
237         @tags = []
238         (0...num_tags).each do
239           tag, contents = contents.unpack("a#{tag_size}a*")
240           tag, encoding, content = tag.unpack("a4nA*")
241           @tags << Id3.frame(tag, content, encoding)
242         end
243       end
244       def to_s
245         [@track_type.to_s, @track_length, @frame_count, @tags.length, TAG_LEN].pack("a4NNnn@16") +
246         @tags.map{|t|t.to_s(TAG_LEN)}.join
247       end
248       def length
249         16 + @tags.length * TAG_LEN
250       end
251     end
252   end
254   module Object
255     def Object.new(magic, contents)
256       Object.new(contents)
257     end
258     class Object
259       attr_reader :magic, :records, :rec_size
260       def initialize(*a)
261         if a.length == 1 and a[0].is_a?(String)
262           parse a[0]
263         end
264       end
265       def parse(contents)
266         @magic, rec_count, @rec_size, contents = contents.unpack("a4nnx8a*")
267         record_klass = begin
268           Record.const_get(magic.capitalize)
269         rescue NameError
270           String
271         end
272         @records = []
273         (0...rec_count).each do
274           record, contents = contents.unpack("a#{rec_size}a*")
275           @records << record_klass.new(record)
276         end
277       end
278       def to_s
279         [@magic, @records.length, rec_size].pack("a4nn@16") +
280           @records.map{|r|[r.to_s].pack("Z#{rec_size}")}.join
281       end
282     end
283   end
286   class ObjectPointer
287     attr_reader :magic, :range
288     def initialize(*a)
289       if a.length == 1 and a[0].is_a?(String)
290         parse a[0]
291       elsif a.length == 3
292         @magic, @range = a[0], (a[1]...(a[1]+a[2]))
293       end
294     end
295     def parse(contents)
296       @magic, offset, length = contents.unpack("a4NN")
297       @range = offset...(offset+length)
298     end
299     def to_s
300       [magic, range.first, range.last - range.first].pack("a4NN@16")
301     end
302   end
304   class File
305     attr_reader :magic, :objects
306     def initialize(*a)
307       if a.length == 1 and a[0].is_a?(String)
308         parse a[0]
309       end
310     end
311     def parse(contents)
312       @magic, numobjects = contents.unpack("a4x4C")
313       objects = []
314       @objects = []
315       (1..numobjects).each do |objindex|
316         start = objindex * 16
317         fin = objindex * 16 + 16
318         objects << ObjectPointer.new(contents[start...fin])
319       end
320       objects.each do |obj_pointer|
321         @objects << Object.new(obj_pointer.magic, contents[obj_pointer.range])
322       end
323     end
324     def to_s
325       header = [@magic, 1, 1, 0, 0, objects.length].pack("a4C4C@16")
326       pointers = ""
327       objects = ""
328       offset = 16 + @objects.length * 16
329       @objects.each do |object|
330         object_raw = object.to_s
331         pointers += ObjectPointer.new(object.magic, offset, object_raw.length).to_s
332         offset += object_raw.length
333         objects += object_raw 
334       end
335       header + pointers + objects
336     end
337   end