mp3file 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/bin/mp3info +25 -13
- data/lib/mp3file/id3v2/frame_header.rb +3 -1
- data/lib/mp3file/id3v2/tag.rb +7 -4
- data/lib/mp3file/mp3_file.rb +45 -16
- data/lib/mp3file/version.rb +1 -1
- data/lib/mp3file/xing_header.rb +2 -1
- data/mp3file.gemspec +2 -0
- metadata +16 -3
- data/Gemfile.lock +0 -29
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 0bfcaa237ab4362c6e5ff6267f84c6becd95aab3
         | 
| 4 | 
            +
              data.tar.gz: 3d86a90044fbf1ed37cf558a660e464993f71ec8
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 13e2cf72a0f6db63c9676d3a78eeed4345bd01e9cfb7487d1b24a864cf9ff35c9a4c945bf3890c97c269efb13ef0125177ee89902d2b5f8eb94f5c798faf36c2
         | 
| 7 | 
            +
              data.tar.gz: e483622cdc9d209500c82809fb619f19e8ef0caa454c3ff209923d66438568b173c0d39cbe67c2852d16650f2b690b7b23b0c5b61a7e1d81c95bd4190fe1b16a
         | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/bin/mp3info
    CHANGED
    
    | @@ -3,21 +3,33 @@ | |
| 3 3 | 
             
            $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
         | 
| 4 4 | 
             
            require 'mp3file'
         | 
| 5 5 |  | 
| 6 | 
            +
            def humanize(num, places = 2)
         | 
| 7 | 
            +
              if num.is_a?(Float)
         | 
| 8 | 
            +
                parts = ("%.#{places}f" % [ num ]).split(".")
         | 
| 9 | 
            +
              else
         | 
| 10 | 
            +
                parts = num.to_s.to_str.split(".")
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
         | 
| 14 | 
            +
              parts.join(".")
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
            alias :h :humanize
         | 
| 17 | 
            +
             | 
| 6 18 | 
             
            ARGV.each do |file|
         | 
| 7 19 | 
             
              begin
         | 
| 8 | 
            -
                mp3 = Mp3file::MP3File.new(file)
         | 
| 20 | 
            +
                mp3 = Mp3file::MP3File.new(file, scan_all_headers: true)
         | 
| 9 21 |  | 
| 10 22 | 
             
                puts("File: #{mp3.file.path}")
         | 
| 11 | 
            -
                puts("    Size: #{mp3.file_size} bytes Audio_size: #{mp3.audio_size} bytes")
         | 
| 23 | 
            +
                puts("    Size: #{h(mp3.file_size)} bytes Audio_size: #{h(mp3.audio_size)} bytes")
         | 
| 12 24 | 
             
                puts("    #{mp3.mpeg_version} #{mp3.layer}")
         | 
