ruby-sox 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.
- checksums.yaml +7 -0
- data/LICENSE.txt +23 -0
- data/README.markdown +114 -0
- data/lib/ruby-sox.rb +2 -0
- data/lib/sox/cmd.rb +85 -0
- data/lib/sox/combiner/base_strategy.rb +37 -0
- data/lib/sox/combiner/process_substitution_strategy.rb +78 -0
- data/lib/sox/combiner/tmp_file_strategy.rb +78 -0
- data/lib/sox/combiner.rb +73 -0
- data/lib/sox/command_builder.rb +98 -0
- data/lib/sox/file.rb +29 -0
- data/lib/sox/shell.rb +45 -0
- data/lib/sox.rb +19 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b3d0a858629f10877dee7bc1be0f707777f3215d
|
4
|
+
data.tar.gz: bae090b2b7124e2a37f15b1c9001bfa6ed79fced
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a532d4ab550278235dc139d9aa297f7494ba49b06d051a0c1e4e709cfbf78e837915f8f128b5482d6f6bbc83425020095b217f50fec6dcaf2e372c41692192e5
|
7
|
+
data.tar.gz: c428ddd99eb71e1b157d79b10c301f7a2dd320e4ed8fe332e924d0ec5e19cd6744c13ecec1142b9dd703e5cfd291d82433d01b057ba2f4f22f9f03f4c9d69bcf
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2013 TMX Credit, author Potapov Sergey
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
The audio files are distributed under Creative Commons Attribution 3.0
|
23
|
+
Unported License.
|
data/README.markdown
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# Ruby SoX
|
2
|
+
|
3
|
+
[](https://travis-ci.org/TMXCredit/ruby-sox)
|
4
|
+
|
5
|
+
A Ruby wrapper for the `sox` command line tool to process sound.
|
6
|
+
|
7
|
+
|
8
|
+
## Dependencies
|
9
|
+
|
10
|
+
* SoX
|
11
|
+
* Bash (for process substitution combiner strategy)
|
12
|
+
* Chromaprint (only to run tests)
|
13
|
+
|
14
|
+
### Debian / Ubuntu
|
15
|
+
|
16
|
+
```bash
|
17
|
+
apt-get install libsox-fmt-all sox libchromaprint-dev
|
18
|
+
```
|
19
|
+
|
20
|
+
### Mac
|
21
|
+
|
22
|
+
```bash
|
23
|
+
# One of the following
|
24
|
+
# Notes:
|
25
|
+
# * chromaprint is not available in MacPorts as of this writing
|
26
|
+
# * flac must be installed before sox so it will link during compilation
|
27
|
+
sudo port install flac sox && brew install chromaprint
|
28
|
+
brew install flac sox chromaprint
|
29
|
+
```
|
30
|
+
|
31
|
+
# Usage
|
32
|
+
|
33
|
+
Remember that it's a wrapper for `sox` to provide Ruby API.
|
34
|
+
It's assumed that you know the basics of sound computing theory and how
|
35
|
+
the `sox` tool works. Otherwise, `man sox` is your good friend.
|
36
|
+
|
37
|
+
## Sox::Cmd
|
38
|
+
|
39
|
+
Allows you to do everything that the `sox` command does.
|
40
|
+
|
41
|
+
Mix 3 files into one (ruby-sox assumes that input files have same rate and number of channels):
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# Build command
|
45
|
+
sox = Sox::Cmd.new(:combine => :mix)
|
46
|
+
.add_input("guitar1.flac")
|
47
|
+
.add_input("guitar2.flac")
|
48
|
+
.add_input("drums.flac")
|
49
|
+
.set_output("hell_rock-n-roll.mp3")
|
50
|
+
.set_effects(:rate => 44100, :channels => 2)
|
51
|
+
|
52
|
+
# Execute command
|
53
|
+
sox.run
|
54
|
+
```
|
55
|
+
|
56
|
+
## Sox::Combiner
|
57
|
+
|
58
|
+
Sox::Combiner combines files even if they have different rates or numbers
|
59
|
+
of channels. Under the hood, it converts the input files into temporary files
|
60
|
+
to have the same rate and number of channels, and then combines them.
|
61
|
+
|
62
|
+
Concatenate:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
combiner = Sox::Combiner.new('in1.mp3', 'in2.ogg', 'in3.wav', :combine => :concatenate)
|
66
|
+
combiner.write('out.mp3')
|
67
|
+
```
|
68
|
+
|
69
|
+
Mix 3 files into an MP3 with 2 channels and a rate of 1600:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
combiner = Sox::Combiner.new('in1.mp3', 'in2.ogg', 'in3.wav', :combine => :mix, :rate => 1600, :channels => 2)
|
73
|
+
combiner.write('out.mp3')
|
74
|
+
```
|
75
|
+
|
76
|
+
## Run specs
|
77
|
+
|
78
|
+
```bash
|
79
|
+
rake spec
|
80
|
+
```
|
81
|
+
|
82
|
+
## Credits
|
83
|
+
|
84
|
+
* [Sergey Potapov](https://github.com/greyblake)
|
85
|
+
|
86
|
+
## License
|
87
|
+
|
88
|
+
Copyright (c) 2013 TMX Credit, author Potapov Sergey
|
89
|
+
|
90
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
91
|
+
a copy of this software and associated documentation files (the
|
92
|
+
"Software"), to deal in the Software without restriction, including
|
93
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
94
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
95
|
+
permit persons to whom the Software is furnished to do so, subject to
|
96
|
+
the following conditions:
|
97
|
+
|
98
|
+
The above copyright notice and this permission notice shall be
|
99
|
+
included in all copies or substantial portions of the Software.
|
100
|
+
|
101
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
102
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
103
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
104
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
105
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
106
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
107
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
108
|
+
|
109
|
+
The audio files are distributed under [Creative Commons Attribution 3.0
|
110
|
+
Unported License](http://creativecommons.org/licenses/by/3.0/legalcode).
|
111
|
+
|
112
|
+
## Copyright
|
113
|
+
|
114
|
+
Copyright (c) 2013 TMX Credit.
|
data/lib/ruby-sox.rb
ADDED
data/lib/sox/cmd.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
module Sox
|
2
|
+
# Process audio files using the +sox+ shell command.
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# # Mix 3 files into one
|
6
|
+
# sox = Sox::Cmd.new(:combine => :mix)
|
7
|
+
# sox.add_input("guitar1.flac")
|
8
|
+
# sox.add_input("guitar2.flac")
|
9
|
+
# sox.add_input("drums.flac")
|
10
|
+
# sox.set_output("hell_rock-n-roll.mp3")
|
11
|
+
# sox.set_effects(:rate => 44100, :channels => 2)
|
12
|
+
# sox.run
|
13
|
+
class Cmd
|
14
|
+
include Sox::Shell
|
15
|
+
|
16
|
+
attr_reader :options, :inputs, :output, :effects
|
17
|
+
|
18
|
+
# @param options [Hash] global options for sox command
|
19
|
+
def initialize(options = {})
|
20
|
+
@options = options
|
21
|
+
@inputs = []
|
22
|
+
@effects = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Add input file with its options.
|
26
|
+
#
|
27
|
+
# @param file_path [String] path to file
|
28
|
+
# @param input_options [Hash] options for input files, see +man sox+
|
29
|
+
#
|
30
|
+
# @return [Sox::Cmd] self
|
31
|
+
def add_input(file_path, input_options = {})
|
32
|
+
@inputs << Sox::File.new(file_path, input_options)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Set output file and its options.
|
37
|
+
#
|
38
|
+
# @param file_path [String] ouput file path
|
39
|
+
# @param output_options [Hash] options for output file, see +man sox+
|
40
|
+
#
|
41
|
+
# @return [Sox::Cmd] self
|
42
|
+
def set_output(file_path, output_options = {})
|
43
|
+
@output = Sox::File.new(file_path, output_options)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Set effects on the output file. See +man sox+ section +EFFECTS+.
|
48
|
+
# It receives the effect name as a hash key and the effect arguments as
|
49
|
+
# hash values which can be a string or an array of strings. If an effect
|
50
|
+
# has no arguments just pass +true+ as the value.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# # Normalize and use 2 channels for output
|
54
|
+
# sox_cmd.set_effects(:channels => 2, :norm => true)
|
55
|
+
#
|
56
|
+
# @param effects [Hash{Symbol, String => Symbol, String, Array<String>}]
|
57
|
+
#
|
58
|
+
# @return [Sox::Cmd] self
|
59
|
+
def set_effects(effects)
|
60
|
+
@effects = effects
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set global options. See +man sox+ section +Global Options+.
|
65
|
+
#
|
66
|
+
# @param options [Hash] global options for +sox+ command
|
67
|
+
#
|
68
|
+
# @return [Sox::Cmd] self
|
69
|
+
def set_options(options)
|
70
|
+
@options = options
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Run `sox` command. Raise {Sox::Error} on fail.
|
75
|
+
#
|
76
|
+
# @return [Boolean] true in case of success
|
77
|
+
def run
|
78
|
+
raise(Sox::Error, "Output is missing, specify it with `set_output`") unless @output
|
79
|
+
raise(Sox::Error, "Inputs are missing, specify them with `add_input`") if @inputs.empty?
|
80
|
+
|
81
|
+
cmd = CommandBuilder.new(@inputs, @output, @options, @effects).build
|
82
|
+
sh(cmd)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sox
|
2
|
+
# Common parent class for combiner strategies.
|
3
|
+
class Combiner::BaseStrategy
|
4
|
+
include Sox::Shell
|
5
|
+
|
6
|
+
# @param input_files [Array<String>] input files
|
7
|
+
# @param options [Hash] see {Sox::Combiner#initialize}
|
8
|
+
def initialize(input_files, options)
|
9
|
+
@input_files = input_files
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
# Run the command, and save output in the output file.
|
14
|
+
#
|
15
|
+
# @param output_file [String]
|
16
|
+
#
|
17
|
+
# @return [void]
|
18
|
+
def write(output_file)
|
19
|
+
raise NotImplementedError, __method__
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Build effects which will be applied on final output.
|
24
|
+
#
|
25
|
+
# @return [Hash]
|
26
|
+
def output_effects
|
27
|
+
{:norm => @options[:norm]}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Build global options for +sox+ command.
|
31
|
+
#
|
32
|
+
# @return [Hash]
|
33
|
+
def output_options
|
34
|
+
{:combine => @options[:combine]}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Sox
|
2
|
+
# Combines files using process substitution to build and use mediate files.
|
3
|
+
# Process substitution is not supported by the standard shell, so we need
|
4
|
+
# to pass the final command to bash via shell.
|
5
|
+
#
|
6
|
+
# Read more about process substitution:
|
7
|
+
# http://en.wikipedia.org/wiki/Process_substitution
|
8
|
+
class Combiner::ProcessSubstitutionStrategy < Combiner::BaseStrategy
|
9
|
+
# Type of mediate files:
|
10
|
+
MEDIATE_TYPE = :sox
|
11
|
+
|
12
|
+
# Number of bits for mediate files:
|
13
|
+
MEDIATE_BITS = 32
|
14
|
+
|
15
|
+
# Encoding of mediate files:
|
16
|
+
MEDIATE_ENCODING = :signed
|
17
|
+
|
18
|
+
# Pseudo file which makes the `sox` command write to output to stdout:
|
19
|
+
SOX_PIPE = '-p'
|
20
|
+
|
21
|
+
# :nodoc:
|
22
|
+
def write(output_file)
|
23
|
+
inputs = @input_files.map { |file| build_input_file(file) }
|
24
|
+
output = Sox::File.new(output_file)
|
25
|
+
|
26
|
+
cmd = CommandBuilder.new(inputs, output, output_options, output_effects).build
|
27
|
+
bash(cmd)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Build the input file which can be used in the command builder to get
|
32
|
+
# the final output.
|
33
|
+
#
|
34
|
+
# @param file_path [String] path to input file
|
35
|
+
#
|
36
|
+
# @return [Sox::File]
|
37
|
+
def build_input_file(file_path)
|
38
|
+
Sox::File.new(build_converted_input(file_path),
|
39
|
+
:type => MEDIATE_TYPE,
|
40
|
+
:encoding => MEDIATE_ENCODING,
|
41
|
+
:bits => MEDIATE_BITS,
|
42
|
+
:channels => @options[:channels],
|
43
|
+
:rate => @options[:rate]
|
44
|
+
).tap { |file| file.escaped = true }
|
45
|
+
end
|
46
|
+
private :build_input_file
|
47
|
+
|
48
|
+
# Build the shell statement which can be used as the input file with
|
49
|
+
# the needed rate and number of channels.
|
50
|
+
#
|
51
|
+
# @param input_file [String]
|
52
|
+
#
|
53
|
+
# @return [String] shell statement which can be used as input file
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# build_converted_input("in.mp3")
|
57
|
+
# # => "<(sox in.mp3 --encoding sox --bits 32 -p rate 22100 channels 1)"
|
58
|
+
def build_converted_input(input_file)
|
59
|
+
input = Sox::File.new(input_file)
|
60
|
+
output = Sox::File.new(SOX_PIPE, :encoding => MEDIATE_ENCODING, :bits => MEDIATE_BITS)
|
61
|
+
effects = {:channels => @options[:channels], :rate => @options[:rate]}
|
62
|
+
command = CommandBuilder.new([input], output, {}, effects).build
|
63
|
+
process_substitution_wrap(command)
|
64
|
+
end
|
65
|
+
private :build_converted_input
|
66
|
+
|
67
|
+
# Wrap shell command to make its output be used in process substitution.
|
68
|
+
# See http://en.wikipedia.org/wiki/Process_substitution for more info.
|
69
|
+
#
|
70
|
+
# @param command [String] shell command
|
71
|
+
#
|
72
|
+
# @return [String]
|
73
|
+
def process_substitution_wrap(command)
|
74
|
+
"<(#{command})"
|
75
|
+
end
|
76
|
+
private :process_substitution_wrap
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Sox
|
2
|
+
# Combines files using temporary files as mediate files.
|
3
|
+
class Combiner::TmpFileStrategy < Combiner::BaseStrategy
|
4
|
+
# Type of temporary mediate files:
|
5
|
+
MEDIATE_TYPE = :raw
|
6
|
+
|
7
|
+
# Number of bits for temporary mediate files:
|
8
|
+
MEDIATE_BITS = 16
|
9
|
+
|
10
|
+
# Encoding of temporary mediate files:
|
11
|
+
MEDIATE_ENCODING = :signed
|
12
|
+
|
13
|
+
|
14
|
+
# :nodoc:
|
15
|
+
def write(output_file)
|
16
|
+
tmp_files = []
|
17
|
+
|
18
|
+
@input_files.each do |input_file|
|
19
|
+
tmp_output_file = gen_tmp_filename
|
20
|
+
tmp_files << tmp_output_file
|
21
|
+
|
22
|
+
cmd = build_convert_command(input_file, tmp_output_file)
|
23
|
+
sh(cmd)
|
24
|
+
end
|
25
|
+
|
26
|
+
cmd = build_output_command(tmp_files, output_file)
|
27
|
+
sh(cmd)
|
28
|
+
ensure
|
29
|
+
# Remove temporary files
|
30
|
+
tmp_files.each { |file| FileUtils.rm(file) if ::File.exists?(file) }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Build +sox+ command to get final output.
|
34
|
+
#
|
35
|
+
# @param input_files [Array<String>]
|
36
|
+
# @param output_file [String]
|
37
|
+
#
|
38
|
+
# @return [String] sox command
|
39
|
+
def build_output_command(input_files, output_file)
|
40
|
+
inputs = input_files.map do |path|
|
41
|
+
Sox::File.new(path,
|
42
|
+
:type => MEDIATE_TYPE,
|
43
|
+
:encoding => MEDIATE_ENCODING,
|
44
|
+
:bits => MEDIATE_BITS,
|
45
|
+
:channels => @options[:channels],
|
46
|
+
:rate => @options[:rate])
|
47
|
+
end
|
48
|
+
|
49
|
+
output = Sox::File.new(output_file)
|
50
|
+
|
51
|
+
builder = CommandBuilder.new(inputs, output, output_options, output_effects)
|
52
|
+
builder.build
|
53
|
+
end
|
54
|
+
private :build_output_command
|
55
|
+
|
56
|
+
# Build shell command which converts input file into temporary file with
|
57
|
+
# desired rate and channels.
|
58
|
+
#
|
59
|
+
# @param input_file [String] input file
|
60
|
+
# @param output_file [String] converted output with desired characteristics
|
61
|
+
#
|
62
|
+
# @return [String] shell command
|
63
|
+
def build_convert_command(input_file, output_file)
|
64
|
+
builder = CommandBuilder.new([Sox::File.new(input_file)], Sox::File.new(output_file))
|
65
|
+
builder.effects = {:channels => @options[:channels], :rate => @options[:rate]}
|
66
|
+
builder.build
|
67
|
+
end
|
68
|
+
private :build_convert_command
|
69
|
+
|
70
|
+
# Generate path to temporary file with unique name.
|
71
|
+
#
|
72
|
+
# @return [String] path to temporary file
|
73
|
+
def gen_tmp_filename
|
74
|
+
Dir::Tmpname.make_tmpname ['/tmp/ruby-sox', ".#{MEDIATE_TYPE}"], nil
|
75
|
+
end
|
76
|
+
private :gen_tmp_filename
|
77
|
+
end
|
78
|
+
end
|
data/lib/sox/combiner.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Sox
|
2
|
+
# Combines input files. Technically it calls +sox+ with +--combine+ option,
|
3
|
+
# but allows you not to care about the rates and numbers of channels in the
|
4
|
+
# input files. It converts them to the same rates/channels using temporary
|
5
|
+
# mediate files.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# # Concatenate
|
9
|
+
# combiner = Sox::Combiner.new('in1.mp3', 'in2.ogg', 'in3.wav', :combine => :concatenate)
|
10
|
+
# combiner.write('out.mp3')
|
11
|
+
class Combiner
|
12
|
+
|
13
|
+
autoload :BaseStrategy , 'sox/combiner/base_strategy'
|
14
|
+
autoload :TmpFileStrategy , 'sox/combiner/tmp_file_strategy'
|
15
|
+
autoload :ProcessSubstitutionStrategy, 'sox/combiner/process_substitution_strategy'
|
16
|
+
|
17
|
+
# Default options
|
18
|
+
DEFAULT_OPTIONS = {
|
19
|
+
# Method to be used for combining sounds. See --combine of sox tool.
|
20
|
+
:combine => :concatenate,
|
21
|
+
|
22
|
+
# Number of channels in the output file.
|
23
|
+
:channels => 1,
|
24
|
+
|
25
|
+
# Rate(samples per seconds) of the output file.
|
26
|
+
:rate => 22050,
|
27
|
+
|
28
|
+
# Apply norm effect on output.
|
29
|
+
:norm => false,
|
30
|
+
|
31
|
+
# Strategy to convert input files into files with the same rates
|
32
|
+
# and channels.
|
33
|
+
:strategy => :process_substitution
|
34
|
+
}
|
35
|
+
|
36
|
+
# Mapping of strategy names and their implementations
|
37
|
+
STRATEGIES = {
|
38
|
+
:tmp_file => TmpFileStrategy,
|
39
|
+
:process_substitution => ProcessSubstitutionStrategy
|
40
|
+
}
|
41
|
+
|
42
|
+
|
43
|
+
# @param input_files [Array<String>] input files
|
44
|
+
# @param options [Hash]
|
45
|
+
#
|
46
|
+
# @option options :combine [Symbol] value for +--combine+ sox option.
|
47
|
+
# Use underscore instead of hyphen, e.g. :mix_power.
|
48
|
+
# @option options :channels [Integer] number of channels in output file.
|
49
|
+
# @option options :rate [Integer] rate of output file
|
50
|
+
# @option options :norm [Boolean] apply +norm+ effect on output.
|
51
|
+
# @option options :strategy [Symbol] strategy to treat temporary files,
|
52
|
+
# default is :process_substitution which reduces disk IO.
|
53
|
+
def initialize(input_files, options = {})
|
54
|
+
raise(ArgumentError, "Input files are missing") if input_files.empty?
|
55
|
+
|
56
|
+
opts = DEFAULT_OPTIONS.merge(options)
|
57
|
+
strategy_name = opts.delete(:strategy)
|
58
|
+
strategy_class = STRATEGIES[strategy_name]
|
59
|
+
raise(ArgumentError, "Unknown strategy #{strategy_name.inspect}") unless strategy_class
|
60
|
+
|
61
|
+
@strategy = strategy_class.new(input_files, opts)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Run +sox+ command and write output to file.
|
65
|
+
#
|
66
|
+
# @param output_file [String] path of output file
|
67
|
+
#
|
68
|
+
# @return [void]
|
69
|
+
def write(output_file)
|
70
|
+
@strategy.write(output_file)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Sox
|
2
|
+
# Builds the +sox+ shell command from input files, an output file, options
|
3
|
+
# and effects.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# builder = Sox::CommandBuilder.new(['in1.mp3', 'in2.ogg'], 'out.wav',
|
7
|
+
# {:combine => :mix},
|
8
|
+
# {:rate => 44100, :channels => 2}
|
9
|
+
# )
|
10
|
+
# builder.build # => "sox --combine mix in1.mp3 in2.ogg out.wav rate 44100 channels 2"
|
11
|
+
class CommandBuilder
|
12
|
+
attr_accessor :input_files, :output_file, :options, :effects
|
13
|
+
|
14
|
+
# @param input_files [Array<Sox::File>]
|
15
|
+
# @param output_file [Sox::File]
|
16
|
+
# @param options [Hash{Symbol => Symbol}]
|
17
|
+
# @param effects [Hash{Symbol => Symbol}]
|
18
|
+
def initialize(input_files, output_file, options = {}, effects = {})
|
19
|
+
@input_files = input_files
|
20
|
+
@output_file = output_file
|
21
|
+
@options = options
|
22
|
+
@effects = effects
|
23
|
+
end
|
24
|
+
|
25
|
+
# Build shell command with all arguments and options.
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
def build
|
29
|
+
[ Sox::SOX_COMMAND,
|
30
|
+
build_options(@options),
|
31
|
+
build_input_files,
|
32
|
+
build_file(@output_file),
|
33
|
+
build_effects
|
34
|
+
].flatten.join(' ')
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Build input files with their options.
|
39
|
+
#
|
40
|
+
# @return [Array<String>]
|
41
|
+
def build_input_files
|
42
|
+
@input_files.map { |file| build_file(file) }
|
43
|
+
end
|
44
|
+
private :build_input_files
|
45
|
+
|
46
|
+
# Build part of SoX command which represents file(input or output).
|
47
|
+
#
|
48
|
+
# @param file [Sox::File] file
|
49
|
+
#
|
50
|
+
# @return [String]
|
51
|
+
def build_file(file)
|
52
|
+
opts = build_options(file.options)
|
53
|
+
file_path = file.escaped? ? file.path : Shellwords.escape(file.path)
|
54
|
+
[opts, file_path]
|
55
|
+
end
|
56
|
+
private :build_file
|
57
|
+
|
58
|
+
# Build options with their values (if present) to be used in shell command.
|
59
|
+
#
|
60
|
+
# @param options [Hash] options
|
61
|
+
#
|
62
|
+
# @return [Array<String>] options to be concatenated into string
|
63
|
+
def build_options(options)
|
64
|
+
options.inject([]) do |result, (opt, val)|
|
65
|
+
if val
|
66
|
+
result << "--#{shellify_opt(opt)}"
|
67
|
+
result << shellify_opt(val) if val != true
|
68
|
+
end
|
69
|
+
result
|
70
|
+
end
|
71
|
+
end
|
72
|
+
private :build_options
|
73
|
+
|
74
|
+
# Build effects with their arguments (if present) to be used in shell command.
|
75
|
+
#
|
76
|
+
# @return [Array<String>] effects to be concatenated into string
|
77
|
+
def build_effects
|
78
|
+
@effects.inject([]) do |result, (effect, val)|
|
79
|
+
if val
|
80
|
+
result << effect
|
81
|
+
result << val.to_s if val != true
|
82
|
+
end
|
83
|
+
result
|
84
|
+
end
|
85
|
+
end
|
86
|
+
private :build_effects
|
87
|
+
|
88
|
+
# Convert option or its value to shell style, separating words with "-".
|
89
|
+
#
|
90
|
+
# @param value [Symbol, String] option or value
|
91
|
+
#
|
92
|
+
# @return [String] shellified option
|
93
|
+
def shellify_opt(value)
|
94
|
+
value.to_s.gsub('_', '-')
|
95
|
+
end
|
96
|
+
private :shellify_opt
|
97
|
+
end
|
98
|
+
end
|
data/lib/sox/file.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Sox
|
2
|
+
# Represents input or output file with its options for the `sox` command.
|
3
|
+
class File
|
4
|
+
# Path to file or whatever.
|
5
|
+
attr_reader :path
|
6
|
+
|
7
|
+
# File options which will be placed right before it.
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
# True if path doesn't need to be escaped.
|
11
|
+
attr_accessor :escaped
|
12
|
+
|
13
|
+
|
14
|
+
# @param path [String] path to file
|
15
|
+
# @param options [Hash{Symbol => Symbol,String,Numeric}] file options
|
16
|
+
def initialize(path, options = {})
|
17
|
+
@path = path
|
18
|
+
@options = options
|
19
|
+
@escaped = false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Does the path need to be escaped?
|
23
|
+
#
|
24
|
+
# @return [Boolean]
|
25
|
+
def escaped?
|
26
|
+
@escaped
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/sox/shell.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sox
|
2
|
+
# Provides methods to run the command in the +/bin/sh+ and +/bin/bash+
|
3
|
+
# interpreters. Ruby's `system` method runs +/bin/sh+ which doesn't support
|
4
|
+
# the process substitution feature. So sometimes we need to use bash with
|
5
|
+
# process substitution in order to avoid disk IO operations.
|
6
|
+
#
|
7
|
+
# Also this module takes care of error handling and raises {Sox::Error} when
|
8
|
+
# a failure message is returned by the shell command.
|
9
|
+
#
|
10
|
+
# See http://en.wikipedia.org/wiki/Process_substitution
|
11
|
+
module Shell
|
12
|
+
# Path to the +bash+ interpreter:
|
13
|
+
BASH_PATH = '/bin/bash'
|
14
|
+
|
15
|
+
# Run a shell command.
|
16
|
+
#
|
17
|
+
# @param command [String] shell command to execute
|
18
|
+
#
|
19
|
+
# @return [Boolean] true in case of success
|
20
|
+
def sh(command)
|
21
|
+
_, _, err_io, thread = Open3.popen3(command)
|
22
|
+
thread.join
|
23
|
+
|
24
|
+
process_status = thread.value
|
25
|
+
if process_status.success?
|
26
|
+
true
|
27
|
+
else
|
28
|
+
raise Error, err_io.read
|
29
|
+
end
|
30
|
+
rescue Errno::ENOENT => err
|
31
|
+
msg = "#{err.message}. Do you have `#{SOX_COMMAND}' installed?"
|
32
|
+
raise Error, msg
|
33
|
+
end
|
34
|
+
|
35
|
+
# Run bash command.
|
36
|
+
#
|
37
|
+
# @param command [String] bash command to execute
|
38
|
+
#
|
39
|
+
# @return [Boolean] true in case of success
|
40
|
+
def bash(command)
|
41
|
+
bash_command = "#{BASH_PATH} -c #{Shellwords.escape(command)}"
|
42
|
+
sh(bash_command)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/sox.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'open3'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
# Wrapper around the +sox+ command line tool.
|
6
|
+
module Sox
|
7
|
+
# Basic SoX error:
|
8
|
+
class Error < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
# The SoX command:
|
12
|
+
SOX_COMMAND = 'sox'.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'sox/file'
|
16
|
+
require 'sox/shell'
|
17
|
+
require 'sox/command_builder'
|
18
|
+
require 'sox/cmd'
|
19
|
+
require 'sox/combiner'
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-sox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Potapov Sergey
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jeweler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.8.7
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.8.7
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: metric_fu
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Wrapper around sox sound tool
|
84
|
+
email: blake131313@gmail.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files:
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.markdown
|
90
|
+
files:
|
91
|
+
- LICENSE.txt
|
92
|
+
- README.markdown
|
93
|
+
- lib/ruby-sox.rb
|
94
|
+
- lib/sox.rb
|
95
|
+
- lib/sox/cmd.rb
|
96
|
+
- lib/sox/combiner.rb
|
97
|
+
- lib/sox/combiner/base_strategy.rb
|
98
|
+
- lib/sox/combiner/process_substitution_strategy.rb
|
99
|
+
- lib/sox/combiner/tmp_file_strategy.rb
|
100
|
+
- lib/sox/command_builder.rb
|
101
|
+
- lib/sox/file.rb
|
102
|
+
- lib/sox/shell.rb
|
103
|
+
homepage: http://github.com/greyblake/ruby-sox
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 2.0.3
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: Wrapper around sox sound tool
|
127
|
+
test_files: []
|
128
|
+
has_rdoc:
|