wahwah 0.1.0.pre.test → 0.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +22 -0
  6. data/README.md +35 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/lib/wahwah.rb +3 -74
  11. data/lib/wahwah/version.rb +2 -4
  12. data/wahwah.gemspec +27 -0
  13. metadata +25 -96
  14. data/LICENSE +0 -21
  15. data/lib/wahwah/asf/object.rb +0 -39
  16. data/lib/wahwah/asf_tag.rb +0 -220
  17. data/lib/wahwah/errors.rb +0 -6
  18. data/lib/wahwah/flac/block.rb +0 -57
  19. data/lib/wahwah/flac/streaminfo_block.rb +0 -51
  20. data/lib/wahwah/flac_tag.rb +0 -84
  21. data/lib/wahwah/helper.rb +0 -37
  22. data/lib/wahwah/id3/comment_frame_body.rb +0 -21
  23. data/lib/wahwah/id3/frame.rb +0 -180
  24. data/lib/wahwah/id3/frame_body.rb +0 -36
  25. data/lib/wahwah/id3/genre_frame_body.rb +0 -15
  26. data/lib/wahwah/id3/image_frame_body.rb +0 -60
  27. data/lib/wahwah/id3/text_frame_body.rb +0 -16
  28. data/lib/wahwah/id3/v1.rb +0 -96
  29. data/lib/wahwah/id3/v2.rb +0 -60
  30. data/lib/wahwah/id3/v2_header.rb +0 -53
  31. data/lib/wahwah/mp3/mpeg_frame_header.rb +0 -141
  32. data/lib/wahwah/mp3/vbri_header.rb +0 -47
  33. data/lib/wahwah/mp3/xing_header.rb +0 -45
  34. data/lib/wahwah/mp3_tag.rb +0 -110
  35. data/lib/wahwah/mp4/atom.rb +0 -105
  36. data/lib/wahwah/mp4_tag.rb +0 -126
  37. data/lib/wahwah/ogg/flac_tag.rb +0 -37
  38. data/lib/wahwah/ogg/opus_tag.rb +0 -33
  39. data/lib/wahwah/ogg/packets.rb +0 -41
  40. data/lib/wahwah/ogg/page.rb +0 -121
  41. data/lib/wahwah/ogg/pages.rb +0 -24
  42. data/lib/wahwah/ogg/vorbis_comment.rb +0 -51
  43. data/lib/wahwah/ogg/vorbis_tag.rb +0 -35
  44. data/lib/wahwah/ogg_tag.rb +0 -66
  45. data/lib/wahwah/riff/chunk.rb +0 -54
  46. data/lib/wahwah/riff_tag.rb +0 -140
  47. data/lib/wahwah/tag.rb +0 -59
  48. data/lib/wahwah/tag_delegate.rb +0 -16
