wahwah 0.1.0 → 1.0.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/LICENSE +21 -0
- data/lib/wahwah.rb +74 -3
- data/lib/wahwah/asf/object.rb +39 -0
- data/lib/wahwah/asf_tag.rb +220 -0
- data/lib/wahwah/errors.rb +6 -0
- data/lib/wahwah/flac/block.rb +57 -0
- data/lib/wahwah/flac/streaminfo_block.rb +51 -0
- data/lib/wahwah/flac_tag.rb +84 -0
- data/lib/wahwah/helper.rb +37 -0
- data/lib/wahwah/id3/comment_frame_body.rb +21 -0
- data/lib/wahwah/id3/frame.rb +180 -0
- data/lib/wahwah/id3/frame_body.rb +36 -0
- data/lib/wahwah/id3/genre_frame_body.rb +15 -0
- data/lib/wahwah/id3/image_frame_body.rb +60 -0
- data/lib/wahwah/id3/text_frame_body.rb +16 -0
- data/lib/wahwah/id3/v1.rb +96 -0
- data/lib/wahwah/id3/v2.rb +60 -0
- data/lib/wahwah/id3/v2_header.rb +53 -0
- data/lib/wahwah/mp3/mpeg_frame_header.rb +141 -0
- data/lib/wahwah/mp3/vbri_header.rb +47 -0
- data/lib/wahwah/mp3/xing_header.rb +45 -0
- data/lib/wahwah/mp3_tag.rb +110 -0
- data/lib/wahwah/mp4/atom.rb +105 -0
- data/lib/wahwah/mp4_tag.rb +126 -0
- data/lib/wahwah/ogg/flac_tag.rb +37 -0
- data/lib/wahwah/ogg/opus_tag.rb +33 -0
- data/lib/wahwah/ogg/packets.rb +41 -0
- data/lib/wahwah/ogg/page.rb +121 -0
- data/lib/wahwah/ogg/pages.rb +24 -0
- data/lib/wahwah/ogg/vorbis_comment.rb +51 -0
- data/lib/wahwah/ogg/vorbis_tag.rb +35 -0
- data/lib/wahwah/ogg_tag.rb +66 -0
- data/lib/wahwah/riff/chunk.rb +54 -0
- data/lib/wahwah/riff_tag.rb +140 -0
- data/lib/wahwah/tag.rb +59 -0
- data/lib/wahwah/tag_delegate.rb +16 -0
- data/lib/wahwah/version.rb +4 -2
- metadata +94 -23
- data/.gitignore +0 -8
- data/.travis.yml +0 -5
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -22
- data/README.md +0 -35
- data/Rakefile +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/wahwah.gemspec +0 -27
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WahWah
         | 
| 4 | 
            +
              class FlacTag < Tag
         | 
| 5 | 
            +
                include Ogg::VorbisComment
         | 
| 6 | 
            +
                include Flac::StreaminfoBlock
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                TAG_ID = 'fLaC'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                private
         | 
| 11 | 
            +
                  # FLAC structure:
         | 
| 12 | 
            +
                  #
         | 
| 13 | 
            +
                  # The four byte string "fLaC"
         | 
| 14 | 
            +
                  # The STREAMINFO metadata block
         | 
| 15 | 
            +
                  # Zero or more other metadata blocks
         | 
| 16 | 
            +
                  # One or more audio frames
         | 
| 17 | 
            +
                  def parse
         | 
| 18 | 
            +
                    # Flac file maybe contain ID3 header on the start, so skip it if exists
         | 
| 19 | 
            +
                    id3_header = ID3::V2Header.new(@file_io)
         | 
| 20 | 
            +
                    id3_header.valid? ? @file_io.seek(id3_header.size) : @file_io.rewind
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    return if @file_io.read(4) != TAG_ID
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    loop do
         | 
| 25 | 
            +
                      block = Flac::Block.new(@file_io)
         | 
| 26 | 
            +
                      parse_block(block)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      break if block.is_last? || @file_io.eof?
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def parse_block(block)
         | 
| 33 | 
            +
                    return unless block.valid?
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    case block.type
         | 
| 36 | 
            +
                    when 'STREAMINFO'
         | 
| 37 | 
            +
                      parse_streaminfo_block(block.data)
         | 
| 38 | 
            +
                    when 'VORBIS_COMMENT'
         | 
| 39 | 
            +
                      parse_vorbis_comment(block.data)
         | 
| 40 | 
            +
                    when 'PICTURE'
         | 
| 41 | 
            +
                      parse_picture_block(block.data)
         | 
| 42 | 
            +
                    else
         | 
| 43 | 
            +
                      @file_io.seek(block.size, IO::SEEK_CUR)
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  # PICTURE block data structure:
         | 
| 48 | 
            +
                  #
         | 
| 49 | 
            +
                  # Length(bit)  Meaning
         | 
| 50 | 
            +
                  #
         | 
| 51 | 
            +
                  # 32           The picture type according to the ID3v2 APIC frame:
         | 
| 52 | 
            +
                  #
         | 
| 53 | 
            +
                  # 32           The length of the MIME type string in bytes.
         | 
| 54 | 
            +
                  #
         | 
| 55 | 
            +
                  # n*8          The MIME type string.
         | 
| 56 | 
            +
                  #
         | 
| 57 | 
            +
                  # 32           The length of the description string in bytes.
         | 
| 58 | 
            +
                  #
         | 
| 59 | 
            +
                  # n*8          The description of the picture, in UTF-8.
         | 
| 60 | 
            +
                  #
         | 
| 61 | 
            +
                  # 32           The width of the picture in pixels.
         | 
| 62 | 
            +
                  #
         | 
| 63 | 
            +
                  # 32           The height of the picture in pixels.
         | 
| 64 | 
            +
                  #
         | 
| 65 | 
            +
                  # 32           The color depth of the picture in bits-per-pixel.
         | 
| 66 | 
            +
                  #
         | 
| 67 | 
            +
                  # 32           For indexed-color pictures (e.g. GIF), the number of colors used, or 0 for non-indexed pictures.
         | 
| 68 | 
            +
                  #
         | 
| 69 | 
            +
                  # 32           The length of the picture data in bytes.
         | 
| 70 | 
            +
                  #
         | 
| 71 | 
            +
                  # n*8          The binary picture data.
         | 
| 72 | 
            +
                  def parse_picture_block(block_data)
         | 
| 73 | 
            +
                    block_content = StringIO.new(block_data)
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    type_index, mime_type_length = block_content.read(8).unpack('NN')
         | 
| 76 | 
            +
                    mime_type = Helper.encode_to_utf8(block_content.read(mime_type_length))
         | 
| 77 | 
            +
                    description_length = block_content.read(4).unpack('N').first
         | 
| 78 | 
            +
                    data_length = block_content.read(description_length + 20).unpack("#{'x' * (description_length + 16)}N").first
         | 
| 79 | 
            +
                    data = block_content.read(data_length)
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    @images.push({ data: data, mime_type: mime_type, type: ID3::ImageFrameBody::TYPES[type_index] })
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WahWah
         | 
| 4 | 
            +
              module Helper
         | 
| 5 | 
            +
                def self.encode_to_utf8(string, source_encoding: '')
         | 
| 6 | 
            +
                  encoded_string = source_encoding.empty? ?
         | 
| 7 | 
            +
                    string.force_encoding('utf-8') :
         | 
| 8 | 
            +
                    string.encode('utf-8', source_encoding, invalid: :replace, undef: :replace, replace: '')
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  encoded_string.valid_encoding? ? encoded_string.strip : ''
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # ID3 size is encoded with four bytes where may the most significant
         | 
| 14 | 
            +
                # bit (bit 7) is set to zero in every byte,
         | 
| 15 | 
            +
                # making a total of 28 bits. The zeroed bits are ignored
         | 
| 16 | 
            +
                def self.id3_size_caculate(bits_string, has_zero_bit: true)
         | 
| 17 | 
            +
                  if has_zero_bit
         | 
| 18 | 
            +
                    bits_string.scan(/.{8}/).map { |byte_string| byte_string[1..-1] }.join.to_i(2)
         | 
| 19 | 
            +
                  else
         | 
| 20 | 
            +
                    bits_string.to_i(2)
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def self.split_with_terminator(string, terminator_size)
         | 
| 25 | 
            +
                  string.split(Regexp.new(('\x00' * terminator_size).b), 2)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def self.file_format(file_path)
         | 
