youpy-scissor 0.0.19 → 0.0.20
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/README.rdoc +21 -18
- data/Rakefile +1 -2
- data/lib/scissor.rb +38 -9
- data/lib/scissor/chunk.rb +3 -101
- data/lib/scissor/loggable.rb +7 -0
- data/lib/scissor/sequence.rb +4 -7
- data/lib/scissor/writer.rb +128 -0
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -18,12 +18,12 @@ supported file format:
|
|
18
18
|
|
19
19
|
=== Archive Installation
|
20
20
|
|
21
|
-
|
21
|
+
rake install
|
22
22
|
|
23
23
|
=== Gem Installation
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
gem sources -a http://gems.github.com/
|
26
|
+
gem install youpy-scissor
|
27
27
|
|
28
28
|
== Features/Problems
|
29
29
|
|
@@ -31,27 +31,30 @@ supported file format:
|
|
31
31
|
|
32
32
|
== Synopsis
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
foo = Scissor('foo.mp3')
|
35
|
+
bar = Scissor('bar.wav')
|
36
|
+
|
37
|
+
# concat
|
38
|
+
foo + bar > 'foobar.mp3'
|
36
39
|
|
37
|
-
|
38
|
-
|
40
|
+
# slice + concat
|
41
|
+
foo[10, 1] + bar[2, 3] > 'slicefoobar.mp3'
|
39
42
|
|
40
|
-
|
41
|
-
|
43
|
+
# slice + concat + loop
|
44
|
+
(foo[10, 1] + bar[2, 3]) * 4 > 'slicefoobarloop.mp3'
|
42
45
|
|
43
|
-
|
44
|
-
|
46
|
+
# split
|
47
|
+
(Scissor('sequence.mp3') / 16).first.to_file('split.mp3')
|
45
48
|
|
46
|
-
|
47
|
-
|
49
|
+
# replace first 10 seconds with 30 seconds of silence
|
50
|
+
foo.replace(0, 10, Scissor.silence(30)).to_file('replace.mp3')
|
48
51
|
|
49
|
-
|
50
|
-
|
52
|
+
# sequence + loop
|
53
|
+
seq = Scissor.sequence('x y xyz', 0.2)
|
54
|
+
seq.apply(:x => foo, :y => Proc.new { bar }, :z => foo.reverse) * 4 > 'sequence.wav'
|
51
55
|
|
52
|
-
|
53
|
-
|
54
|
-
seq.apply(:x => foo, :y => Proc.new { bar }, :z => foo.reverse) * 4 > 'sequence.wav'
|
56
|
+
# mix
|
57
|
+
Scissor.mix([foo, bar], 'mix.mp3')
|
55
58
|
|
56
59
|
== Copyright
|
57
60
|
|
data/Rakefile
CHANGED
@@ -17,7 +17,7 @@ DESCRIPTION = "utility to chop sound files"
|
|
17
17
|
RUBYFORGE_PROJECT = "scissor"
|
18
18
|
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
19
19
|
BIN_FILES = %w( )
|
20
|
-
VERS = "0.0.
|
20
|
+
VERS = "0.0.20"
|
21
21
|
|
22
22
|
REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
23
23
|
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
@@ -87,7 +87,6 @@ task :uninstall => [:clean] do
|
|
87
87
|
sh %{sudo gem uninstall #{NAME}}
|
88
88
|
end
|
89
89
|
|
90
|
-
|
91
90
|
Rake::RDocTask.new do |rdoc|
|
92
91
|
rdoc.rdoc_dir = 'html'
|
93
92
|
rdoc.options += RDOC_OPTS
|
data/lib/scissor.rb
CHANGED
@@ -1,26 +1,55 @@
|
|
1
|
+
require 'scissor/loggable'
|
1
2
|
require 'scissor/chunk'
|
2
3
|
require 'scissor/fragment'
|
3
4
|
require 'scissor/sound_file'
|
4
5
|
require 'scissor/sequence'
|
6
|
+
require 'scissor/writer'
|
5
7
|
|
6
8
|
def Scissor(*args)
|
7
9
|
Scissor::Chunk.new(*args)
|
8
10
|
end
|
9
11
|
|
12
|
+
require 'logger'
|
13
|
+
|
10
14
|
module Scissor
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
@logger = Logger.new(STDOUT)
|
16
|
+
@logger.level = Logger::INFO
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :logger
|
15
20
|
end
|
16
21
|
|
17
|
-
def
|
18
|
-
|
22
|
+
def logger
|
23
|
+
self.class.logger
|
19
24
|
end
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
class << self
|
27
|
+
def silence(duration)
|
28
|
+
Scissor(File.dirname(__FILE__) + '/../data/silence.mp3').
|
29
|
+
slice(0, 1).
|
30
|
+
fill(duration)
|
31
|
+
end
|
32
|
+
|
33
|
+
def sequence(*args)
|
34
|
+
Scissor::Sequence.new(*args)
|
35
|
+
end
|
36
|
+
|
37
|
+
def join(scissor_array)
|
38
|
+
scissor_array.inject(Scissor()) do |m, scissor|
|
39
|
+
m + scissor
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def mix(scissor_array, filename, options = {})
|
44
|
+
writer = Scissor::Writer.new
|
45
|
+
|
46
|
+
scissor_array.each do |scissor|
|
47
|
+
writer.add_track(scissor.fragments)
|
48
|
+
end
|
49
|
+
|
50
|
+
writer.to_file(filename, options)
|
51
|
+
|
52
|
+
Scissor(filename)
|
24
53
|
end
|
25
54
|
end
|
26
55
|
end
|
data/lib/scissor/chunk.rb
CHANGED
@@ -1,23 +1,11 @@
|
|
1
1
|
require 'digest/md5'
|
2
2
|
require 'pathname'
|
3
|
-
require 'open4'
|
4
|
-
require 'logger'
|
5
|
-
require 'temp_dir'
|
6
3
|
|
7
4
|
module Scissor
|
8
5
|
class Chunk
|
9
|
-
@logger = Logger.new(STDOUT)
|
10
|
-
@logger.level = Logger::INFO
|
11
|
-
|
12
|
-
class << self
|
13
|
-
attr_accessor :logger
|
14
|
-
end
|
15
|
-
|
16
6
|
class Error < StandardError; end
|
17
|
-
class FileExists < Error; end
|
18
7
|
class EmptyFragment < Error; end
|
19
8
|
class OutOfDuration < Error; end
|
20
|
-
class CommandFailed < Error; end
|
21
9
|
|
22
10
|
attr_reader :fragments
|
23
11
|
|
@@ -170,72 +158,10 @@ module Scissor
|
|
170
158
|
end
|
171
159
|
|
172
160
|
def to_file(filename, options = {})
|
173
|
-
|
174
|
-
|
175
|
-
if @fragments.empty?
|
176
|
-
raise EmptyFragment
|
177
|
-
end
|
178
|
-
|
179
|
-
which('ecasound')
|
180
|
-
which('ffmpeg')
|
181
|
-
|
182
|
-
options = {
|
183
|
-
:overwrite => false
|
184
|
-
}.merge(options)
|
185
|
-
|
186
|
-
filename = Pathname.new(filename)
|
187
|
-
|
188
|
-
if filename.exist?
|
189
|
-
if options[:overwrite]
|
190
|
-
filename.unlink
|
191
|
-
else
|
192
|
-
raise FileExists
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
position = 0.0
|
197
|
-
|
198
|
-
TempDir.create do |dir|
|
199
|
-
tmpdir = Pathname.new(dir)
|
200
|
-
tmpfile = tmpdir + 'tmp.wav'
|
201
|
-
cmd = %w/ecasound/
|
202
|
-
|
203
|
-
@fragments.each_with_index do |fragment, index|
|
204
|
-
fragment_filename = fragment.filename
|
205
|
-
fragment_duration = fragment.duration
|
206
|
-
|
207
|
-
if !index.zero? && (index % 80).zero?
|
208
|
-
run_command(cmd.join(' '))
|
209
|
-
cmd = %w/ecasound/
|
210
|
-
end
|
211
|
-
|
212
|
-
fragment_tmpfile =
|
213
|
-
fragment_filename.extname.downcase == '.wav' ? fragment_filename :
|
214
|
-
tmpdir + (Digest::MD5.hexdigest(fragment_filename) + '.wav')
|
161
|
+
writer = Writer.new
|
215
162
|
|
216
|
-
|
217
|
-
|
218
|
-
end
|
219
|
-
|
220
|
-
cmd <<
|
221
|
-
"-a:#{index} " +
|
222
|
-
"-i:" +
|
223
|
-
(fragment.reversed? ? 'reverse,' : '') +
|
224
|
-
"select,#{fragment.start},#{fragment_duration},\"#{fragment_tmpfile}\" " +
|
225
|
-
"-o:#{tmpfile} " +
|
226
|
-
"-y:#{position}"
|
227
|
-
|
228
|
-
position += fragment_duration
|
229
|
-
end
|
230
|
-
|
231
|
-
run_command(cmd.join(' '))
|
232
|
-
|
233
|
-
if filename.extname == '.wav'
|
234
|
-
File.rename(tmpfile, filename)
|
235
|
-
else
|
236
|
-
run_command("ffmpeg -i \"#{tmpfile}\" \"#{filename}\"")
|
237
|
-
end
|
238
|
-
end
|
163
|
+
writer.add_track(@fragments)
|
164
|
+
writer.to_file(filename, options)
|
239
165
|
|
240
166
|
self.class.new(filename)
|
241
167
|
end
|
@@ -245,29 +171,5 @@ module Scissor
|
|
245
171
|
def >>(filename)
|
246
172
|
to_file(filename, :overwrite => true)
|
247
173
|
end
|
248
|
-
|
249
|
-
def which(command)
|
250
|
-
run_command("which #{command}")
|
251
|
-
end
|
252
|
-
|
253
|
-
def run_command(cmd)
|
254
|
-
logger.debug("run_command: #{cmd}")
|
255
|
-
|
256
|
-
result = ''
|
257
|
-
status = Open4.popen4(cmd) do |pid, stdin, stdout, stderr|
|
258
|
-
logger.debug(stderr.read)
|
259
|
-
result = stdout.read
|
260
|
-
end
|
261
|
-
|
262
|
-
if status.exitstatus != 0
|
263
|
-
raise CommandFailed.new(cmd)
|
264
|
-
end
|
265
|
-
|
266
|
-
return result
|
267
|
-
end
|
268
|
-
|
269
|
-
def logger
|
270
|
-
self.class.logger
|
271
|
-
end
|
272
174
|
end
|
273
175
|
end
|
data/lib/scissor/sequence.rb
CHANGED
@@ -6,9 +6,7 @@ module Scissor
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def apply(instruments)
|
9
|
-
|
10
|
-
|
11
|
-
@pattern.split(//).each do |c|
|
9
|
+
@pattern.split(//).inject(Scissor()) do |result, c|
|
12
10
|
if instruments.include?(c.to_sym)
|
13
11
|
instrument = instruments[c.to_sym]
|
14
12
|
|
@@ -17,17 +15,16 @@ module Scissor
|
|
17
15
|
end
|
18
16
|
|
19
17
|
if @duration_per_step > instrument.duration
|
20
|
-
result += instrument
|
21
|
-
result += Scissor.silence(@duration_per_step - instrument.duration)
|
18
|
+
result += instrument + Scissor.silence(@duration_per_step - instrument.duration)
|
22
19
|
else
|
23
20
|
result += instrument.slice(0, @duration_per_step)
|
24
21
|
end
|
25
22
|
else
|
26
23
|
result += Scissor.silence(@duration_per_step)
|
27
24
|
end
|
28
|
-
end
|
29
25
|
|
30
|
-
|
26
|
+
result
|
27
|
+
end
|
31
28
|
end
|
32
29
|
end
|
33
30
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'open4'
|
2
|
+
require 'temp_dir'
|
3
|
+
|
4
|
+
module Scissor
|
5
|
+
class Writer
|
6
|
+
include Loggable
|
7
|
+
|
8
|
+
class Error < StandardError; end
|
9
|
+
class FileExists < Error; end
|
10
|
+
class EmptyFragment < Error; end
|
11
|
+
class CommandFailed < Error; end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@tracks = []
|
15
|
+
|
16
|
+
which('ecasound')
|
17
|
+
which('ffmpeg')
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_track(fragments)
|
21
|
+
@tracks << fragments
|
22
|
+
end
|
23
|
+
|
24
|
+
def fragments_to_file(fragments, outfile, tmpdir)
|
25
|
+
position = 0.0
|
26
|
+
cmd = %w/ecasound/
|
27
|
+
|
28
|
+
fragments.each_with_index do |fragment, index|
|
29
|
+
fragment_filename = fragment.filename
|
30
|
+
fragment_duration = fragment.duration
|
31
|
+
|
32
|
+
if !index.zero? && (index % 80).zero?
|
33
|
+
run_command(cmd.join(' '))
|
34
|
+
cmd = %w/ecasound/
|
35
|
+
end
|
36
|
+
|
37
|
+
fragment_outfile =
|
38
|
+
fragment_filename.extname.downcase == '.wav' ? fragment_filename :
|
39
|
+
tmpdir + (Digest::MD5.hexdigest(fragment_filename) + '.wav')
|
40
|
+
|
41
|
+
unless fragment_outfile.exist?
|
42
|
+
run_command("ffmpeg -i \"#{fragment_filename}\" \"#{fragment_outfile}\"")
|
43
|
+
end
|
44
|
+
|
45
|
+
cmd <<
|
46
|
+
"-a:#{index} " +
|
47
|
+
"-i:" +
|
48
|
+
(fragment.reversed? ? 'reverse,' : '') +
|
49
|
+
"select,#{fragment.start},#{fragment_duration},\"#{fragment_outfile}\" " +
|
50
|
+
"-o:#{outfile} " +
|
51
|
+
"-y:#{position}"
|
52
|
+
|
53
|
+
position += fragment_duration
|
54
|
+
end
|
55
|
+
|
56
|
+
run_command(cmd.join(' '))
|
57
|
+
end
|
58
|
+
|
59
|
+
def mix_files(filenames, outfile)
|
60
|
+
cmd = %w/ecasound/
|
61
|
+
|
62
|
+
filenames.each_with_index do |tf, index|
|
63
|
+
cmd << "-a:#{index} -i:#{tf}"
|
64
|
+
end
|
65
|
+
|
66
|
+
cmd << "-a:all -o:#{outfile}"
|
67
|
+
run_command(cmd.join(' '))
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_file(filename, options)
|
71
|
+
filename = Pathname.new(filename)
|
72
|
+
|
73
|
+
if @tracks.flatten.empty?
|
74
|
+
raise EmptyFragment
|
75
|
+
end
|
76
|
+
|
77
|
+
options = {
|
78
|
+
:overwrite => false
|
79
|
+
}.merge(options)
|
80
|
+
|
81
|
+
if filename.exist?
|
82
|
+
if options[:overwrite]
|
83
|
+
filename.unlink
|
84
|
+
else
|
85
|
+
raise FileExists
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
TempDir.create do |dir|
|
90
|
+
tmpdir = Pathname.new(dir)
|
91
|
+
tmpfiles = []
|
92
|
+
|
93
|
+
@tracks.each_with_index do |fragments, track_index|
|
94
|
+
tmpfiles << tmpfile = tmpdir + 'track_%s.wav' % track_index.to_s
|
95
|
+
fragments_to_file(fragments, tmpfile, tmpdir)
|
96
|
+
end
|
97
|
+
|
98
|
+
mix_files(tmpfiles, final_tmpfile = tmpdir + 'tmp.wav')
|
99
|
+
|
100
|
+
if filename.extname == '.wav'
|
101
|
+
File.rename(final_tmpfile, filename)
|
102
|
+
else
|
103
|
+
run_command("ffmpeg -i \"#{final_tmpfile}\" \"#{filename}\"")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def which(command)
|
109
|
+
run_command("which #{command}")
|
110
|
+
end
|
111
|
+
|
112
|
+
def run_command(cmd)
|
113
|
+
logger.debug("run_command: #{cmd}")
|
114
|
+
|
115
|
+
result = ''
|
116
|
+
status = Open4.popen4(cmd) do |pid, stdin, stdout, stderr|
|
117
|
+
logger.debug(stderr.read)
|
118
|
+
result = stdout.read
|
119
|
+
end
|
120
|
+
|
121
|
+
if status.exitstatus != 0
|
122
|
+
raise CommandFailed.new(cmd)
|
123
|
+
end
|
124
|
+
|
125
|
+
return result
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: youpy-scissor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- youpy
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-19 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -68,8 +68,10 @@ files:
|
|
68
68
|
- lib/scissor
|
69
69
|
- lib/scissor/chunk.rb
|
70
70
|
- lib/scissor/fragment.rb
|
71
|
+
- lib/scissor/loggable.rb
|
71
72
|
- lib/scissor/sequence.rb
|
72
73
|
- lib/scissor/sound_file.rb
|
74
|
+
- lib/scissor/writer.rb
|
73
75
|
- lib/scissor.rb
|
74
76
|
- data/silence.mp3
|
75
77
|
has_rdoc: true
|