scissor 0.0.26 → 0.1.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/README.rdoc CHANGED
@@ -15,6 +15,7 @@ supported file format:
15
15
 
16
16
  * {FFmpeg}[http://ffmpeg.mplayerhq.hu/]
17
17
  * {Ecasound}[http://www.eca.cx/ecasound/] 2.5.0 or higher
18
+ * {Rubber Band}[http://breakfastquay.com/rubberband/]
18
19
 
19
20
  === Archive Installation
20
21
 
@@ -67,6 +68,10 @@ supported file format:
67
68
 
68
69
  foo.pitch(50)
69
70
 
71
+ === 200% time stretch without changing the pitch
72
+
73
+ foo.stretch(200)
74
+
70
75
  === mix
71
76
 
72
77
  Scissor.mix([foo, bar], 'mix.mp3')
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ gem 'rspec', '< 2'
2
3
  require 'rake'
3
4
  require 'rake/clean'
4
5
  require 'spec/rake/spectask'
@@ -17,7 +18,7 @@ DESCRIPTION = "utility to chop sound files"
17
18
  RUBYFORGE_PROJECT = "scissor"
18
19
  HOMEPATH = "http://github.com/youpy/scissor"
19
20
  BIN_FILES = %w( )
20
- VERS = "0.0.26"
21
+ VERS = "0.1.0"
21
22
 
22
23
  REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
23
24
  CLEAN.include ['**/.*.sw?', '*.gem', '.config']
data/lib/scissor/chunk.rb CHANGED
@@ -147,7 +147,7 @@ module Scissor
147
147
  new_instance.add_fragment(Fragment.new(
148
148
  fragment.filename,
149
149
  fragment.start,
150
- fragment.true_duration,
150
+ fragment.original_duration,
151
151
  !fragment.reversed?,
152
152
  fragment.pitch))
153
153
  end
@@ -155,21 +155,27 @@ module Scissor
155
155
  new_instance
156
156
  end
157
157
 
158
- def pitch(pitch)
158
+ def pitch(pitch, stretch = false)
159
159
  new_instance = self.class.new
160
160
 
161
161
  @fragments.each do |fragment|
162
162
  new_instance.add_fragment(Fragment.new(
163
163
  fragment.filename,
164
164
  fragment.start,
165
- fragment.true_duration,
165
+ fragment.original_duration,
166
166
  fragment.reversed?,
167
- fragment.pitch * (pitch.to_f / 100)))
167
+ fragment.pitch * (pitch.to_f / 100),
168
+ stretch))
168
169
  end
169
170
 
170
171
  new_instance
171
172
  end
172
173
 
174
+ def stretch(factor)
175
+ factor_for_pitch = ((100 / factor.to_f) * 100).to_i
176
+ pitch(factor_for_pitch, true)
177
+ end
178
+
173
179
  def to_file(filename, options = {})
174
180
  Scissor.mix([self], filename, options)
175
181
  end
@@ -4,12 +4,13 @@ module Scissor
4
4
  class Fragment
5
5
  attr_reader :filename, :start, :pitch
6
6
 
7
- def initialize(filename, start, duration, reverse = false, pitch = 100)
7
+ def initialize(filename, start, duration, reverse = false, pitch = 100, stretch = false)
8
8
  @filename = Pathname.new(filename).realpath
9
9
  @start = start
10
10
  @duration = duration
11
11
  @reverse = reverse
12
12
  @pitch = pitch
13
+ @is_stretched = stretch
13
14
 
14
15
  freeze
15
16
  end
@@ -18,7 +19,7 @@ module Scissor
18
19
  @duration * (100 / pitch.to_f)
19
20
  end
20
21
 
21
- def true_duration
22
+ def original_duration
22
23
  @duration
23
24
  end
24
25
 
@@ -26,6 +27,10 @@ module Scissor
26
27
  @reverse
27
28
  end
28
29
 
30
+ def stretched?
31
+ @is_stretched
32
+ end
33
+
29
34
  def create(remaining_start, remaining_length)
30
35
  new_fragment = nil
31
36
 
@@ -11,49 +11,65 @@ module Scissor
11
11
  class FileExists < Error; end
12
12
  class EmptyFragment < Error; end
13
13
  class CommandFailed < Error; end
14
+ class CommandNotFound < Error; end
14
15
 
15
16
  def initialize
16
17
  @tracks = []
17
18
 
18
19
  which('ecasound')
19
20
  which('ffmpeg')
21
+ which('rubberband')
20
22
  end
21
23
 
22
24
  def add_track(fragments)
23
25
  @tracks << fragments
24
26
  end
25
27
 
26
- def fragments_to_file(fragments, outfile, tmpdir)
28
+ def join_fragments(fragments, outfile, tmpdir)
27
29
  position = 0.0
28
30
  cmd = %w/ecasound/
29
31
 
30
32
  fragments.each_with_index do |fragment, index|
31
33
  fragment_filename = fragment.filename
32
- fragment_duration = fragment.duration
33
34
 
34
35
  if !index.zero? && (index % 28).zero?
35
36
  run_command(cmd.join(' '))
36
37
  cmd = %w/ecasound/
37
38
  end
38
39
 
39
- fragment_outfile =
40
- fragment_filename.extname.downcase == '.wav' ? fragment_filename :
41
- tmpdir + (Digest::MD5.hexdigest(fragment_filename.to_s) + '.wav')
40
+ if fragment_filename.extname.downcase == '.wav'
41
+ fragment_outfile = fragment_filename
42
+ else
43
+ fragment_outfile = tmpdir + (Digest::MD5.hexdigest(fragment_filename.to_s) + '.wav')
44
+ end
42
45
 
43
46
  unless fragment_outfile.exist?
44
47
  run_command("ffmpeg -i \"#{fragment_filename}\" \"#{fragment_outfile}\"")
45
48
  end
46
49
 
47
- cmd <<
48
- "-a:#{index} " +
49
- "-i:" +
50
- (fragment.reversed? ? 'reverse,' : '') +
51
- "select,#{fragment.start},#{fragment.true_duration},\"#{fragment_outfile}\" " +
52
- "-o:#{outfile} " +
53
- (fragment.pitch.to_f == 100.0 ? "" : "-ei:#{fragment.pitch} ") +
54
- "-y:#{position}"
50
+ cmd << "-a:#{index} -o:#{outfile} -y:#{position}"
51
+
52
+ if fragment.stretched? && fragment.pitch.to_f != 100.0
53
+ rubberband_out = tmpdir + (Digest::MD5.hexdigest(fragment_filename.to_s) + "rubberband_#{index}.wav")
54
+ rubberband_temp = tmpdir + "_rubberband.wav"
55
+
56
+ run_command("ecasound " +
57
+ "-i:" +
58
+ (fragment.reversed? ? 'reverse,' : '') +
59
+ "select,#{fragment.start},#{fragment.original_duration},\"#{fragment_outfile}\" -o:#{rubberband_temp} "
60
+ )
61
+ run_command("rubberband -T #{fragment.pitch.to_f/100} \"#{rubberband_temp}\" \"#{rubberband_out}\"")
62
+
63
+ cmd << "-i:\"#{rubberband_out}\""
64
+ else
65
+ cmd <<
66
+ "-i:" +
67
+ (fragment.reversed? ? 'reverse,' : '') +
68
+ "select,#{fragment.start},#{fragment.original_duration},\"#{fragment_outfile}\" " +
69
+ (fragment.pitch.to_f == 100.0 ? "" : "-ei:#{fragment.pitch} ")
70
+ end
55
71
 
56
- position += fragment_duration
72
+ position += fragment.duration
57
73
  end
58
74
 
59
75
  run_command(cmd.join(' '))
@@ -96,8 +112,9 @@ module Scissor
96
112
  tmpfiles = []
97
113
 
98
114
  @tracks.each_with_index do |fragments, track_index|
99
- tmpfiles << tmpfile = tmpdir + 'track_%s.wav' % track_index.to_s
100
- fragments_to_file(fragments, tmpfile, tmpdir)
115
+ tmpfile = tmpdir + 'track_%s.wav' % track_index.to_s
116
+ tmpfiles << tmpfile
117
+ join_fragments(fragments, tmpfile, tmpdir)
101
118
  end
102
119
 
103
120
  mix_files(tmpfiles, final_tmpfile = tmpdir + 'tmp.wav')
@@ -112,6 +129,8 @@ module Scissor
112
129
 
113
130
  def which(command)
114
131
  run_command("which #{command}")
132
+ rescue
133
+ raise CommandNotFound.new(command + ': command not found')
115
134
  end
116
135
 
117
136
  def run_command(cmd)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scissor
3
3
  version: !ruby/object:Gem::Version
4
- hash: 43
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 26
10
- version: 0.0.26
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - youpy
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-06-18 00:00:00 +09:00
18
+ date: 2011-07-20 00:00:00 +09:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency