carrierwave-audio 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/carrierwave/audio.rb +18 -72
- data/lib/carrierwave/audio/processor.rb +226 -0
- data/lib/carrierwave/audio/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7be11dce3db2c4671534890d709b8c722552194b
|
4
|
+
data.tar.gz: 982706f2ba999761581f510862c7bb9da9a594db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9aa03b8e0299a9c7862bda92ee5cee76919c0e57fe9a55643e7117c37ae99bc4313cbf8a0ac8c6fdb3aeb76ba87c45e708831fa516163d3303321198edb58b26
|
7
|
+
data.tar.gz: 6e69134f3fc3437b1cdca8eb2eb66ef1fe680d658a1a3afddb171845a079a04414bfc39b0e8f10e33017375f71ffe59eea15bb12a48491184cc427d9b8014827
|
data/lib/carrierwave/audio.rb
CHANGED
@@ -1,96 +1,42 @@
|
|
1
1
|
require 'carrierwave'
|
2
|
-
require '
|
3
|
-
require 'soxi/wrapper'
|
2
|
+
require 'carrierwave/audio/processor'
|
4
3
|
|
5
4
|
module CarrierWave
|
6
5
|
module Audio
|
7
6
|
module ClassMethods
|
8
7
|
extend ActiveSupport::Concern
|
9
8
|
|
10
|
-
def convert
|
11
|
-
process convert: [
|
9
|
+
def convert options={}
|
10
|
+
process convert: [ options ]
|
12
11
|
end
|
13
12
|
|
14
|
-
def watermark
|
15
|
-
process watermark: [
|
13
|
+
def watermark options={}
|
14
|
+
process watermark: [ options ]
|
16
15
|
end
|
17
16
|
end
|
18
17
|
|
19
|
-
def convert
|
18
|
+
def convert options={}
|
20
19
|
cache_stored_file! if !cached?
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
convert_file(current_path, input_options, tmp_path, default_output_options(format).merge(output_options))
|
27
|
-
File.rename tmp_path, current_path
|
28
|
-
set_content_type format
|
20
|
+
|
21
|
+
audio_filename = Processor.convert(current_path, options)
|
22
|
+
extension = File.extname(audio_filename)
|
23
|
+
File.rename audio_filename, current_path
|
24
|
+
set_content_type extension
|
29
25
|
end
|
30
26
|
|
31
|
-
def watermark
|
27
|
+
def watermark options={}
|
32
28
|
cache_stored_file! if !cached?
|
33
|
-
format = sanitized_format(output_format)
|
34
|
-
ext = File.extname(current_path)
|
35
|
-
watermark_ext = File.extname(watermark_file_path)
|
36
|
-
input_options = { type: ext.gsub(/\./, '') }
|
37
|
-
watermark_options = { type: watermark_ext.gsub(/\./, '') }
|
38
|
-
current_filename_without_extension = File.basename(current_path, ext)
|
39
|
-
|
40
|
-
# Normalize file to -6dB
|
41
|
-
normalized_tmp_path = File.join File.dirname(current_path), "tmp_norm_#{current_filename_without_extension}_#{Time.current.to_i}.#{input_options[:type]}"
|
42
|
-
convert_file(current_path, input_options, normalized_tmp_path, default_output_options(input_options[:type]), { gain: "-n -6" })
|
43
29
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
combiner.add_input watermark_file_path, watermark_options
|
49
|
-
combiner.set_output final_tmp_path, default_output_options(format).merge(output_options)
|
50
|
-
combiner.set_effects({ trim: "0 #{Soxi::Wrapper.file(current_path).seconds}", gain: "-n" })
|
51
|
-
combiner.run
|
52
|
-
File.rename final_tmp_path, current_path
|
53
|
-
set_content_type format
|
30
|
+
audio_filename = Processor.watermark(current_path, options)
|
31
|
+
extension = File.extname(audio_filename)
|
32
|
+
File.rename audio_filename, current_path
|
33
|
+
set_content_type extension
|
54
34
|
end
|
55
35
|
|
56
36
|
private
|
57
37
|
|
58
|
-
def
|
59
|
-
|
60
|
-
converter.add_input input_file_path, input_options
|
61
|
-
converter.set_output output_file_path, output_options
|
62
|
-
converter.set_effects fx
|
63
|
-
converter.run
|
64
|
-
end
|
65
|
-
|
66
|
-
def sanitized_format format
|
67
|
-
supported_formats = [:mp3]
|
68
|
-
if supported_formats.include?(format.to_sym)
|
69
|
-
format.to_s
|
70
|
-
else
|
71
|
-
raise CarrierWave::ProcessingError.new("Unsupported audio format #{format}. Only conversion to #{supported_formats.to_sentence} allowed.")
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def default_output_options format
|
76
|
-
if format.to_sym == :mp3
|
77
|
-
{
|
78
|
-
type: format.to_s,
|
79
|
-
rate: 44100,
|
80
|
-
channels: 2,
|
81
|
-
compression: 128
|
82
|
-
}
|
83
|
-
else
|
84
|
-
{
|
85
|
-
type: format.to_s,
|
86
|
-
rate: 44100,
|
87
|
-
channels: 2
|
88
|
-
}
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def set_content_type format
|
93
|
-
case format.to_sym
|
38
|
+
def set_content_type extension
|
39
|
+
case extension.to_sym
|
94
40
|
when :mp3
|
95
41
|
self.file.instance_variable_set(:@content_type, "audio/mpeg3")
|
96
42
|
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'ruby-sox'
|
2
|
+
require 'soxi/wrapper'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module CarrierWave
|
6
|
+
module Audio
|
7
|
+
class Processor
|
8
|
+
DefaultConvertOptions = {
|
9
|
+
output_format: :mp3,
|
10
|
+
logger: nil
|
11
|
+
}
|
12
|
+
DefaultWatermarkOptions = {
|
13
|
+
output_format: :mp3,
|
14
|
+
logger: nil
|
15
|
+
}
|
16
|
+
|
17
|
+
# Scope these under Processor so you can catch the ones generated by just this
|
18
|
+
# class.
|
19
|
+
class RuntimeError < ::RuntimeError;end;
|
20
|
+
class ArgumentError < ::ArgumentError;end;
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# Convert an audio file at the given filename with the given options.
|
24
|
+
#
|
25
|
+
# Available options (all optional) are:
|
26
|
+
#
|
27
|
+
# :output_format => Output file format
|
28
|
+
#
|
29
|
+
# Currently only :mp3 supported
|
30
|
+
# Default is :mp3.
|
31
|
+
#
|
32
|
+
# :logger => IOStream to log progress to.
|
33
|
+
#
|
34
|
+
# Example:
|
35
|
+
# CarrierWave::Audio::Processor.convert("Kickstart My Heart.wav")
|
36
|
+
# CarrierWave::Audio::Processor.generate("Kickstart My Heart.wav", :output_format => :wav)
|
37
|
+
#
|
38
|
+
def convert(source, options={})
|
39
|
+
options = DefaultConvertOptions.merge(options)
|
40
|
+
format = sanitized_format(options[:output_format])
|
41
|
+
|
42
|
+
@log = Log.new(options[:logger])
|
43
|
+
@log.start!
|
44
|
+
|
45
|
+
ext = File.extname(source)
|
46
|
+
input_options = { type: ext.gsub(/\./, '') }
|
47
|
+
final_filename = tmp_filename(source: source, format: format)
|
48
|
+
@log.timed("\nConverting...") do
|
49
|
+
convert_file(
|
50
|
+
input_file_path: source,
|
51
|
+
input_options: input_options,
|
52
|
+
output_file_path: final_filename,
|
53
|
+
output_options: output_options_for_format(format)
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
@log.done!("Converted audio file '#{final_filename}'")
|
58
|
+
|
59
|
+
final_filename
|
60
|
+
end
|
61
|
+
|
62
|
+
# Watermark an audio file at the given filename with the given options.
|
63
|
+
#
|
64
|
+
# Available options are:
|
65
|
+
#
|
66
|
+
# :watermark_file => (REQUIRED) Watermark audio file path
|
67
|
+
#
|
68
|
+
# :output_format => (Optional) Output file format
|
69
|
+
#
|
70
|
+
# Currently only :mp3 supported
|
71
|
+
# Default is :mp3.
|
72
|
+
#
|
73
|
+
# :logger => (Optional) IOStream to log progress to.
|
74
|
+
#
|
75
|
+
# Example:
|
76
|
+
# CarrierWave::Audio::Processor.watermark("Kickstart My Heart.wav", { watermark_file: "#{Rails.root}/db/watermark.mp3" })
|
77
|
+
#
|
78
|
+
def watermark(source, options={})
|
79
|
+
options = DefaultWatermarkOptions.merge(options)
|
80
|
+
format = sanitized_format(options[:output_format])
|
81
|
+
watermark_file_path = options[:watermark_file]
|
82
|
+
|
83
|
+
raise ArgumentError.new("No watermark filename given, must be a path to an existing sound file.") unless watermark_file_path
|
84
|
+
raise RuntimeError.new("Watermark file '#{watermark_file_path}' not found.") unless File.exist?(watermark_file_path)
|
85
|
+
|
86
|
+
@log = Log.new(options[:logger])
|
87
|
+
@log.start!
|
88
|
+
|
89
|
+
ext = File.extname(source)
|
90
|
+
input_options = { type: ext.gsub(/\./, '') }
|
91
|
+
normalized_filename = tmp_filename(source: source, format: format, prefix: "norm")
|
92
|
+
@log.timed("\nNormalizing file to -6dB...") do
|
93
|
+
convert_file(
|
94
|
+
input_file_path: source,
|
95
|
+
input_options: input_options,
|
96
|
+
output_file_path: normalized_filename,
|
97
|
+
output_options: output_options_for_format(input_options[:type]),
|
98
|
+
fx: { gain: "-n -6" }
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
watermark_ext = File.extname(watermark_file_path)
|
103
|
+
watermark_options = { type: watermark_ext.gsub(/\./, '') }
|
104
|
+
final_filename = tmp_filename(source: source, format: format, prefix: "wtmk")
|
105
|
+
@log.timed("\nCombining normalized file and watermark, normalizing final output to 0dB...") do
|
106
|
+
combiner = Sox::Cmd.new(combine: :mix)
|
107
|
+
combiner.add_input normalized_filename, input_options
|
108
|
+
combiner.add_input watermark_file_path, watermark_options
|
109
|
+
combiner.set_output final_filename, output_options_for_format(format)
|
110
|
+
combiner.set_effects({ trim: "0 =#{Soxi::Wrapper.file(source).seconds}", gain: "-n" })
|
111
|
+
combiner.run
|
112
|
+
end
|
113
|
+
|
114
|
+
@log.done!("Watermarked audio file '#{final_filename}'")
|
115
|
+
|
116
|
+
final_filename
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# Convert file using options
|
122
|
+
def convert_file input_file_path:, input_options:, output_file_path:, output_options:, fx: {}
|
123
|
+
converter = Sox::Cmd.new
|
124
|
+
converter.add_input input_file_path, input_options
|
125
|
+
converter.set_output output_file_path, output_options
|
126
|
+
converter.set_effects fx
|
127
|
+
converter.run
|
128
|
+
end
|
129
|
+
|
130
|
+
def sanitized_format format
|
131
|
+
supported_formats = [:mp3]
|
132
|
+
if supported_formats.include?(format.to_sym)
|
133
|
+
format.to_s
|
134
|
+
else
|
135
|
+
raise ArgumentError.new("Unsupported audio format #{format}. Only conversion to #{supported_formats.to_sentence} allowed.")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def output_options_for_format format
|
140
|
+
shared_options = {
|
141
|
+
channels: 2
|
142
|
+
}
|
143
|
+
|
144
|
+
if format.to_sym == :mp3
|
145
|
+
{
|
146
|
+
type: format.to_s,
|
147
|
+
rate: 44100,
|
148
|
+
compression: 128
|
149
|
+
}.merge(shared_options)
|
150
|
+
else
|
151
|
+
{
|
152
|
+
type: format.to_s,
|
153
|
+
rate: 44100
|
154
|
+
}.merge(shared_options)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Generate a temporary filename
|
159
|
+
def tmp_filename source:, format:, prefix: nil
|
160
|
+
ext = File.extname(source)
|
161
|
+
source_filename_without_ext = File.basename(source, ext)
|
162
|
+
File.join File.dirname(source), "tmp#{prefix.present? ? '_' + prefix : ''}_#{source_filename_without_ext}_#{Time.now.to_i}.#{format}"
|
163
|
+
end
|
164
|
+
|
165
|
+
# A simple class for logging + benchmarking, nice to have good feedback on a
|
166
|
+
# long batch operation.
|
167
|
+
#
|
168
|
+
# There's probably 10,000,000 other bechmarking classes, but writing this was
|
169
|
+
# easier than using Google.
|
170
|
+
class Log
|
171
|
+
attr_accessor :io
|
172
|
+
|
173
|
+
def initialize(io=$stdout)
|
174
|
+
@io = io
|
175
|
+
end
|
176
|
+
|
177
|
+
# Prints the given message to the log
|
178
|
+
def out(msg)
|
179
|
+
io.print(msg) if io
|
180
|
+
end
|
181
|
+
|
182
|
+
# Prints the given message to the log followed by the most recent benchmark
|
183
|
+
# (note that it calls .end! which will stop the benchmark)
|
184
|
+
def done!(msg="")
|
185
|
+
out "#{msg} (#{self.end!}s)\n"
|
186
|
+
end
|
187
|
+
|
188
|
+
# Starts a new benchmark clock and returns the index of the new clock.
|
189
|
+
#
|
190
|
+
# If .start! is called again before .end! then the time returned will be
|
191
|
+
# the elapsed time from the next call to start!, and calling .end! again
|
192
|
+
# will return the time from *this* call to start! (that is, the clocks are
|
193
|
+
# LIFO)
|
194
|
+
def start!
|
195
|
+
(@benchmarks ||= []) << Time.now
|
196
|
+
@current = @benchmarks.size - 1
|
197
|
+
end
|
198
|
+
|
199
|
+
# Returns the elapsed time from the most recently started benchmark clock
|
200
|
+
# and ends the benchmark, so that a subsequent call to .end! will return
|
201
|
+
# the elapsed time from the previously started benchmark clock.
|
202
|
+
def end!
|
203
|
+
elapsed = (Time.now - @benchmarks[@current])
|
204
|
+
@current -= 1
|
205
|
+
elapsed
|
206
|
+
end
|
207
|
+
|
208
|
+
# Returns the elapsed time from the benchmark clock w/ the given index (as
|
209
|
+
# returned from when .start! was called).
|
210
|
+
def time?(index)
|
211
|
+
Time.now - @benchmarks[index]
|
212
|
+
end
|
213
|
+
|
214
|
+
# Benchmarks the given block, printing out the given message first (if
|
215
|
+
# given).
|
216
|
+
def timed(message=nil, &block)
|
217
|
+
start!
|
218
|
+
out(message) if message
|
219
|
+
yield
|
220
|
+
done!
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: carrierwave-audio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Trevor Hinesley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: carrierwave
|
@@ -91,6 +91,7 @@ files:
|
|
91
91
|
- README.md
|
92
92
|
- lib/carrierwave-audio.rb
|
93
93
|
- lib/carrierwave/audio.rb
|
94
|
+
- lib/carrierwave/audio/processor.rb
|
94
95
|
- lib/carrierwave/audio/version.rb
|
95
96
|
homepage: https://github.com/TrevorHinesley/carrierwave-audio
|
96
97
|
licenses:
|