| 13 | 
            -
                puts("    Bitrate: #{mp3.bitrate}  | 
| 14 | 
            -
                puts("    Duration: #{mp3.length} s")
         | 
| 15 | 
            -
                puts("    Frames: #{mp3.num_frames} Samples: #{mp3.total_samples}")
         | 
| 25 | 
            +
                puts("    Bitrate: #{h(mp3.bitrate)} kbits/sec (vbr? #{mp3.vbr?.to_s}) Samplerate: #{mp3.samplerate} Hz Mode: #{mp3.mode}")
         | 
| 26 | 
            +
                puts("    Duration: #{h(mp3.length)} s")
         | 
| 27 | 
            +
                puts("    Frames: #{h(mp3.num_frames)} Samples: #{h(mp3.total_samples)}")
         | 
| 16 28 |  | 
| 17 29 | 
             
                header = mp3.first_header
         | 
| 18 | 
            -
                puts("    First MPEG frame at #{mp3.first_header_offset} bytes:")
         | 
| 30 | 
            +
                puts("    First MPEG frame at #{h(mp3.first_header_offset)} bytes:")
         | 
| 19 31 | 
             
                puts("        #{header.version} #{header.layer}")
         | 
| 20 | 
            -
                puts("        Bitrate: #{header.bitrate} bps Samplerate: #{header.samplerate} Hz")
         | 
| 32 | 
            +
                puts("        Bitrate: #{header.bitrate} bps Samplerate: #{h(header.samplerate)} Hz")
         | 
| 21 33 | 
             
                puts("        Mode: #{header.mode} Mode Extension: #{header.mode_extension} Emphasis: #{header.emphasis}")
         | 
| 22 34 | 
             
                puts("        CRC: #{header.has_crc} Padding: #{header.has_padding}")
         | 
| 23 35 | 
             
                puts("        Copyright: #{header.copyright} Original: #{header.original}")
         | 
| @@ -26,7 +38,7 @@ ARGV.each do |file| | |
| 26 38 | 
             
                if mp3.xing_header
         | 
| 27 39 | 
             
                  xing = mp3.xing_header
         | 
| 28 40 | 
             
                  puts("    Xing header:")
         | 
| 29 | 
            -
                  puts("        Frames: #{xing.frames} Bytes: #{xing.bytes} Quality: #{xing.quality} TOC: #{xing.toc ? 'present' : 'not present'}")
         | 
| 41 | 
            +
                  puts("        Frames: #{h(xing.frames)} Bytes: #{h(xing.bytes)} Quality: #{xing.quality} TOC: #{xing.toc ? 'present' : 'not present'}")
         | 
| 30 42 | 
             
                end
         | 
| 31 43 |  | 
| 32 44 | 
             
                if mp3.vbri_header
         | 
| @@ -47,13 +59,13 @@ ARGV.each do |file| | |
| 47 59 | 
             
                if mp3.id3v2tag?
         | 
| 48 60 | 
             
                  tag = mp3.id3v2_tag
         | 
| 49 61 | 
             
                  puts("    ID3v2 tag:")
         | 
| 50 | 
            -
                  puts("        Size: #{tag.size} bytes")
         | 
| 62 | 
            +
                  puts("        Size: #{h(tag.size)} bytes")
         | 
| 51 63 | 
             
                  puts("        Version: #{tag.version.inspect}")
         | 
| 52 | 
            -
                  puts("        Unused byes: #{tag.unused_bytes | 
| 64 | 
            +
                  puts("        Unused byes: #{h(tag.unused_bytes)}")
         | 
| 53 65 | 
             
                  if !tag.frames.empty?
         | 
| 54 66 | 
             
                    puts("        Frames:")
         | 
| 55 67 | 
             
                    tag.frames.each do |frame|
         | 
| 56 | 
            -
                      puts("            ID: #{frame.frame_id.inspect} size: #{frame.frame_size}")
         | 
| 68 | 
            +
                      puts("            ID: #{frame.frame_id.inspect} size: #{h(frame.frame_size)}")
         | 
| 57 69 | 
             
                    end
         | 
| 58 70 | 
             
                  end
         | 
| 59 71 | 
             
                end
         | 
| @@ -63,11 +75,11 @@ ARGV.each do |file| | |
| 63 75 | 
             
                  puts("        Offset: #{offset} bytes")
         | 
| 64 76 | 
             
                  puts("        Size: #{tag2.size} bytes")
         | 
| 65 77 | 
             
                  puts("        Version: #{tag2.version.inspect}")
         | 
| 66 | 
            -
                  puts("        Unused byes: #{tag2.unused_bytes | 
| 78 | 
            +
                  puts("        Unused byes: #{h(tag2.unused_bytes)}")
         | 
| 67 79 | 
             
                  if !tag2.frames.empty?
         | 
| 68 80 | 
             
                    puts("        Frames:")
         | 
| 69 81 | 
             
                    tag2.frames.each do |frame|
         | 
| 70 | 
            -
                      puts("            ID: #{frame.frame_id.inspect} size: #{frame.frame_size}")
         | 
| 82 | 
            +
                      puts("            ID: #{frame.frame_id.inspect} size: #{h(frame.frame_size)}")
         | 
| 71 83 | 
             
                    end
         | 
| 72 84 | 
             
                  end
         | 
| 73 85 | 
             
                end
         | 
| @@ -61,6 +61,7 @@ module Mp3file::ID3v2 | |
| 61 61 | 
             
                    if @tag.version >= ID3V2_2_0 && @tag.version < ID3V2_3_0
         | 
| 62 62 | 
             
                      header = ID3v220FrameHeaderFormat.read(io)
         | 
| 63 63 | 
             
                      @header_size = 6
         | 
| 64 | 
            +
                      @frame_size = header.frame_size
         | 
| 64 65 | 
             
                    elsif @tag.version >= ID3V2_3_0 && @tag.version < ID3V2_4_0
         | 
| 65 66 | 
             
                      header = ID3v230FrameHeaderFormat.read(io)
         | 
| 66 67 | 
             
                      @header_size = 10
         | 
| @@ -75,16 +76,17 @@ module Mp3file::ID3v2 | |
| 75 76 | 
             
                      if header.has_group == 1
         | 
| 76 77 | 
             
                        @group = header.group_id
         | 
| 77 78 | 
             
                      end
         | 
| 79 | 
            +
                      @frame_size = header.frame_size
         | 
| 78 80 | 
             
                    elsif @tag.version >= ID3V2_4_0
         | 
| 79 81 | 
             
                      header = ID3v240FrameHeaderFormat.read(io)
         | 
| 80 82 | 
             
                      @header_size = 10
         | 
| 83 | 
            +
                      @frame_size = BitPaddedInt.unpad_number(header.frame_size)
         | 
| 81 84 | 
             
                    end
         | 
| 82 85 | 
             
                  rescue BinData::ValidityError => ve
         | 
| 83 86 | 
             
                    raise InvalidID3v2TagError, ve.message
         | 
| 84 87 | 
             
                  end
         | 
| 85 88 |  | 
| 86 89 | 
             
                  @frame_id = header.frame_id
         | 
| 87 | 
            -
                  @frame_size = BitPaddedInt.unpad_number(header.frame_size)
         | 
| 88 90 | 
             
                  @size = @header_size + @frame_size
         | 
| 89 91 | 
             
                end
         | 
| 90 92 | 
             
              end
         | 
    
        data/lib/mp3file/id3v2/tag.rb
    CHANGED
    
    | @@ -25,11 +25,14 @@ module Mp3file::ID3v2 | |
| 25 25 | 
             
                  data.force_encoding("ASCII-8BIT")
         | 
| 26 26 | 
             
                  offset = 0
         | 
| 27 27 |  | 
| 28 | 
            -
                   | 
| 29 | 
            -
                  while frame
         | 
| 30 | 
            -
                    @frames << frame
         | 
| 31 | 
            -
                    offset = frame_offset + frame.size
         | 
| 28 | 
            +
                  loop do
         | 
| 32 29 | 
             
                    frame_offset, frame = get_next_frame_header(data, offset)
         | 
| 30 | 
            +
                    if frame
         | 
| 31 | 
            +
                      @frames << frame
         | 
| 32 | 
            +
                      offset = frame_offset + frame.size
         | 
| 33 | 
            +
                    else
         | 
| 34 | 
            +
                      break
         | 
| 35 | 
            +
                    end
         | 
| 33 36 | 
             
                  end
         | 
| 34 37 | 
             
                end
         | 
| 35 38 |  | 
    
        data/lib/mp3file/mp3_file.rb
    CHANGED
    
    | @@ -11,9 +11,9 @@ module Mp3file | |
| 11 11 |  | 
| 12 12 | 
             
                attr_accessor(:id3v1_tag, :id3v2_tag, :extra_id3v2_tags)
         | 
| 13 13 |  | 
| 14 | 
            -
                def initialize(file_path)
         | 
| 14 | 
            +
                def initialize(file_path, options = {})
         | 
| 15 15 | 
             
                  file_path = Pathname.new(file_path).expand_path if file_path.is_a?(String)
         | 
| 16 | 
            -
                  load_file(file_path)
         | 
| 16 | 
            +
                  load_file(file_path, options)
         | 
| 17 17 | 
             
                end
         | 
| 18 18 |  | 
| 19 19 | 
             
                def vbr?
         | 
| @@ -82,7 +82,9 @@ module Mp3file | |
| 82 82 | 
             
                  end
         | 
| 83 83 | 
             
                end
         | 
| 84 84 |  | 
| 85 | 
            -
                def load_file(file_path)
         | 
| 85 | 
            +
                def load_file(file_path, options = {})
         | 
| 86 | 
            +
                  scan_all_headers = %w{ t true 1 }.include?(options[:scan_all_headers].to_s)
         | 
| 87 | 
            +
             | 
| 86 88 | 
             
                  @file = file_path.open('rb')
         | 
| 87 89 | 
             
                  @file.seek(0, IO::SEEK_END)
         | 
| 88 90 | 
             
                  @file_size = @file.tell
         | 
| @@ -150,8 +152,8 @@ module Mp3file | |
| 150 152 | 
             
                  if @id3v1_tag
         | 
| 151 153 | 
             
                    @audio_size -= 128
         | 
| 152 154 | 
             
                  end
         | 
| 153 | 
            -
                  if @ | 
| 154 | 
            -
                    @audio_size -=  | 
| 155 | 
            +
                  if @id3v2_tag
         | 
| 156 | 
            +
                    @audio_size -= @id3v2_tag.size
         | 
| 155 157 | 
             
                  end
         | 
| 156 158 |  | 
| 157 159 | 
             
                  # If it's VBR, there should be an Xing header after the
         | 
| @@ -164,22 +166,43 @@ module Mp3file | |
| 164 166 | 
             
                    @file.seek(@first_header_offset + 4, IO::SEEK_CUR)
         | 
| 165 167 | 
             
                  end
         | 
| 166 168 |  | 
| 167 | 
            -
                  if  | 
| 168 | 
            -
                     | 
| 169 | 
            -
                    #  | 
| 170 | 
            -
                     | 
| 171 | 
            -
                     | 
| 172 | 
            -
             | 
| 173 | 
            -
                       | 
| 174 | 
            -
                       | 
| 175 | 
            -
                       | 
| 169 | 
            +
                  if scan_all_headers
         | 
| 170 | 
            +
                    # Loop through all the frame headers, to check for VBR / CBR (as
         | 
| 171 | 
            +
                    # a Xing header can show up in either case).
         | 
| 172 | 
            +
                    frame_headers = [ @first_header ]
         | 
| 173 | 
            +
                    last_header_offset = @first_header_offset
         | 
| 174 | 
            +
                    loop do
         | 
| 175 | 
            +
                      file.seek(last_header_offset + frame_headers.last.frame_size)
         | 
| 176 | 
            +
                      last_header_offset, header = get_next_header(file)
         | 
| 177 | 
            +
                      if header.nil?
         | 
| 178 | 
            +
                        break
         | 
| 179 | 
            +
                      else
         | 
| 180 | 
            +
                        frame_headers << header
         | 
| 181 | 
            +
                      end
         | 
| 182 | 
            +
                    end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                    uniq_brs = frame_headers.map { |h| h.bitrate }.uniq
         | 
| 185 | 
            +
                    @vbr = uniq_brs.size > 1
         | 
| 186 | 
            +
                    if uniq_brs.size == 1
         | 
| 187 | 
            +
                      @bitrate = uniq_brs.first / 1000
         | 
| 176 188 | 
             
                    end
         | 
| 189 | 
            +
                  else
         | 
| 190 | 
            +
                    # Use the Xing header to make the VBR / CBR call. Assume that
         | 
| 191 | 
            +
                    # Xing headers, when present in a CBR file, are called "Info".
         | 
| 192 | 
            +
                    @vbr = @xing_header.nil? || @xing_header.name == "Xing"
         | 
| 193 | 
            +
                  end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                  if @xing_header && @xing_header.frames && @xing_header.bytes
         | 
| 196 | 
            +
                    # Use the Xing header to calculate the duration (and overall bitrate).
         | 
| 197 | 
            +
                    @num_frames = @xing_header.frames
         | 
| 198 | 
            +
                    @total_samples = @xing_header.frames * @first_header.samples
         | 
| 199 | 
            +
                    @length = total_samples.to_f / @samplerate.to_f
         | 
| 200 | 
            +
                    @bitrate = ((@xing_header.bytes.to_f / @length.to_f) * 8 / 1000)
         | 
| 177 201 | 
             
                  else
         | 
| 178 202 | 
             
                    # Do the CBR length calculation.
         | 
| 179 | 
            -
                    @vbr = false
         | 
| 180 203 | 
             
                    @num_frames = @audio_size / @first_header.frame_size
         | 
| 181 204 | 
             
                    @total_samples = @num_frames * @first_header.samples
         | 
| 182 | 
            -
                    @length = @total_samples / @samplerate
         | 
| 205 | 
            +
                    @length = @total_samples.to_f / @samplerate.to_f
         | 
| 183 206 | 
             
                  end
         | 
| 184 207 |  | 
| 185 208 | 
             
                  @file.close
         | 
| @@ -206,6 +229,8 @@ module Mp3file | |
| 206 229 | 
             
                        file.seek(header_offset, IO::SEEK_SET)
         | 
| 207 230 | 
             
                        retry
         | 
| 208 231 | 
             
                      end
         | 
| 232 | 
            +
                    rescue EOFError
         | 
| 233 | 
            +
                      break
         | 
| 209 234 | 
             
                    end
         | 
| 210 235 |  | 
| 211 236 | 
             
                    # byte = file.readbyte
         | 
| @@ -226,6 +251,10 @@ module Mp3file | |
| 226 251 | 
             
                    # end
         | 
| 227 252 | 
             
                  end
         | 
| 228 253 |  | 
| 254 | 
            +
                  # if initial_header_offset != header_offset
         | 
| 255 | 
            +
                  #   puts "Had to skip past #{header_offset - initial_header_offset} to find the next header."
         | 
| 256 | 
            +
                  # end
         | 
| 257 | 
            +
             | 
| 229 258 | 
             
                  [ header_offset, header ]
         | 
| 230 259 | 
             
                end
         | 
| 231 260 | 
             
              end
         | 
    
        data/lib/mp3file/version.rb
    CHANGED
    
    
    
        data/lib/mp3file/xing_header.rb
    CHANGED
    
    | @@ -2,7 +2,7 @@ module Mp3file | |
| 2 2 | 
             
              class InvalidXingHeaderError < Mp3fileError; end
         | 
| 3 3 |  | 
| 4 4 | 
             
              class XingHeader
         | 
| 5 | 
            -
                attr_reader(:frames, :bytes, :toc, :quality)
         | 
| 5 | 
            +
                attr_reader(:name, :frames, :bytes, :toc, :quality)
         | 
| 6 6 |  | 
| 7 7 | 
             
                class XingHeaderFormat < BinData::Record
         | 
| 8 8 | 
             
                  string(:vbr_id, :length => 4, :check_value => lambda { value == 'Xing' || value == 'Info' })
         | 
| @@ -30,6 +30,7 @@ module Mp3file | |
| 30 30 | 
             
                    raise InvalidXingHeaderError, ve.message
         | 
| 31 31 | 
             
                  end
         | 
| 32 32 |  | 
| 33 | 
            +
                  @name = head.vbr_id
         | 
| 33 34 | 
             
                  @frames = head.frames if head.frames_present == 1
         | 
| 34 35 | 
             
                  @bytes = head.bytes if head.bytes_present == 1
         | 
| 35 36 | 
             
                  @toc = head.toc.dup if head.toc_present == 1
         | 
    
        data/mp3file.gemspec
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: mp3file
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1.0 | 
| 4 | 
            +
              version: 1.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Andrew Watts
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2015- | 
| 11 | 
            +
            date: 2015-06-18 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rspec
         | 
| @@ -38,6 +38,20 @@ dependencies: | |
| 38 38 | 
             
                - - ">="
         | 
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 40 | 
             
                    version: '0'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: pry
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - ">="
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '0'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - ">="
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0'
         | 
| 41 55 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 42 56 | 
             
              name: bindata
         | 
| 43 57 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -65,7 +79,6 @@ files: | |
| 65 79 | 
             
            - ".ruby-gemset"
         | 
| 66 80 | 
             
            - ".ruby-version"
         | 
| 67 81 | 
             
            - Gemfile
         | 
| 68 | 
            -
            - Gemfile.lock
         | 
| 69 82 | 
             
            - README.md
         | 
| 70 83 | 
             
            - Rakefile
         | 
| 71 84 | 
             
            - bin/mp3info
         | 
    
        data/Gemfile.lock
    DELETED
    
    | @@ -1,29 +0,0 @@ | |
| 1 | 
            -
            PATH
         | 
| 2 | 
            -
              remote: .
         | 
| 3 | 
            -
              specs:
         | 
| 4 | 
            -
                mp3file (1.0.3)
         | 
| 5 | 
            -
                  bindata (~> 1.5.0)
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            GEM
         | 
| 8 | 
            -
              remote: http://rubygems.org/
         | 
| 9 | 
            -
              specs:
         | 
| 10 | 
            -
                bindata (1.5.1)
         | 
| 11 | 
            -
                diff-lcs (1.1.2)
         | 
| 12 | 
            -
                rake (0.9.2)
         | 
| 13 | 
            -
                rspec (2.6.0)
         | 
| 14 | 
            -
                  rspec-core (~> 2.6.0)
         | 
| 15 | 
            -
                  rspec-expectations (~> 2.6.0)
         | 
| 16 | 
            -
                  rspec-mocks (~> 2.6.0)
         | 
| 17 | 
            -
                rspec-core (2.6.4)
         | 
| 18 | 
            -
                rspec-expectations (2.6.0)
         | 
| 19 | 
            -
                  diff-lcs (~> 1.1.2)
         | 
| 20 | 
            -
                rspec-mocks (2.6.0)
         | 
| 21 | 
            -
             | 
| 22 | 
            -
            PLATFORMS
         | 
| 23 | 
            -
              ruby
         | 
| 24 | 
            -
              x86-mingw32
         | 
| 25 | 
            -
             | 
| 26 | 
            -
            DEPENDENCIES
         | 
| 27 | 
            -
              mp3file!
         | 
| 28 | 
            -
              rake
         | 
| 29 | 
            -
              rspec
         |