lamer 0.1.2 → 1.0.0

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/lib/lamer.rb CHANGED
@@ -1,119 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lamer/version"
4
+ require_relative "lamer/error"
5
+ require_relative "lamer/ffi"
6
+ require_relative "lamer/encoder"
7
+ require_relative "lamer/decoder"
8
+
9
+ # Lamer is a Ruby wrapper around the LAME MP3 encoder library via FFI.
1
10
  class Lamer
2
-
3
- # These constants come from the LAME documentation
4
- MP3_Bitrates = [32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]
5
- Sample_Rates = [8, 11.025, 12, 16, 22.05, 24, 32, 44.1, 48]
6
- Encoding_Quality = (0..9)
7
- VBR_Quality = (0..6)
8
- Channels = {:mono => 'm', :stereo => 's', :joint => 'j', :auto => 'a', :mid_side => 'f'}
9
- Replay_Gain = {:fast => "--replaygain-fast", :accurate => "--replaygain-accurate",
10
- :none => "--noreplaygain", :clip_detect => "--clipdetect", :default => nil}
11
-
12
- attr_accessor :options, :id3_options
13
-
14
- def initialize
11
+ # Valid MP3 bitrates in kbps
12
+ MP3_BITRATES = [32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320].freeze
13
+
14
+ # Valid sample rates in kHz
15
+ SAMPLE_RATES = [8, 11.025, 12, 16, 22.05, 24, 32, 44.1, 48].freeze
16
+
17
+ # Valid encoding quality values (0 = best/slowest, 9 = worst/fastest)
18
+ ENCODING_QUALITY = (0..9).freeze
19
+
20
+ # Valid VBR quality values (0 = best, 9 = worst)
21
+ VBR_QUALITY = (0..9).freeze
22
+
23
+ # Channel mode mappings
24
+ CHANNELS = {
25
+ mono: :mono,
26
+ stereo: :stereo,
27
+ joint: :joint_stereo,
28
+ auto: :joint_stereo,
29
+ mid_side: :dual_channel
30
+ }.freeze
31
+
32
+ # Replay gain options (not directly supported in FFI, kept for API compatibility)
33
+ REPLAY_GAIN = {
34
+ fast: :fast,
35
+ accurate: :accurate,
36
+ none: :none,
37
+ clip_detect: :clip_detect,
38
+ default: nil
39
+ }.freeze
40
+
41
+ attr_accessor :options, :id3_options
42
+
43
+ def initialize(input = nil, output = nil, **opts)
15
44
  @options = {}
45
+ @id3_options = nil
46
+ @input_file = nil
47
+ @output_file = nil
48
+
49
+ input_file(input) if input
50
+ output_file(output) if output
51
+
52
+ opts.each do |key, value|
53
+ case key
54
+ when :bitrate then bitrate(value)
55
+ when :sample_rate then sample_rate(value)
56
+ when :quality then encode_quality(value)
57
+ when :vbr then vbr_quality(value)
58
+ when :mode then mode(value)
59
+ when :id3 then id3(value)
60
+ when :highpass then highpass(value)
61
+ when :lowpass then lowpass(value)
62
+ end
63
+ end
64
+
65
+ yield self if block_given?
16
66
  end
17
-
18
- def argument_list
19
- @options.delete :id3_version unless @id3_options
20
- @options.collect {|k,v| v}.join(' ')
67
+
68
+ # Convenience class method for encoding
69
+ def self.encode(input, output, **opts, &block)
70
+ encoder = new(input, output, **opts, &block)
71
+ encoder.convert!
21
72
  end
22
-
23
- def command_line
24
- raise ArgumentError, "No input file specified." unless @input_file
25
- ['lame', argument_list, @input_file, @output_file, id3_arguments].select{|x| !(x.nil? || x.empty?)}.join(' ')
73
+
74
+ # Convenience class method for decoding
75
+ def self.decode(input, output)
76
+ decoder = new
77
+ decoder.decode_mp3!
78
+ decoder.input_file(input)
79
+ decoder.output_file(output)
80
+ decoder.decode!
26
81
  end
