Skip to content
Browse files

use tempfiles when decoding to temporary wavs, updated gemspec and re…

…adme
  • Loading branch information...
1 parent 6a8d322 commit 1b181b1603c1b09b074d360ebaa7ed6e5270645d @benalavi benalavi committed Jul 28, 2011
Showing with 59 additions and 35 deletions.
  1. +1 −0 .gitignore
  2. +1 −1 README.md
  3. +33 −32 lib/waveform.rb
  4. +1 −0 test/fixtures/sample.txt
  5. +21 −0 test/waveform_test.rb
  6. +2 −2 waveform.gemspec
View
1 .gitignore
@@ -6,3 +6,4 @@ vagrant/*
*.wav
*.png
test/output/*
+.DS_Store
View
2 README.md
@@ -47,7 +47,7 @@ There are also some less-nifty options:
-q will generate your waveform without printing out a bunch of stuff.
-h will print out a help screen with all this info.
- -F will automatically remove existing PNG or WAV files.
+ -F will automatically overwrite destination file.
Generating a small waveform "cut out" of a white background is pretty useful,
then you can overlay it on a web-gradient on the website for your new startup
View
65 lib/waveform.rb
@@ -1,4 +1,5 @@
require "ruby-audio"
+require "tempfile"
begin
require "oily_png"
@@ -7,7 +8,7 @@
end
class Waveform
- VERSION = "0.0.2"
+ VERSION = "0.0.3"
DefaultOptions = {
:method => :peak,
@@ -21,7 +22,7 @@ class Waveform
TransparencyMask = "#00ff00"
TransparencyAlternate = "#ffff00" # in case the mask is the background color!
- attr_reader :audio
+ attr_reader :source
# Scope these under Waveform so you can catch the ones generated by just this
# class.
@@ -43,19 +44,30 @@ class ArgumentError < ::ArgumentError;end;
# Waveform.new("mp3s/Kickstart My Heart.mp3")
# Waveform.new("mp3s/Kickstart My Heart.mp3", $stdout)
#
- def initialize(audio, log=nil)
- raise ArgumentError.new("No source audio filename given, must be an existing sound file.") unless audio
- raise RuntimeError.new("Source audio file '#{audio}' not found.") unless File.exist?(audio)
+ def initialize(source, log=nil)
+ raise ArgumentError.new("No source audio filename given, must be an existing sound file.") unless source
+ raise RuntimeError.new("Source audio file '#{source}' not found.") unless File.exist?(source)
@log = Log.new(log)
@log.start!
- @source = audio
-
- if File.extname(audio) != ".wav"
- @audio = audio.sub /(.+)\.(.+)/, "\\1.wav"
+
+ # @source is the path to the given source file, whatever it may be
+ @source = source
+
+ # @audio is the path to the actual audio file we will process, always wav
+ if File.extname(source) == ".wav"
+ @audio = File.open(source, "rb")
else
- @audio = audio
+ # This happens in initialize so you can generate multiple waveforms from
+ # the same audio without decoding multiple times
+ #
+ # Note that we're leaving it up to the ruby/system GC to clean up these
+ # tempfiles because someone may be generating multiple waveform images
+ # from a single audio source so we can't explicitly unlink the tempfile.
+ @audio = to_wav(source)
end
+
+ raise RuntimeError.new("Unable to decode source \'#{@source}\' to WAV. Do you have ffmpeg installed with an appropriate decoder for your source file?") unless @audio
end
# Generate a Waveform image at the given filename with the given options.
@@ -106,11 +118,7 @@ def generate(filename, options={})
else
raise RuntimeError.new("Destination file #{filename} exists. Use --force if you want to automatically remove it.")
end
- end
-
- unless File.extname(@source) == ".wav"
- raise RuntimeError.new("Unable to decode source \'#{audio}\' to WAV. Do you have ffmpeg installed with an appropriate decoder for your source file?") unless to_wav(@source, @audio, options[:force])
- end
+ end
options = DefaultOptions.merge(options)
@@ -175,7 +183,7 @@ def frames(width, method = :peak)
frames = []
- RubyAudio::Sound.open(audio) do |snd|
+ RubyAudio::Sound.open(@audio.path) do |snd|
frames_read = 0
frames_per_sample = (snd.info.frames.to_f / width.to_f).to_i
sample = RubyAudio::Buffer.new("float", frames_per_sample, snd.info.channels)
@@ -193,24 +201,17 @@ def frames(width, method = :peak)
private
- # Decode audio to a wav file, returns true if the decode succeeded or false
- # otherwise.
- def to_wav(src, dest, force=false)
- @log.out("Decoding source audio '#{src}' to WAV...")
-
- if File.exists?(dest)
- if force
- @log.out("Removing destination WAV file '#{dest}'.")
- File.unlink(dest)
- else
- raise RuntimeError.new("Destination WAV file '#{dest}' exists! Use --force if you want to automatically remove it.")
- end
+ # Decode given src file to a wav Tempfile. Returns the Tempfile if the decode
+ # succeeded, or false if the decode failed.
+ def to_wav(src, force=false)
+ wav = nil
+
+ @log.timed("Decoding source audio '#{src}' to WAV...") do
+ wav = Tempfile.new(File.basename(src))
+ system %Q{ffmpeg -y -i "#{src}" -f wav "#{wav.path}" > /dev/null 2>&1}
end
-
- system %Q{ffmpeg -i "#{src}" -f wav "#{dest}" > /dev/null 2>&1}
- @log.done!
- File.exists?(dest)
+ return wav.size == 0 ? false : wav
end
# Returns an array of the peak of each channel for the given collection of
View
1 test/fixtures/sample.txt
@@ -0,0 +1 @@
+foo bar baz
View
21 test/waveform_test.rb
@@ -116,6 +116,27 @@ def open_png(file)
assert_equal ChunkyPNG::Color::TRANSPARENT, image[60, 120]
end
+ # Not sure how to best test this as it's totally dependent on the ruby and
+ # system GC when the tempfiles are removed (as we're not explicitly
+ # unlinking them).
+ # should "use a tempfile when generating a temporary wav" do
+ # tempfiles = Dir[File.join(Dir.tmpdir(), "sample_mp3*")].size
+ # Waveform.new(fixture("sample_mp3.mp3")).generate(output("cleanup_temporary_wav.png"))
+ # assert_equal tempfiles + 1, Dir[File.join(Dir.tmpdir(), "sample_mp3*")].size
+ # end
+
+ should "not delete source wav file if one was given" do
+ assert File.exists?(fixture("sample.wav"))
+ Waveform.new(fixture("sample.wav")).generate(output("keep_source_wav.png"))
+ assert File.exists?(fixture("sample.wav"))
+ end
+
+ should "raise an error if unable to decode to wav" do
+ assert_raise(Waveform::RuntimeError) do
+ Waveform.new(fixture("sample.txt")).generate(output("shouldnt_exist.png"))
+ end
+ end
+
context "with existing PNG files" do
setup do
@existing = output("existing.png")
View
4 waveform.gemspec
@@ -3,8 +3,8 @@ require "./lib/waveform"
Gem::Specification.new do |s|
s.name = "waveform"
s.version = Waveform::VERSION
- s.summary = "Generate waveform images from WAV and MP3 files"
- s.description = "Generate waveform images from WAV and MP3 files -- in your code or via included CLI."
+ s.summary = "Generate waveform images from WAV, MP3, etc... files"
+ s.description = "Generate waveform images from WAV, MP3, etc... files - as a gem or via CLI."
s.authors = ["Ben Alavi"]
s.email = ["benalavi@gmail.com"]
s.homepage = "http://github.com/benalavi/waveform"

0 comments on commit 1b181b1

Please sign in to comment.
Something went wrong with that request. Please try again.