Permalink
Browse files

* wav support

 * version 0.0.11
  • Loading branch information...
1 parent 0bf87e1 commit fb4e7a0c89547cb2000900abb1ca8c134c035547 @youpy committed Apr 5, 2009
Showing with 156 additions and 68 deletions.
  1. +20 −5 README.rdoc
  2. +3 −2 Rakefile
  3. +33 −3 lib/scissor.rb
  4. +6 −3 scissor.gemspec
  5. BIN spec/fixtures/sine.wav
  6. +67 −55 spec/scissor_spec.rb
  7. +27 −0 spec/sound_file_spec.rb
View
@@ -1,15 +1,28 @@
= Scissor
== Description
-utility to chop mp3 files
+
+utility to chop sound files
+
+supported file format:
+
+* mp3
+* wav
+
+== Installation
== Requirements
* {ruby-mp3info}[http://ruby-mp3info.rubyforge.org/]
-* {FFmpeg}[http://ffmpeg.mplayerhq.hu/]
-* {Ecasound}[http://www.eca.cx/ecasound/]
-== Installation
+ gem install ruby-mp3info
+
+* {RiffLib}[http://rubyforge.org/projects/riff/]
+
+ gem install riff
+
+* {FFmpeg}[http://ffmpeg.mplayerhq.hu/]
+* {Ecasound}[http://www.eca.cx/ecasound/] 2.5.0 or higher
=== Archive Installation
@@ -21,10 +34,12 @@ utility to chop mp3 files
== Features/Problems
+* When you concatenate two or more files, format(sample rate, bit rate, ...) mismatch causes unexpected changes to output file.
+
== Synopsis
foo = Scissor.new('foo.mp3')
- bar = Scissor.new('bar.mp3')
+ bar = Scissor.new('bar.wav')
# concat
(foo + bar).to_file('foobar.mp3')
View
@@ -13,11 +13,11 @@ include FileUtils
NAME = "scissor"
AUTHOR = "youpy"
EMAIL = "youpy@buycheapviagraonlinenow.com"
-DESCRIPTION = "utility to chop mp3 files"
+DESCRIPTION = "utility to chop sound files"
RUBYFORGE_PROJECT = "scissor"
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
BIN_FILES = %w( )
-VERS = "0.0.10"
+VERS = "0.0.11"
REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
@@ -58,6 +58,7 @@ spec = Gem::Specification.new do |s|
s.test_files = Dir["test/test_*.rb"]
s.add_dependency('ruby-mp3info')
+ s.add_dependency('riff')
#s.required_ruby_version = '>= 1.8.2'
s.files = %w(README.rdoc ChangeLog Rakefile) +
View
@@ -1,6 +1,7 @@
require 'mp3info'
require 'digest/md5'
require 'pathname'
+require 'riff/reader'
class Scissor
class Error < StandardError; end
@@ -19,7 +20,7 @@ def initialize(filename = nil)
@fragments << Fragment.new(
Pathname.new(filename),
0,
- Mp3Info.new(filename).length)
+ SoundFile.new(filename).length)
end
end
@@ -188,13 +189,13 @@ def to_file(filename, options = {})
tmpdir = Pathname.new('/tmp/scissor-' + $$.to_s)
tmpdir.mkpath
tmpfile = tmpdir + 'tmp.wav'
- cmd = %w/ecasound/
+ cmd = %w/ecasound -q/
begin
@fragments.each_with_index do |fragment, index|
if !index.zero? && (index % 80).zero?
run_command(cmd.join(' '))
- cmd = %w/ecasound/
+ cmd = %w/ecasound -q/
end
fragment_tmpfile =
@@ -245,6 +246,35 @@ def silence(duration)
end
end
+ class SoundFile
+ SUPPORTED_FORMAT = %w/mp3 wav/
+
+ class Error < StandardError; end
+ class UnknownFormat < Error; end
+
+ def initialize(filename)
+ @filename = Pathname.new(filename)
+ @ext = @filename.extname.sub(/^\./, '').downcase
+
+ unless SUPPORTED_FORMAT.include?(@ext)
+ raise UnknownFormat
+ end
+ end
+
+ def length
+ case @ext
+ when 'mp3'
+ Mp3Info.new(@filename).length
+ when 'wav'
+ riff = Riff::Reader.open(@filename ,"r")
+ data = riff.root_chunk['data']
+ fmt = riff.root_chunk['fmt ']
+
+ data.length / fmt.body.unpack('s2i2')[3].to_f
+ end
+ end
+ end
+
class Fragment
attr_reader :filename, :start, :duration
View
@@ -1,11 +1,11 @@
Gem::Specification.new do |s|
s.name = %q{scissor}
- s.version = "0.0.10"
+ s.version = "0.0.11"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["youpy"]
s.date = %q{2009-04-05}
- s.description = %q{utility to chop mp3 files}
+ s.description = %q{utility to chop sound files}
s.email = %q{youpy@buycheapviagraonlinenow.com}
s.extra_rdoc_files = ["README.rdoc", "ChangeLog"]
s.files = ["README.rdoc", "ChangeLog", "Rakefile", "lib/scissor.rb", "data/silence.mp3"]
@@ -15,18 +15,21 @@ Gem::Specification.new do |s|
s.require_paths = ["lib"]
s.rubyforge_project = %q{scissor}
s.rubygems_version = %q{1.2.0}
- s.summary = %q{utility to chop mp3 files}
+ s.summary = %q{utility to chop sound files}
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 2
if current_version >= 3 then
s.add_runtime_dependency(%q<ruby-mp3info>, [">= 0"])
+ s.add_runtime_dependency(%q<riff>, [">= 0"])
else
s.add_dependency(%q<ruby-mp3info>, [">= 0"])
+ s.add_dependency(%q<riff>, [">= 0"])
end
else
s.add_dependency(%q<ruby-mp3info>, [">= 0"])
+ s.add_dependency(%q<riff>, [">= 0"])
end
end
View
Binary file not shown.
View
@@ -33,39 +33,39 @@
end
it "should concatenate" do
- new_mp3 = @mp3.slice(0, 120).concat(@mp3.slice(150, 20))
- new_mp3.duration.should eql(140)
+ scissor = @mp3.slice(0, 120).concat(@mp3.slice(150, 20))
+ scissor.duration.should eql(140)
end
it "should concatenate using arithmetic operator" do
- new_mp3 = @mp3.slice(0, 120) + @mp3.slice(150, 20)
- new_mp3.duration.should eql(140)
+ scissor = @mp3.slice(0, 120) + @mp3.slice(150, 20)
+ scissor.duration.should eql(140)
end
it "should concat silence" do
- new_mp3 = @mp3.slice(0, 12).concat(Scissor.silence(3))
- new_mp3.duration.should eql(15)
+ scissor = @mp3.slice(0, 12).concat(Scissor.silence(3))
+ scissor.duration.should eql(15)
end
it "should slice concatenated one" do
- new_mp3 = @mp3.slice(0.33, 1).concat(@mp3.slice(0.2, 0.1)).slice(0.9, 0.2)
+ scissor = @mp3.slice(0.33, 1).concat(@mp3.slice(0.2, 0.1)).slice(0.9, 0.2)
- new_mp3.duration.to_s.should == '0.2'
- new_mp3.fragments.size.should eql(2)
- new_mp3.fragments[0].start.to_s.should == '1.23'
- new_mp3.fragments[0].duration.to_s.should == '0.1'
- new_mp3.fragments[1].start.to_s.should == '0.2'
- new_mp3.fragments[1].duration.to_s.should == '0.1'
+ scissor.duration.to_s.should == '0.2'
+ scissor.fragments.size.should eql(2)
+ scissor.fragments[0].start.to_s.should == '1.23'
+ scissor.fragments[0].duration.to_s.should == '0.1'
+ scissor.fragments[1].start.to_s.should == '0.2'
+ scissor.fragments[1].duration.to_s.should == '0.1'
end
it "should loop" do
- new_mp3 = @mp3.slice(0, 10).loop(3)
- new_mp3.duration.should eql(30)
+ scissor = @mp3.slice(0, 10).loop(3)
+ scissor.duration.should eql(30)
end
it "should loop using arithmetic operator" do
- new_mp3 = @mp3.slice(0, 10) * 3
- new_mp3.duration.should eql(30)
+ scissor = @mp3.slice(0, 10) * 3
+ scissor.duration.should eql(30)
end
it "should split" do
@@ -97,49 +97,49 @@
end
it "should fill" do
- new_mp3 = (@mp3.slice(0, 6) + @mp3.slice(0, 2)).fill(15)
- new_mp3.duration.should eql(15)
- new_mp3.fragments.size.should eql(4)
- new_mp3.fragments[0].duration.should eql(6)
- new_mp3.fragments[1].duration.should eql(2)
- new_mp3.fragments[2].duration.should eql(6)
- new_mp3.fragments[3].duration.should eql(1)
+ scissor = (@mp3.slice(0, 6) + @mp3.slice(0, 2)).fill(15)
+ scissor.duration.should eql(15)
+ scissor.fragments.size.should eql(4)
+ scissor.fragments[0].duration.should eql(6)
+ scissor.fragments[1].duration.should eql(2)
+ scissor.fragments[2].duration.should eql(6)
+ scissor.fragments[3].duration.should eql(1)
end
it "should replace" do
- new_mp3 = @mp3.slice(0, 100).replace(60, 30, @mp3.slice(0, 60))
- new_mp3.duration.should eql(130)
- new_mp3.fragments.size.should eql(3)
- new_mp3.fragments[0].start.should eql(0)
- new_mp3.fragments[0].duration.should eql(60)
- new_mp3.fragments[1].start.should eql(0)
- new_mp3.fragments[1].duration.should eql(60)
- new_mp3.fragments[2].start.should eql(90)
- new_mp3.fragments[2].duration.should eql(10)
+ scissor = @mp3.slice(0, 100).replace(60, 30, @mp3.slice(0, 60))
+ scissor.duration.should eql(130)
+ scissor.fragments.size.should eql(3)
+ scissor.fragments[0].start.should eql(0)
+ scissor.fragments[0].duration.should eql(60)
+ scissor.fragments[1].start.should eql(0)
+ scissor.fragments[1].duration.should eql(60)
+ scissor.fragments[2].start.should eql(90)
+ scissor.fragments[2].duration.should eql(10)
end
it "should reverse" do
- new_mp3 = (@mp3.slice(0, 10) + @mp3.slice(0, 5)).reverse
- new_mp3.duration.should eql(15)
- new_mp3.fragments.size.should eql(2)
- new_mp3.fragments[0].start.should eql(0)
- new_mp3.fragments[0].duration.should eql(5)
- new_mp3.fragments[0].should be_reversed
- new_mp3.fragments[1].start.should eql(0)
- new_mp3.fragments[1].duration.should eql(10)
- new_mp3.fragments[0].should be_reversed
+ scissor = (@mp3.slice(0, 10) + @mp3.slice(0, 5)).reverse
+ scissor.duration.should eql(15)
+ scissor.fragments.size.should eql(2)
+ scissor.fragments[0].start.should eql(0)
+ scissor.fragments[0].duration.should eql(5)
+ scissor.fragments[0].should be_reversed
+ scissor.fragments[1].start.should eql(0)
+ scissor.fragments[1].duration.should eql(10)
+ scissor.fragments[0].should be_reversed
end
it "should re-reverse" do
- new_mp3 = (@mp3.slice(0, 10) + @mp3.slice(0, 5)).reverse.reverse
- new_mp3.duration.should eql(15)
- new_mp3.fragments.size.should eql(2)
- new_mp3.fragments[0].start.should eql(0)
- new_mp3.fragments[0].duration.should eql(10)
- new_mp3.fragments[0].should_not be_reversed
- new_mp3.fragments[1].start.should eql(0)
- new_mp3.fragments[1].duration.should eql(5)
- new_mp3.fragments[0].should_not be_reversed
+ scissor = (@mp3.slice(0, 10) + @mp3.slice(0, 5)).reverse.reverse
+ scissor.duration.should eql(15)
+ scissor.fragments.size.should eql(2)
+ scissor.fragments[0].start.should eql(0)
+ scissor.fragments[0].duration.should eql(10)
+ scissor.fragments[0].should_not be_reversed
+ scissor.fragments[1].start.should eql(0)
+ scissor.fragments[1].duration.should eql(5)
+ scissor.fragments[0].should_not be_reversed
end
it "should raise error if replaced range is out of duration" do
@@ -149,15 +149,27 @@
end
it "should write to file and return new instance of Scissor" do
- new_mp3 = @mp3.slice(0, 120) + @mp3.slice(150, 20)
- result = new_mp3.to_file('/tmp/scissor-test/out.mp3')
+ scissor = @mp3.slice(0, 120) + @mp3.slice(150, 20)
+ result = scissor.to_file('/tmp/scissor-test/out.mp3')
result.should be_an_instance_of(Scissor)
result.duration.to_i.should eql(140)
end
+ it "should write to mp3 file" do
+ scissor = @mp3.slice(0, 120) + @mp3.slice(150, 20)
+ result = scissor.to_file('/tmp/scissor-test/out.mp3')
+ result.duration.to_i.should eql(140)
+ end
+
+ it "should write to wav file" do
+ scissor = @mp3.slice(0, 120) + @mp3.slice(150, 20)
+ result = scissor.to_file('/tmp/scissor-test/out.wav')
+ result.duration.to_i.should eql(140)
+ end
+
it "should write to file with many fragments" do
- new_mp3 = (@mp3.slice(0, 120) / 100).inject(Scissor.new){|m, s| m + s } + @mp3.slice(10, 20)
- result = new_mp3.to_file('/tmp/scissor-test/out.mp3')
+ scissor = (@mp3.slice(0, 120) / 100).inject(Scissor.new){|m, s| m + s } + @mp3.slice(10, 20)
+ result = scissor.to_file('/tmp/scissor-test/out.mp3')
result.should be_an_instance_of(Scissor)
result.duration.to_i.should eql(140)
end
View
@@ -0,0 +1,27 @@
+$:.unshift File.dirname(__FILE__)
+
+require 'spec_helper'
+require 'fileutils'
+
+include FileUtils
+
+describe Scissor::SoundFile do
+ before do
+ @mp3 = Scissor::SoundFile.new(fixture('sample.mp3'))
+ @wav = Scissor::SoundFile.new(fixture('sine.wav'))
+ end
+
+ after do
+ end
+
+ it "raise error if unknown file format" do
+ lambda {
+ Scissor::SoundFile.new(fixture('foo.bar'))
+ }.should raise_error(Scissor::SoundFile::UnknownFormat)
+ end
+
+ it "should get length" do
+ @mp3.length.should eql(178.183)
+ @wav.length.should eql(1.0)
+ end
+end

0 comments on commit fb4e7a0

Please sign in to comment.