27
-
28
- def convert!
29
- system command_line
82
+
83
+ # Set the output bitrate in kbps
84
+ def bitrate(kbps)
85
+ raise ArgumentError, "legal bitrates: #{MP3_BITRATES.join(', ')}" unless MP3_BITRATES.include?(kbps)
86
+
87
+ @options[:bitrate] = kbps
30
88
  end
31
-
32
- # methods for setting options on the encoder
33
-
89
+
90
+ # Set the output sample rate in kHz
34
91
  def sample_rate(rate)
35
- raise ArgumentError unless Sample_Rates.include? rate
36
- @options[:sample_rate] = "--resample #{rate}"
37
- end
38
-
39
- def bitrate(kbps)
40
- raise ArgumentError, "legal bitrates: #{MP3_Bitrates.join(', ')}" unless MP3_Bitrates.include? kbps
41
- @options[:bitrate] = "-b #{kbps}"
92
+ raise ArgumentError, "legal sample rates: #{SAMPLE_RATES.join(', ')}" unless SAMPLE_RATES.include?(rate)
93
+
94
+ @options[:out_samplerate] = (rate * 1000).to_i
42
95
  end
43
-
96
+
97
+ # Set encoding quality (0 = best/slowest, 9 = worst/fastest)
98
+ # Also accepts :high (2) and :fast (7) as shortcuts
44
99
  def encode_quality(quality)
45
- quality_keys = Hash.new { |h,k| k }.merge( { :high => 2, :fast => 7 } )
46
- quality = quality_keys[quality]
47
- raise ArgumentError, "legal qualities: #{Encoding_Quality.to_a.join(', ')}" unless Encoding_Quality.include? quality
48
- @options[:encode_quality] = "-q #{quality}"
100
+ quality_map = { high: 2, fast: 7 }
101
+ quality = quality_map[quality] || quality
102
+ raise ArgumentError, "legal qualities: #{ENCODING_QUALITY.to_a.join(', ')}" unless ENCODING_QUALITY.include?(quality)
103
+
104
+ @options[:quality] = quality
49
105
  end
50
-
106
+
107
+ # Set VBR quality (0 = best, 9 = worst)
51
108
  def vbr_quality(quality)
52
- raise ArgumentError, "legal qualities: #{VBR_Quality.to_a.join(', ')}" unless VBR_Quality.include? quality
53
- @options[:vbr_quality], @options[:vbr] = "-V #{quality}", "-v"
109
+ raise ArgumentError, "legal qualities: #{VBR_QUALITY.to_a.join(', ')}" unless VBR_QUALITY.include?(quality)
110
+
111
+ @options[:vbr_quality] = quality
112
+ @options[:vbr] = true
54
113
  end
55
-
114
+
115
+ # Set channel mode (:mono, :stereo, :joint, :auto, :mid_side)
56
116
  def mode(channels)
57
- @options[:mode] = Channels[channels] ? "-m #{Channels[channels]}" : nil
117
+ @options[:mode] = CHANNELS[channels]
58
118
  end
59
119
 
120
+ # Set replay gain mode (kept for API compatibility)
60
121
  def replay_gain(gain)
61
- @options[:replay_gain] = Replay_Gain[gain]
122
+ @options[:replay_gain] = REPLAY_GAIN[gain]
62
123
  end
63
124
 
64
- # options for dealing with the input and output files
65
-
125
+ # Set the input file path
66
126
  def input_file(filename)
67
127
  @input_file = filename
68
- mark_as_copy! if filename =~ /\.mp3$/
128
+ mark_as_copy! if filename =~ /\.mp3$/i
69
129
  end
70
-
130
+
131
+ # Set the output file path
71
132
  def output_file(filename)
72
133
  @output_file = filename
73
134
  end
74
135
 
75
- def decode_mp3!
76
- @options.clear
77
- @options[:decode_mp3] = "--decode"
78
- end
79
-
80
- def output_ogg!
81
- @options[:output_ogg] = "--ogg"
82
- end
83
-
136
+ # Mark input as MP3 (for re-encoding)
84
137
  def input_mp3!
85
- @options[:input_mp3] = "--mp3input"
138
+ @options[:input_mp3] = true
86
139
  mark_as_copy!
