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 ############################################################################
28 ["TIT2", 0, 0, data].pack("a4cca#{TAG_LEN-6}")
35 COLLECTION_IDS = { "ARTIST" => 2, "ALBUM" => 3, "CONTENTTYPE" => 1 }
40 class Collection < Hash
43 @other_id = ID3::Frame.new(key, "2.4.0", "\0Unknown").recode(2).dump_short
54 object.rec_size = 16 + 128
55 (keys + [@other_id]).each do |frame|
56 record = [0, 0, 0x2eeeb, 1, 128].pack("N3n2")
58 object.records << record
59 @reverse_keys[frame] = idx
62 file.objects << object
71 gplb.obj_size = 0x4010
78 (keys.sort + [@other_id]).each do |key|
79 gplb.records << [@reverse_keys[key], 0x0100, index, 0x0100].pack("n4")
80 (self[key] || @other).each do |song|
81 tplb.records << [song.slot].pack("n")
90 { sprintf("01tree%02x.dat", COLLECTION_IDS[@key]) => tree_file,
91 sprintf("03ginf%02x.dat", COLLECTION_IDS[@key]) => ginf_file}
97 attr_accessor :collections
98 IMPORTANT_TAGS = [ 'ARTIST', 'TITLE', 'ALBUM', 'CONTENTTYPE' ]
100 @cnt = Walkman::File.new
102 @songs = Walkman::Object.new
103 @songs.rec_size = 656
104 @songs.magic = "CNFB"
105 @cnt.objects << @songs
107 COLLECTION_IDS.keys.each do |key|
108 @collections[key] = Collection.new(key)
116 oma.tags.each do |k, v|
117 if IMPORTANT_TAGS.include? k
122 oma.tags.each do |k, v|
123 if left > 0 and !IMPORTANT_TAGS.include? k
129 record = Walkman::Record::Cnfb.new
130 record.track_type = Walkman::Record::Cnfb::OBFUSCATED_MP3
131 record.track_length = oma.length
133 record.frame_count = oma.frames
134 @songs.records << record
136 COLLECTION_IDS.keys.each do |key|
137 value = oma.tags[key]
139 (@collections[key][value.dump_short] ||= []) << oma
141 @collections[key].other << oma
150 MPEG_VERSION = [:v2_5, :reserved, :v2, :v1]
151 LAYER = [:reserved, :layer3, :layer2, :layer1]
153 # v1l1 v1l2 v1l3 v2l1 v2l*
155 [ 32, 32, 32, 32, 8],
156 [ 64, 48, 40, 48, 16],
157 [ 96, 56, 48, 56, 24],
158 [ 128, 64, 56, 64, 32],
159 [ 160, 80, 64, 80, 40],
160 [ 192, 96, 80, 96, 48],
161 [ 224, 112, 96, 112, 56],
162 [ 256, 128, 112, 128, 64],
163 [ 288, 160, 128, 144, 80],
164 [ 320, 192, 160, 160, 96],
165 [ 352, 224, 192, 176, 112],
166 [ 384, 256, 224, 192, 128],
167 [ 416, 320, 256, 224, 144],
168 [ 448, 384, 320, 256, 160],
169 [ -1, -1, -1, -1, -1]].transpose
171 SAMPLING_RATE = { :v1 => [44100, 48000, 32000, -1],
172 :v2 => [22050, 24000, 16000, -1],
173 :v2_5 => [11025, 12000, 8000, -1] }
174 SAMPLE_PER_FRAME = { :v1 => [0, 1152, 1152, 384],
175 :v2 => [0, 576, 1152, 384],
176 :v2_5 => [0, 576, 1152, 384]}
191 elsif layer == :layer1
198 attr_reader :version, :layer, :tags, :bitrate, :length, :frames, :slot
199 def initialize(slot = nil, dvid = nil)
204 f = ::File.new(filename)
205 tag_data = f.read(3072)
208 @length, @frames = f.read(8).unpack("NN")
210 tag_data[0..2] = "ID3"
211 @tags = ID3::Tag2.new
216 def fromMp3(filename)
217 @tags = ID3::AudioFile.new(filename)
218 @filename = ::File.basename(filename)
219 @file = ::File.new(filename)
220 @file.seek(@tags.audioStartX)
225 if @file.readchar == 0xFF
226 @tags.audioStartX = @file.pos
227 if (byte = @file.readchar) & 0xE0 == 0xE0
228 @version = MPEG_VERSION[(byte & 0x18) >> 3]
229 @layer = LAYER[(byte & 0x06) >> 1]
230 @has_crc = (byte & 1) == 0
231 @encoding = (byte & 0x1e) << 3
237 byte = @file.readchar
238 @bitrate = bitrates[(byte & 0xF0) >> 4]
242 @sampling_rate = SAMPLING_RATE[version][(byte & 0x0C) >> 2]
243 @is_padded = (byte & 2) == 2
244 @length = (tags.audioEndX - tags.audioStartX) * 8 / bitrate
245 @sample_per_frame = SAMPLE_PER_FRAME[version][LAYER.index(layer)]
246 @frames = @length * @sampling_rate / 1000 / @sample_per_frame
247 @framelen = bitrate * 1000 * @sample_per_frame / 8 / @sampling_rate
248 @encoding |= (byte & 0xF0) >> 4
250 @crc = @file.read(2).unpack("n")
252 if @sampling_rate > 0
260 # outfile << ["ea3", 3, 0x1776].pack("a3c@8n@3072")
262 tags = @tags.tagID3v2 || @tags.tagID3v1
264 tags.each { |key, value|
271 tag["TITLE"].recode(2) if tag["TITLE"]
272 tag["ALBUM"].recode(2) if tag["ALBUM"]
273 tag["ARTIST"].recode(2) if tag["ARTIST"]
274 # tag["CONTENTTYPE"] = ""
275 # tag["CONTENTTYPE"].recode(2) if tag["CONTENTTYPE"]
276 tag["ORIGFILENAME"] = "\0" + @filename
279 tag[0...10] = ["ea3", 3, 0x1776].pack("a3c@8n")
280 outfile << [tag].pack("a3072")
281 # tag = ID3::Tag2.new.dump
285 outfile << [2, 0, 0x60, 0xff, 0xfe, 1, 0x0f, 0x50, 0x00].pack("c5@9c4")
286 outfile << [0, 4, 0, 0, 0, 1, 2, 3, 0xc8, 0xd8, 0x36, 0xd8, 0x11, 0x22, 0x33, 0x44].pack("c*")
287 outfile << [0x03, 0x80, @encoding, 10, @length, @frames, 0].pack("c4N3")
288 outfile << [0,0,0,0].pack("N4")
289 outfile << [0,0,0,0].pack("N4")
290 outfile << [0,0,0,0].pack("N4")
293 def scramble(outfile)
294 key = 0xFFFFFFFF & (( 0x2465 + @slot * 0x5296E435 ) ^ @dvid);
295 @file.seek(tags.audioStartX)
296 # printf "Please remember to put the file at\nOMGAUDIO\\10F%02x\\1%07x.OMA in the device root.\n\nScrambling...", track >> 8, track & 0xFF
297 left = tags.audioEndX - tags.audioStartX
298 while left > 0 and block = @file.read([8, left].min)
302 outfile << block.unpack("N2").map{|x|x^key}.pack("N2")
305 # print "\rBytes left: #{left}"
311 FRAME2SYMBOL = ID3::Framename2symbol["2.4.0"]
312 SYMBOL2FRAME = ID3::SUPPORTED_SYMBOLS["2.4.0"]
325 [SYMBOL2FRAME[self[:tag]], self[:encoding], Id3.encode(self[:encoding], self[:content])].
330 def Id3.frame(tag, content, encoding)
331 tag = FRAME2SYMBOL[tag] || tag
333 Frame.new :tag => tag, :content => Id3.decode(encoding, content), :encoding => encoding
335 def Id3.decode(encoding, content)
338 Iconv.conv('utf8', 'utf16be', content)
340 raise "Unknown id3 encoding (#{encoding})"
343 def Id3.encode(encoding, content)
346 Iconv.conv('utf16be', 'utf8', content)
348 raise "Unknown id3 encoding (#{encoding})"
355 attr_accessor :track_length, :frame_count, :tags, :content
356 attr_reader :track_type
357 OBFUSCATED_MP3 = [ 0, 0, 0xFF, 0xFE ]
358 PLAIN_MP3 = [ 0, 0, 0xFF, 0xFF ]
361 if a.length == 1 and a[0].is_a?(String)
365 def track_type=(new_type)
366 @track_type = new_type
367 @track_type.instance_eval do
384 track_type, @track_length, @frame_count, num_tags, tag_size, contents =
385 contents.unpack("a4NNnna*")
387 self.track_type = track_type.unpack("C4")
389 @tags = ID3::Tag2.new
390 (0...num_tags).each do
391 tag, contents = contents.unpack("a#{tag_size}a*")
392 tag, content = tag.unpack("a4A*")
393 symbol = ID3::Framename2symbol["2.4.0"][tag]
395 @tags[symbol] = ID3::Frame.new(symbol, "2.4.0", content)
400 [@track_type.to_s, @track_length, @frame_count, @tags.length, TAG_LEN].pack("a4NNnn@16") +
402 [ID3::SUPPORTED_SYMBOLS["2.4.0"][t], 0, v.data].pack("a4ca#{TAG_LEN-5}")
406 16 + @tags.length * TAG_LEN
416 attr_accessor :magic, :records, :rec_size, :obj_size
420 if a.length == 2 and a[1].is_a?(String)
425 @magic, rec_count, @rec_size, contents = contents.unpack("a4nnx8a*")
427 Record.const_get(magic.capitalize)
431 (0...rec_count).each do
432 record, contents = contents.unpack("a#{rec_size}a*")
433 @records << record_klass.new(record)
437 res = [@magic, @records.length, rec_size, @records.length].pack("a4nnN@16") +
438 @records.map{|r|[r.to_s].pack("Z#{rec_size}")}.join
440 [res].pack("a#{obj_size}")
450 attr_reader :magic, :range
452 if a.length == 1 and a[0].is_a?(String)
455 @magic, @range = a[0], (a[1]...(a[1]+a[2]))
459 @magic, offset, length = contents.unpack("a4NN")
460 @range = offset...(offset+length)
463 [magic, range.first, range.last - range.first].pack("a4NN@16")
468 attr_accessor :magic, :objects
471 if a.length == 1 and a[0].is_a?(String)
476 @magic, numobjects = contents.unpack("a4x4C")
478 (1..numobjects).each do |objindex|
479 start = objindex * 16
480 fin = objindex * 16 + 16
481 objects << ObjectPointer.new(contents[start...fin])
483 objects.each do |obj_pointer|
484 @objects << Object.new(obj_pointer.magic, contents[obj_pointer.range])
488 header = [@magic, 1, 1, 0, 0, objects.length].pack("a4C4C@16")
491 offset = 16 + @objects.length * 16
492 @objects.each do |object|
493 object_raw = object.to_s
494 pointers += ObjectPointer.new(object.magic, offset, object_raw.length).to_s
495 offset += object_raw.length
496 objects += object_raw
498 header + pointers + objects