1 ############################################################################
2 # Copyright (C) 2008 by RafaĆ Rzepecki #
3 # divided.mind@gmail.com #
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. #
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. #
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 ############################################################################
25 MPEG_VERSION = [:v2_5, :reserved, :v2, :v1]
26 LAYER = [:reserved, :layer3, :layer2, :layer1]
28 # v1l1 v1l2 v1l3 v2l1 v2l*
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]}
66 elsif layer == :layer1
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)
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
95 @bitrate = bitrates[(byte & 0xF0) >> 4]
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
107 @crc = @file.read(2).unpack("n")
109 if @sampling_rate > 0
116 def write(outfile, track, dvid)
117 # outfile << ["ea3", 3, 0x1776].pack("a3c@8n@3072")
119 tags = @tags.tagID3v2 || @tags.tagID3v1
121 tags.each { |key, value|
124 tag[key] = tag[key].recode(2)
131 tag[0...10] = ["ea3", 3, 0x1776].pack("a3c@8n")
132 outfile << [tag].pack("a3072")
133 # tag = ID3::Tag2.new.dump
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)
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)
154 outfile << block.unpack("N2").map{|x|x^key}.pack("N2")
157 # print "\rBytes left: #{left}"
163 FRAME2SYMBOL = ID3::Framename2symbol["2.4.0"]
164 SYMBOL2FRAME = ID3::SUPPORTED_SYMBOLS["2.4.0"]
177 [SYMBOL2FRAME[self[:tag]], self[:encoding], Id3.encode(self[:encoding], self[:content])].
182 def Id3.frame(tag, content, encoding)
183 tag = FRAME2SYMBOL[tag] || tag
185 Frame.new :tag => tag, :content => Id3.decode(encoding, content), :encoding => encoding
187 def Id3.decode(encoding, content)
190 Iconv.conv('utf8', 'utf16be', content)
192 raise "Unknown id3 encoding (#{encoding})"
195 def Id3.encode(encoding, content)
198 Iconv.conv('utf16be', 'utf8', content)
200 raise "Unknown id3 encoding (#{encoding})"
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 ]
212 if a.length == 1 and a[0].is_a?(String)
217 track_type, @track_length, @frame_count, num_tags, tag_size, contents =
218 contents.unpack("a4NNnna*")
220 @track_type = track_type.unpack("C4")
221 @track_type.instance_eval do
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)
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
249 16 + @tags.length * TAG_LEN
255 def Object.new(magic, contents)
259 attr_reader :magic, :records, :rec_size
261 if a.length == 1 and a[0].is_a?(String)
266 @magic, rec_count, @rec_size, contents = contents.unpack("a4nnx8a*")
268 Record.const_get(magic.capitalize)
273 (0...rec_count).each do
274 record, contents = contents.unpack("a#{rec_size}a*")
275 @records << record_klass.new(record)
279 [@magic, @records.length, rec_size].pack("a4nn@16") +
280 @records.map{|r|[r.to_s].pack("Z#{rec_size}")}.join
287 attr_reader :magic, :range
289 if a.length == 1 and a[0].is_a?(String)
292 @magic, @range = a[0], (a[1]...(a[1]+a[2]))
296 @magic, offset, length = contents.unpack("a4NN")
297 @range = offset...(offset+length)
300 [magic, range.first, range.last - range.first].pack("a4NN@16")
305 attr_reader :magic, :objects
307 if a.length == 1 and a[0].is_a?(String)
312 @magic, numobjects = contents.unpack("a4x4C")
315 (1..numobjects).each do |objindex|
316 start = objindex * 16
317 fin = objindex * 16 + 16
318 objects << ObjectPointer.new(contents[start...fin])
320 objects.each do |obj_pointer|
321 @objects << Object.new(obj_pointer.magic, contents[obj_pointer.range])
325 header = [@magic, 1, 1, 0, 0, objects.length].pack("a4C4C@16")
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
335 header + pointers + objects