87
140
  end
88
-
141
+
142
+ # Set input as raw PCM data
89
143
  def input_raw(sample_rate, swapbytes = false)
90
- @options[:input_raw] = "-r -s #{sample_rate}#{' -x' if swapbytes}"
144
+ @options[:input_raw] = true
145
+ @options[:in_samplerate] = (sample_rate * 1000).to_i
146
+ @options[:swapbytes] = swapbytes
91
147
  end
92
-
148
+
149
+ # Mark the output as a copy (for MP3 to MP3 encoding)
93
150
  def mark_as_copy!
94
- @options[:copy] = "-o"
95
- end
96
-
97
- # id3 options
98
-
99
- def id3 options
100
- @id3_options = @id3_options ? @id3_options.merge(options) : options
101
- end
102
-
103
- def id3_arguments
104
- id3_fields = { :title => 'tt', :artist => 'ta', :album => 'tl',
105
- :year => 'ty', :comment => 'tc', :track_number => 'tn',
106
- :genre => 'tg' }
107
- return nil if @id3_options.nil? || @id3_options.empty?
108
- @id3_options.select{|k,v| id3_fields[k]}.collect {|k,v| "--#{id3_fields[k]} #{v}"}.join(' ')
109
- end
110
-
111
- def id3_version_only version
112
- @options[:id3_version] = "--id3v#{version}-only"
113
- end
114
-
151
+ @options[:copy] = true
152
+ end
153
+
154
+ # Set ID3 tag options
155
+ def id3(opts)
156
+ @id3_options = @id3_options ? @id3_options.merge(opts) : opts
157
+ end
158
+
159
+ # Set ID3 version (1 or 2 only)
160
+ def id3_version_only(version)
161
+ @id3_options ||= {}
162
+ @id3_options[:version] = version
163
+ end
164
+
165
+ # Add ID3v2 tags
115
166
  def id3_add_v2!
116
- @options[:id3_version] = "--add-id3v2"
167
+ @id3_options ||= {}
168
+ @id3_options[:add_v2] = true
169
+ end
170
+
171
+ # Set highpass filter frequency in kHz
172
+ def highpass(width)
173
+ @options[:highpass] = (width * 1000).to_i
174
+ end
175
+
176
+ # Set lowpass filter frequency in kHz
177
+ def lowpass(width)
178
+ @options[:lowpass] = (width * 1000).to_i
179
+ end
180
+
181
+ # Encode the input file to MP3
182
+ def convert!
183
+ raise ArgumentError, "No input file specified." unless @input_file
184
+
185
+ encoder = Encoder.new(@options)
186
+ encoder.apply_id3(@id3_options) if @id3_options && !@id3_options.empty?
187
+
188
+ output = @output_file || @input_file.sub(/\.[^.]+$/, ".mp3")
189
+ encoder.encode_file(@input_file, output)
190
+
191
+ true
192
+ end
193
+
194
+ # Encode PCM samples directly to MP3 data (new capability)
195
+ # left_samples and right_samples are arrays of 16-bit signed integers
196
+ def encode_buffer(left_samples, right_samples = nil)
197
+ encoder = Encoder.new(@options)
198
+ encoder.apply_id3(@id3_options) if @id3_options && !@id3_options.empty?
199
+
200
+ result = encoder.encode_short(left_samples, right_samples)
201
+ result + encoder.flush
202
+ end
203
+
204
+ # Encode interleaved PCM samples directly to MP3 data
205
+ def encode_buffer_interleaved(interleaved_samples)
206
+ encoder = Encoder.new(@options)
207
+ encoder.apply_id3(@id3_options) if @id3_options && !@id3_options.empty?
208
+
209
+ result = encoder.encode_short_interleaved(interleaved_samples)
210
+ result + encoder.flush
211
+ end
212
+
213
+ # Encode float samples directly to MP3 data (new capability)
214
+ # samples are arrays of floats in range -1.0 to 1.0
215
+ def encode_float_buffer(left_samples, right_samples = nil)
216
+ encoder = Encoder.new(@options)
217
+ encoder.apply_id3(@id3_options) if @id3_options && !@id3_options.empty?
218
+
219
+ result = encoder.encode_float(left_samples, right_samples)
220
+ result + encoder.flush
221
+ end
222
+
223
+ # Get LAME library version
224
+ def self.lame_version
225
+ FFI.get_lame_version
226
+ end
227
+
228
+ # Set decode mode for converting MP3 to WAV
229
+ def decode_mp3!
230
+ @options.clear
231
+ @options[:decode_mp3] = true
232
+ end
233
+
234
+ # Decode an MP3 file to WAV
235
+ # Must call decode_mp3! first, then input_file and output_file
236
+ def decode!
237
+ raise ArgumentError, "No input file specified." unless @input_file
238
+ raise ConfigurationError, "Call decode_mp3! before decode!" unless @options[:decode_mp3]
239
+
240
+ decoder = Decoder.new
241
+ output = @output_file || @input_file.sub(/\.mp3$/i, ".wav")
242
+ decoder.decode_file(@input_file, output)
243
+ end
244
+
245
+ # Decode MP3 data to PCM samples
246
+ def decode_buffer(mp3_data)
247
+ decoder = Decoder.new
248
+ decoder.decode_buffer(mp3_data)
117
249
  end