| 29 | 
            +
                  File.extname(file_path).downcase.delete('.')
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def self.byte_string_to_guid(byte_string)
         | 
| 33 | 
            +
                  guid = byte_string.unpack('NnnA*').pack('VvvA*').unpack('H*').first
         | 
| 34 | 
            +
                  [guid[0..7], guid[8..11], guid[12..15], guid[16..19], guid[20..-1]].join('-').upcase
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WahWah
         | 
| 4 | 
            +
              module ID3
         | 
| 5 | 
            +
                class CommentFrameBody < FrameBody
         | 
| 6 | 
            +
                  # Comment frame body structure:
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  # Text encoding             $xx
         | 
| 9 | 
            +
                  # Language                  $xx xx xx
         | 
| 10 | 
            +
                  # Short content description <textstring> $00 (00)
         | 
| 11 | 
            +
                  # The actual text           <textstring>
         | 
| 12 | 
            +
                  def parse
         | 
| 13 | 
            +
                    encoding_id, _language, reset_content = @content.unpack('CA3a*')
         | 
| 14 | 
            +
                    encoding = ENCODING_MAPPING[encoding_id]
         | 
| 15 | 
            +
                    _description, comment_text = Helper.split_with_terminator(reset_content, ENCODING_TERMINATOR_SIZE[encoding])
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    @value = Helper.encode_to_utf8(comment_text, source_encoding: encoding)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,180 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'zlib'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module WahWah
         | 
| 6 | 
            +
              module ID3
         | 
| 7 | 
            +
                class Frame
         | 
| 8 | 
            +
                  ID_MAPPING = {
         | 
| 9 | 
            +
                    # ID3v2.2 frame id
         | 
| 10 | 
            +
                    COM: :comment,
         | 
| 11 | 
            +
                    TRK: :track,
         | 
| 12 | 
            +
                    TYE: :year,
         | 
| 13 | 
            +
                    TAL: :album,
         | 
| 14 | 
            +
                    TP1: :artist,
         | 
| 15 | 
            +
                    TT2: :title,
         | 
| 16 | 
            +
                    TCO: :genre,
         | 
| 17 | 
            +
                    TPA: :disc,
         | 
| 18 | 
            +
                    TP2: :albumartist,
         | 
| 19 | 
            +
                    TCM: :composer,
         | 
| 20 | 
            +
                    PIC: :image,
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    # ID3v2.3 and ID3v2.4 frame id
         | 
| 23 | 
            +
                    COMM: :comment,
         | 
| 24 | 
            +
                    TRCK: :track,
         | 
| 25 | 
            +
                    TYER: :year,
         | 
| 26 | 
            +
                    TALB: :album,
         | 
| 27 | 
            +
                    TPE1: :artist,
         | 
| 28 | 
            +
                    TIT2: :title,
         | 
| 29 | 
            +
                    TCON: :genre,
         | 
| 30 | 
            +
                    TPOS: :disc,
         | 
| 31 | 
            +
                    TPE2: :albumartist,
         | 
| 32 | 
            +
                    TCOM: :composer,
         | 
| 33 | 
            +
                    APIC: :image,
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    # ID3v2.4 use TDRC replace TYER
         | 
| 36 | 
            +
                    TDRC: :year
         | 
| 37 | 
            +
                  }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  # ID3v2.3 frame flags field is defined as follows.
         | 
| 40 | 
            +
                  #
         | 
| 41 | 
            +
                  # %abc00000 %ijk00000
         | 
| 42 | 
            +
                  #
         | 
| 43 | 
            +
                  # a - Tag alter preservation
         | 
| 44 | 
            +
                  # b - File alter preservation
         | 
| 45 | 
            +
                  # c - Read only
         | 
| 46 | 
            +
                  # i - Compression
         | 
| 47 | 
            +
                  # j - Encryption
         | 
| 48 | 
            +
                  # k - Grouping identity
         | 
| 49 | 
            +
                  V3_HEADER_FLAGS_INDICATIONS = Array.new(16).tap do |array|
         | 
| 50 | 
            +
                    array[0] = :tag_alter_preservation
         | 
| 51 | 
            +
                    array[1] = :file_alter_preservation
         | 