@@ -1,105 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- module Mp4
5
- class Atom
6
- VERSIONED_ATOMS = %w(meta stsd)
7
- FLAGGED_ATOMS = %w(stsd)
8
- HEADER_SIZE = 8
9
- HEADER_SIZE_FIELD_SIZE = 4
10
- EXTENDED_HEADER_SIZE = 8
11
-
12
- attr_reader :size, :type
13
-
14
- def self.find(file_io, *atom_path)
15
- file_io.rewind
16
-
17
- atom_type = atom_path.shift
18
-
19
- until file_io.eof?
20
- atom = new(file_io)
21
-
22
- next unless atom.valid?
23
- file_io.seek(atom.size, IO::SEEK_CUR); next unless atom.type == atom_type
24
-
25
- return atom if atom_path.empty?
26
- return atom.find(*atom_path)
27
- end
28
-
29
- # Return empty atom if can not found
30
- new(StringIO.new(''))
31
- end
32
-
33
- # An atom header consists of the following fields:
34
- #
35
- # Atom size:
36
- # A 32-bit integer that indicates the size of the atom, including both the atom header and the atom’s contents,
37
- # including any contained atoms. Normally, the size field contains the actual size of the atom.
38
- #
39
- # Type:
40
- # A 32-bit integer that contains the type of the atom.
41
- # This can often be usefully treated as a four-character field with a mnemonic value .
42
- def initialize(file_io)
43
- @size, @type = file_io.read(HEADER_SIZE)&.unpack('Na4')
44
- return unless valid?
45
-
46
- # If the size field of an atom is set to 1, the type field is followed by a 64-bit extended size field,
47
- # which contains the actual size of the atom as a 64-bit unsigned integer.
48
- @size = file_io.read(EXTENDED_HEADER_SIZE).unpack('Q>').first - EXTENDED_HEADER_SIZE if @size == 1
49
-
50
- # If the size field of an atom is set to 0, which is allowed only for a top-level atom,
51
- # designates the last atom in the file and indicates that the atom extends to the end of the file.
52
- @size = file_io.size if @size == 0
53
- @size = @size - HEADER_SIZE
54
- @file_io = file_io
55
- @position = file_io.pos
56
- end
57
-
58
- def data
59
- @file_io.seek(@position)
60
- @file_io.read(size)
61
- end
62
-
63
- def valid?
64
- !@size.nil? && @size >= HEADER_SIZE
65
- end
66
-
67
- def find(*atom_path)
68
- child_atom_index = data.index(atom_path.first)
69
-
70
- # Return empty atom if can not found
71
- return self.class.new(StringIO.new('')) if child_atom_index.nil?
72
-
73
- # Because before atom type field there are 4 bytes of size field,
74
- # So the child_atom_index should reduce 4.
75
- self.class.find(StringIO.new(data[child_atom_index - HEADER_SIZE_FIELD_SIZE..-1]), *atom_path)
76
- end
77
-
78
-
79
- def children
80
- @children ||= parse_children_atoms
81
- end
82
-
83
- private
84
- def parse_children_atoms
85
- children_atoms = []
86
- atoms_data = data
87
-
88
- # Some atoms data contain extra content before child atom data.
89
- # So reduce those extra content to get child atom data.
90
- atoms_data = atoms_data[4..-1] if VERSIONED_ATOMS.include? type # Skip 4 bytes for version
91
- atoms_data = atoms_data[4..-1] if FLAGGED_ATOMS.include? type # Skip 4 bytes for flag
92
- atoms_data_io = StringIO.new(atoms_data)
93
-
94
- until atoms_data_io.eof?
95
- atom = self.class.new(atoms_data_io)
96
- children_atoms.push(atom)
97
-
98
- atoms_data_io.seek(atom.size, IO::SEEK_CUR)
99
- end
100
-
101
- children_atoms
102
- end
103
- end
104
- end
105
- end
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- class Mp4Tag < Tag
5
- META_ATOM_MAPPING = {
6
- "\xA9alb".b => :album,
7
- "\xA9ART".b => :artist,
8
- "\xA9cmt".b => :comment,
9
- "\xA9wrt".b => :composer,
10
- "\xA9day".b => :year,
11
- "\xA9gen".b => :genre,
12
- "\xA9nam".b => :title,
13
- 'covr'.b => :image,
14
- 'disk'.b => :disc,
15
- 'trkn'.b => :track,
16
- 'aART'.b => :albumartist
17
- }
18
-
19
- META_ATOM_DECODE_BY_TYPE = {
20
- 0 => -> (data) { data }, # reserved
21
- 1 => -> (data) { Helper.encode_to_utf8(data) }, # UTF-8
22
- 2 => -> (data) { Helper.encode_to_utf8(data, 'UTF-16BE') }, # UTF-16BE
23
- 3 => -> (data) { Helper.encode_to_utf8(data, 'SJIS') }, # SJIS
24
-
25
- 13 => -> (data) { { data: data, mime_type: 'image/jpeg', type: :cover } }, # JPEG
26
- 14 => -> (data) { { data: data, mime_type: 'image/png', type: :cover } }, # PNG
27
-
28
- 21 => -> (data) { data.unpack('i>').first }, # Big endian signed integer
29
- 22 => -> (data) { data.unpack('I>').first }, # Big endian unsigned integer
30
- 23 => -> (data) { data.unpack('g').first }, # Big endian 32-bit floating point value
31
- 24 => -> (data) { data.unpack('G').first }, # Big endian 64-bit floating point value
32
-
33
- 65 => -> (data) { data.unpack('c').first }, # 8-bit signed integer
34
- 66 => -> (data) { data.unpack('s>').first }, # Big-endian 16-bit signed integer
35
- 67 => -> (data) { data.unpack('l>').first }, # Big-endian 32-bit signed integer
36
- 74 => -> (data) { data.unpack('q>').first }, # Big-endian 64-bit signed integer
37
-
38
- 75 => -> (data) { data.unpack('C').first }, # 8-bit unsigned integer
39
- 76 => -> (data) { data.unpack('S>').first }, # Big-endian 16-bit unsigned integer
40
- 77 => -> (data) { data.unpack('L>').first }, # Big-endian 32-bit unsigned integer
41
- 78 => -> (data) { data.unpack('Q>').first } # Big-endian 64-bit unsigned integer
42
- }
43
-
44
- private
45
- def parse
46
- movie_atom = Mp4::Atom.find(@file_io, 'moov')
47
- return unless movie_atom.valid?
48
-
49
- parse_meta_list_atom movie_atom.find('udta', 'meta', 'ilst')
50
- parse_mvhd_atom movie_atom.find('mvhd')
51
- parse_stsd_atom movie_atom.find('trak', 'mdia', 'minf', 'stbl', 'stsd')
52
- end
53
-
54
- def parse_meta_list_atom(atom)
55
- return unless atom.valid?
56
-
57
- # The metadata item list atom holds a list of actual metadata values that are present in the metadata atom.
58
- # The metadata items are formatted as a list of items.
59
- # The metadata item list atom is of type ‘ilst’ and contains a number of metadata items, each of which is an atom.
60
- # each metadata item atom contains a Value Atom, to hold the value of the metadata item
61
- atom.children.each do |child_atom|
62
- attribute_name = META_ATOM_MAPPING[child_atom.type]
63
-
64
- # The value of the metadata item is expressed as immediate data in a value atom.
65
- # The value atom starts with two fields: a type indicator, and a locale indicator.
66
- # Both the type and locale indicators are four bytes long.
67
- # There may be multiple ‘value’ entries, using different type
68
- data_atom = child_atom.find('data')
69
- return unless data_atom.valid?
70
-
71
- data_type, data_value = data_atom.data.unpack('Nx4a*')
72
- encoded_data_value = META_ATOM_DECODE_BY_TYPE[data_type]&.call(data_value)
73
-
74
- next if attribute_name.nil? || encoded_data_value.nil?
75
-
76
- case attribute_name
77
- when :image
78
- @images.push(encoded_data_value)
79
- when :comment
80
- @comments.push(encoded_data_value)
81
- when :track, :disc
82
- count, total_count = encoded_data_value.unpack('x2nn')
83
-
84
- instance_variable_set("@#{attribute_name}", count) unless count.zero?
85
- instance_variable_set("@#{attribute_name}_total", total_count) unless total_count.zero?
86
- else
87
- instance_variable_set("@#{attribute_name}", encoded_data_value)
88
- end
89
- end
90
- end
91
-
92
- def parse_mvhd_atom(atom)
93
- return unless atom.valid?
94
-
95
- atom_data = StringIO.new(atom.data)
96
- version = atom_data.read(1).unpack('c').first
97
-
98
- # Skip flags
99
- atom_data.seek(3, IO::SEEK_CUR)
100
-
101
- if version == 0
102
- # Skip creation and modification time
103
- atom_data.seek(8, IO::SEEK_CUR)
104
-
105
- time_scale, duration = atom_data.read(8).unpack('l>l>')
106
- elsif version == 1
107
- # Skip creation and modification time
108
- atom_data.seek(16, IO::SEEK_CUR)
109
-
110
- time_scale, duration = atom_data.read(12).unpack('l>q>')
111
- end
112
-
113
- @duration = (duration / time_scale.to_f).round
114
- end
115
-
116
- def parse_stsd_atom(atom)
117
- return unless atom.valid?
118
-
119
- mp4a_atom = atom.find('mp4a')
120
- esds_atom = atom.find('esds')
121
-
122
- @sample_rate = mp4a_atom.data.unpack('x22I>').first if mp4a_atom.valid?
123
- @bitrate = esds_atom.data.unpack('x26I>').first / 1000 if esds_atom.valid?
124
- end
125
- end
126
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- module Ogg
5
- class FlacTag
6
- include VorbisComment
7
- include Flac::StreaminfoBlock
8
-
9
- attr_reader :bitrate, :duration, :sample_rate, *COMMET_FIELD_MAPPING.values
10
-
11
- def initialize(identification_packet, comment_packet)
12
- # Identification packet structure:
13
- #
14
- # The one-byte packet type 0x7F
15
- # The four-byte ASCII signature "FLAC", i.e. 0x46, 0x4C, 0x41, 0x43
16
- # A one-byte binary major version number for the mapping, e.g. 0x01 for mapping version 1.0
17
- # A one-byte binary minor version number for the mapping, e.g. 0x00 for mapping version 1.0
18
- # A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one.
19
- # The four-byte ASCII native FLAC signature "fLaC" according to the FLAC format specification
20
- # The STREAMINFO metadata block for the stream.
21
- #
22
- # The first identification packet is followed by one or more header packets.
23
- # Each such packet will contain a single native FLAC metadata block.
24
- # The first of these must be a VORBIS_COMMENT block.
25
-
26
- id, streaminfo_block_data = identification_packet.unpack('x9A4A*')
27
-
28
- return unless id == 'fLaC'
29
- streaminfo_block = Flac::Block.new(StringIO.new(streaminfo_block_data))
30
- vorbis_comment_block = Flac::Block.new(StringIO.new(comment_packet))
31
-
32
- parse_streaminfo_block(streaminfo_block.data)
33
- parse_vorbis_comment(vorbis_comment_block.data)
34
- end
35
- end
36
- end
37
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- module Ogg
5
- class OpusTag
6
- include VorbisComment
7
-
8
- attr_reader :sample_rate, :pre_skip, *COMMET_FIELD_MAPPING.values
9
-
10
- def initialize(identification_packet, comment_packet)
11
- # Identification packet structure:
12
- #
13
- # 1) "OpusHead"
14
- # 2) [version] = read 8 bits as unsigned integer
15
- # 3) [audio_channels] = read 8 bit as unsigned integer
16
- # 4) [pre_skip] = read 16 bits as unsigned little endian integer
17
- # 5) [input_sample_rate] = read 32 bits as unsigned little endian integer
18
- # 6) [output_gain] = read 16 bits as unsigned little endian integer
19
- # 7) [channel_mapping_family] = read 8 bit as unsigned integer
20
- # 8) [channel_mapping_table]
21
- @sample_rate = 48000
22
- @pre_skip = identification_packet[10..11].unpack('v').first
23
-
24
- comment_packet_id, comment_packet_body = [comment_packet[0..7], comment_packet[8..-1]]
25
-
26
- # Opus comment packet start with 'OpusTags'
27
- return unless comment_packet_id == 'OpusTags'
28
-
29
- parse_vorbis_comment(comment_packet_body)
30
- end
31
- end
32
- end
33
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- module Ogg
5
- # From Ogg's perspective, packets can be of any arbitrary size. A
6
- # specific media mapping will define how to group or break up packets
7
- # from a specific media encoder. As Ogg pages have a maximum size of
8
- # about 64 kBytes, sometimes a packet has to be distributed over
9
- # several pages. To simplify that process, Ogg divides each packet
10
- # into 255 byte long chunks plus a final shorter chunk. These chunks
11
- # are called "Ogg Segments". They are only a logical construct and do
12
- # not have a header for themselves.
13
- class Packets
14
- include Enumerable
15
-
16
- def initialize(file_io)
17
- @file_io = file_io
18
- end
19
-
20
- def each
21
- @file_io.rewind
22
-
23
- packet = +''
24
- pages = Ogg::Pages.new(@file_io)
25
-
26
- pages.each do |page|
27
- page.segments.each do |segment|
28
- packet << segment
29
-
30
- # Ogg divides each packet into 255 byte long segments plus a final shorter segment.
31
- # So when segment length is less than 255 byte, it's the final segment.
32
- if segment.length < 255
33
- yield packet
34
- packet = +''
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
41
- end
@@ -1,121 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- module Ogg
5
- # The Ogg page header has the following format:
6
- #
7
- # 0 1 2 3
8
- # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| Byte
9
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
10
- # | capture_pattern: Magic number for page start "OggS" | 0-3
11
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12
- # | version | header_type | granule_position | 4-7
13
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
14
- # | | 8-11
15
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16
- # | | bitstream_serial_number | 12-15
17
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18
- # | | page_sequence_number | 16-19
19
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20
- # | | CRC_checksum | 20-23
21
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22
- # | |page_segments | segment_table | 24-27
23
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24
- # | ... | 28-
25
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26
- #
27
- #
28
- # The fields in the page header have the following meaning:
29
- #
30
- # 1. capture_pattern: a 4 Byte field that signifies the beginning of a
31
- # page. It contains the magic numbers:
32
-
33
- # 0x4f 'O'
34
-
35
- # 0x67 'g'
36
-
37
- # 0x67 'g'
38
-
39
- # 0x53 'S'
40
-
41
- # It helps a decoder to find the page boundaries and regain
42
- # synchronisation after parsing a corrupted stream. Once the
43
- # capture pattern is found, the decoder verifies page sync and
44
- # integrity by computing and comparing the checksum.
45
-
46
- # 2. stream_structure_version: 1 Byte signifying the version number of
47
- # the Ogg file format used in this stream (this document specifies
48
- # version 0).
49
-
50
- # 3. header_type_flag: the bits in this 1 Byte field identify the
51
- # specific type of this page.
52
-
53
- # * bit 0x01
54
-
55
- # set: page contains data of a packet continued from the previous
56
- # page
57
-
58
- # unset: page contains a fresh packet
59
-
60
- # * bit 0x02
61
-
62
- # set: this is the first page of a logical bitstream (bos)
63
-
64
- # unset: this page is not a first page
65
-
66
- # * bit 0x04
67
-
68
- # set: this is the last page of a logical bitstream (eos)
69
-
70
- # unset: this page is not a last page
71
-
72
- # 4. granule_position: an 8 Byte field containing position information.
73
- # For example, for an audio stream, it MAY contain the total number
74
- # of PCM samples encoded after including all frames finished on this
75
- # page. For a video stream it MAY contain the total number of video
76
- # frames encoded after this page. This is a hint for the decoder
77
- # and gives it some timing and position information. Its meaning is
78
- # dependent on the codec for that logical bitstream and specified in
79
- # a specific media mapping. A special value of -1 (in two's
80
- # complement) indicates that no packets finish on this page.
81
-
82
- # 5. bitstream_serial_number: a 4 Byte field containing the unique
83
- # serial number by which the logical bitstream is identified.
84
-
85
- # 6. page_sequence_number: a 4 Byte field containing the sequence
86
- # number of the page so the decoder can identify page loss. This
87
- # sequence number is increasing on each logical bitstream
88
- # separately.
89
-
90
- # 7. CRC_checksum: a 4 Byte field containing a 32 bit CRC checksum of
91
- # the page (including header with zero CRC field and page content).
92
- # The generator polynomial is 0x04c11db7.
93
-
94
- # 8. number_page_segments: 1 Byte giving the number of segment entries
95
- # encoded in the segment table.
96
-
97
- # 9. segment_table: number_page_segments Bytes containing the lacing
98
- # values of all segments in this page. Each Byte contains one
99
- # lacing value.
100
- class Page
101
- HEADER_SIZE = 27
102
- HEADER_FORMAT = 'A4CxQx12C'
103
-
104
- attr_reader :segments, :granule_position
105
-
106
- def initialize(file_io)
107
- header_content = file_io.read(HEADER_SIZE)
108
- @capture_pattern, @version, @granule_position, page_segments = header_content.unpack(HEADER_FORMAT) if header_content.size >= HEADER_SIZE
109
-
110
- return unless valid?
111
-
112
- segment_table = file_io.read(page_segments).unpack('C' * page_segments)
113
- @segments = segment_table.map { |segment_length| file_io.read(segment_length) }
114
- end
115
-
116
- def valid?
117
- @capture_pattern == 'OggS' && @version == 0
118
- end
119
- end
120
- end
121
- end