ruby - IO.popen не работает с хромой кодировкой stdin и stdout

Я работал с каналами и IO.popen конкретно в Ruby и столкнулся с проблемой, которую не могу понять. Я пытаюсь записать двоичные данные из процесса 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 остается бездействующим (вообще не использует процессор). Ошибки или исключения не возникают.

Я использую cygwin в Windows 7 с пакетом cygwin ruby ​​(1.9.3p429 (2013-05-15) [i386-cygwin]).

Должно быть, я делаю что-то не так, любая помощь очень ценится. Спасибо!

ДОПОЛНИТЕЛЬНО #1

Я хочу вводить и выводить двоичные данные из процесса lame, потому что я пытаюсь создать независимую от платформы (поддержка ruby, конечно, ограничена) для перекодирования аудиофайлов, а двоичный файл Windows только для lame поддерживает имена путей Windows, а не cygwin.

ИЗМЕНИТЬ №1

Я читал в некоторых местах (URL-адреса не сохранял, попробую поискать в истории браузера), что у IO.popen были известные проблемы с блокировкой процессов в Windows и что это может быть так.

Я экспериментировал с другими библиотеками, включая Ruby Open3.popen3 и Open4, однако, следуя структуре кода, очень похожей на приведенную выше, процесс 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)

person Hans    schedule 08.08.2013    source источник
comment
отредактировано, чтобы попробовать Open3#capture3 и установить :stdin_data . Или это то же самое, что и lame << read_wav?   -  person icy    schedule 13.08.2013


Ответы (2)


Вы пробовали символ трубы |?
Протестировано на 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 pipe: Как сделать Я связываю выходные данные двух подпроцессов вместе?

EDIT: используя 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
person icy    schedule 12.08.2013
comment
Привет @icy, спасибо за ответ. Я пробовал это и знаю, что это работает, однако для моего сценария я намереваюсь, чтобы код декодера/кодировщика был отдельным. Вот почему я пытаюсь выполнить конвейер через ruby, а не на системном уровне. - person Hans; 13.08.2013
comment
Привет @icy, ты запускаешь свой скрипт в Windows cmd.exe или под cygwin? - person Hans; 13.08.2013
comment
Привет, @icy, твое первое предложение, использование конвейерной реализации ОС отлично работает, спасибо :) - person Hans; 16.08.2013
comment
Ваш пример IO::pipe работает только в том случае, если буфер канала достаточно велик, чтобы захватить весь вывод первой команды. В противном случае запись в него будет заблокирована, и первая команда никогда не завершится, в результате чего первый вызов popen3 зависнет навсегда при попытке зафиксировать статус выхода команды. Если вы уже используете open3, почему вы не используете команду pipeline? - person Mecki; 21.04.2019
comment
Open3.capture3 с :stdin_data решил мою блокировку stdin с popen3. - person Ludovic Kuty; 28.12.2020

Используйте pipeline от Open3:

require "open3"

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

Open3.pipeline(wavComamnd, lameCommand)

Последняя строка порождает два процесса и соединяет stdout первого процесса с stdin второго. В качестве альтернативы вы можете получить доступ к stdin первого процесса, используя pipeline_w, или вы можете получить stdout последней команды, используя pipeline_r, или вы можете получить и то, и другое, используя pipline_rw.

person Mecki    schedule 20.04.2019