| 52 | 
            +
                    array[2] = :read_only
         | 
| 53 | 
            +
                    array[8] = :compression
         | 
| 54 | 
            +
                    array[9] = :encryption
         | 
| 55 | 
            +
                    array[10] = :grouping_identity
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  # ID3v2.4 frame flags field is defined as follows.
         | 
| 59 | 
            +
                  #
         | 
| 60 | 
            +
                  # %0abc0000 %0h00kmnp
         | 
| 61 | 
            +
                  #
         | 
| 62 | 
            +
                  # a - Tag alter preservation
         | 
| 63 | 
            +
                  # b - File alter preservation
         | 
| 64 | 
            +
                  # c - Read only
         | 
| 65 | 
            +
                  # h - Grouping identity
         | 
| 66 | 
            +
                  # k - Compression
         | 
| 67 | 
            +
                  # m - Encryption
         | 
| 68 | 
            +
                  # n - Unsynchronisation
         | 
| 69 | 
            +
                  # p - Data length indicator
         | 
| 70 | 
            +
                  V4_HEADER_FLAGS_INDICATIONS = Array.new(16).tap do |array|
         | 
| 71 | 
            +
                    array[1] = :tag_alter_preservation
         | 
| 72 | 
            +
                    array[2] = :file_alter_preservation
         | 
| 73 | 
            +
                    array[3] = :read_only
         | 
| 74 | 
            +
                    array[9] = :grouping_identity
         | 
| 75 | 
            +
                    array[12] = :compression
         | 
| 76 | 
            +
                    array[13] = :encryption
         | 
| 77 | 
            +
                    array[14] = :unsynchronisation
         | 
| 78 | 
            +
                    array[15] = :data_length_indicator
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  attr_reader :name, :value
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  def initialize(file_io, version)
         | 
| 84 | 
            +
                    @file_io = file_io
         | 
| 85 | 
            +
                    @version = version
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    parse_frame_header
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    # In ID3v2.3 when frame is compressed using zlib
         | 
| 90 | 
            +
                    # with 4 bytes for 'decompressed size' appended to the frame header.
         | 
| 91 | 
            +
                    #
         | 
| 92 | 
            +
                    # In ID3v2.4 A 'Data Length Indicator' byte MUST be included in the frame
         | 
| 93 | 
            +
                    # when frame is compressed, and 'Data Length Indicator'represented as a 32 bit
         | 
| 94 | 
            +
                    # synchsafe integer
         | 
| 95 | 
            +
                    #
         | 
| 96 | 
            +
                    # So skip those 4 byte.
         | 
| 97 | 
            +
                    if compressed? || data_length_indicator?
         | 
| 98 | 
            +
                      @file_io.seek(4, IO::SEEK_CUR)
         | 
| 99 | 
            +
                      @size = @size - 4
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    parse_body
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  def valid?
         | 
| 106 | 
            +
                    @size > 0 && !@name.nil?
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  def compressed?
         | 
| 110 | 
            +
                    @flags.include? :compression
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  def data_length_indicator?
         | 
| 114 | 
            +
                    @flags.include? :data_length_indicator
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
             | 
| 118 | 
            +
                  private
         | 
| 119 | 
            +
                    # ID3v2.2 frame header structure:
         | 
| 120 | 
            +
                    #
         | 
| 121 | 
            +
                    # Frame ID      $xx xx xx(tree characters)
         | 
| 122 | 
            +
                    # Size          3 * %xxxxxxxx
         | 
| 123 | 
            +
                    #
         | 
| 124 | 
            +
                    # ID3v2.3 frame header structure:
         | 
| 125 | 
            +
                    #
         | 
| 126 | 
            +
                    # Frame ID      $xx xx xx xx (four characters)
         | 
| 127 | 
            +
                    # Size          4 * %xxxxxxxx
         | 
| 128 | 
            +
                    # Flags         $xx xx
         | 
| 129 | 
            +
                    #
         | 
| 130 | 
            +
                    # ID3v2.4 frame header structure:
         | 
| 131 | 
            +
                    #
         | 
| 132 | 
            +
                    # Frame ID      $xx xx xx xx (four characters)
         | 
| 133 | 
            +
                    # Size          4 * %0xxxxxxx
         | 
