lame 0.0.1

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