I've been working with pipes and IO.popen
specifically in Ruby and have come across a problem that I can't figure out. I am trying to write binary data from the flac
process to the lame
process into a file. The code structure I am using is below.
# file paths
file = Pathname.new('example.flac').realpath
dest = Pathname.new('example.mp3')
# execute the process and return the IO object
wav = IO.popen("flac --decode --stdout \"#{file}\"", 'rb')
lame = IO.popen("lame -V0 --vbr-new - -", 'r+b')
# write output from wav to the lame IO object
lame << wav.read
# close pipe for writing (should indicate to the
# process that input for stdin is finished).
lame.close_write
# open up destiniation file and write from lame stdout
dest.open('wb'){|out|
out << lame.read
}
# close all pipes
wav.close
lame.close
However, it doesn't work. After flac
has run, the script hangs and lame
remains idle (no processor usage at all). No errors or exceptions occur.
I am using cygwin on Windows 7, with the cygwin ruby package (1.9.3p429 (2013-05-15) [i386-cygwin]).
I must be doing something wrong, any help is much appreciated. Thanks!
EXTRA #1
I am wanting to pipe in and out the binary data from the lame
process because I am trying to create a platform independent (ruby support limited of course) to transcode audio files, and the Windows binary of lame
only supports Windows' path names, and not cygwin's.
EDIT #1
I read in some places (I did not save the URLs, I'll try looking for them in my browser history) that IO.popen
has known issues with blocking processes in Windows and that this could be the case.
I have played around with other libraries including Ruby's Open3.popen3
and Open4
, however following a very similar code structure to the one above, the lame
process still hangs and remains unresponsive.
EDIT #2
I found this article which talked about the limitations of of Windows's cmd.exe
and how it prevents the use of streamed data from files to stdin.
I refactored my code to look like shown below to test this out, and as it turns out, lame
freezes on stdin write. If I removed (comment out) that line, the lame
process executes (with an 'unsupported audio format' warning). Perhaps what the article said could explain my problem here.
# file paths
file = Pathname.new('example.flac').realpath
dest = Pathname.new('example.mp3')
# some local variables
read_wav = nil
read_lame = nil
# the flac process, which exits succesfully
IO.popen("flac --decode --stdout \"#{file}\"", 'rb'){|wav|
until wav.eof do
read_wav = wav.read
end
}
# the lame process, which fails
IO.popen("lame -V0 --vbr-new --verbose - -", 'r+b'){|lame|
lame << read_wav # if I comment out this, the process exits, instead of hanging
lame.close_write
until lame.eof do
read_lame << lame.read
end
}
EDIT #3
I found this stackoverflow which (in the first answer) mentioned that cygwin
pipe implementation is unreliable. This could perhaps not actually be related to Windows (at least not directly) but instead to cygwin and its emulation. I have instead opted to use the following code, based upon icy's answer, which works!
flac = "flac --decode --stdout \"#{file}\""
lame = "lame -V0 --vbr-new --verbose - \"#{dest}\""
system(flac + ' | ' + lame)
Did you try the pipe |
character?
Tested this on windows with ruby installer
require 'open3'
command = 'dir /B | sort /R' # a windows example command
Open3.popen3(command) {|stdin, stdout, stderr, wait_thr|
pid = wait_thr.pid
puts stdout.read #<a list of files in cwd in reverse order>
}
Other ways: Ruby pipes: How do I tie the output of two subprocesses together?
EDIT:
using IO::pipe
require 'open3'
command1 = 'dir /B'
command2 = 'sort /R'
reader,writer = IO.pipe
Open3.popen3(command1) {|stdin, stdout, stderr, wait_thr|
writer.write stdout.read
}
writer.close
stdout, stderr, status = Open3.capture3(command2, :stdin_data => reader.read)
reader.close
puts "status: #{status}" #pid and exit code
puts "stderr: #{stderr}" #use this to debug command2 errors
puts stdout
Embedding the two also appears to work, yet, as the blog you referred to said, one must wait for the first command to finish (not real-time -- test with a ping command)
stdout2 = ''
Open3.popen3(command1) {|stdin, stdout, stderr, wait_thr|
stdout2, stderr2, status2 = Open3.capture3(command2, :stdin_data => stdout.read)
}
puts stdout2