| 134 | 
            +
                    # Flags         $xx xx
         | 
| 135 | 
            +
                    def parse_frame_header
         | 
| 136 | 
            +
                      header_size = @version == 2 ? 6 : 10
         | 
| 137 | 
            +
                      header_formate = @version == 2 ? 'A3B24' : 'A4B32B16'
         | 
| 138 | 
            +
                      id, size_bits, flags_bits = @file_io.read(header_size).unpack(header_formate)
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                      @name = ID_MAPPING[id.to_sym]
         | 
| 141 | 
            +
                      @size = Helper.id3_size_caculate(size_bits, has_zero_bit: @version == 4)
         | 
| 142 | 
            +
                      @flags = parse_flags(flags_bits)
         | 
| 143 | 
            +
                    end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                    def parse_flags(flags_bits)
         | 
| 146 | 
            +
                      return [] if flags_bits.nil?
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                      frame_flags_indications = @version == 4 ?
         | 
| 149 | 
            +
                        V4_HEADER_FLAGS_INDICATIONS :
         | 
| 150 | 
            +
                        V3_HEADER_FLAGS_INDICATIONS
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                      flags_bits.split('').map.with_index do |flag_bit, index|
         | 
| 153 | 
            +
                        frame_flags_indications[index] if flag_bit == '1'
         | 
| 154 | 
            +
                      end.compact
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    def parse_body
         | 
| 158 | 
            +
                      return unless @size > 0
         | 
| 159 | 
            +
                      (@file_io.seek(@size, IO::SEEK_CUR); return) if @name.nil?
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                      content = compressed? ? Zlib.inflate(@file_io.read(@size)) : @file_io.read(@size)
         | 
| 162 | 
            +
                      frame_body = frame_body_class.new(content, @version)
         | 
| 163 | 
            +
                      @value = frame_body.value
         | 
| 164 | 
            +
                    end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                    def frame_body_class
         | 
| 167 | 
            +
                      case @name
         | 
| 168 | 
            +
                      when :comment
         | 
| 169 | 
            +
                        CommentFrameBody
         | 
| 170 | 
            +
                      when :genre
         | 
| 171 | 
            +
                        GenreFrameBody
         | 
| 172 | 
            +
                      when :image
         | 
| 173 | 
            +
                        ImageFrameBody
         | 
| 174 | 
            +
                      else
         | 
| 175 | 
            +
                        TextFrameBody
         | 
| 176 | 
            +
                      end
         | 
| 177 | 
            +
                    end
         | 
| 178 | 
            +
                end
         | 
| 179 | 
            +
              end
         | 
| 180 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WahWah
         | 
| 4 | 
            +
              module ID3
         | 
| 5 | 
            +
                class FrameBody
         | 
| 6 | 
            +
                  # Textual frames are marked with an encoding byte.
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  # $00   ISO-8859-1 [ISO-8859-1]. Terminated with $00.
         | 
| 9 | 
            +
                  # $01   UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM.
         | 
| 10 | 
            +
                  # $02   UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM.
         | 
| 11 | 
            +
                  # $03   UTF-8 [UTF-8] encoded Unicode [UNICODE].
         | 
| 12 | 
            +
                  ENCODING_MAPPING = %w(ISO-8859-1 UTF-16 UTF-16BE UTF-8)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  ENCODING_TERMINATOR_SIZE = {
         | 
| 15 | 
            +
                    'ISO-8859-1' => 1,
         | 
| 16 | 
            +
                    'UTF-16' => 2,
         | 
| 17 | 
            +
                    'UTF-16BE' => 2,
         | 
| 18 | 
            +
                    'UTF-8' => 1
         | 
| 19 | 
            +
                  }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
             | 
| 22 | 
            +
                  attr_reader :value
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def initialize(content, version)
         | 
| 25 | 
            +
                    @content = content
         | 
| 26 | 
            +
                    @version = version
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    parse
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def parse
         | 
| 32 | 
            +
                    raise WahWahNotImplementedError, 'The parse method is not implemented'
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WahWah
         | 
| 4 | 
            +
              module ID3
         | 
| 5 | 
            +
                class GenreFrameBody < TextFrameBody
         | 
| 6 | 
            +
                  def parse
         | 