118
-
119
- end
250
+ end
metadata CHANGED
@@ -1,71 +1,67 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: lamer
3
- version: !ruby/object:Gem::Version
4
- version: 0.1.2
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
5
  platform: ruby
6
- authors:
7
- - Chris Anderson
6
+ authors:
8
7
  - Mauricio Gomes
9
- autorequire:
10
8
  bindir: bin
11
9
  cert_chain: []
12
-
13
- date: 2010-02-09 00:00:00 -05:00
14
- default_executable:
15
- dependencies: []
16
-
17
- description: Ruby wrapper for the LAME library. Currently wraps around the LAME executable so you can use LAME in your Ruby programs.
18
- email: mauricio@edge14.com
10
+ date: 2025-12-28 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ffi
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.15'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.15'
26
+ description: Native Ruby bindings to libmp3lame via FFI. Encode audio to MP3 directly
27
+ from Ruby without shelling out.
28
+ email:
29
+ - mauricio@edge14.com
19
30
  executables: []
20
-
21
31
  extensions: []
22
-
23
- extra_rdoc_files:
32
+ extra_rdoc_files: []
33
+ files:
24
34
  - LICENSE
25
35
  - README.md
26
- files:
27
- - LICENSE
28
- - README.md
29
- - Rakefile
30
- - VERSION
31
- - lamer.gemspec
32
36
  - lib/lamer.rb
33
- - spec/decoding_spec.rb
34
- - spec/encoding_spec.rb
35
- - spec/lamer_spec.rb
36
- - spec/output.mp3
37
- - spec/spec_helper.rb
38
- - spec/test.mp3
39
- has_rdoc: true
40
- homepage: http://github.com/mgomes/lamer
41
- licenses: []
42
-
43
- post_install_message:
44
- rdoc_options:
45
- - --charset=UTF-8
46
- require_paths:
37
+ - lib/lamer/decoder.rb
38
+ - lib/lamer/encoder.rb
39
+ - lib/lamer/error.rb
40
+ - lib/lamer/ffi.rb
41
+ - lib/lamer/ffi/enums.rb
42
+ - lib/lamer/ffi/functions.rb
43
+ - lib/lamer/ffi/library.rb
44
+ - lib/lamer/version.rb
45
+ homepage: https://github.com/mgomes/lamer
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ source_code_uri: https://github.com/mgomes/lamer
50
+ rdoc_options: []
51
+ require_paths:
47
52
  - lib
48
- required_ruby_version: !ruby/object:Gem::Requirement
49
- requirements:
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
50
55
  - - ">="
51
- - !ruby/object:Gem::Version
52
- version: "0"
53
- version:
54
- required_rubygems_version: !ruby/object:Gem::Requirement
55
- requirements:
56
+ - !ruby/object:Gem::Version
57
+ version: 3.2.0
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
56
60
  - - ">="
57
- - !ruby/object:Gem::Version
58
- version: "0"
59
- version:
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
60
63
  requirements: []
