wahwah 0.1.0.pre.test → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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