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.
Files changed (64) hide show
  1. data/.gitignore +20 -0
  2. data/.rspec +3 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +181 -0
  8. data/Rakefile +6 -0
  9. data/docs/id3v2.4.0-structure.txt +731 -0
  10. data/docs/lame-3.99.5.h +1323 -0
  11. data/lame.gemspec +28 -0
  12. data/lib/lame.rb +31 -0
  13. data/lib/lame/buffer.rb +21 -0
  14. data/lib/lame/configuration.rb +228 -0
  15. data/lib/lame/decoder.rb +38 -0
  16. data/lib/lame/decoding/decoded_frame.rb +25 -0
  17. data/lib/lame/decoding/id3_tag_parser.rb +53 -0
  18. data/lib/lame/decoding/mp3_data_header_parser.rb +64 -0
  19. data/lib/lame/decoding/mpeg_audio_frame_finder.rb +46 -0
  20. data/lib/lame/decoding/mpeg_audio_frame_matcher.rb +98 -0
  21. data/lib/lame/decoding/single_frame_decoder.rb +51 -0
  22. data/lib/lame/decoding/stream_decoder.rb +37 -0
  23. data/lib/lame/delegation.rb +68 -0
  24. data/lib/lame/encoder.rb +73 -0
  25. data/lib/lame/encoding/encode_short_buffer.rb +26 -0
  26. data/lib/lame/encoding/flusher.rb +22 -0
  27. data/lib/lame/encoding/id3.rb +46 -0
  28. data/lib/lame/encoding/vbr_info.rb +22 -0
  29. data/lib/lame/error.rb +13 -0
  30. data/lib/lame/ffi.rb +20 -0
  31. data/lib/lame/ffi/decode_flags.rb +19 -0
  32. data/lib/lame/ffi/enums.rb +75 -0
  33. data/lib/lame/ffi/functions.rb +272 -0
  34. data/lib/lame/ffi/global_flags.rb +20 -0
  35. data/lib/lame/ffi/mp3_data.rb +37 -0
  36. data/lib/lame/ffi/version.rb +17 -0
  37. data/lib/lame/version.rb +3 -0
  38. data/spec/buffer_spec.rb +26 -0
  39. data/spec/configuration_spec.rb +391 -0
  40. data/spec/decoder_spec.rb +120 -0
  41. data/spec/decoding/decoded_frame_spec.rb +44 -0
  42. data/spec/decoding/id3_tag_parser_spec.rb +54 -0
  43. data/spec/decoding/mp3_data_header_parser_spec.rb +95 -0
  44. data/spec/decoding/mpeg_audio_frame_finder_spec.rb +50 -0
  45. data/spec/decoding/mpeg_audio_frame_matcher_spec.rb +179 -0
  46. data/spec/decoding/single_frame_decoder_spec.rb +104 -0
  47. data/spec/decoding/stream_decoder_spec.rb +43 -0
  48. data/spec/delegation_spec.rb +146 -0
  49. data/spec/encoder_spec.rb +279 -0
  50. data/spec/encoding/encode_short_buffer_spec.rb +72 -0
  51. data/spec/encoding/flusher_spec.rb +47 -0
  52. data/spec/encoding/id3_spec.rb +106 -0
  53. data/spec/encoding/vbr_info_spec.rb +47 -0
  54. data/spec/ffi/decode_flags_spec.rb +16 -0
  55. data/spec/ffi/encoding_spec.rb +223 -0
  56. data/spec/ffi/global_flags_spec.rb +542 -0
  57. data/spec/ffi/id3tag_spec.rb +135 -0
  58. data/spec/ffi/mp3_data_spec.rb +26 -0
  59. data/spec/files/dies-irae.wav +0 -0
  60. data/spec/integration/decoding_spec.rb +179 -0
  61. data/spec/integration/encoding_spec.rb +126 -0
  62. data/spec/integration/id3_tags_spec.rb +96 -0
  63. data/spec/spec_helper.rb +175 -0
  64. metadata +254 -0
@@ -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
@@ -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
@@ -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
@@ -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