61
-
62
- rubyforge_project:
63
- rubygems_version: 1.3.5
64
- signing_key:
65
- specification_version: 3
66
- summary: Ruby wrapper for the LAME library
67
- test_files:
68
- - spec/decoding_spec.rb
69
- - spec/encoding_spec.rb
70
- - spec/lamer_spec.rb
71
- - spec/spec_helper.rb
64
+ rubygems_version: 3.6.6
65
+ specification_version: 4
66
+ summary: Ruby FFI bindings for the LAME MP3 encoder
67
+ test_files: []
data/Rakefile DELETED
@@ -1,33 +0,0 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "lamer"
8
- gem.summary = "Ruby wrapper for the LAME library"
9
- gem.description = "Ruby wrapper for the LAME library. Currently wraps around the LAME executable so you can use LAME in your Ruby programs."
10
- gem.email = "mauricio@edge14.com"
11
- gem.homepage = "http://github.com/mgomes/lamer"
12
- gem.authors = ["Chris Anderson", "Mauricio Gomes"]
13
- end
14
- Jeweler::GemcutterTasks.new
15
- rescue LoadError
16
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
- end
18
-
19
- require 'spec/rake/spectask'
20
- Spec::Rake::SpecTask.new(:spec) do |spec|
21
- spec.libs << 'lib' << 'spec'
22
- spec.spec_files = FileList['spec/**/*_spec.rb']
23
- end
24
-
25
- Spec::Rake::SpecTask.new(:rcov) do |spec|
26
- spec.libs << 'lib' << 'spec'
27
- spec.pattern = 'spec/**/*_spec.rb'
28
- spec.rcov = true
29
- end
30
-
31
- task :spec => :check_dependencies
32
-
33
- task :default => :spec
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.2
data/lamer.gemspec DELETED
@@ -1,55 +0,0 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
- # -*- encoding: utf-8 -*-
5
-
6
- Gem::Specification.new do |s|
7
- s.name = %q{lamer}
8
- s.version = "0.1.2"
9
-
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Chris Anderson", "Mauricio Gomes"]
12
- s.date = %q{2010-02-09}
13
- s.description = %q{Ruby wrapper for the LAME library. Currently wraps around the LAME executable so you can use LAME in your Ruby programs.}
14
- s.email = %q{mauricio@edge14.com}
15
- s.extra_rdoc_files = [
16
- "LICENSE",
17
- "README.md"
18
- ]
19
- s.files = [
20
- "LICENSE",
21
- "README.md",
22
- "Rakefile",
23
- "VERSION",
24
- "lamer.gemspec",
25
- "lib/lamer.rb",
26
- "spec/decoding_spec.rb",
27
- "spec/encoding_spec.rb",
28
- "spec/lamer_spec.rb",
29
- "spec/output.mp3",
30
- "spec/spec_helper.rb",
31
- "spec/test.mp3"
32
- ]
33
- s.homepage = %q{http://github.com/mgomes/lamer}
34
- s.rdoc_options = ["--charset=UTF-8"]
35
- s.require_paths = ["lib"]
36
- s.rubygems_version = %q{1.3.5}
37
- s.summary = %q{Ruby wrapper for the LAME library}
38
- s.test_files = [
39
- "spec/decoding_spec.rb",
40
- "spec/encoding_spec.rb",
41
- "spec/lamer_spec.rb",
42
- "spec/spec_helper.rb"
43
- ]
44
-
45
- if s.respond_to? :specification_version then
46
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
- s.specification_version = 3
48
-
49
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
- else
51
- end
52
- else
53
- end
54
- end
55
-
@@ -1,18 +0,0 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
2
-
3
- describe "A Lamer, used for decoding," do
4
- before(:each) do
5
- @la = Lamer.new
6
- end
7
-
8
- it "should decode mp3s" do
9
- @la.decode_mp3!
10
- @la.options[:decode_mp3].should == "--decode"
11
- end
12
-
13
- it "should decode mp3s without other options" do
14
- @la.vbr_quality 4
15
- @la.decode_mp3!
16
- @la.options.length.should == 1
17
- end
18
- end