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,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- module Asf
5
- # The base unit of organization for ASF files is called the ASF object.
6
- # It consists of a 128-bit GUID for the object, a 64-bit integer object size, and the variable-length object data.
7
- # The value of the object size field is the sum of 24 bytes plus the size of the object data in bytes.
8
- # The following diagram illustrates the ASF object structure:
9
- #
10
- # 16 bytes: Object GUID
11
- # 8 bytes: Object size
12
- # variable-sized: Object data
13
- class Object
14
- HEADER_SIZE = 24
15
- HEADER_FORMAT = 'a16Q<'
16
-
17
- attr_reader :size, :guid
18
-
19
- def initialize(file_io)
20
- guid_bytes, @size = file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
21
- return unless valid?
22
-
23
- @size = @size - HEADER_SIZE
24
- @guid = Helper.byte_string_to_guid(guid_bytes)
25
- @file_io = file_io
26
- @position = file_io.pos
27
- end
28
-
29
- def valid?
30
- !@size.nil? && @size >= HEADER_SIZE
31
- end
32
-
33
- def data
34
- @file_io.seek(@position)
35
- @file_io.read(size)
36
- end
37
- end
38
- end
39
- end
@@ -1,220 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- class AsfTag < Tag
5
- HEADER_OBJECT_CONTENT_SIZE = 6
6
- HEADER_OBJECT_GUID = '75B22630-668E-11CF-A6D9-00AA0062CE6C'
7
- FILE_PROPERTIES_OBJECT_GUID = '8CABDCA1-A947-11CF-8EE4-00C00C205365'
8
- EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID = 'D2D0A440-E307-11D2-97F0-00A0C95EA850'
9
- STREAM_PROPERTIES_OBJECT_GUID = 'B7DC0791-A9B7-11CF-8EE6-00C00C205365'
10
- AUDIO_MEDIA_OBJECT_GUID = 'F8699E40-5B4D-11CF-A8FD-00805F5C442B'
11
- CONTENT_DESCRIPTION_OBJECT_GUID = '75B22633-668E-11CF-A6D9-00AA0062CE6C'
12
-
13
- EXTENDED_CONTENT_DESCRIPTOR_NAME_MAPPING = {
14
- 'WM/AlbumArtist' => :albumartist,
15
- 'WM/AlbumTitle' => :album,
16
- 'WM/Composer' => :composer,
17
- 'WM/Genre' => :genre,
18
- 'WM/PartOfSet' => :disc,
19
- 'WM/TrackNumber' => :track,
20
- 'WM/Year' => :year
21
- }
22
-
23
- private
24
- # ASF files are logically composed of three types of top-level objects:
25
- # the Header Object, the Data Object, and the Index Object(s).
26
- # The Header Object is mandatory and must be placed at the beginning of every ASF file.
27
- # Of the three top-level ASF objects, the Header Object is the only one that contains other ASF objects.
28
- # All Unicode strings in ASF uses UTF-16, little endian, and the Byte-Order Marker (BOM) character is not present.
29
- def parse
30
- header_object = Asf::Object.new(@file_io)
31
- return unless header_object.valid?
32
-
33
- total_header_object_size = header_object.size + Asf::Object::HEADER_SIZE
34
-
35
- return unless header_object.guid == HEADER_OBJECT_GUID
36
-
37
- # Header Object contains 6 bytes useless data, so skip it.
38
- @file_io.seek(HEADER_OBJECT_CONTENT_SIZE, IO::SEEK_CUR)
39
-
40
- until total_header_object_size <= @file_io.pos
41
- sub_object = Asf::Object.new(@file_io)
42
- parse_sub_object(sub_object)
43
- end
44
- end
45
-
46
- def parse_sub_object(sub_object)
47
- case sub_object.guid
48
- when FILE_PROPERTIES_OBJECT_GUID
49
- parse_file_properties_object(sub_object)
50
- when EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID
51
- parse_extended_content_description_object(sub_object)
52
- when STREAM_PROPERTIES_OBJECT_GUID
53
- parse_stream_properties_object(sub_object)
54
- when CONTENT_DESCRIPTION_OBJECT_GUID
55
- parse_content_description_object(sub_object)
56
- else
57
- @file_io.seek(sub_object.size, IO::SEEK_CUR)
58
- end
59
- end
60
-
61
- # File Properties Object structure:
62
- #
63
- # Field name Field type Size (bits)
64
- #
65
- # Object ID GUID 128
66
- # Object Size QWORD 64
67
- # File ID GUID 128
68
- # File Size QWORD 64
69
- # Creation Date QWORD 64
70
- # Data Packets Count QWORD 64
71
- # Play Duration QWORD 64
72
- # Send Duration QWORD 64
73
- # Preroll QWORD 64
74
- # Flags DWORD 32
75
- # Broadcast Flag 1 (LSB)
76
- # Seekable Flag 1
77
- # Reserved 30
78
- # Minimum Data Packet Size DWORD 32
79
- # Maximum Data Packet Size DWORD 32
80
- # Maximum Bitrate DWORD 32
81
- #
82
- # Play Duration Specifies the time needed to play the file in 100-nanosecond units.
83
- # The value of this field is invalid if the Broadcast Flag bit in the Flags field is set to 1.
84
- #
85
- # Preroll Specifies the amount of time to buffer data before starting to play the file, in millisecond units.
86
- # If this value is nonzero, the Play Duration field and all of the payload Presentation Time fields have been offset by this amount.
87
- def parse_file_properties_object(object)
88
- play_duration, preroll, flags = object.data.unpack('x40Q<x8Q<b32')
89
- @duration = (play_duration / 10000000.0 - preroll / 1000.0).round if flags[0] == '0'
90
- end
91
-
92
- # Extended Content Description Object structure:
93
- #
94
- # Field name Field type Size (bits)
95
- #
96
- # Object ID GUID 128
97
- # Object Size QWORD 64
98
- # Content Descriptors Count WORD 16
99
- # Content Descriptors See text varies
100
- #
101
- #
102
- # The structure of each Content Descriptor:
103
- #
104
- # Field Name Field Type Size (bits)
105
- #
106
- # Descriptor Name Length WORD 16
107
- # Descriptor Name WCHAR varies
108
- # Descriptor Value Data Type WORD 16
109
- # Descriptor Value Length WORD 16
110
- # Descriptor Value See text varies
111
- #
112
- #
113
- # Specifies the type of data stored in the Descriptor Value field.
114
- # The types are defined in the following table.
115
- #
116
- # Value Type Descriptor value length
117
- #
118
- # 0x0000 Unicode string varies
119
- # 0x0001 BYTE array varies
120
- # 0x0002 BOOL 32
121
- # 0x0003 DWORD 32
122
- # 0x0004 QWORD 64
123
- # 0x0005 WORD 16
124
- def parse_extended_content_description_object(object)
125
- object_data = StringIO.new(object.data)
126
- descriptors_count = object_data.read(2).unpack('v').first
127
-
128
- descriptors_count.times do
129
- name_length = object_data.read(2).unpack('v').first
130
- name = Helper.encode_to_utf8(object_data.read(name_length), source_encoding: 'UTF-16LE')
131
- value_type, value_length = object_data.read(4).unpack('vv')
132
- value = object_data.read(value_length)
133
-
134
- attr_value = case value_type
135
- when 0
136
- Helper.encode_to_utf8(value, source_encoding: 'UTF-16LE')
137
- when 1
138
- value
139
- when 2, 3
140
- value.unpack('V').first
141
- when 4
142
- value.unpack('Q<').first
143
- when 5
144
- value.unpack('v').first
145
- end
146
-
147
- attr_name = EXTENDED_CONTENT_DESCRIPTOR_NAME_MAPPING[name]
148
- instance_variable_set("@#{attr_name}", attr_value) unless attr_name.nil?
149
- end
150
- end
151
-
152
- # Stream Properties Object structure:
153
- #
154
- # Field Name Field Type Size (bits)
155
- # Object ID GUID 128
156
- # Object Size QWORD 64
157
- # Stream Type GUID 128
158
- # Error Correction Type GUID 128
159
- # Time Offset QWORD 64
160
- # Type-Specific Data Length DWORD 32
161
- # Error Correction Data Length DWORD 32
162
- # Flags WORD 16
163
- # Stream Number 7 (LSB)
164
- # Reserved 8
165
- # Encrypted Content Flag 1
166
- # Reserved DWORD 32
167
- # Type-Specific Data BYTE varies
168
- # Error Correction Data BYTE varies
169
- #
170
- # Stream Type specifies the type of the stream (for example, audio, video, and so on).
171
- # Any streams with unrecognized Stream Type values should be ignored.
172
- #
173
- # Audio media type Object structure:
174
- #
175
- # Field name Field type Size (bits)
176
- #
177
- # Codec ID / Format Tag WORD 16
178
- # Number of Channels WORD 16
179
- # Samples Per Second DWORD 32
180
- # Average Number of Bytes Per Second DWORD 32
181
- # Block Alignment WORD 16
182
- # Bits Per Sample WORD 16
183
- def parse_stream_properties_object(object)
184
- object_data = StringIO.new(object.data)
185
- stream_type, type_specific_data_length = object_data.read(54).unpack('a16x24V')
186
- stream_type_guid = Helper.byte_string_to_guid(stream_type)
187
-
188
- return unless stream_type_guid == AUDIO_MEDIA_OBJECT_GUID
189
-
190
- @sample_rate, bytes_per_second = object_data.read(type_specific_data_length).unpack('x4VV')
191
- @bitrate = (bytes_per_second * 8.0 / 1000).round
192
- end
193
-
194
- # Content Description Object structure:
195
- #
196
- # Field name Field type Size (bits)
197
- #
198
- # Object ID GUID 128
199
- # Object Size QWORD 64
200
- # Title Length WORD 16
201
- # Author Length WORD 16
202
- # Copyright Length WORD 16
203
- # Description Length WORD 16
204
- # Rating Length WORD 16
205
- # Title WCHAR Varies
206
- # Author WCHAR Varies
207
- # Copyright WCHAR Varies
208
- # Description WCHAR Varies
209
- # Rating WCHAR Varies
210
- def parse_content_description_object(object)
211
- object_data = StringIO.new(object.data)
212
- title_length, author_length, copyright_length, description_length, _ = object_data.read(10).unpack('v' * 5)
213
-
214
- @title = Helper.encode_to_utf8(object_data.read(title_length), source_encoding: 'UTF-16LE')
215
- @artist = Helper.encode_to_utf8(object_data.read(author_length), source_encoding: 'UTF-16LE')
216
- object_data.seek(copyright_length, IO::SEEK_CUR)
217
- @comments.push(Helper.encode_to_utf8(object_data.read(description_length), source_encoding: 'UTF-16LE'))
218
- end
219
- end
220
- end
data/lib/wahwah/errors.rb DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- class WahWahArgumentError < ArgumentError; end
5
- class WahWahNotImplementedError < NotImplementedError; end
6
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- module Flac
5
- class Block
6
- HEADER_SIZE = 4
7
- HEADER_FORMAT = 'B*'
8
- BLOCK_TYPE_INDEX = %w(STREAMINFO PADDING APPLICATION SEEKTABLE VORBIS_COMMENT CUESHEET PICTURE)
9
-
10
- attr_reader :size, :type
11
-
12
- def initialize(file_io)
13
- # Block header structure:
14
- #
15
- # Length(bit) Meaning
16
- #
17
- # 1 Last-metadata-block flag:
18
- # '1' if this block is the last metadata block before the audio blocks, '0' otherwise.
19
- #
20
- # 7 BLOCK_TYPE
21
- # 0 : STREAMINFO
22
- # 1 : PADDING
23
- # 2 : APPLICATION
24
- # 3 : SEEKTABLE
25
- # 4 : VORBIS_COMMENT
26
- # 5 : CUESHEET
27
- # 6 : PICTURE
28
- # 7-126 : reserved
29
- # 127 : invalid, to avoid confusion with a frame sync code
30
- #
31
- # 24 Length (in bytes) of metadata to follow
32
- # (does not include the size of the METADATA_BLOCK_HEADER)
33
- header_bits = file_io.read(HEADER_SIZE).unpack(HEADER_FORMAT).first
34
-
35
- @last_flag = header_bits[0]
36
- @type = BLOCK_TYPE_INDEX[header_bits[1..7].to_i(2)]
37
- @size = header_bits[8..-1].to_i(2)
38
-
39
- @file_io = file_io
40
- @position = file_io.pos
41
- end
42
-
43
- def valid?
44
- @size > 0
45
- end
46
-
47
- def is_last?
48
- @last_flag.to_i == 1
49
- end
50
-
51
- def data
52
- @file_io.seek(@position)
53
- @file_io.read(size)
54
- end
55
- end
56
- end
57
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WahWah
4
- module Flac
5
- module StreaminfoBlock
6
- STREAMINFO_BLOCK_SIZE = 34
7
-
8
- # STREAMINFO block data structure:
9
- #
10
- # Length(bit) Meaning
11
- #
12
- # 16 The minimum block size (in samples) used in the stream.
13
- #
14
- # 16 The maximum block size (in samples) used in the stream.
15
- # (Minimum blocksize == maximum blocksize) implies a fixed-blocksize stream.
16
- #
17
- # 24 The minimum frame size (in bytes) used in the stream.
18
- # May be 0 to imply the value is not known.
19
- #
20
- # 24 The maximum frame size (in bytes) used in the stream.
21
- # May be 0 to imply the value is not known.
22
- #
23
- # 20 Sample rate in Hz. Though 20 bits are available,
24
- # the maximum sample rate is limited by the structure of frame headers to 655350Hz.
25
- # Also, a value of 0 is invalid.
26
- #
27
- # 3 (number of channels)-1. FLAC supports from 1 to 8 channels
28
- #
29
- # 5 (bits per sample)-1. FLAC supports from 4 to 32 bits per sample.
30
- # Currently the reference encoder and decoders only support up to 24 bits per sample.
31
- #
32
- # 36 Total samples in stream. 'Samples' means inter-channel sample,
33
- # i.e. one second of 44.1Khz audio will have 44100 samples regardless of the number of channels.
34
- # A value of zero here means the number of total samples is unknown.
35
- #
36
- # 128 MD5 signature of the unencoded audio data.
37
- def parse_streaminfo_block(block_data)
38
- return unless block_data.size == STREAMINFO_BLOCK_SIZE
39
-
40
- info_bits = block_data.unpack('x10B64').first
41
-
42
- @sample_rate = info_bits[0..19].to_i(2)
43
- bits_per_sample = info_bits[23..27].to_i(2) + 1
44
- total_samples = info_bits[28..-1].to_i(2)
45
-
46
- @duration = (total_samples.to_f / @sample_rate).round if @sample_rate > 0
47
- @bitrate = @sample_rate * bits_per_sample / 1000
48
- end
49
- end
50
- end
51
- end
@@ -1,84 +0,0 @@
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