Ruby - IO.popen无法工作,Lame的标准输入和输出编码

4

我一直在使用Ruby中的管道和IO.popen,并遇到了一个问题,我无法解决。我正在尝试将flac进程中的二进制数据写入lame进程并保存到文件中。我使用的代码结构如下。

# 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

然而,它没有起作用。在flac运行之后,脚本挂起并且lame保持空闲(根本没有处理器使用)。没有出现任何错误或异常。
我正在Windows 7上使用cygwin,使用cygwin ruby包(1.9.3p429(2013-05-15)[i386-cygwin])。
我一定是做错了什么,任何帮助都非常感谢。谢谢!
额外#1
我想从lame进程中进行二进制数据的输入和输出,因为我正在尝试创建一个平台无关(当然,ruby支持有限)用于转码音频文件,而lame的Windows二进制文件仅支持Windows的路径名,而不是cygwin的路径名。
编辑#1
我在某些地方读到(我没有保存URL,我将尝试在我的浏览器历史记录中查找它们),IO.popen在Windows中有已知的阻塞进程问题,并且可能是这种情况。
我已经尝试过其他库,包括Ruby的Open3.popen3Open4,但是遵循与上面非常相似的代码结构时,lame进程仍然挂起并保持无响应。
编辑#2
我在这篇文章中发现了关于Windows的cmd.exe限制及其如何阻止使用从文件流传输数据到标准输入的内容。
我重构了我的代码以测试这一点,结果发现lame在标准输入写入时会冻结。如果我删除(注释掉)那行,lame进程将执行(带有“不支持的音频格式”警告)。也许这篇文章所说的可以解释我在这里遇到的问题。
# 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
}

编辑 #3

我在 stackoverflow 上找到了一篇回答,其中提到cygwin管道实现不可靠。这可能与Windows无关(至少不是直接相关),而是与cygwin及其仿真有关。我选择使用以下代码,该代码基于 icy 的答案,它可以正常工作

flac = "flac --decode --stdout \"#{file}\""
lame = "lame -V0 --vbr-new --verbose - \"#{dest}\""

system(flac + ' | ' + lame)

修改为尝试使用 Open3#capture3 并设置 :stdin_data。这和 lame << read_wav 是一样的吗? - icy
2个回答

3

你尝试过竖线 | 字符吗?
我在Windows上使用Ruby安装程序测试过了。

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>
}

其他方法:Ruby pipes: How do I tie the output of two subprocesses together? 编辑: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

嵌入这两个似乎也是可行的,但正如你所提到的博客所说,必须等待第一个命令完成(不是实时的 - 请使用ping测试)

stdout2 = ''
Open3.popen3(command1) {|stdin, stdout, stderr, wait_thr|
  stdout2, stderr2, status2 = Open3.capture3(command2, :stdin_data => stdout.read)
}
puts stdout2

嗨@icy,感谢您的回答。我已经尝试过这个方法并知道它是有效的,但是对于我的脚本,我打算将解码器/编码器代码分开。这就是为什么我正在尝试通过Ruby进行管道传输,而不是在系统级别上进行。 - Hans
你好 @icy,你是在Windows的 cmd.exe中运行脚本还是在 cygwin 下运行? - Hans
嗨@icy,你的第一个建议,使用操作系统的管道实现完美地解决了问题,谢谢 :) - Hans
你的 IO::pipe 示例只有在管道缓冲区足够大以捕获第一个命令的整个输出时才能正常工作。否则,向其写入将会阻塞,并且第一个命令将永远不会终止,导致第一个 popen3 调用在尝试捕获命令的退出状态时永久挂起。如果你已经在使用 open3,为什么不使用 pipeline 命令呢? - Mecki
使用 Open3.capture3:stdin_data 解决了我在使用 popen3 时遇到的阻塞 stdin 的问题。 - Ludovic Kuty

0

使用Open3中的pipeline

require "open3"

wavCommand = "flac --decode --stdout \"#{file}\""
lameCommand = "lame -V0 --vbr-new - -"

Open3.pipeline(wavComamnd, lameCommand)

最后一行生成两个进程并将第一个进程的stdout连接到第二个进程的stdin。或者您可以使用pipeline_w访问第一个进程的stdin,或者使用pipeline_r获取最后一个命令的stdout,或者同时使用两者,使用pipline_rw


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接