lame 0.0.1
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.
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.rvmrc +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +181 -0
- data/Rakefile +6 -0
- data/docs/id3v2.4.0-structure.txt +731 -0
- data/docs/lame-3.99.5.h +1323 -0
- data/lame.gemspec +28 -0
- data/lib/lame.rb +31 -0
- data/lib/lame/buffer.rb +21 -0
- data/lib/lame/configuration.rb +228 -0
- data/lib/lame/decoder.rb +38 -0
- data/lib/lame/decoding/decoded_frame.rb +25 -0
- data/lib/lame/decoding/id3_tag_parser.rb +53 -0
- data/lib/lame/decoding/mp3_data_header_parser.rb +64 -0
- data/lib/lame/decoding/mpeg_audio_frame_finder.rb +46 -0
- data/lib/lame/decoding/mpeg_audio_frame_matcher.rb +98 -0
- data/lib/lame/decoding/single_frame_decoder.rb +51 -0
- data/lib/lame/decoding/stream_decoder.rb +37 -0
- data/lib/lame/delegation.rb +68 -0
- data/lib/lame/encoder.rb +73 -0
- data/lib/lame/encoding/encode_short_buffer.rb +26 -0
- data/lib/lame/encoding/flusher.rb +22 -0
- data/lib/lame/encoding/id3.rb +46 -0
- data/lib/lame/encoding/vbr_info.rb +22 -0
- data/lib/lame/error.rb +13 -0
- data/lib/lame/ffi.rb +20 -0
- data/lib/lame/ffi/decode_flags.rb +19 -0
- data/lib/lame/ffi/enums.rb +75 -0
- data/lib/lame/ffi/functions.rb +272 -0
- data/lib/lame/ffi/global_flags.rb +20 -0
- data/lib/lame/ffi/mp3_data.rb +37 -0
- data/lib/lame/ffi/version.rb +17 -0
- data/lib/lame/version.rb +3 -0
- data/spec/buffer_spec.rb +26 -0
- data/spec/configuration_spec.rb +391 -0
- data/spec/decoder_spec.rb +120 -0
- data/spec/decoding/decoded_frame_spec.rb +44 -0
- data/spec/decoding/id3_tag_parser_spec.rb +54 -0
- data/spec/decoding/mp3_data_header_parser_spec.rb +95 -0
- data/spec/decoding/mpeg_audio_frame_finder_spec.rb +50 -0
- data/spec/decoding/mpeg_audio_frame_matcher_spec.rb +179 -0
- data/spec/decoding/single_frame_decoder_spec.rb +104 -0
- data/spec/decoding/stream_decoder_spec.rb +43 -0
- data/spec/delegation_spec.rb +146 -0
- data/spec/encoder_spec.rb +279 -0
- data/spec/encoding/encode_short_buffer_spec.rb +72 -0
- data/spec/encoding/flusher_spec.rb +47 -0
- data/spec/encoding/id3_spec.rb +106 -0
- data/spec/encoding/vbr_info_spec.rb +47 -0
- data/spec/ffi/decode_flags_spec.rb +16 -0
- data/spec/ffi/encoding_spec.rb +223 -0
- data/spec/ffi/global_flags_spec.rb +542 -0
- data/spec/ffi/id3tag_spec.rb +135 -0
- data/spec/ffi/mp3_data_spec.rb +26 -0
- data/spec/files/dies-irae.wav +0 -0
- data/spec/integration/decoding_spec.rb +179 -0
- data/spec/integration/encoding_spec.rb +126 -0
- data/spec/integration/id3_tags_spec.rb +96 -0
- data/spec/spec_helper.rb +175 -0
- metadata +254 -0
data/lame.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lame/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "lame"
|
8
|
+
gem.version = LAME::VERSION
|
9
|
+
gem.authors = ["Roel van Dijk"]
|
10
|
+
gem.email = ["roel@rustradio.org"]
|
11
|
+
gem.description = %q{FFI powered library for the LAME MP3 encoder.}
|
12
|
+
gem.summary = %q{Easily encode MP3 files using the LAME MP3 encoder.}
|
13
|
+
gem.homepage = "http://github.com/rdvdijk/lame"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "ffi"
|
21
|
+
|
22
|
+
gem.add_development_dependency "rake"
|
23
|
+
gem.add_development_dependency "rspec"
|
24
|
+
gem.add_development_dependency "pry"
|
25
|
+
gem.add_development_dependency "wavefile"
|
26
|
+
gem.add_development_dependency "ruby-mp3info"
|
27
|
+
gem.add_development_dependency "coveralls"
|
28
|
+
end
|
data/lib/lame.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "ffi"
|
2
|
+
require "lame/version"
|
3
|
+
require "lame/ffi"
|
4
|
+
require "lame/error"
|
5
|
+
require "lame/delegation"
|
6
|
+
require "lame/configuration"
|
7
|
+
require "lame/buffer"
|
8
|
+
|
9
|
+
require "lame/encoding/encode_short_buffer"
|
10
|
+
require "lame/encoding/flusher"
|
11
|
+
require "lame/encoding/id3"
|
12
|
+
require "lame/encoding/vbr_info"
|
13
|
+
require "lame/encoder"
|
14
|
+
|
15
|
+
require "lame/decoding/id3_tag_parser"
|
16
|
+
require "lame/decoding/mp3_data_header_parser"
|
17
|
+
require "lame/decoding/mpeg_audio_frame_matcher"
|
18
|
+
require "lame/decoding/mpeg_audio_frame_finder"
|
19
|
+
require "lame/decoding/single_frame_decoder"
|
20
|
+
require "lame/decoding/stream_decoder"
|
21
|
+
require "lame/decoding/decoded_frame"
|
22
|
+
require "lame/decoder"
|
23
|
+
|
24
|
+
module LAME
|
25
|
+
|
26
|
+
extend ::FFI::Library
|
27
|
+
ffi_lib "libmp3lame"
|
28
|
+
|
29
|
+
include FFI
|
30
|
+
|
31
|
+
end
|
data/lib/lame/buffer.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module LAME
|
2
|
+
class Buffer
|
3
|
+
|
4
|
+
def self.create(type, input)
|
5
|
+
buffer = ::FFI::MemoryPointer.new(type, input.size)
|
6
|
+
buffer.put_array_of_short(0, input)
|
7
|
+
buffer
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.create_uchar(input)
|
11
|
+
buffer = ::FFI::MemoryPointer.new(:uchar, input.size)
|
12
|
+
buffer.put_array_of_uchar(0, input.bytes.to_a)
|
13
|
+
buffer
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.create_empty(type, size)
|
17
|
+
::FFI::MemoryPointer.new(type, size)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
module LAME
|
2
|
+
class ConfigurationBase
|
3
|
+
extend Delegation
|
4
|
+
|
5
|
+
attr_reader :global_flags
|
6
|
+
|
7
|
+
def initialize(global_flags)
|
8
|
+
@global_flags = global_flags
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def boolean_to_int(value)
|
14
|
+
Delegation::TypeConvertor.convert(value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Configuration < ConfigurationBase
|
19
|
+
|
20
|
+
delegate_alias_to_lame :number_of_samples => :num_samples,
|
21
|
+
:number_of_channels => :num_channels,
|
22
|
+
:input_samplerate => :in_samplerate,
|
23
|
+
:output_samplerate => :out_samplerate,
|
24
|
+
:force_mid_side => :force_ms,
|
25
|
+
:replay_gain => :findReplayGain,
|
26
|
+
:bitrate => :brate,
|
27
|
+
:strict_iso => :strict_ISO,
|
28
|
+
:allow_different_block_types => :allow_diff_short,
|
29
|
+
:temporal_masking => :useTemporal,
|
30
|
+
:inter_channel_ratio => :interChRatio,
|
31
|
+
:disable_short_blocks => :no_short_blocks
|
32
|
+
|
33
|
+
delegate_to_lame :scale, :scale_left, :scale_right, :analysis,
|
34
|
+
:decode_only, :quality, :mode, :free_format, :decode_on_the_fly,
|
35
|
+
:preset, :copyright, :original, :error_protection, :extension,
|
36
|
+
:force_short_blocks, :emphasis
|
37
|
+
|
38
|
+
def apply!
|
39
|
+
init_return = LAME.lame_init_params(global_flags)
|
40
|
+
if init_return == -1
|
41
|
+
raise ConfigurationError
|
42
|
+
else
|
43
|
+
@applied = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def applied?
|
48
|
+
@applied
|
49
|
+
end
|
50
|
+
|
51
|
+
def framesize
|
52
|
+
raise ConfigurationError unless applied?
|
53
|
+
LAME.lame_get_framesize(global_flags)
|
54
|
+
end
|
55
|
+
|
56
|
+
def output_buffer_size
|
57
|
+
((framesize * 1.25) + 7200).ceil
|
58
|
+
end
|
59
|
+
|
60
|
+
def asm_optimization
|
61
|
+
@asm_optimization ||= AsmOptimization.new(global_flags)
|
62
|
+
end
|
63
|
+
|
64
|
+
def id3
|
65
|
+
@id3 ||= Id3.new(global_flags)
|
66
|
+
end
|
67
|
+
|
68
|
+
def quantization
|
69
|
+
@quantization ||= Quantization.new(global_flags)
|
70
|
+
end
|
71
|
+
|
72
|
+
def vbr
|
73
|
+
@vbr ||= VBR.new(global_flags)
|
74
|
+
end
|
75
|
+
|
76
|
+
def filtering
|
77
|
+
@filtering ||= Filtering.new(global_flags)
|
78
|
+
end
|
79
|
+
|
80
|
+
def psycho_acoustics
|
81
|
+
@psycho_acoustics ||= PsychoAcoustics.new(global_flags)
|
82
|
+
end
|
83
|
+
|
84
|
+
class AsmOptimization < ConfigurationBase
|
85
|
+
def mmx=(value)
|
86
|
+
LAME.lame_set_asm_optimizations(global_flags, :MMX, boolean_to_int(value))
|
87
|
+
end
|
88
|
+
|
89
|
+
def amd_3dnow=(value)
|
90
|
+
LAME.lame_set_asm_optimizations(global_flags, :AMD_3DNOW, boolean_to_int(value))
|
91
|
+
end
|
92
|
+
|
93
|
+
def sse=(value)
|
94
|
+
LAME.lame_set_asm_optimizations(global_flags, :SSE, boolean_to_int(value))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class Id3 < ConfigurationBase
|
99
|
+
|
100
|
+
delegate_alias_to_lame :write_automatic => :write_id3tag_automatic
|
101
|
+
|
102
|
+
delegate_id3_to_lame :title, :artist, :album, :year, :comment
|
103
|
+
|
104
|
+
def initialize(global_flags)
|
105
|
+
super(global_flags)
|
106
|
+
LAME.id3tag_init(global_flags)
|
107
|
+
end
|
108
|
+
|
109
|
+
def v2=(value)
|
110
|
+
LAME.id3tag_add_v2(global_flags) if value
|
111
|
+
end
|
112
|
+
|
113
|
+
def v1_only=(value)
|
114
|
+
LAME.id3tag_v1_only(global_flags) if value
|
115
|
+
end
|
116
|
+
|
117
|
+
def v2_only=(value)
|
118
|
+
LAME.id3tag_v2_only(global_flags) if value
|
119
|
+
end
|
120
|
+
|
121
|
+
def v1_space=(value)
|
122
|
+
LAME.id3tag_space_v1(global_flags) if value
|
123
|
+
end
|
124
|
+
|
125
|
+
def v2_padding=(value)
|
126
|
+
LAME.id3tag_pad_v2(global_flags) if value
|
127
|
+
end
|
128
|
+
|
129
|
+
def v2_padding_size=(size)
|
130
|
+
LAME.id3tag_set_pad(global_flags, size)
|
131
|
+
end
|
132
|
+
|
133
|
+
def track=(value)
|
134
|
+
LAME.id3tag_set_track(global_flags, value)
|
135
|
+
end
|
136
|
+
|
137
|
+
def genre=(value)
|
138
|
+
genre_id = find_genre_id_by_name(value)
|
139
|
+
genre_id_string = ::FFI::MemoryPointer.from_string(genre_id.to_s)
|
140
|
+
|
141
|
+
LAME.id3tag_set_genre(global_flags, genre_id_string)
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def find_genre_id_by_name(name)
|
147
|
+
genres[name] || name
|
148
|
+
end
|
149
|
+
|
150
|
+
def genres
|
151
|
+
@genres ||= begin
|
152
|
+
genres = {}
|
153
|
+
|
154
|
+
genre_collector_callback = ::FFI::Function.new(:void, [:int, :string, :pointer]) do |id, name, _|
|
155
|
+
genres[name] = id
|
156
|
+
end
|
157
|
+
LAME.id3tag_genre_list(genre_collector_callback, nil)
|
158
|
+
|
159
|
+
genres
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class Quantization < ConfigurationBase
|
165
|
+
delegate_alias_to_lame :comp => :quant_comp,
|
166
|
+
:comp_short => :quant_comp_short,
|
167
|
+
:experimental_x => :experimentalX,
|
168
|
+
:experimental_y => :experimentalY,
|
169
|
+
:experimental_z => :experimentalZ,
|
170
|
+
:naoki => :exp_nspsytune
|
171
|
+
|
172
|
+
delegate_to_lame :msfix
|
173
|
+
|
174
|
+
def reservoir=(value)
|
175
|
+
LAME.lame_set_disable_reservoir(global_flags, boolean_to_int(!value))
|
176
|
+
end
|
177
|
+
|
178
|
+
def reservoir
|
179
|
+
LAME.lame_get_disable_reservoir(global_flags)
|
180
|
+
end
|
181
|
+
|
182
|
+
def reservoir?
|
183
|
+
Delegation::TypeConvertor.convert_return(reservoir)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class VBR < ConfigurationBase
|
188
|
+
delegate_alias_to_lame :write_tag => :bWriteVbrTag,
|
189
|
+
:mode => :VBR,
|
190
|
+
:q => :VBR_q,
|
191
|
+
:quality => :VBR_quality,
|
192
|
+
:mean_bitrate => :VBR_mean_bitrate_kbps,
|
193
|
+
:min_bitrate => :VBR_min_bitrate_kbps,
|
194
|
+
:max_bitrate => :VBR_max_bitrate_kbps,
|
195
|
+
:enforce_min_bitrate => :VBR_hard_min
|
196
|
+
end
|
197
|
+
|
198
|
+
class Filtering < ConfigurationBase
|
199
|
+
delegate_alias_to_lame :low_pass_frequency => :lowpassfreq,
|
200
|
+
:low_pass_width => :lowpasswidth,
|
201
|
+
:high_pass_frequency => :highpassfreq,
|
202
|
+
:high_pass_width => :highpasswidth
|
203
|
+
end
|
204
|
+
|
205
|
+
class PsychoAcoustics < ConfigurationBase
|
206
|
+
delegate_alias_to_lame :ath_only => :ATHonly,
|
207
|
+
:ath_short => :ATHshort,
|
208
|
+
:ath_type => :ATHtype,
|
209
|
+
:ath_lower => :ATHlower
|
210
|
+
|
211
|
+
delegate_to_lame :athaa_type, :athaa_sensitivity
|
212
|
+
|
213
|
+
def ath=(value)
|
214
|
+
LAME.lame_set_noATH(global_flags, boolean_to_int(!value))
|
215
|
+
end
|
216
|
+
|
217
|
+
def ath
|
218
|
+
LAME.lame_get_noATH(global_flags)
|
219
|
+
end
|
220
|
+
|
221
|
+
def ath?
|
222
|
+
!Delegation::TypeConvertor.convert_return(ath)
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|
data/lib/lame/decoder.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module LAME
|
2
|
+
class Decoder
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_reader :decode_flags, :mp3_file, :mp3_data
|
6
|
+
|
7
|
+
def_delegators :mp3_data, :channel_mode, :sample_rate
|
8
|
+
|
9
|
+
def initialize(mp3_file)
|
10
|
+
@decode_flags = FFI::DecodeFlags.new
|
11
|
+
@mp3_file = mp3_file
|
12
|
+
|
13
|
+
skip_id3_tag
|
14
|
+
@mp3_data = parse_mp3_data
|
15
|
+
end
|
16
|
+
|
17
|
+
def each_decoded_frame
|
18
|
+
stream_decoder.each_decoded_frame do |decoded_frame|
|
19
|
+
yield decoded_frame
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def stream_decoder
|
26
|
+
@stream_decoder ||= Decoding::StreamDecoder.new(decode_flags, mp3_data, mp3_file)
|
27
|
+
end
|
28
|
+
|
29
|
+
def skip_id3_tag
|
30
|
+
Decoding::Id3TagParser.new(mp3_file).skip!
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_mp3_data
|
34
|
+
Decoding::Mp3DataHeaderParser.new(decode_flags, mp3_file).parse!
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module LAME
|
2
|
+
module Decoding
|
3
|
+
class DecodedFrame
|
4
|
+
|
5
|
+
attr_reader :left, :right
|
6
|
+
|
7
|
+
def initialize(left, right)
|
8
|
+
@left = left
|
9
|
+
@right = right
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.from_short_buffers(left_buffer, right_buffer)
|
13
|
+
left = left_buffer.read_array_of_short(left_buffer.size/2)
|
14
|
+
right = right_buffer.read_array_of_short(right_buffer.size/2)
|
15
|
+
|
16
|
+
new(left, right)
|
17
|
+
end
|
18
|
+
|
19
|
+
def samples
|
20
|
+
left.zip(right)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module LAME
|
2
|
+
module Decoding
|
3
|
+
|
4
|
+
# http://id3.org/id3v2.4.0-structure
|
5
|
+
# Section 3.1. ID3v2 header
|
6
|
+
#
|
7
|
+
# 10 bytes header:
|
8
|
+
# - "ID3"
|
9
|
+
# - "xx" version (2 bytes)
|
10
|
+
# - "x" flags (1 byte)
|
11
|
+
# - "xxxx" size (4 bytes)
|
12
|
+
class Id3TagParser
|
13
|
+
|
14
|
+
HEADER_SIZE = 10
|
15
|
+
|
16
|
+
def initialize(file)
|
17
|
+
@file = file
|
18
|
+
end
|
19
|
+
|
20
|
+
def skip!
|
21
|
+
@file.seek(0)
|
22
|
+
@file.seek(id3_length)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def id3_length
|
28
|
+
header = @file.read(HEADER_SIZE)
|
29
|
+
|
30
|
+
if id3?(header)
|
31
|
+
HEADER_SIZE + parse_id3_tag_length(header[-4..-1])
|
32
|
+
else
|
33
|
+
0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_id3_tag_length(size_bits)
|
38
|
+
b0 = size_bits[0].ord
|
39
|
+
b1 = size_bits[1].ord
|
40
|
+
b2 = size_bits[2].ord
|
41
|
+
b3 = size_bits[3].ord
|
42
|
+
|
43
|
+
# rubify this sometime:
|
44
|
+
(((((b0 << 7) + b1) << 7) + b2) << 7) + b3
|
45
|
+
end
|
46
|
+
|
47
|
+
def id3?(header)
|
48
|
+
header.start_with?("ID3")
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module LAME
|
2
|
+
module Decoding
|
3
|
+
class Mp3DataHeaderParser
|
4
|
+
|
5
|
+
SIZE = 100
|
6
|
+
|
7
|
+
def initialize(decode_flags, stream)
|
8
|
+
@decode_flags = decode_flags
|
9
|
+
@stream = stream
|
10
|
+
@mp3_data = LAME::FFI::MP3Data.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse!
|
14
|
+
find_first_mpeg_audio_frame!
|
15
|
+
parse_mp3_data_header!
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_first_mpeg_audio_frame!
|
21
|
+
MPEGAudioFrameFinder.new(@stream).find!
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_mp3_data_header!
|
25
|
+
begin
|
26
|
+
@data = @stream.read(SIZE)
|
27
|
+
parse_headers
|
28
|
+
end until parsed? || end_of_stream?
|
29
|
+
|
30
|
+
if parsed?
|
31
|
+
@mp3_data
|
32
|
+
else
|
33
|
+
raise Mp3DataHeaderNotFoundError
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_headers
|
38
|
+
return if !@data
|
39
|
+
|
40
|
+
in_buffer = LAME::Buffer.create_uchar(@data)
|
41
|
+
out_left = LAME::Buffer.create_empty(:short, 0)
|
42
|
+
out_right = LAME::Buffer.create_empty(:short, 0)
|
43
|
+
|
44
|
+
enc_delay = ::FFI::MemoryPointer.new(:int, 1)
|
45
|
+
enc_padding = ::FFI::MemoryPointer.new(:int, 1)
|
46
|
+
|
47
|
+
LAME.hip_decode1_headersB(@decode_flags,
|
48
|
+
in_buffer, @data.length,
|
49
|
+
out_left, out_right,
|
50
|
+
@mp3_data,
|
51
|
+
enc_delay, enc_padding)
|
52
|
+
end
|
53
|
+
|
54
|
+
def parsed?
|
55
|
+
@mp3_data.header_parsed?
|
56
|
+
end
|
57
|
+
|
58
|
+
def end_of_stream?
|
59
|
+
!@data
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|