| 7 | 
            +
                    super
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    # If value is numeric value, or contain numeric value in parens
         | 
| 10 | 
            +
                    # can use as index for ID3v1 genre list
         | 
| 11 | 
            +
                    @value = ID3::V1::GENRES[$1.to_i] if @value =~ /^\((\d+)\)$/ || @value =~ /^(\d+)$/
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WahWah
         | 
| 4 | 
            +
              module ID3
         | 
| 5 | 
            +
                class ImageFrameBody < FrameBody
         | 
| 6 | 
            +
                  TYPES = %i(
         | 
| 7 | 
            +
                    other
         | 
| 8 | 
            +
                    file_icon
         | 
| 9 | 
            +
                    other_file_icon
         | 
| 10 | 
            +
                    cover_front
         | 
| 11 | 
            +
                    cover_back
         | 
| 12 | 
            +
                    leaflet
         | 
| 13 | 
            +
                    media
         | 
| 14 | 
            +
                    lead_artist
         | 
| 15 | 
            +
                    artist
         | 
| 16 | 
            +
                    conductor
         | 
| 17 | 
            +
                    band
         | 
| 18 | 
            +
                    composer
         | 
| 19 | 
            +
                    lyricist
         | 
| 20 | 
            +
                    recording_location
         | 
| 21 | 
            +
                    during_recording
         | 
| 22 | 
            +
                    during_performance
         | 
| 23 | 
            +
                    movie_screen_capture
         | 
| 24 | 
            +
                    bright_coloured_fish
         | 
| 25 | 
            +
                    illustration
         | 
| 26 | 
            +
                    band_logotype
         | 
| 27 | 
            +
                    publisher_logotype
         | 
| 28 | 
            +
                  )
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def mime_type
         | 
| 31 | 
            +
                    mime_type = @mime_type.downcase.yield_self { |type| type == 'jpg' ? 'jpeg' : type }
         | 
| 32 | 
            +
                    @version > 2 ? mime_type : "image/#{mime_type}"
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  # ID3v2.2 image frame structure:
         | 
| 36 | 
            +
                  #
         | 
| 37 | 
            +
                  # Text encoding  $xx
         | 
| 38 | 
            +
                  # Image format   $xx xx xx
         | 
| 39 | 
            +
                  # Picture type   $xx
         | 
| 40 | 
            +
                  # Description    <text string according to encoding> $00 (00)
         | 
| 41 | 
            +
                  # Picture data   <binary data>
         | 
| 42 | 
            +
                  #
         | 
| 43 | 
            +
                  # ID3v2.3 and ID3v2.4 image frame structure:
         | 
| 44 | 
            +
                  #
         | 
| 45 | 
            +
                  # Text encoding $xx
         | 
| 46 | 
            +
                  # MIME type     <text string> $00
         | 
| 47 | 
            +
                  # Picture type  $xx
         | 
| 48 | 
            +
                  # Description   <text string according to encoding> $00 (00)
         | 
| 49 | 
            +
                  # Picture data  <binary data>
         | 
| 50 | 
            +
                  def parse
         | 
| 51 | 
            +
                    frame_format = @version > 2 ? 'CZ*Ca*' : 'Ca3Ca*'
         | 
| 52 | 
            +
                    encoding_id, @mime_type, type_index, reset_content = @content.unpack(frame_format)
         | 
| 53 | 
            +
                    encoding = ENCODING_MAPPING[encoding_id]
         | 
| 54 | 
            +
                    _description, data = Helper.split_with_terminator(reset_content, ENCODING_TERMINATOR_SIZE[encoding])
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    @value = { data: data, mime_type: mime_type, type: TYPES[type_index] }
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WahWah
         | 
| 4 | 
            +
              module ID3
         | 
| 5 | 
            +
                class TextFrameBody < FrameBody
         | 
| 6 | 
            +
                  # Text frame boby structure:
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  # Text encoding  $xx
         | 
| 9 | 
            +
                  # Information    <text string according to encoding>
         | 
| 10 | 
            +
                  def parse
         | 
| 11 | 
            +
                    encoding_id, text = @content.unpack('Ca*')
         | 
| 12 | 
            +
                    @value = Helper.encode_to_utf8(text, source_encoding: ENCODING_MAPPING[encoding_id])
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         |