Prevent ffmpeg from taking over stdout - ruby-on-rails

When I do system "ffmpeg -i just-do-it.mp4 -ab 96k -ar 22050 -qscale 6 output.flv" ffmpeg takes over the ruby process till the job is done, which sometimes take a long time. I've tried using threads amd fork in Ruby to no avail, also system equivalent commands like exec %x[] I also tried the latest Fibers in ruby 1.9.2, but I don't think I'm using it properly.
My question is how to run two ffmpeg processes from ruby concurrently?
EDIT:
fork do
fork do
system "ffmpeg -i you-know.mp4 -ab 96k -ar 22050 -qscale 6 #{Time.now.sec}.flv"
end
fork do
system "ffmpeg -i bangbang.mp4 -ab 96k -ar 22050 -qscale 6 #{Time.now.sec}.flv"
end
end

fork/exec is the right solution. Since forked processes inherit the parent processes fopen file handles/etc., you'll have to close (or redirect) the file handles you don't want children processes to use.
For example:
# this will print nothing, but yes is running as a forked process
# you'll want to `killall yes` after running this script.
fork do
[$stdout, $stderr].each { |fh| fh.reopen File.open("/dev/null", "w") }
exec "yes"
end
Ok, some comments on the code you posted. The outer fork is pointless. Just fork the two ffmpeg process from the main process. Maybe write a helper function like:
def ffmpeg(mp4)
fork do
[$stdout, $stderr].each { ... }
exec "ffmpeg -i #{mp4} ..."
end
end
ffmpeg("you-know.mp4")
ffmpeg("bangbang.mp4")

Try the subprocess gem - that's what I'm using now for dealing with process forking and finding it much easier to use.
E.g.
work_list.each do |cmd|
process = Subprocess::Popen.new(cmd)
process.run
process.wait
#puts process.stdout
#puts process.stderr
puts process.status
end

Related

Redirect shell output for ruby script

I have simple ruby script :
#! /usr/bin/env ruby
require 'fileutils'
FileUtils.rm "output.mkv" if File.exists?("outp ut.mkv")
pid = Process.spawn("ffmpeg -i wrong_file.mp4 -c:v libx264 -preset veryslow -qp 0 output.mkv", STDOUT => "output.txt", STDERR => "error.txt")
puts "pid : #{pid}"
Process.wait(pid)
But, STDOUT and STDERR outputs into error.txt, why ?
It looks, that ffmpeg have another exit codes ?(in usual case 0 for stdout, and 1 for stdin)
Note: I don't want to use native shell redirect like '> output.txt 2> error.txt' because i want to get pid of ffmpeg process, not shell process and kill it in future.
According to spawn method documentation you should be doing this:
pid = Process.spawn("ffmpeg -i wrong_file.mp4 -c:v libx264 -preset veryslow -qp 0 output.mkv", :out => "output.txt", :err => "error.txt")
The problem was resolved - FFMPEG redirects all output to STDERR

How can I execute a binary through ruby for a limited time?

I want to run a binary through ruby for a limited time. In my case its airodump-ng with the full command of:
airodump-ng -c <mychan> mon0 --essid "my_wlan" --write capture
For the ones who don't know airdump-ng for normal it starts and doesn't terminate itself. Its running forever if the user doesn't stop it by pressing Strg + C. This isn't a problem at the bash but executing it through ruby it's causing serious trouble. Is there a way to limit the time a binary is runned by the system method?
Try timeout library:
require 'timeout'
begin
Timeout.timeout(30) do
system('airodump-ng -c <mychan> mon0 --essid "my_wlan" --write capture')
end
rescue Timeout::Error
# timeout
end
You could use the Ruby spawn method. From the Ruby docs:
This method is similar to #system but it doesn’t wait for the command to finish.
Something like this:
# Start airodump
pid = spawn('airodump-ng -c <mychan> mon0 --essid "my_wlan" --write capture')
# Wait a little while...
sleep 60
# Then stop airodump (similar to pressing CTRL-C)
Process.kill("INT", pid)

ffmpeg - Continuously stream webcam to single .jpg file (overwrite)

I have installed ffmpeg and mjpeg-streamer. The latter reads a .jpg file from /tmp/stream and outputs it via http onto a website, so I can stream whatever is in that folder through a web browser.
I wrote a bash script that continuously captures a frame from the webcam and puts it in /tmp/stream:
while true
do
ffmpeg -f video4linux2 -i /dev/v4l/by-id/usb-Microsoft_Microsoft_LifeCam_VX-5000-video-index0 -vframes 1 /tmp/stream/pic.jpg
done
This works great, but is very slow (~1 fps). In the hopes of speeding it up, I want to use a single ffmpeg command which continuously updates the .jpg at, let's say 10 fps. What I tried was the following:
ffmpeg -f video4linux2 -r 10 -i /dev/v4l/by-id/usb-Microsoft_Microsoft_LifeCam_VX-5000-video-index0 /tmp/stream/pic.jpg
However this - understandably - results in the error message:
[image2 # 0x1f6c0c0] Could not get frame filename number 2 from pattern '/tmp/stream/pic.jpg'
av_interleaved_write_frame(): Input/output error
...because the output pattern is bad for a continuous stream of images.
Is it possible to stream to just one jpg with ffmpeg?
Thanks...
You can use the -update option:
ffmpeg -y -f v4l2 -i /dev/video0 -update 1 -r 1 output.jpg
From the image2 file muxer documentation:
-update number
If number is nonzero, the filename will always be interpreted as just a
filename, not a pattern, and this file will be continuously overwritten
with new images.
It is possible to achieve what I wanted by using:
./mjpg_streamer -i "input_uvc.so -r 1280×1024 -d /dev/video0 -y" -o "output_http.so -p 8080 -w ./www"
...from within the mjpg_streamer's directory. It will do all the nasty work for you by displaying the stream in the browser when using the address:
http://{IP-OF-THE-SERVER}:8080/
It's also light-weight enough to run on a Raspberry Pi.
Here is a good tutorial for setting it up.
Thanks for the help!

save FFMPEG screenshot output file to variable

How can i get the output file of this FFMPEG code saved to a variable?
def take_screenshot
logger.debug "Trying to grab a screenshot from #{self.file}"
system "ffmpeg -i #{self.file} -ss 00:00:02 -vframes 1 #{Rails.root}/public/uploads/tmp/screenshots/#{File.basename(self.file)}.jpg"
self.save!
end
I have tried:
self.screenshot = system "ffmpeg -i #{self.file} -ss 00:00:02 -vframes 1 #{Rails.root}/public/uploads/tmp/screenshots/#{File.basename(self.file)}.jpg"
but this doesn't save anything.
thanks in advance!
ffmpeg usually outputs nothing on stdout and all of its debug messages on stderr. You can make it output the video (or image) to stdout when you pass - as the output file. You'd then also need to suppress stderr.
system "ffmpeg -i #{self.file} -ss 00:00:02 -c:v mjpeg -f mjpeg -vframes 1 - 2>/dev/null"
This will output the raw data of the JPEG-encoded image to stdout. From there you can save the data to a variable and, for example, transfer it somewhere else.
To get stdout from system calls, see here: Getting output of system() calls in ruby – especially popen3 should help you in that case, where you could discard the stderr from within Ruby.

cron job is not completing the process?

I have a ruby program to convert video to MP4 format using ffmpeg. And I'm using the crontab to run the ruby program every 15 minutes. crontab actually runs the ruby program, but the conversion of the file is not complete. The process is stopped before completing the conversion. My sample code for testin is below.
def convert(y)
system "ffmpeg -i #{SOURCE_FOLDER + LOCATION_SOURCE}/#{y} -acodec libfaac -ar 44100 -ab 96k -vcodec libx264 #{DEST_FOLDER + LOCATION_DEST}/#{y}"
end
SOURCE_FOLDER = "/home/someone/work/videoapp/public/"
DEST_FOLDER = "/home/someone/work/videoapp/public/"
LOCATION_SOURCE = "source"
LOCATION_DEST = "dest"
files = Dir.new(SOURCE_FOLDER + LOCATION_SOURCE)
files.each do |x|
convert(x)
end
This code works fine, if i run it manually in the console.
My first guess is that it's dying on "dot" directories. In Unix there are two directories in every directory/folder: "." and "..". You'll either need to specifically skip those in your script:
next if File.directory?(x) # OR
next file x.match(/^\.+$/)
-- OR --
Look specifically for whatever filetypes you are wanting
Dir[SOURCE_FOLDER + LOCATION_SOURCE + "*.wav"].each do |file|
convert(file)
end
Update: 20110401
Add Unix redirects to the crontab entry to see what the output is
* * * * * /your/program/location/file.rb 1> /some/output/file.txt 2>&1

Resources